PageRenderTime 73ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/sources/subs/Profile.subs.php

https://github.com/Arantor/Elkarte
PHP | 2297 lines | 1757 code | 210 blank | 330 comment | 414 complexity | 80f7cadff06881423ab3653a73ec2f4f MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. /**
  3. * @name ElkArte Forum
  4. * @copyright ElkArte Forum contributors
  5. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  6. *
  7. * This software is a derived product, based on:
  8. *
  9. * Simple Machines Forum (SMF)
  10. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  11. * license: BSD, See included LICENSE.TXT for terms and conditions.
  12. *
  13. * @version 1.0 Alpha
  14. *
  15. */
  16. if (!defined('ELKARTE'))
  17. die('No access...');
  18. /**
  19. * Setup the context for a page load!
  20. *
  21. * @param array $fields
  22. */
  23. function setupProfileContext($fields)
  24. {
  25. global $profile_fields, $context, $cur_profile, $smcFunc, $txt;
  26. // Make sure we have this!
  27. loadProfileFields(true);
  28. // First check for any linked sets.
  29. foreach ($profile_fields as $key => $field)
  30. if (isset($field['link_with']) && in_array($field['link_with'], $fields))
  31. $fields[] = $key;
  32. // Some default bits.
  33. $context['profile_prehtml'] = '';
  34. $context['profile_posthtml'] = '';
  35. $context['profile_javascript'] = '';
  36. $context['profile_onsubmit_javascript'] = '';
  37. $i = 0;
  38. $last_type = '';
  39. foreach ($fields as $key => $field)
  40. {
  41. if (isset($profile_fields[$field]))
  42. {
  43. // Shortcut.
  44. $cur_field = &$profile_fields[$field];
  45. // Does it have a preload and does that preload succeed?
  46. if (isset($cur_field['preload']) && !$cur_field['preload']())
  47. continue;
  48. // If this is anything but complex we need to do more cleaning!
  49. if ($cur_field['type'] != 'callback' && $cur_field['type'] != 'hidden')
  50. {
  51. if (!isset($cur_field['label']))
  52. $cur_field['label'] = isset($txt[$field]) ? $txt[$field] : $field;
  53. // Everything has a value!
  54. if (!isset($cur_field['value']))
  55. {
  56. $cur_field['value'] = isset($cur_profile[$field]) ? $cur_profile[$field] : '';
  57. }
  58. // Any input attributes?
  59. $cur_field['input_attr'] = !empty($cur_field['input_attr']) ? implode(',', $cur_field['input_attr']) : '';
  60. }
  61. // Was there an error with this field on posting?
  62. if (isset($context['profile_errors'][$field]))
  63. $cur_field['is_error'] = true;
  64. // Any javascript stuff?
  65. if (!empty($cur_field['js_submit']))
  66. $context['profile_onsubmit_javascript'] .= $cur_field['js_submit'];
  67. if (!empty($cur_field['js']))
  68. $context['profile_javascript'] .= $cur_field['js'];
  69. // Any template stuff?
  70. if (!empty($cur_field['prehtml']))
  71. $context['profile_prehtml'] .= $cur_field['prehtml'];
  72. if (!empty($cur_field['posthtml']))
  73. $context['profile_posthtml'] .= $cur_field['posthtml'];
  74. // Finally put it into context?
  75. if ($cur_field['type'] != 'hidden')
  76. {
  77. $last_type = $cur_field['type'];
  78. $context['profile_fields'][$field] = &$profile_fields[$field];
  79. }
  80. }
  81. // Bodge in a line break - without doing two in a row ;)
  82. elseif ($field == 'hr' && $last_type != 'hr' && $last_type != '')
  83. {
  84. $last_type = 'hr';
  85. $context['profile_fields'][$i++]['type'] = 'hr';
  86. }
  87. }
  88. // Free up some memory.
  89. unset($profile_fields);
  90. }
  91. /**
  92. * Load any custom fields for this area.
  93. * No area means load all, 'summary' loads all public ones.
  94. *
  95. * @param int $memID
  96. * @param string $area = 'summary'
  97. */
  98. function loadCustomFields($memID, $area = 'summary')
  99. {
  100. global $context, $txt, $user_profile, $smcFunc, $user_info, $settings, $scripturl;
  101. // Get the right restrictions in place...
  102. $where = 'active = 1';
  103. if (!allowedTo('admin_forum') && $area != 'register')
  104. {
  105. // If it's the owner they can see two types of private fields, regardless.
  106. if ($memID == $user_info['id'])
  107. $where .= $area == 'summary' ? ' AND private < 3' : ' AND (private = 0 OR private = 2)';
  108. else
  109. $where .= $area == 'summary' ? ' AND private < 2' : ' AND private = 0';
  110. }
  111. if ($area == 'register')
  112. $where .= ' AND show_reg != 0';
  113. elseif ($area != 'summary')
  114. $where .= ' AND show_profile = {string:area}';
  115. // Load all the relevant fields - and data.
  116. $request = $smcFunc['db_query']('', '
  117. SELECT
  118. col_name, field_name, field_desc, field_type, show_reg, field_length, field_options,
  119. default_value, bbc, enclose, placement
  120. FROM {db_prefix}custom_fields
  121. WHERE ' . $where,
  122. array(
  123. 'area' => $area,
  124. )
  125. );
  126. $context['custom_fields'] = array();
  127. $context['custom_fields_required'] = false;
  128. while ($row = $smcFunc['db_fetch_assoc']($request))
  129. {
  130. // Shortcut.
  131. $exists = $memID && isset($user_profile[$memID], $user_profile[$memID]['options'][$row['col_name']]);
  132. $value = $exists ? $user_profile[$memID]['options'][$row['col_name']] : '';
  133. // If this was submitted already then make the value the posted version.
  134. if (isset($_POST['customfield']) && isset($_POST['customfield'][$row['col_name']]))
  135. {
  136. $value = $smcFunc['htmlspecialchars']($_POST['customfield'][$row['col_name']]);
  137. if (in_array($row['field_type'], array('select', 'radio')))
  138. $value = ($options = explode(',', $row['field_options'])) && isset($options[$value]) ? $options[$value] : '';
  139. }
  140. // HTML for the input form.
  141. $output_html = $value;
  142. if ($row['field_type'] == 'check')
  143. {
  144. $true = (!$exists && $row['default_value']) || $value;
  145. $input_html = '<input type="checkbox" name="customfield[' . $row['col_name'] . ']" ' . ($true ? 'checked="checked"' : '') . ' class="input_check" />';
  146. $output_html = $true ? $txt['yes'] : $txt['no'];
  147. }
  148. elseif ($row['field_type'] == 'select')
  149. {
  150. $input_html = '<select name="customfield[' . $row['col_name'] . ']"><option value="-1"></option>';
  151. $options = explode(',', $row['field_options']);
  152. foreach ($options as $k => $v)
  153. {
  154. $true = (!$exists && $row['default_value'] == $v) || $value == $v;
  155. $input_html .= '<option value="' . $k . '"' . ($true ? ' selected="selected"' : '') . '>' . $v . '</option>';
  156. if ($true)
  157. $output_html = $v;
  158. }
  159. $input_html .= '</select>';
  160. }
  161. elseif ($row['field_type'] == 'radio')
  162. {
  163. $input_html = '<fieldset>';
  164. $options = explode(',', $row['field_options']);
  165. foreach ($options as $k => $v)
  166. {
  167. $true = (!$exists && $row['default_value'] == $v) || $value == $v;
  168. $input_html .= '<label for="customfield_' . $row['col_name'] . '_' . $k . '"><input type="radio" name="customfield[' . $row['col_name'] . ']" class="input_radio" id="customfield_' . $row['col_name'] . '_' . $k . '" value="' . $k . '" ' . ($true ? 'checked="checked"' : '') . ' />' . $v . '</label><br />';
  169. if ($true)
  170. $output_html = $v;
  171. }
  172. $input_html .= '</fieldset>';
  173. }
  174. elseif ($row['field_type'] == 'text')
  175. {
  176. $input_html = '<input type="text" name="customfield[' . $row['col_name'] . ']" ' . ($row['field_length'] != 0 ? 'maxlength="' . $row['field_length'] . '"' : '') . ' size="' . ($row['field_length'] == 0 || $row['field_length'] >= 50 ? 50 : ($row['field_length'] > 30 ? 30 : ($row['field_length'] > 10 ? 20 : 10))) . '" value="' . $value . '" class="input_text" />';
  177. }
  178. else
  179. {
  180. @list ($rows, $cols) = @explode(',', $row['default_value']);
  181. $input_html = '<textarea name="customfield[' . $row['col_name'] . ']" ' . (!empty($rows) ? 'rows="' . $rows . '"' : '') . ' ' . (!empty($cols) ? 'cols="' . $cols . '"' : '') . '>' . $value . '</textarea>';
  182. }
  183. // Parse BBCode
  184. if ($row['bbc'])
  185. $output_html = parse_bbc($output_html);
  186. elseif ($row['field_type'] == 'textarea')
  187. // Allow for newlines at least
  188. $output_html = strtr($output_html, array("\n" => '<br />'));
  189. // Enclosing the user input within some other text?
  190. if (!empty($row['enclose']) && !empty($output_html))
  191. $output_html = strtr($row['enclose'], array(
  192. '{SCRIPTURL}' => $scripturl,
  193. '{IMAGES_URL}' => $settings['images_url'],
  194. '{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
  195. '{INPUT}' => $output_html,
  196. ));
  197. $context['custom_fields'][] = array(
  198. 'name' => $row['field_name'],
  199. 'desc' => $row['field_desc'],
  200. 'type' => $row['field_type'],
  201. 'input_html' => $input_html,
  202. 'output_html' => $output_html,
  203. 'placement' => $row['placement'],
  204. 'colname' => $row['col_name'],
  205. 'value' => $value,
  206. 'show_reg' => $row['show_reg'],
  207. );
  208. $context['custom_fields_required'] = $context['custom_fields_required'] || $row['show_reg'];
  209. }
  210. $smcFunc['db_free_result']($request);
  211. call_integration_hook('integrate_load_custom_profile_fields', array($memID, $area));
  212. }
  213. /**
  214. * This defines every profile field known to man.
  215. *
  216. * @param bool $force_reload = false
  217. */
  218. function loadProfileFields($force_reload = false)
  219. {
  220. global $context, $profile_fields, $txt, $scripturl, $modSettings, $user_info, $old_profile, $smcFunc, $cur_profile, $language;
  221. // Don't load this twice!
  222. if (!empty($profile_fields) && !$force_reload)
  223. return;
  224. /* This horrific array defines all the profile fields in the whole world!
  225. In general each "field" has one array - the key of which is the database column name associated with said field. Each item
  226. can have the following attributes:
  227. string $type: The type of field this is - valid types are:
  228. - callback: This is a field which has its own callback mechanism for templating.
  229. - check: A simple checkbox.
  230. - hidden: This doesn't have any visual aspects but may have some validity.
  231. - password: A password box.
  232. - select: A select box.
  233. - text: A string of some description.
  234. string $label: The label for this item - default will be $txt[$key] if this isn't set.
  235. string $subtext: The subtext (Small label) for this item.
  236. int $size: Optional size for a text area.
  237. array $input_attr: An array of text strings to be added to the input box for this item.
  238. string $value: The value of the item. If not set $cur_profile[$key] is assumed.
  239. string $permission: Permission required for this item (Excluded _any/_own subfix which is applied automatically).
  240. function $input_validate: A runtime function which validates the element before going to the database. It is passed
  241. the relevant $_POST element if it exists and should be treated like a reference.
  242. Return types:
  243. - true: Element can be stored.
  244. - false: Skip this element.
  245. - a text string: An error occured - this is the error message.
  246. function $preload: A function that is used to load data required for this element to be displayed. Must return
  247. true to be displayed at all.
  248. string $cast_type: If set casts the element to a certain type. Valid types (bool, int, float).
  249. string $save_key: If the index of this element isn't the database column name it can be overriden
  250. with this string.
  251. bool $is_dummy: If set then nothing is acted upon for this element.
  252. bool $enabled: A test to determine whether this is even available - if not is unset.
  253. string $link_with: Key which links this field to an overall set.
  254. Note that all elements that have a custom input_validate must ensure they set the value of $cur_profile correct to enable
  255. the changes to be displayed correctly on submit of the form.
  256. */
  257. $profile_fields = array(
  258. 'avatar_choice' => array(
  259. 'type' => 'callback',
  260. 'callback_func' => 'avatar_select',
  261. // This handles the permissions too.
  262. 'preload' => 'profileLoadAvatarData',
  263. 'input_validate' => 'profileSaveAvatarData',
  264. 'save_key' => 'avatar',
  265. ),
  266. 'bday1' => array(
  267. 'type' => 'callback',
  268. 'callback_func' => 'birthdate',
  269. 'permission' => 'profile_extra',
  270. 'preload' => create_function('', '
  271. global $cur_profile, $context;
  272. // Split up the birthdate....
  273. list ($uyear, $umonth, $uday) = explode(\'-\', empty($cur_profile[\'birthdate\']) || $cur_profile[\'birthdate\'] == \'0001-01-01\' ? \'0000-00-00\' : $cur_profile[\'birthdate\']);
  274. $context[\'member\'][\'birth_date\'] = array(
  275. \'year\' => $uyear == \'0004\' ? \'0000\' : $uyear,
  276. \'month\' => $umonth,
  277. \'day\' => $uday,
  278. );
  279. return true;
  280. '),
  281. 'input_validate' => create_function('&$value', '
  282. global $profile_vars, $cur_profile;
  283. if (isset($_POST[\'bday2\'], $_POST[\'bday3\']) && $value > 0 && $_POST[\'bday2\'] > 0)
  284. {
  285. // Set to blank?
  286. if ((int) $_POST[\'bday3\'] == 1 && (int) $_POST[\'bday2\'] == 1 && (int) $value == 1)
  287. $value = \'0001-01-01\';
  288. else
  289. $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\';
  290. }
  291. else
  292. $value = \'0001-01-01\';
  293. $profile_vars[\'birthdate\'] = $value;
  294. $cur_profile[\'birthdate\'] = $value;
  295. return false;
  296. '),
  297. ),
  298. // Setting the birthdate the old style way?
  299. 'birthdate' => array(
  300. 'type' => 'hidden',
  301. 'permission' => 'profile_extra',
  302. 'input_validate' => create_function('&$value', '
  303. global $cur_profile;
  304. // @todo Should we check for this year and tell them they made a mistake :P? (based on coppa at least?)
  305. if (preg_match(\'/(\d{4})[\-\., ](\d{2})[\-\., ](\d{2})/\', $value, $dates) === 1)
  306. {
  307. $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\';
  308. return true;
  309. }
  310. else
  311. {
  312. $value = empty($cur_profile[\'birthdate\']) ? \'0001-01-01\' : $cur_profile[\'birthdate\'];
  313. return false;
  314. }
  315. '),
  316. ),
  317. 'date_registered' => array(
  318. 'type' => 'text',
  319. '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),
  320. 'label' => $txt['date_registered'],
  321. 'log_change' => true,
  322. 'permission' => 'moderate_forum',
  323. 'input_validate' => create_function('&$value', '
  324. global $txt, $user_info, $modSettings, $cur_profile, $context;
  325. // Bad date! Go try again - please?
  326. if (($value = strtotime($value)) === -1)
  327. {
  328. $value = $cur_profile[\'date_registered\'];
  329. 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));
  330. }
  331. // As long as it doesn\'t equal "N/A"...
  332. elseif ($value != $txt[\'not_applicable\'] && $value != strtotime(strftime(\'%Y-%m-%d\', $cur_profile[\'date_registered\'] + ($user_info[\'time_offset\'] + $modSettings[\'time_offset\']) * 3600)))
  333. $value = $value - ($user_info[\'time_offset\'] + $modSettings[\'time_offset\']) * 3600;
  334. else
  335. $value = $cur_profile[\'date_registered\'];
  336. return true;
  337. '),
  338. ),
  339. 'email_address' => array(
  340. 'type' => 'text',
  341. 'label' => $txt['user_email_address'],
  342. 'subtext' => $txt['valid_email'],
  343. 'log_change' => true,
  344. 'permission' => 'profile_identity',
  345. 'input_validate' => create_function('&$value', '
  346. global $context, $old_profile, $context, $profile_vars, $modSettings;
  347. if (strtolower($value) == strtolower($old_profile[\'email_address\']))
  348. return false;
  349. $isValid = profileValidateEmail($value, $context[\'id_member\']);
  350. // Do they need to revalidate? If so schedule the function!
  351. if ($isValid === true && !empty($modSettings[\'send_validation_onChange\']) && !allowedTo(\'moderate_forum\'))
  352. {
  353. require_once(SUBSDIR . \'/Members.subs.php\');
  354. $profile_vars[\'validation_code\'] = generateValidationCode();
  355. $profile_vars[\'is_activated\'] = 2;
  356. $context[\'profile_execute_on_save\'][] = \'profileSendActivation\';
  357. unset($context[\'profile_execute_on_save\'][\'reload_user\']);
  358. }
  359. return $isValid;
  360. '),
  361. ),
  362. 'gender' => array(
  363. 'type' => 'select',
  364. 'cast_type' => 'int',
  365. 'options' => 'return array(0 => \'\', 1 => $txt[\'male\'], 2 => $txt[\'female\']);',
  366. 'label' => $txt['gender'],
  367. 'permission' => 'profile_extra',
  368. ),
  369. 'hide_email' => array(
  370. 'type' => 'check',
  371. 'value' => empty($cur_profile['hide_email']) ? true : false,
  372. 'label' => $txt['allow_user_email'],
  373. 'permission' => 'profile_identity',
  374. 'input_validate' => create_function('&$value', '
  375. $value = $value == 0 ? 1 : 0;
  376. return true;
  377. '),
  378. ),
  379. // Selecting group membership is a complicated one so we treat it separate!
  380. 'id_group' => array(
  381. 'type' => 'callback',
  382. 'callback_func' => 'group_manage',
  383. 'permission' => 'manage_membergroups',
  384. 'preload' => 'profileLoadGroups',
  385. 'log_change' => true,
  386. 'input_validate' => 'profileSaveGroups',
  387. ),
  388. 'id_theme' => array(
  389. 'type' => 'callback',
  390. 'callback_func' => 'theme_pick',
  391. 'permission' => 'profile_extra',
  392. 'enabled' => $modSettings['theme_allow'] || allowedTo('admin_forum'),
  393. 'preload' => create_function('', '
  394. global $smcFunc, $context, $cur_profile, $txt;
  395. $request = $smcFunc[\'db_query\'](\'\', \'
  396. SELECT value
  397. FROM {db_prefix}themes
  398. WHERE id_theme = {int:id_theme}
  399. AND variable = {string:variable}
  400. LIMIT 1\', array(
  401. \'id_theme\' => $cur_profile[\'id_theme\'],
  402. \'variable\' => \'name\',
  403. )
  404. );
  405. list ($name) = $smcFunc[\'db_fetch_row\']($request);
  406. $smcFunc[\'db_free_result\']($request);
  407. $context[\'member\'][\'theme\'] = array(
  408. \'id\' => $cur_profile[\'id_theme\'],
  409. \'name\' => empty($cur_profile[\'id_theme\']) ? $txt[\'theme_forum_default\'] : $name
  410. );
  411. return true;
  412. '),
  413. 'input_validate' => create_function('&$value', '
  414. $value = (int) $value;
  415. return true;
  416. '),
  417. ),
  418. 'karma_good' => array(
  419. 'type' => 'callback',
  420. 'callback_func' => 'karma_modify',
  421. 'permission' => 'admin_forum',
  422. // Set karma_bad too!
  423. 'input_validate' => create_function('&$value', '
  424. global $profile_vars, $cur_profile;
  425. $value = (int) $value;
  426. if (isset($_POST[\'karma_bad\']))
  427. {
  428. $profile_vars[\'karma_bad\'] = $_POST[\'karma_bad\'] != \'\' ? (int) $_POST[\'karma_bad\'] : 0;
  429. $cur_profile[\'karma_bad\'] = $_POST[\'karma_bad\'] != \'\' ? (int) $_POST[\'karma_bad\'] : 0;
  430. }
  431. return true;
  432. '),
  433. 'preload' => create_function('', '
  434. global $context, $cur_profile;
  435. $context[\'member\'][\'karma\'][\'good\'] = $cur_profile[\'karma_good\'];
  436. $context[\'member\'][\'karma\'][\'bad\'] = $cur_profile[\'karma_bad\'];
  437. return true;
  438. '),
  439. 'enabled' => !empty($modSettings['karmaMode']),
  440. ),
  441. 'lngfile' => array(
  442. 'type' => 'select',
  443. 'options' => 'return $context[\'profile_languages\'];',
  444. 'label' => $txt['preferred_language'],
  445. 'permission' => 'profile_identity',
  446. 'preload' => 'profileLoadLanguages',
  447. 'enabled' => !empty($modSettings['userLanguage']),
  448. 'value' => empty($cur_profile['lngfile']) ? $language : $cur_profile['lngfile'],
  449. 'input_validate' => create_function('&$value', '
  450. global $context, $cur_profile;
  451. // Load the languages.
  452. profileLoadLanguages();
  453. if (isset($context[\'profile_languages\'][$value]))
  454. {
  455. if ($context[\'user\'][\'is_owner\'] && empty($context[\'password_auth_failed\']))
  456. $_SESSION[\'language\'] = $value;
  457. return true;
  458. }
  459. else
  460. {
  461. $value = $cur_profile[\'lngfile\'];
  462. return false;
  463. }
  464. '),
  465. ),
  466. 'location' => array(
  467. 'type' => 'text',
  468. 'label' => $txt['location'],
  469. 'log_change' => true,
  470. 'size' => 50,
  471. 'permission' => 'profile_extra',
  472. ),
  473. // The username is not always editable - so adjust it as such.
  474. 'member_name' => array(
  475. 'type' => allowedTo('admin_forum') && isset($_GET['changeusername']) ? 'text' : 'label',
  476. 'label' => $txt['username'],
  477. '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>]' : '',
  478. 'log_change' => true,
  479. 'permission' => 'profile_identity',
  480. 'prehtml' => allowedTo('admin_forum') && isset($_GET['changeusername']) ? '<div class="alert">' . $txt['username_warning'] . '</div>' : '',
  481. 'input_validate' => create_function('&$value', '
  482. global $context, $user_info, $cur_profile;
  483. if (allowedTo(\'admin_forum\'))
  484. {
  485. // We\'ll need this...
  486. require_once(SUBSDIR . \'/Auth.subs.php\');
  487. // Maybe they are trying to change their password as well?
  488. $resetPassword = true;
  489. 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)
  490. $resetPassword = false;
  491. // Do the reset... this will send them an email too.
  492. if ($resetPassword)
  493. resetPassword($context[\'id_member\'], $value);
  494. elseif ($value !== null)
  495. {
  496. validateUsername($context[\'id_member\'], $value);
  497. updateMemberData($context[\'id_member\'], array(\'member_name\' => $value));
  498. }
  499. }
  500. return false;
  501. '),
  502. ),
  503. 'passwrd1' => array(
  504. 'type' => 'password',
  505. 'label' => ucwords($txt['choose_pass']),
  506. 'subtext' => $txt['password_strength'],
  507. 'size' => 20,
  508. 'value' => '',
  509. 'enabled' => empty($cur_profile['openid_uri']),
  510. 'permission' => 'profile_identity',
  511. 'save_key' => 'passwd',
  512. // Note this will only work if passwrd2 also exists!
  513. 'input_validate' => create_function('&$value', '
  514. global $user_info, $smcFunc, $cur_profile;
  515. // If we didn\'t try it then ignore it!
  516. if ($value == \'\')
  517. return false;
  518. // Do the two entries for the password even match?
  519. if (!isset($_POST[\'passwrd2\']) || $value != $_POST[\'passwrd2\'])
  520. return \'bad_new_password\';
  521. // Let\'s get the validation function into play...
  522. require_once(SUBSDIR . \'/Auth.subs.php\');
  523. $passwordErrors = validatePassword($value, $cur_profile[\'member_name\'], array($cur_profile[\'real_name\'], $user_info[\'username\'], $user_info[\'name\'], $user_info[\'email\']));
  524. // Were there errors?
  525. if ($passwordErrors != null)
  526. return \'password_\' . $passwordErrors;
  527. // Set up the new password variable... ready for storage.
  528. $value = sha1(strtolower($cur_profile[\'member_name\']) . un_htmlspecialchars($value));
  529. return true;
  530. '),
  531. ),
  532. 'passwrd2' => array(
  533. 'type' => 'password',
  534. 'label' => ucwords($txt['verify_pass']),
  535. 'enabled' => empty($cur_profile['openid_uri']),
  536. 'size' => 20,
  537. 'value' => '',
  538. 'permission' => 'profile_identity',
  539. 'is_dummy' => true,
  540. ),
  541. 'personal_text' => array(
  542. 'type' => 'text',
  543. 'label' => $txt['personal_text'],
  544. 'log_change' => true,
  545. 'input_attr' => array('maxlength="50"'),
  546. 'size' => 50,
  547. 'permission' => 'profile_extra',
  548. 'input_validate' => create_function('&$value', '
  549. global $smcFunc;
  550. if ($smcFunc[\'strlen\']($value) > 50)
  551. return \'personal_text_too_long\';
  552. return true;
  553. '),
  554. ),
  555. // This does ALL the pm settings
  556. 'pm_prefs' => array(
  557. 'type' => 'callback',
  558. 'callback_func' => 'pm_settings',
  559. 'permission' => 'pm_read',
  560. 'preload' => create_function('', '
  561. global $context, $cur_profile;
  562. $context[\'display_mode\'] = $cur_profile[\'pm_prefs\'] & 3;
  563. $context[\'send_email\'] = $cur_profile[\'pm_email_notify\'];
  564. $context[\'receive_from\'] = !empty($cur_profile[\'pm_receive_from\']) ? $cur_profile[\'pm_receive_from\'] : 0;
  565. return true;
  566. '),
  567. 'input_validate' => create_function('&$value', '
  568. global $cur_profile, $profile_vars;
  569. // Simple validate and apply the two "sub settings"
  570. $value = max(min($value, 2), 0);
  571. $cur_profile[\'pm_email_notify\'] = $profile_vars[\'pm_email_notify\'] = max(min((int) $_POST[\'pm_email_notify\'], 2), 0);
  572. $cur_profile[\'pm_receive_from\'] = $profile_vars[\'pm_receive_from\'] = max(min((int) $_POST[\'pm_receive_from\'], 4), 0);
  573. return true;
  574. '),
  575. ),
  576. 'posts' => array(
  577. 'type' => 'int',
  578. 'label' => $txt['profile_posts'],
  579. 'log_change' => true,
  580. 'size' => 7,
  581. 'permission' => 'moderate_forum',
  582. 'input_validate' => create_function('&$value', '
  583. if (!is_numeric($value))
  584. return \'digits_only\';
  585. else
  586. $value = $value != \'\' ? strtr($value, array(\',\' => \'\', \'.\' => \'\', \' \' => \'\')) : 0;
  587. return true;
  588. '),
  589. ),
  590. 'real_name' => array(
  591. 'type' => !empty($modSettings['allow_editDisplayName']) || allowedTo('moderate_forum') ? 'text' : 'label',
  592. 'label' => $txt['name'],
  593. 'subtext' => $txt['display_name_desc'],
  594. 'log_change' => true,
  595. 'input_attr' => array('maxlength="60"'),
  596. 'permission' => 'profile_identity',
  597. 'enabled' => !empty($modSettings['allow_editDisplayName']) || allowedTo('moderate_forum'),
  598. 'input_validate' => create_function('&$value', '
  599. global $context, $smcFunc, $cur_profile;
  600. $value = trim(preg_replace(\'~[\s]~u\', \' \', $value));
  601. if (trim($value) == \'\')
  602. return \'no_name\';
  603. elseif ($smcFunc[\'strlen\']($value) > 60)
  604. return \'name_too_long\';
  605. elseif ($cur_profile[\'real_name\'] != $value)
  606. {
  607. require_once(SUBSDIR . \'/Members.subs.php\');
  608. if (isReservedName($value, $context[\'id_member\']))
  609. return \'name_taken\';
  610. }
  611. return true;
  612. '),
  613. ),
  614. 'secret_question' => array(
  615. 'type' => 'text',
  616. 'label' => $txt['secret_question'],
  617. 'subtext' => $txt['secret_desc'],
  618. 'size' => 50,
  619. 'permission' => 'profile_identity',
  620. ),
  621. 'secret_answer' => array(
  622. 'type' => 'text',
  623. 'label' => $txt['secret_answer'],
  624. 'subtext' => $txt['secret_desc2'],
  625. 'size' => 20,
  626. 'postinput' => '<span class="smalltext" style="margin-left: 4ex;">[<a href="' . $scripturl . '?action=quickhelp;help=secret_why_blank" onclick="return reqOverlayDiv(this.href);">' . $txt['secret_why_blank'] . '</a>]</span>',
  627. 'value' => '',
  628. 'permission' => 'profile_identity',
  629. 'input_validate' => create_function('&$value', '
  630. $value = $value != \'\' ? md5($value) : \'\';
  631. return true;
  632. '),
  633. ),
  634. 'signature' => array(
  635. 'type' => 'callback',
  636. 'callback_func' => 'signature_modify',
  637. 'permission' => 'profile_extra',
  638. 'enabled' => substr($modSettings['signature_settings'], 0, 1) == 1,
  639. 'preload' => 'profileLoadSignatureData',
  640. 'input_validate' => 'profileValidateSignature',
  641. ),
  642. 'show_online' => array(
  643. 'type' => 'check',
  644. 'label' => $txt['show_online'],
  645. 'permission' => 'profile_identity',
  646. 'enabled' => !empty($modSettings['allow_hideOnline']) || allowedTo('moderate_forum'),
  647. ),
  648. 'smiley_set' => array(
  649. 'type' => 'callback',
  650. 'callback_func' => 'smiley_pick',
  651. 'enabled' => !empty($modSettings['smiley_sets_enable']),
  652. 'permission' => 'profile_extra',
  653. 'preload' => create_function('', '
  654. global $modSettings, $context, $txt, $cur_profile;
  655. $context[\'member\'][\'smiley_set\'][\'id\'] = empty($cur_profile[\'smiley_set\']) ? \'\' : $cur_profile[\'smiley_set\'];
  656. $context[\'smiley_sets\'] = explode(\',\', \'none,,\' . $modSettings[\'smiley_sets_known\']);
  657. $set_names = explode("\n", $txt[\'smileys_none\'] . "\n" . $txt[\'smileys_forum_board_default\'] . "\n" . $modSettings[\'smiley_sets_names\']);
  658. foreach ($context[\'smiley_sets\'] as $i => $set)
  659. {
  660. $context[\'smiley_sets\'][$i] = array(
  661. \'id\' => htmlspecialchars($set),
  662. \'name\' => htmlspecialchars($set_names[$i]),
  663. \'selected\' => $set == $context[\'member\'][\'smiley_set\'][\'id\']
  664. );
  665. if ($context[\'smiley_sets\'][$i][\'selected\'])
  666. $context[\'member\'][\'smiley_set\'][\'name\'] = $set_names[$i];
  667. }
  668. return true;
  669. '),
  670. 'input_validate' => create_function('&$value', '
  671. global $modSettings;
  672. $smiley_sets = explode(\',\', $modSettings[\'smiley_sets_known\']);
  673. if (!in_array($value, $smiley_sets) && $value != \'none\')
  674. $value = \'\';
  675. return true;
  676. '),
  677. ),
  678. // Pretty much a dummy entry - it populates all the theme settings.
  679. 'theme_settings' => array(
  680. 'type' => 'callback',
  681. 'callback_func' => 'theme_settings',
  682. 'permission' => 'profile_extra',
  683. 'is_dummy' => true,
  684. 'preload' => create_function('', '
  685. global $context, $user_info;
  686. loadLanguage(\'Settings\');
  687. $context[\'allow_no_censored\'] = false;
  688. if ($user_info[\'is_admin\'] || $context[\'user\'][\'is_owner\'])
  689. $context[\'allow_no_censored\'] = allowedTo(\'disable_censor\');
  690. return true;
  691. '),
  692. ),
  693. 'time_format' => array(
  694. 'type' => 'callback',
  695. 'callback_func' => 'timeformat_modify',
  696. 'permission' => 'profile_extra',
  697. 'preload' => create_function('', '
  698. global $context, $user_info, $txt, $cur_profile, $modSettings;
  699. $context[\'easy_timeformats\'] = array(
  700. array(\'format\' => \'\', \'title\' => $txt[\'timeformat_default\']),
  701. array(\'format\' => \'%B %d, %Y, %I:%M:%S %p\', \'title\' => $txt[\'timeformat_easy1\']),
  702. array(\'format\' => \'%B %d, %Y, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy2\']),
  703. array(\'format\' => \'%Y-%m-%d, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy3\']),
  704. array(\'format\' => \'%d %B %Y, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy4\']),
  705. array(\'format\' => \'%d-%m-%Y, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy5\'])
  706. );
  707. $context[\'member\'][\'time_format\'] = $cur_profile[\'time_format\'];
  708. $context[\'current_forum_time\'] = timeformat(time() - $user_info[\'time_offset\'] * 3600, false);
  709. $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);
  710. $context[\'current_forum_time_hour\'] = (int) strftime(\'%H\', forum_time(false));
  711. return true;
  712. '),
  713. ),
  714. 'time_offset' => array(
  715. 'type' => 'callback',
  716. 'callback_func' => 'timeoffset_modify',
  717. 'permission' => 'profile_extra',
  718. 'preload' => create_function('', '
  719. global $context, $cur_profile;
  720. $context[\'member\'][\'time_offset\'] = $cur_profile[\'time_offset\'];
  721. return true;
  722. '),
  723. 'input_validate' => create_function('&$value', '
  724. // Validate the time_offset...
  725. $value = (float) strtr($value, \',\', \'.\');
  726. if ($value < -23.5 || $value > 23.5)
  727. return \'bad_offset\';
  728. return true;
  729. '),
  730. ),
  731. 'usertitle' => array(
  732. 'type' => 'text',
  733. 'label' => $txt['custom_title'],
  734. 'log_change' => true,
  735. 'input_attr' => array('maxlength="50"'),
  736. 'size' => 50,
  737. 'permission' => 'profile_title',
  738. 'enabled' => !empty($modSettings['titlesEnable']),
  739. 'input_validate' => create_function('&$value', '
  740. global $smcFunc;
  741. if ($smcFunc[\'strlen\'] > 50)
  742. return \'user_title_too_long\';
  743. return true;
  744. '),
  745. ),
  746. 'website_title' => array(
  747. 'type' => 'text',
  748. 'label' => $txt['website_title'],
  749. 'subtext' => $txt['include_website_url'],
  750. 'size' => 50,
  751. 'permission' => 'profile_extra',
  752. 'link_with' => 'website',
  753. ),
  754. 'website_url' => array(
  755. 'type' => 'text',
  756. 'label' => $txt['website_url'],
  757. 'subtext' => $txt['complete_url'],
  758. 'size' => 50,
  759. 'permission' => 'profile_extra',
  760. // Fix the URL...
  761. 'input_validate' => create_function('&$value', '
  762. if (strlen(trim($value)) > 0 && strpos($value, \'://\') === false)
  763. $value = \'http://\' . $value;
  764. if (strlen($value) < 8 || (substr($value, 0, 7) !== \'http://\' && substr($value, 0, 8) !== \'https://\'))
  765. $value = \'\';
  766. return true;
  767. '),
  768. 'link_with' => 'website',
  769. ),
  770. );
  771. call_integration_hook('integrate_load_profile_fields', array($profile_fields));
  772. $disabled_fields = !empty($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array();
  773. // For each of the above let's take out the bits which don't apply - to save memory and security!
  774. foreach ($profile_fields as $key => $field)
  775. {
  776. // Do we have permission to do this?
  777. if (isset($field['permission']) && !allowedTo(($context['user']['is_owner'] ? array($field['permission'] . '_own', $field['permission'] . '_any') : $field['permission'] . '_any')) && !allowedTo($field['permission']))
  778. unset($profile_fields[$key]);
  779. // Is it enabled?
  780. if (isset($field['enabled']) && !$field['enabled'])
  781. unset($profile_fields[$key]);
  782. // Is it specifically disabled?
  783. if (in_array($key, $disabled_fields) || (isset($field['link_with']) && in_array($field['link_with'], $disabled_fields)))
  784. unset($profile_fields[$key]);
  785. }
  786. }
  787. /**
  788. * Save the profile changes.
  789. */
  790. function saveProfileFields()
  791. {
  792. global $profile_fields, $profile_vars, $context, $old_profile;
  793. global $post_errors, $modSettings, $cur_profile, $smcFunc;
  794. // Load them up.
  795. loadProfileFields();
  796. // This makes things easier...
  797. $old_profile = $cur_profile;
  798. // This allows variables to call activities when they save
  799. // - by default just to reload their settings
  800. $context['profile_execute_on_save'] = array();
  801. if ($context['user']['is_owner'])
  802. $context['profile_execute_on_save']['reload_user'] = 'profileReloadUser';
  803. // Assume we log nothing.
  804. $context['log_changes'] = array();
  805. // Cycle through the profile fields working out what to do!
  806. foreach ($profile_fields as $key => $field)
  807. {
  808. if (!isset($_POST[$key]) || !empty($field['is_dummy']) || (isset($_POST['preview_signature']) && $key == 'signature'))
  809. continue;
  810. // What gets updated?
  811. $db_key = isset($field['save_key']) ? $field['save_key'] : $key;
  812. // Right - we have something that is enabled, we can act upon and has a value posted to it. Does it have a validation function?
  813. if (isset($field['input_validate']))
  814. {
  815. $is_valid = $field['input_validate']($_POST[$key]);
  816. // An error occured - set it as such!
  817. if ($is_valid !== true)
  818. {
  819. // Is this an actual error?
  820. if ($is_valid !== false)
  821. {
  822. $post_errors[$key] = $is_valid;
  823. $profile_fields[$key]['is_error'] = $is_valid;
  824. }
  825. // Retain the old value.
  826. $cur_profile[$key] = $_POST[$key];
  827. continue;
  828. }
  829. }
  830. // Are we doing a cast?
  831. $field['cast_type'] = empty($field['cast_type']) ? $field['type'] : $field['cast_type'];
  832. // Finally, clean up certain types.
  833. if ($field['cast_type'] == 'int')
  834. $_POST[$key] = (int) $_POST[$key];
  835. elseif ($field['cast_type'] == 'float')
  836. $_POST[$key] = (float) $_POST[$key];
  837. elseif ($field['cast_type'] == 'check')
  838. $_POST[$key] = !empty($_POST[$key]) ? 1 : 0;
  839. // If we got here we're doing OK.
  840. if ($field['type'] != 'hidden' && (!isset($old_profile[$key]) || $_POST[$key] != $old_profile[$key]))
  841. {
  842. // Set the save variable.
  843. $profile_vars[$db_key] = $_POST[$key];
  844. // And update the user profile.
  845. $cur_profile[$key] = $_POST[$key];
  846. // Are we logging it?
  847. if (!empty($field['log_change']) && isset($old_profile[$key]))
  848. $context['log_changes'][$key] = array(
  849. 'previous' => $old_profile[$key],
  850. 'new' => $_POST[$key],
  851. );
  852. }
  853. // Logging group changes are a bit different...
  854. if ($key == 'id_group' && $field['log_change'])
  855. {
  856. profileLoadGroups();
  857. // Any changes to primary group?
  858. if ($_POST['id_group'] != $old_profile['id_group'])
  859. {
  860. $context['log_changes']['id_group'] = array(
  861. 'previous' => !empty($old_profile[$key]) && isset($context['member_groups'][$old_profile[$key]]) ? $context['member_groups'][$old_profile[$key]]['name'] : '',
  862. 'new' => !empty($_POST[$key]) && isset($context['member_groups'][$_POST[$key]]) ? $context['member_groups'][$_POST[$key]]['name'] : '',
  863. );
  864. }
  865. // Prepare additional groups for comparison.
  866. $additional_groups = array(
  867. 'previous' => !empty($old_profile['additional_groups']) ? explode(',', $old_profile['additional_groups']) : array(),
  868. 'new' => !empty($_POST['additional_groups']) ? array_diff($_POST['additional_groups'], array(0)) : array(),
  869. );
  870. sort($additional_groups['previous']);
  871. sort($additional_groups['new']);
  872. // What about additional groups?
  873. if ($additional_groups['previous'] != $additional_groups['new'])
  874. {
  875. foreach ($additional_groups as $type => $groups)
  876. {
  877. foreach ($groups as $id => $group)
  878. {
  879. if (isset($context['member_groups'][$group]))
  880. $additional_groups[$type][$id] = $context['member_groups'][$group]['name'];
  881. else
  882. unset($additional_groups[$type][$id]);
  883. }
  884. $additional_groups[$type] = implode(', ', $additional_groups[$type]);
  885. }
  886. $context['log_changes']['additional_groups'] = $additional_groups;
  887. }
  888. }
  889. }
  890. // @todo Temporary
  891. if ($context['user']['is_owner'])
  892. $changeOther = allowedTo(array('profile_extra_any', 'profile_extra_own'));
  893. else
  894. $changeOther = allowedTo('profile_extra_any');
  895. if ($changeOther && empty($post_errors))
  896. {
  897. makeThemeChanges($context['id_member'], isset($_POST['id_theme']) ? (int) $_POST['id_theme'] : $old_profile['id_theme']);
  898. if (!empty($_REQUEST['sa']))
  899. makeCustomFieldChanges($context['id_member'], $_REQUEST['sa'], false);
  900. }
  901. // Free memory!
  902. unset($profile_fields);
  903. }
  904. /**
  905. * Validate an email address.
  906. *
  907. * @param string $email
  908. * @param int $memID = 0
  909. * @return boolean|string
  910. */
  911. function profileValidateEmail($email, $memID = 0)
  912. {
  913. global $smcFunc, $context;
  914. $email = strtr($email, array('&#039;' => '\''));
  915. // Check the name and email for validity.
  916. if (trim($email) == '')
  917. return 'no_email';
  918. if (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $email) == 0)
  919. return 'bad_email';
  920. // Email addresses should be and stay unique.
  921. $request = $smcFunc['db_query']('', '
  922. SELECT id_member
  923. FROM {db_prefix}members
  924. WHERE ' . ($memID != 0 ? 'id_member != {int:selected_member} AND ' : '') . '
  925. email_address = {string:email_address}
  926. LIMIT 1',
  927. array(
  928. 'selected_member' => $memID,
  929. 'email_address' => $email,
  930. )
  931. );
  932. if ($smcFunc['db_num_rows']($request) > 0)
  933. return 'email_taken';
  934. $smcFunc['db_free_result']($request);
  935. return true;
  936. }
  937. /**
  938. * Save the profile changes
  939. *
  940. * @param array &$profile_variables
  941. * @param array &$post_errors
  942. * @param int $memID id_member
  943. */
  944. function saveProfileChanges(&$profile_vars, &$post_errors, $memID)
  945. {
  946. global $user_info, $txt, $modSettings, $user_profile;
  947. global $context, $settings;
  948. global $smcFunc;
  949. // These make life easier....
  950. $old_profile = &$user_profile[$memID];
  951. // Permissions...
  952. if ($context['user']['is_owner'])
  953. {
  954. $changeIdentity = allowedTo(array('profile_identity_any', 'profile_identity_own'));
  955. $changeOther = allowedTo(array('profile_extra_any', 'profile_extra_own'));
  956. }
  957. else
  958. {
  959. $changeIdentity = allowedTo('profile_identity_any');
  960. $changeOther = allowedTo('profile_extra_any');
  961. }
  962. // Arrays of all the changes - makes things easier.
  963. $profile_bools = array(
  964. 'notify_announcements', 'notify_send_body',
  965. );
  966. $profile_ints = array(
  967. 'notify_regularity',
  968. 'notify_types',
  969. );
  970. $profile_floats = array(
  971. );
  972. $profile_strings = array(
  973. 'buddy_list',
  974. 'ignore_boards',
  975. );
  976. if (isset($_POST['sa']) && $_POST['sa'] == 'ignoreboards' && empty($_POST['ignore_brd']))
  977. $_POST['ignore_brd'] = array();
  978. unset($_POST['ignore_boards']); // Whatever it is set to is a dirty fithy thing. Kinda like our minds.
  979. if (isset($_POST['ignore_brd']))
  980. {
  981. if (!is_array($_POST['ignore_brd']))
  982. $_POST['ignore_brd'] = array ($_POST['ignore_brd']);
  983. foreach ($_POST['ignore_brd'] as $k => $d)
  984. {
  985. $d = (int) $d;
  986. if ($d != 0)
  987. $_POST['ignore_brd'][$k] = $d;
  988. else
  989. unset($_POST['ignore_brd'][$k]);
  990. }
  991. $_POST['ignore_boards'] = implode(',', $_POST['ignore_brd']);
  992. unset($_POST['ignore_brd']);
  993. }
  994. // Here's where we sort out all the 'other' values...
  995. if ($changeOther)
  996. {
  997. makeThemeChanges($memID, isset($_POST['id_theme']) ? (int) $_POST['id_theme'] : $old_profile['id_theme']);
  998. //makeAvatarChanges($memID, $post_errors);
  999. makeNotificationChanges($memID);
  1000. if (!empty($_REQUEST['sa']))
  1001. makeCustomFieldChanges($memID, $_REQUEST['sa'], false);
  1002. foreach ($profile_bools as $var)
  1003. if (isset($_POST[$var]))
  1004. $profile_vars[$var] = empty($_POST[$var]) ? '0' : '1';
  1005. foreach ($profile_ints as $var)
  1006. if (isset($_POST[$var]))
  1007. $profile_vars[$var] = $_POST[$var] != '' ? (int) $_POST[$var] : '';
  1008. foreach ($profile_floats as $var)
  1009. if (isset($_POST[$var]))
  1010. $profile_vars[$var] = (float) $_POST[$var];
  1011. foreach ($profile_strings as $var)
  1012. if (isset($_POST[$var]))
  1013. $profile_vars[$var] = $_POST[$var];
  1014. }
  1015. }
  1016. /**
  1017. * Make any theme changes that are sent with the profile.
  1018. *
  1019. * @param int $memID
  1020. * @param int $id_theme
  1021. */
  1022. function makeThemeChanges($memID, $id_theme)
  1023. {
  1024. global $modSettings, $smcFunc, $context, $user_info;
  1025. $reservedVars = array(
  1026. 'actual_theme_url',
  1027. 'actual_images_url',
  1028. 'base_theme_dir',
  1029. 'base_theme_url',
  1030. 'default_images_url',
  1031. 'default_theme_dir',
  1032. 'default_theme_url',
  1033. 'default_template',
  1034. 'images_url',
  1035. 'number_recent_posts',
  1036. 'smiley_sets_default',
  1037. 'theme_dir',
  1038. 'theme_id',
  1039. 'theme_layers',
  1040. 'theme_templates',
  1041. 'theme_url',
  1042. );
  1043. // Can't change reserved vars.
  1044. if ((isset($_POST['options']) && count(array_intersect(array_keys($_POST['options']), $reservedVars)) != 0) || (isset($_POST['default_options']) && count(array_intersect(array_keys($_POST['default_options']), $reservedVars)) != 0))
  1045. fatal_lang_error('no_access', false);
  1046. // Don't allow any overriding of custom fields with default or non-default options.
  1047. $request = $smcFunc['db_query']('', '
  1048. SELECT col_name
  1049. FROM {db_prefix}custom_fields
  1050. WHERE active = {int:is_active}',
  1051. array(
  1052. 'is_active' => 1,
  1053. )
  1054. );
  1055. $custom_fields = array();
  1056. while ($row = $smcFunc['db_fetch_assoc']($request))
  1057. $custom_fields[] = $row['col_name'];
  1058. $smcFunc['db_free_result']($request);
  1059. // These are the theme changes...
  1060. $themeSetArray = array();
  1061. if (isset($_POST['options']) && is_array($_POST['options']))
  1062. {
  1063. foreach ($_POST['options'] as $opt => $val)
  1064. {
  1065. if (in_array($opt, $custom_fields))
  1066. continue;
  1067. // These need to be controlled.
  1068. if ($opt == 'topics_per_page' || $opt == 'messages_per_page')
  1069. $val = max(0, min($val, 50));
  1070. // We don't set this per theme anymore.
  1071. elseif ($opt == 'allow_no_censored')
  1072. continue;
  1073. $themeSetArray[] = array($memID, $id_theme, $opt, is_array($val) ? implode(',', $val) : $val);
  1074. }
  1075. }
  1076. $erase_options = array();
  1077. if (isset($_POST['default_options']) && is_array($_POST['default_options']))
  1078. foreach ($_POST['default_options'] as $opt => $val)
  1079. {
  1080. if (in_array($opt, $custom_fields))
  1081. continue;
  1082. // These need to be controlled.
  1083. if ($opt == 'topics_per_page' || $opt == 'messages_per_page')
  1084. $val = max(0, min($val, 50));
  1085. // Only let admins and owners change the censor.
  1086. elseif ($opt == 'allow_no_censored' && !$user_info['is_admin'] && !$context['user']['is_owner'])
  1087. continue;
  1088. $themeSetArray[] = array($memID, 1, $opt, is_array($val) ? implode(',', $val) : $val);
  1089. $erase_options[] = $opt;
  1090. }
  1091. // If themeSetArray isn't still empty, send it to the database.
  1092. if (empty($context['password_auth_failed']))
  1093. {
  1094. if (!empty($themeSetArray))
  1095. {
  1096. $smcFunc['db_insert']('replace',
  1097. '{db_prefix}themes',
  1098. array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
  1099. $themeSetArray,
  1100. array('id_member', 'id_theme', 'variable')
  1101. );
  1102. }
  1103. if (!empty($erase_options))
  1104. {
  1105. $smcFunc['db_query']('', '
  1106. DELETE FROM {db_prefix}themes
  1107. WHERE id_theme != {int:id_theme}
  1108. AND variable IN ({array_string:erase_variables})
  1109. AND id_member = {int:id_member}',
  1110. array(
  1111. 'id_theme' => 1,
  1112. 'id_member' => $memID,
  1113. 'erase_variables' => $erase_options
  1114. )
  1115. );
  1116. }
  1117. $themes = explode(',', $modSettings['knownThemes']);
  1118. foreach ($themes as $t)
  1119. cache_put_data('theme_settings-' . $t . ':' . $memID, null, 60);
  1120. }
  1121. }
  1122. /**
  1123. * Make any notification changes that need to be made.
  1124. *
  1125. * @param int $memID id_member
  1126. */
  1127. function makeNotificationChanges($memID)
  1128. {
  1129. global $smcFunc;
  1130. // Update the boards they are being notified on.
  1131. if (isset($_POST['edit_notify_boards']) && !empty($_POST['notify_boards']))
  1132. {
  1133. // Make sure only integers are deleted.
  1134. foreach ($_POST['notify_boards'] as $index => $id)
  1135. $_POST['notify_boards'][$index] = (int) $id;
  1136. // id_board = 0 is reserved for topic notifications.
  1137. $_POST['notify_boards'] = array_diff($_POST['notify_boards'], array(0));
  1138. $smcFunc['db_query']('', '
  1139. DELETE FROM {db_prefix}log_notify
  1140. WHERE id_board IN ({array_int:board_list})
  1141. AND id_member = {int:selected_member}',
  1142. array(
  1143. 'board_list' => $_POST['notify_boards'],
  1144. 'selected_member' => $memID,
  1145. )
  1146. );
  1147. }
  1148. // We are editing topic notifications......
  1149. elseif (isset($_POST['edit_notify_topics']) && !empty($_POST['notify_topics']))
  1150. {
  1151. foreach ($_POST['notify_topics'] as $index => $id)
  1152. $_POST['notify_topics'][$index] = (int) $id;
  1153. // Make sure there are no zeros left.
  1154. $_POST['notify_topics'] = array_diff($_POST['notify_topics'], array(0));
  1155. $smcFunc['db_query']('', '
  1156. DELETE FROM {db_prefix}log_notify
  1157. WHERE id_topic IN ({array_int:topic_list})
  1158. AND id_member = {int:selected_member}',
  1159. array(
  1160. 'topic_list' => $_POST['notify_topics'],
  1161. 'selected_member' => $memID,
  1162. )
  1163. );
  1164. }
  1165. }
  1166. /**
  1167. * Save any changes to the custom profile fields
  1168. *
  1169. * @param int $memID
  1170. * @param string $area
  1171. * @param bool $sanitize = true
  1172. */
  1173. function makeCustomFieldChanges($memID, $area, $sanitize = true)
  1174. {
  1175. global $context, $smcFunc, $user_profile, $user_info, $modSettings;
  1176. if ($sanitize && isset($_POST['customfield']))
  1177. $_POST['customfield'] = htmlspecialchars__recursive($_POST['customfield']);
  1178. $where = $area == 'register' ? 'show_reg != 0' : 'show_profile = {string:area}';
  1179. // Load the fields we are saving too - make sure we save valid data (etc).
  1180. $request = $smcFunc['db_query']('', '
  1181. SELECT col_name, field_name, field_desc, field_type, field_length, field_options, default_value, show_reg, mask, private
  1182. FROM {db_prefix}custom_fields
  1183. WHERE ' . $where . '
  1184. AND active = {int:is_active}',
  1185. array(
  1186. 'is_active' => 1,
  1187. 'area' => $area,
  1188. )
  1189. );
  1190. $changes = array();
  1191. $log_changes = array();
  1192. while ($row = $smcFunc['db_fetch_assoc']($request))
  1193. {
  1194. /* This means don't save if:
  1195. - The user is NOT an admin.
  1196. - The data is not freely viewable and editable by users.
  1197. - The data is not invisible to users but editable by the owner (or if it is the user is not the owner)
  1198. - The area isn't registration, and if it is that the field is not suppossed to be shown there.
  1199. */
  1200. if ($row['private'] != 0 && !allowedTo('admin_forum') && ($memID != $user_info['id'] || $row['private'] != 2) && ($area != 'register' || $row['show_reg'] == 0))
  1201. continue;
  1202. // Validate the user data.
  1203. if ($row['field_type'] == 'check')
  1204. $value = isset($_POST['customfield'][$row['col_name']]) ? 1 : 0;
  1205. elseif ($row['field_type'] == 'select' || $row['field_type'] == 'radio')
  1206. {
  1207. $value = $row['default_value'];
  1208. foreach (explode(',', $row['field_options']) as $k => $v)
  1209. if (isset($_POST['customfield'][$row['col_name']]) && $_POST['customfield'][$row['col_name']] == $k)
  1210. $value = $v;
  1211. }
  1212. // Otherwise some form of text!
  1213. else
  1214. {
  1215. $value = isset($_POST['customfield'][$row['col_name']]) ? $_POST['customfield'][$row['col_name']] : '';
  1216. if ($row['field_length'])
  1217. $value = $smcFunc['substr']($value, 0, $row['field_length']);
  1218. // Any masks?
  1219. if ($row['field_type'] == 'text' && !empty($row['mask']) && $row['mask'] != 'none')
  1220. {
  1221. // @todo We never error on this - just ignore it at the moment...
  1222. if ($row['mask'] == 'email' && (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $value) === 0 || strlen($value) > 255))
  1223. $value = '';
  1224. elseif ($row['mask'] == 'number')
  1225. {
  1226. $value = (int) $value;
  1227. }
  1228. elseif (substr($row['mask'], 0, 5) == 'regex' && preg_match(substr($row['mask'], 5), $value) === 0)
  1229. $value = '';
  1230. }
  1231. }
  1232. // Did it change?
  1233. if (!isset($user_profile[$memID]['options'][$row['col_name']]) || $user_profile[$memID]['options'][$row['col_name']] != $value)
  1234. {
  1235. $log_changes[] = array(
  1236. 'action' => 'customfield_' . $row['col_name'],
  1237. 'log_type' => 'user',
  1238. 'extra' => array(
  1239. 'previous' => !empty($user_profile[$memID]['options'][$row['col_name']]) ? $user_profile[$memID]['options'][$row['col_name']] : '',
  1240. 'new' => $value,
  1241. 'applicator' => $user_info['id'],
  1242. 'member_affected' => $memID,
  1243. ),
  1244. );
  1245. $changes[] = array(1, $row['col_name'], $value, $memID);
  1246. $user_profile[$memID]['options'][$row['col_name']] = $value;
  1247. }
  1248. }
  1249. $smcFunc['db_free_result']($request);
  1250. call_integration_hook('integrate_save_custom_profile_fields', array($changes, $log_changes, $memID, $area, $sanitize));
  1251. // Make those changes!
  1252. if (!empty($changes) && empty($context['password_auth_failed']))
  1253. {
  1254. $smcFunc['db_insert']('replace',
  1255. '{db_prefix}themes',
  1256. array('id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534', 'id_member' => 'int'),
  1257. $changes,
  1258. array('id_theme', 'variable', 'id_member')
  1259. );
  1260. if (!empty($log_changes) && !empty($modSettings['modlog_enabled']))
  1261. {
  1262. require_once(SOURCEDIR . '/Logging.php');
  1263. logActions($log_changes);
  1264. }
  1265. }
  1266. }
  1267. /**
  1268. * Send the user a new activation email if they need to reactivate!
  1269. */
  1270. function profileSendActivation()
  1271. {
  1272. global $profile_vars, $txt, $context, $scripturl, $smcFunc, $cookiename, $cur_profile, $language, $modSettings;
  1273. require_once(SUBSDIR . '/Mail.subs.php');
  1274. // Shouldn't happen but just in case.
  1275. if (empty($profile_vars['email_address']))
  1276. return;
  1277. $replacements = array(
  1278. 'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $context['id_member'] . ';code=' . $profile_vars['validation_code'],
  1279. 'ACTIVATIONCODE' => $profile_vars['validation_code'],
  1280. 'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $context['id_member'],
  1281. );
  1282. // Send off the email.
  1283. $emaildata = loadEmailTemplate('activate_reactivate', $replacements, empty($cur_profile['lngfile']) || empty($modSettings['userLanguage']) ? $language : $cur_profile['lngfile']);
  1284. sendmail($profile_vars['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
  1285. // Log the user out.
  1286. $smcFunc['db_query']('', '
  1287. DELETE FROM {db_prefix}log_online
  1288. WHERE id_member = {int:selected_member}',
  1289. array(
  1290. 'selected_member' => $context['id_member'],
  1291. )
  1292. );
  1293. $_SESSION['log_time'] = 0;
  1294. $_SESSION['login_' . $cookiename] = serialize(array(0, '', 0));
  1295. if (isset($_COOKIE[$cookiename]))
  1296. $_COOKIE[$cookiename] = '';
  1297. loadUserSettings();
  1298. $context['user']['is_logged'] = false;
  1299. $context['user']['is_guest'] = true;
  1300. // Send them to the done-with-registration-login screen.
  1301. loadTemplate('Register');
  1302. $context['page_title'] = $txt['profile'];
  1303. $context['sub_template'] = 'after';
  1304. $context['title'] = $txt['activate_changed_email_title'];
  1305. $context['description'] = $txt['activate_changed_email_desc'];
  1306. // We're gone!
  1307. obExit();
  1308. }
  1309. /**
  1310. * Load key signature context data.
  1311. *
  1312. * @return true
  1313. */
  1314. function profileLoadSignatureData()
  1315. {
  1316. global $modSettings, $context, $txt, $cur_profile, $smcFunc, $memberContext;
  1317. // Signature limits.
  1318. list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
  1319. $sig_limits = explode(',', $sig_limits);
  1320. $context['signature_enabled'] = isset($sig_limits[0]) ? $sig_limits[0] : 0;
  1321. $context['signature_limits'] = array(
  1322. 'max_length' => isset($sig_limits[1]) ? $sig_limits[1] : 0,
  1323. 'max_lines' => isset($sig_limits[2]) ? $sig_limits[2] : 0,
  1324. 'max_images' => isset($sig_limits[3]) ? $sig_limits[3] : 0,
  1325. 'max_smileys' => isset($sig_limits[4]) ? $sig_limits[4] : 0,
  1326. 'max_image_width' => isset($sig_limits[5]) ? $sig_limits[5] : 0,
  1327. 'max_image_height' => isset($sig_limits[6]) ? $sig_limits[6] : 0,
  1328. 'max_font_size' => isset($sig_limits[7]) ? $sig_limits[7] : 0,
  1329. 'bbc' => !empty($sig_bbc) ? explode(',', $sig_bbc) : array(),
  1330. );
  1331. // Kept this line in for backwards compatibility!
  1332. $context['max_signature_length'] = $context['signature_limits']['max_length'];
  1333. // Warning message for signature image limits?
  1334. $context['signature_warning'] = '';
  1335. if ($context['signature_limits']['max_image_width'] && $context['signature_limits']['max_image_height'])
  1336. $context['signature_warning'] = sprintf($txt['profile_error_signature_max_image_size'], $context['signature_limits']['max_image_width'], $context['signature_limits']['max_image_height']);
  1337. elseif ($context['signature_limits']['max_image_width'] || $context['signature_limits']['max_image_height'])
  1338. $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']);
  1339. $context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
  1340. if (empty($context['do_preview']))
  1341. $context['member']['signature'] = empty($cur_profile['signature']) ? '' : str_replace(array('<br />', '<', '>', '"', '\''), array("\n", '&lt;', '&gt;', '&quot;', '&#039;'), $cur_profile['signature']);
  1342. else
  1343. {
  1344. $signature = !empty($_POST['signature']) ? $_POST['signature'] : '';
  1345. $validation = profileValidateSignature($signature);
  1346. if (empty($context['post_errors']))
  1347. {
  1348. loadLanguage('Errors');
  1349. $context['post_errors'] = array();
  1350. }
  1351. $context['post_errors'][] = 'signature_not_yet_saved';
  1352. if ($validation !== true && $validation !== false)
  1353. $context['post_errors'][] = $validation;
  1354. censorText($context['member']['signature']);
  1355. $context['member']['current_signature'] = $context['member']['signature'];
  1356. censorText($signature);
  1357. $context['member']['signature_preview'] = parse_bbc($signature, true, 'sig' . $memberContext[$context['id_member']]);
  1358. $context['member']['signature'] = $_POST['signature'];
  1359. }
  1360. return true;
  1361. }
  1362. /**
  1363. * Load avatar context data.
  1364. *
  1365. * @return true
  1366. */
  1367. function profileLoadAvatarData()
  1368. {
  1369. global $context, $cur_profile, $modSettings, $scripturl;
  1370. $context['avatar_url'] = $modSettings['avatar_url'];
  1371. // Default context.
  1372. $context['member']['avatar'] += array(
  1373. 'custom' => stristr($cur_profile['avatar'], 'http://') ? $cur_profile['avatar'] : 'http://',
  1374. 'selection' => $cur_profile['avatar'] == '' || stristr($cur_profile['avatar'], 'http://') ? '' : $cur_profile['avatar'],
  1375. 'id_attach' => $cur_profile['id_attach'],
  1376. 'filename' => $cur_profile['filename'],
  1377. 'allow_server_stored' => allowedTo('profile_server_avatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')),
  1378. 'allow_upload' => allowedTo('profile_upload_avatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')),
  1379. 'allow_external' => allowedTo('profile_remote_avatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')),
  1380. 'allow_gravatar' => allowedTo('profile_gravatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')),
  1381. );
  1382. if ($cur_profile['avatar'] == '' && $cur_profile['id_attach'] > 0 && $context['member']['avatar']['allow_upload'])
  1383. {
  1384. $context['member']['avatar'] += array(
  1385. 'choice' => 'upload',
  1386. 'server_pic' => 'blank.png',
  1387. 'external' => 'http://'
  1388. );
  1389. $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'];
  1390. }
  1391. elseif (stristr($cur_profile['avatar'], 'http://') && $context['member']['avatar']['allow_external'])
  1392. $context['member']['avatar'] += array(
  1393. 'choice' => 'external',
  1394. 'server_pic' => 'blank.png',
  1395. 'external' => $cur_profile['avatar']
  1396. );
  1397. elseif ($cur_profile['avatar'] == 'gravatar' && $context['member']['avatar']['allow_gravatar'])
  1398. $context['member']['avatar'] += array(
  1399. 'choice' => 'gravatar',
  1400. 'server_pic' => 'blank.png',
  1401. 'external' => 'http://'
  1402. );
  1403. elseif ($cur_profile['avatar'] != '' && file_exists($modSettings['avatar_directory'] . '/' . $cur_profile['avatar']) && $context['member']['avatar']['allow_server_stored'])
  1404. $context['member']['avatar'] += array(
  1405. 'choice' => 'server_stored',
  1406. 'server_pic' => $cur_profile['avatar'] == '' ? 'blank.png' : $cur_profile['avatar'],
  1407. 'external' => 'http://'
  1408. );
  1409. else
  1410. $context['member']['avatar'] += array(
  1411. 'choice' => 'none',
  1412. 'server_pic' => 'blank.png',
  1413. 'external' => 'http://'
  1414. );
  1415. // Get a list of all the avatars.
  1416. if ($context['member']['avatar']['allow_server_stored'])
  1417. {
  1418. require_once(SUBSDIR . '/Attachments.subs.php');
  1419. $context['avatar_list'] = array();
  1420. $context['avatars'] = is_dir($modSettings['avatar_directory']) ? getServerStoredAvatars('', 0) : array();
  1421. }
  1422. else
  1423. $context['avatars'] = array();
  1424. // Second level selected avatar...
  1425. $context['avatar_selected'] = substr(strrchr($context['member']['avatar']['server_pic'], '/'), 1);
  1426. return true;
  1427. }
  1428. /**
  1429. * @todo needs description
  1430. *
  1431. * @return true
  1432. */
  1433. function profileLoadGroups()
  1434. {
  1435. global $cur_profile, $txt, $context, $smcFunc, $user_settings;
  1436. $context['member_groups'] = array(
  1437. 0 => array(
  1438. 'id' => 0,
  1439. 'name' => $txt['no_primary_membergroup'],
  1440. 'is_primary' => $cur_profile['id_group'] == 0,
  1441. 'can_be_additional' => false,
  1442. 'can_be_primary' => true,
  1443. )
  1444. );
  1445. $curGroups = explode(',', $cur_profile['additional_groups']);
  1446. // Load membergroups, but only those groups the user can assign.
  1447. $request = $smcFunc['db_query']('', '
  1448. SELECT group_name, id_group, hidden
  1449. FROM {db_prefix}membergroups
  1450. WHERE id_group != {int:moderator_group}
  1451. AND min_posts = {int:min_posts}' . (allowedTo('admin_forum') ? '' : '
  1452. AND group_type != {int:is_protected}') . '
  1453. ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name',
  1454. array(
  1455. 'moderator_group' => 3,
  1456. 'min_posts' => -1,
  1457. 'is_protected' => 1,
  1458. 'newbie_group' => 4,
  1459. )
  1460. );
  1461. while ($row = $smcFunc['db_fetch_assoc']($request))
  1462. {
  1463. // We should skip the administrator group if they don't have the admin_forum permission!
  1464. if ($row['id_group'] == 1 && !allowedTo('admin_forum'))
  1465. continue;
  1466. $context['member_groups'][$row['id_group']] = array(
  1467. 'id' => $row['id_group'],
  1468. 'name' => $row['group_name'],
  1469. 'is_primary' => $cur_profile['id_group'] == $row['id_group'],
  1470. 'is_additional' => in_array($row['id_group'], $curGroups),
  1471. 'can_be_additional' => true,
  1472. 'can_be_primary' => $row['hidden'] != 2,
  1473. );
  1474. }
  1475. $smcFunc['db_free_result']($request);
  1476. $context['member']['group_id'] = $user_settings['id_group'];
  1477. return true;
  1478. }
  1479. /**
  1480. * Load all the languages for the profile.
  1481. * @return boolean
  1482. */
  1483. function profileLoadLanguages()
  1484. {
  1485. global $context, $modSettings, $settings, $cur_profile, $language, $smcFunc;
  1486. $context['profile_languages'] = array();
  1487. // Get our languages!
  1488. getLanguages(true);
  1489. // Setup our languages.
  1490. foreach ($context['languages'] as $lang)
  1491. $context['profile_languages'][$lang['filename']] = $lang['name'];
  1492. ksort($context['profile_languages']);
  1493. // Return whether we should proceed with this.
  1494. return count($context['profile_languages']) > 1 ? true : false;
  1495. }
  1496. /**
  1497. * Reload a users settings.
  1498. */
  1499. function profileReloadUser()
  1500. {
  1501. global $modSettings, $context, $cur_profile, $smcFunc, $profile_vars;
  1502. // Log them back in - using the verify password as they must have matched and this one doesn't get changed by anyone!
  1503. if (isset($_POST['passwrd2']) && $_POST['passwrd2'] != '')
  1504. {
  1505. require_once(SUBSDIR . '/Auth.subs.php');
  1506. setLoginCookie(60 * $modSettings['cookieTime'], $context['id_member'], sha1(sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd2'])) . $cur_profile['password_salt']));
  1507. }
  1508. loadUserSettings();
  1509. writeLog();
  1510. }
  1511. /**
  1512. * Validate the signature
  1513. *
  1514. * @param mixed &$value
  1515. * @return boolean|string
  1516. */
  1517. function profileValidateSignature(&$value)
  1518. {
  1519. global $modSettings, $smcFunc, $txt;
  1520. require_once(SUBSDIR . '/Post.subs.php');
  1521. // Admins can do whatever they hell they want!
  1522. if (!allowedTo('admin_forum'))
  1523. {
  1524. // Load all the signature limits.
  1525. list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
  1526. $sig_limits = explode(',', $sig_limits);
  1527. $disabledTags = !empty($sig_bbc) ? explode(',', $sig_bbc) : array();
  1528. $unparsed_signature = strtr(un_htmlspecialchars($value), array("\r" => '', '&#039' => '\''));
  1529. // Too many lines?
  1530. if (!empty($sig_limits[2]) && substr_count($unparsed_signature, "\n") >= $sig_limits[2])
  1531. {
  1532. $txt['profile_error_signature_max_lines'] = sprintf($txt['profile_error_signature_max_lines'], $sig_limits[2]);
  1533. return 'signature_max_lines';
  1534. }
  1535. // Too many images?!
  1536. if (!empty($sig_limits[3]) && (substr_count(strtolower($unparsed_signature), '[img') + substr_count(strtolower($unparsed_signature), '<img')) > $sig_limits[3])
  1537. {
  1538. $txt['profile_error_signature_max_image_count'] = sprintf($txt['profile_error_signature_max_image_count'], $sig_limits[3]);
  1539. return 'signature_max_image_count';
  1540. }
  1541. // What about too many smileys!
  1542. $smiley_parsed = $unparsed_signature;
  1543. parsesmileys($smiley_parsed);
  1544. $smiley_count = substr_count(strtolower($smiley_parsed), '<img') - substr_count(strtolower($unparsed_signature), '<img');
  1545. if (!empty($sig_limits[4]) && $sig_limits[4] == -1 && $smiley_count > 0)
  1546. return 'signature_allow_smileys';
  1547. elseif (!empty($sig_limits[4]) && $sig_limits[4] > 0 && $smiley_count > $sig_limits[4])
  1548. {
  1549. $txt['profile_error_signature_max_smileys'] = sprintf($txt['profile_error_signature_max_smileys'], $sig_limits[4]);
  1550. return 'signature_max_smileys';
  1551. }
  1552. // Maybe we are abusing font sizes?
  1553. if (!empty($sig_limits[7]) && preg_match_all('~\[size=([\d\.]+)?(px|pt|em|x-large|larger)~i', $unparsed_signature, $matches) !== false && isset($matches[2]))
  1554. {
  1555. foreach ($matches[1] as $ind => $size)
  1556. {
  1557. $limit_broke = 0;
  1558. // Attempt to allow all sizes of abuse, so to speak.
  1559. if ($matches[2][$ind] == 'px' && $size > $sig_limits[7])
  1560. $limit_broke = $sig_limits[7] . 'px';
  1561. elseif ($matches[2][$ind] == 'pt' && $size > ($sig_limits[7] * 0.75))
  1562. $limit_broke = ((int) $sig_limits[7] * 0.75) . 'pt';
  1563. elseif ($matches[2][$ind] == 'em' && $size > ((float) $sig_limits[7] / 16))
  1564. $limit_broke = ((float) $sig_limits[7] / 16) . 'em';
  1565. elseif ($matches[2][$ind] != 'px' && $matches[2][$ind] != 'pt' && $matches[2][$ind] != 'em' && $sig_limits[7] < 18)
  1566. $limit_broke = 'large';
  1567. if ($limit_broke)
  1568. {
  1569. $txt['profile_error_signature_max_font_size'] = sprintf($txt['profile_error_signature_max_font_size'], $limit_broke);
  1570. return 'signature_max_font_size';
  1571. }
  1572. }
  1573. }
  1574. // The difficult one - image sizes! Don't error on this - just fix it.
  1575. if ((!empty($sig_limits[5]) || !empty($sig_limits[6])))
  1576. {
  1577. // Get all BBC tags...
  1578. preg_match_all('~\[img(\s+width=([\d]+))?(\s+height=([\d]+))?(\s+width=([\d]+))?\s*\](?:<br />)*([^<">]+?)(?:<br />)*\[/img\]~i', $unparsed_signature, $matches);
  1579. // ... and all HTML ones.
  1580. preg_match_all('~<img\s+src=(?:")?((?:http://|ftp://|https://|ftps://).+?)(?:")?(?:\s+alt=(?:")?(.*?)(?:")?)?(?:\s?/)?>~i', $unparsed_signature, $matches2, PREG_PATTERN_ORDER);
  1581. // And stick the HTML in the BBC.
  1582. if (!empty($matches2))
  1583. {
  1584. foreach ($matches2[0] as $ind => $dummy)
  1585. {
  1586. $matches[0][] = $matches2[0][$ind];
  1587. $matches[1][] = '';
  1588. $matches[2][] = '';
  1589. $matches[3][] = '';
  1590. $matches[4][] = '';
  1591. $matches[5][] = '';
  1592. $matches[6][] = '';
  1593. $matches[7][] = $matches2[1][$ind];
  1594. }
  1595. }
  1596. $replaces = array();
  1597. // Try to find all the images!
  1598. if (!empty($matches))
  1599. {
  1600. foreach ($matches[0] as $key => $image)
  1601. {
  1602. $width = -1; $height = -1;
  1603. // Does it have predefined restraints? Width first.
  1604. if ($matches[6][$key])
  1605. $matches[2][$key] = $matches[6][$key];
  1606. if ($matches[2][$key] && $sig_limits[5] && $matches[2][$key] > $sig_limits[5])
  1607. {
  1608. $width = $sig_limits[5];
  1609. $matches[4][$key] = $matches[4][$key] * ($width / $matches[2][$key]);
  1610. }
  1611. elseif ($matches[2][$key])
  1612. $width = $matches[2][$key];
  1613. // ... and height.
  1614. if ($matches[4][$key] && $sig_limits[6] && $matches[4][$key] > $sig_limits[6])
  1615. {
  1616. $height = $sig_limits[6];
  1617. if ($width != -1)
  1618. $width = $width * ($height / $matches[4][$key]);
  1619. }
  1620. elseif ($matches[4][$key])
  1621. $height = $matches[4][$key];
  1622. // If the dimensions are still not fixed - we need to check the actual image.
  1623. if (($width == -1 && $sig_limits[5]) || ($height == -1 && $sig_limits[6]))
  1624. {
  1625. $sizes = url_image_size($matches[7][$key]);
  1626. if (is_array($sizes))
  1627. {
  1628. // Too wide?
  1629. if ($sizes[0] > $sig_limits[5] && $sig_limits[5])
  1630. {
  1631. $width = $sig_limits[5];
  1632. $sizes[1] = $sizes[1] * ($width / $sizes[0]);
  1633. }
  1634. // Too high?
  1635. if ($sizes[1] > $sig_limits[6] && $sig_limits[6])
  1636. {
  1637. $height = $sig_limits[6];
  1638. if ($width == -1)
  1639. $width = $sizes[0];
  1640. $width = $width * ($height / $sizes[1]);
  1641. }
  1642. elseif ($width != -1)
  1643. $height = $sizes[1];
  1644. }
  1645. }
  1646. // Did we come up with some changes? If so remake the string.
  1647. if ($width != -1 || $height != -1)
  1648. $replaces[$image] = '[img' . ($width != -1 ? ' width=' . round($width) : '') . ($height != -1 ? ' height=' . round($height) : '') . ']' . $matches[7][$key] . '[/img]';
  1649. }
  1650. if (!empty($replaces))
  1651. $value = str_replace(array_keys($replaces), array_values($replaces), $value);
  1652. }
  1653. }
  1654. // Any disabled BBC?
  1655. $disabledSigBBC = implode('|', $disabledTags);
  1656. if (!empty($disabledSigBBC))
  1657. {
  1658. if (preg_match('~\[(' . $disabledSigBBC . '[ =\]/])~i', $unparsed_signature, $matches) !== false && isset($matches[1]))
  1659. {
  1660. $disabledTags = array_unique($disabledTags);
  1661. $txt['profile_error_signature_disabled_bbc'] = sprintf($txt['profile_error_signature_disabled_bbc'], implode(', ', $disabledTags));
  1662. return 'signature_disabled_bbc';
  1663. }
  1664. }
  1665. }
  1666. preparsecode($value);
  1667. // Too long?
  1668. if (!allowedTo('admin_forum') && !empty($sig_limits[1]) && $smcFunc['strlen'](str_replace('<br />', "\n", $value)) > $sig_limits[1])
  1669. {
  1670. $_POST['signature'] = trim(htmlspecialchars(str_replace('<br />', "\n", $value), ENT_QUOTES));
  1671. $txt['profile_error_signature_max_length'] = sprintf($txt['profile_error_signature_max_length'], $sig_limits[1]);
  1672. return 'signature_max_length';
  1673. }
  1674. return true;
  1675. }
  1676. /**
  1677. * The avatar is incredibly complicated, what with the options... and what not.
  1678. * @todo argh, the avatar here. Take this out of here!
  1679. *
  1680. * @param array &$value
  1681. * @return mixed
  1682. */
  1683. function profileSaveAvatarData(&$value)
  1684. {
  1685. global $modSettings, $smcFunc, $profile_vars, $cur_profile, $context;
  1686. $memID = $context['id_member'];
  1687. if (empty($memID) && !empty($context['password_auth_failed']))
  1688. return false;
  1689. require_once(SUBSDIR . '/Attachments.subs.php');
  1690. // We need to know where we're going to be putting it..
  1691. $uploadDir = getAvatarPath();
  1692. $id_folder = getAvatarPathID();
  1693. $downloadedExternalAvatar = false;
  1694. if ($value == 'external' && allowedTo('profile_remote_avatar') && stripos($_POST['userpicpersonal'], 'http://') === 0 && strlen($_POST['userpicpersonal']) > 7 && !empty($modSettings['avatar_download_external']))
  1695. {
  1696. if (!is_writable($uploadDir))
  1697. fatal_lang_error('attachments_no_write', 'critical');
  1698. require_once(SUBSDIR . '/Package.subs.php');
  1699. $url = parse_url($_POST['userpicpersonal']);
  1700. $contents = fetch_web_data('http://' . $url['host'] . (empty($url['port']) ? '' : ':' . $url['port']) . str_replace(' ', '%20', trim($url['path'])));
  1701. if ($contents != false && $tmpAvatar = fopen($uploadDir . '/avatar_tmp_' . $memID, 'wb'))
  1702. {
  1703. fwrite($tmpAvatar, $contents);
  1704. fclose($tmpAvatar);
  1705. $downloadedExternalAvatar = true;
  1706. $_FILES['attachment']['tmp_name'] = $uploadDir . '/avatar_tmp_' . $memID;
  1707. }
  1708. }
  1709. if ($value == 'none')
  1710. {
  1711. $profile_vars['avatar'] = '';
  1712. // Reset the attach ID.
  1713. $cur_profile['id_attach'] = 0;
  1714. $cur_profile['attachment_type'] = 0;
  1715. $cur_profile['filename'] = '';
  1716. removeAttachments(array('id_member' => $memID));
  1717. }
  1718. elseif ($value == 'server_stored' && allowedTo('profile_server_avatar'))
  1719. {
  1720. $profile_vars['avatar'] = strtr(empty($_POST['file']) ? (empty($_POST['cat']) ? '' : $_POST['cat']) : $_POST['file'], array('&amp;' => '&'));
  1721. $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.png' ? '' : $profile_vars['avatar']) : '';
  1722. // Clear current profile...
  1723. $cur_profile['id_attach'] = 0;
  1724. $cur_profile['attachment_type'] = 0;
  1725. $cur_profile['filename'] = '';
  1726. // Get rid of their old avatar. (if uploaded.)
  1727. removeAttachments(array('id_member' => $memID));
  1728. }
  1729. elseif ($value == 'gravatar' && allowedTo('profile_gavatar'))
  1730. {
  1731. $profile_vars['avatar'] = 'gravatar';
  1732. }
  1733. elseif ($value == 'external' && allowedTo('profile_remote_avatar') && stripos($_POST['userpicpersonal'], 'http://') === 0 && empty($modSettings['avatar_download_external']))
  1734. {
  1735. // We need these clean...
  1736. $cur_profile['id_attach'] = 0;
  1737. $cur_profile['attachment_type'] = 0;
  1738. $cur_profile['filename'] = '';
  1739. // Remove any attached avatar...
  1740. removeAttachments(array('id_member' => $memID));
  1741. $profile_vars['avatar'] = str_replace(' ', '%20', preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $_POST['userpicpersonal']));
  1742. if ($profile_vars['avatar'] == 'http://' || $profile_vars['avatar'] == 'http:///')
  1743. $profile_vars['avatar'] = '';
  1744. // Trying to make us do something we'll regret?
  1745. elseif (substr($profile_vars['avatar'], 0, 7) != 'http://')
  1746. return 'bad_avatar';
  1747. // Should we check dimensions?
  1748. elseif (!empty($modSettings['avatar_max_height_external']) || !empty($modSettings['avatar_max_width_external']))
  1749. {
  1750. // Now let's validate the avatar.
  1751. $sizes = url_image_size($profile_vars['avatar']);
  1752. 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']))))
  1753. {
  1754. // Houston, we have a problem. The avatar is too large!!
  1755. if ($modSettings['avatar_action_too_large'] == 'option_refuse')
  1756. return 'bad_avatar';
  1757. elseif ($modSettings['avatar_action_too_large'] == 'option_download_and_resize')
  1758. {
  1759. // @todo remove this if appropriate
  1760. require_once(SUBSDIR . '/Attachments.subs.php');
  1761. if (saveAvatar($profile_vars['avatar'], $memID, $modSettings['avatar_max_width_external'], $modSettings['avatar_max_height_external']))
  1762. {
  1763. $profile_vars['avatar'] = '';
  1764. $cur_profile['id_attach'] = $modSettings['new_avatar_data']['id'];
  1765. $cur_profile['filename'] = $modSettings['new_avatar_data']['filename'];
  1766. $cur_profile['attachment_type'] = $modSettings['new_avatar_data']['type'];
  1767. }
  1768. else
  1769. return 'bad_avatar';
  1770. }
  1771. }
  1772. }
  1773. }
  1774. elseif (($value == 'upload' && allowedTo('profile_upload_avatar')) || $downloadedExternalAvatar)
  1775. {
  1776. if ((isset($_FILES['attachment']['name']) && $_FILES['attachment']['name'] != '') || $downloadedExternalAvatar)
  1777. {
  1778. // Get the dimensions of the image.
  1779. if (!$downloadedExternalAvatar)
  1780. {
  1781. if (!is_writable($uploadDir))
  1782. fatal_lang_error('attachments_no_write', 'critical');
  1783. if (!move_uploaded_file($_FILES['attachment']['tmp_name'], $uploadDir . '/avatar_tmp_' . $memID))
  1784. fatal_lang_error('attach_timeout', 'critical');
  1785. $_FILES['attachment']['tmp_name'] = $uploadDir . '/avatar_tmp_' . $memID;
  1786. }
  1787. $sizes = @getimagesize($_FILES['attachment']['tmp_name']);
  1788. // No size, then it's probably not a valid pic.
  1789. if ($sizes === false)
  1790. return 'bad_avatar';
  1791. // Check whether the image is too large.
  1792. 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']))
  1793. {
  1794. if (!empty($modSettings['avatar_resize_upload']))
  1795. {
  1796. // Attempt to chmod it.
  1797. @chmod($uploadDir . '/avatar_tmp_' . $memID, 0644);
  1798. // @todo remove this require when appropriate
  1799. require_once(SUBSDIR . '/Attachments.subs.php');
  1800. if (!saveAvatar($uploadDir . '/avatar_tmp_' . $memID, $memID, $modSettings['avatar_max_width_upload'], $modSettings['avatar_max_height_upload']))
  1801. return 'bad_avatar';
  1802. // Reset attachment avatar data.
  1803. $cur_profile['id_attach'] = $modSettings['new_avatar_data']['id'];
  1804. $cur_profile['filename'] = $modSettings['new_avatar_data']['filename'];
  1805. $cur_profile['attachment_type'] = $modSettings['new_avatar_data']['type'];
  1806. }
  1807. else
  1808. return 'bad_avatar';
  1809. }
  1810. elseif (is_array($sizes))
  1811. {
  1812. // Now try to find an infection.
  1813. require_once(SUBSDIR . '/Graphics.subs.php');
  1814. if (!checkImageContents($_FILES['attachment']['tmp_name'], !empty($modSettings['avatar_paranoid'])))
  1815. {
  1816. // It's bad. Try to re-encode the contents?
  1817. if (empty($modSettings['avatar_reencode']) || (!reencodeImage($_FILES['attachment']['tmp_name'], $sizes[2])))
  1818. return 'bad_avatar';
  1819. // We were successful. However, at what price?
  1820. $sizes = @getimagesize($_FILES['attachment']['tmp_name']);
  1821. // Hard to believe this would happen, but can you bet?
  1822. if ($sizes === false)
  1823. return 'bad_avatar';
  1824. }
  1825. $extensions = array(
  1826. '1' => 'gif',
  1827. '2' => 'jpg',
  1828. '3' => 'png',
  1829. '6' => 'bmp'
  1830. );
  1831. $extension = isset($extensions[$sizes[2]]) ? $extensions[$sizes[2]] : 'bmp';
  1832. $mime_type = 'image/' . ($extension === 'jpg' ? 'jpeg' : ($extension === 'bmp' ? 'x-ms-bmp' : $extension));
  1833. $destName = 'avatar_' . $memID . '_' . time() . '.' . $extension;
  1834. list ($width, $height) = getimagesize($_FILES['attachment']['tmp_name']);
  1835. $file_hash = empty($modSettings['custom_avatar_enabled']) ? getAttachmentFilename($destName, false, null, true) : '';
  1836. // Remove previous attachments this member might have had.
  1837. removeAttachments(array('id_member' => $memID));
  1838. $smcFunc['db_insert']('',
  1839. '{db_prefix}attachments',
  1840. array(
  1841. 'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string', 'file_hash' => 'string', 'fileext' => 'string', 'size' => 'int',
  1842. 'width' => 'int', 'height' => 'int', 'mime_type' => 'string', 'id_folder' => 'int',
  1843. ),
  1844. array(
  1845. $memID, (empty($modSettings['custom_avatar_enabled']) ? 0 : 1), $destName, $file_hash, $extension, filesize($_FILES['attachment']['tmp_name']),
  1846. (int) $width, (int) $height, $mime_type, $id_folder,
  1847. ),
  1848. array('id_attach')
  1849. );
  1850. $cur_profile['id_attach'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
  1851. $cur_profile['filename'] = $destName;
  1852. $cur_profile['attachment_type'] = empty($modSettings['custom_avatar_enabled']) ? 0 : 1;
  1853. $destinationPath = $uploadDir . '/' . (empty($file_hash) ? $destName : $cur_profile['id_attach'] . '_' . $file_hash);
  1854. if (!rename($_FILES['attachment']['tmp_name'], $destinationPath))
  1855. {
  1856. // I guess a man can try.
  1857. removeAttachments(array('id_member' => $memID));
  1858. fatal_lang_error('attach_timeout', 'critical');
  1859. }
  1860. // Attempt to chmod it.
  1861. @chmod($uploadDir . '/' . $destinationPath, 0644);
  1862. }
  1863. $profile_vars['avatar'] = '';
  1864. // Delete any temporary file.
  1865. if (file_exists($uploadDir . '/avatar_tmp_' . $memID))
  1866. @unlink($uploadDir . '/avatar_tmp_' . $memID);
  1867. }
  1868. // Selected the upload avatar option and had one already uploaded before or didn't upload one.
  1869. else
  1870. $profile_vars['avatar'] = '';
  1871. }
  1872. else
  1873. $profile_vars['avatar'] = '';
  1874. // Setup the profile variables so it shows things right on display!
  1875. $cur_profile['avatar'] = $profile_vars['avatar'];
  1876. return false;
  1877. }
  1878. /**
  1879. * Save a members group.
  1880. *
  1881. * @param int &$value
  1882. * @return true
  1883. */
  1884. function profileSaveGroups(&$value)
  1885. {
  1886. global $profile_vars, $old_profile, $context, $smcFunc, $cur_profile;
  1887. // Do we need to protect some groups?
  1888. if (!allowedTo('admin_forum'))
  1889. {
  1890. $request = $smcFunc['db_query']('', '
  1891. SELECT id_group
  1892. FROM {db_prefix}membergroups
  1893. WHERE group_type = {int:is_protected}',
  1894. array(
  1895. 'is_protected' => 1,
  1896. )
  1897. );
  1898. $protected_groups = array(1);
  1899. while ($row = $smcFunc['db_fetch_assoc']($request))
  1900. $protected_groups[] = $row['id_group'];
  1901. $smcFunc['db_free_result']($request);
  1902. $protected_groups = array_unique($protected_groups);
  1903. }
  1904. // The account page allows the change of your id_group - but not to a protected group!
  1905. if (empty($protected_groups) || count(array_intersect(array((int) $value, $old_profile['id_group']), $protected_groups)) == 0)
  1906. $value = (int) $value;
  1907. // ... otherwise it's the old group sir.
  1908. else
  1909. $value = $old_profile['id_group'];
  1910. // Find the additional membergroups (if any)
  1911. if (isset($_POST['additional_groups']) && is_array($_POST['additional_groups']))
  1912. {
  1913. $additional_groups = array();
  1914. foreach ($_POST['additional_groups'] as $group_id)
  1915. {
  1916. $group_id = (int) $group_id;
  1917. if (!empty($group_id) && (empty($protected_groups) || !in_array($group_id, $protected_groups)))
  1918. $additional_groups[] = $group_id;
  1919. }
  1920. // Put the protected groups back in there if you don't have permission to take them away.
  1921. $old_additional_groups = explode(',', $old_profile['additional_groups']);
  1922. foreach ($old_additional_groups as $group_id)
  1923. {
  1924. if (!empty($protected_groups) && in_array($group_id, $protected_groups))
  1925. $additional_groups[] = $group_id;
  1926. }
  1927. if (implode(',', $additional_groups) !== $old_profile['additional_groups'])
  1928. {
  1929. $profile_vars['additional_groups'] = implode(',', $additional_groups);
  1930. $cur_profile['additional_groups'] = implode(',', $additional_groups);
  1931. }
  1932. }
  1933. // Too often, people remove delete their own account, or something.
  1934. if (in_array(1, explode(',', $old_profile['additional_groups'])) || $old_profile['id_group'] == 1)
  1935. {
  1936. $stillAdmin = $value == 1 || (isset($additional_groups) && in_array(1, $additional_groups));
  1937. // If they would no longer be an admin, look for any other...
  1938. if (!$stillAdmin)
  1939. {
  1940. $request = $smcFunc['db_query']('', '
  1941. SELECT id_member
  1942. FROM {db_prefix}members
  1943. WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0)
  1944. AND id_member != {int:selected_member}
  1945. LIMIT 1',
  1946. array(
  1947. 'admin_group' => 1,
  1948. 'selected_member' => $context['id_member'],
  1949. )
  1950. );
  1951. list ($another) = $smcFunc['db_fetch_row']($request);
  1952. $smcFunc['db_free_result']($request);
  1953. if (empty($another))
  1954. fatal_lang_error('at_least_one_admin', 'critical');
  1955. }
  1956. }
  1957. // If we are changing group status, update permission cache as necessary.
  1958. if ($value != $old_profile['id_group'] || isset($profile_vars['additional_groups']))
  1959. {
  1960. if ($context['user']['is_owner'])
  1961. $_SESSION['mc']['time'] = 0;
  1962. else
  1963. updateSettings(array('settings_updated' => time()));
  1964. }
  1965. return true;
  1966. }
  1967. /**
  1968. * Get the data about a users warnings.
  1969. *
  1970. * @param int $start
  1971. * @param int $items_per_page
  1972. * @param string $sort
  1973. * @param int $memID the member ID
  1974. * @return array the preview warnings
  1975. */
  1976. function list_getUserWarnings($start, $items_per_page, $sort, $memID)
  1977. {
  1978. global $smcFunc, $scripturl;
  1979. $request = $smcFunc['db_query']('', '
  1980. SELECT IFNULL(mem.id_member, 0) AS id_member, IFNULL(mem.real_name, lc.member_name) AS member_name,
  1981. lc.log_time, lc.body, lc.counter, lc.id_notice
  1982. FROM {db_prefix}log_comments AS lc
  1983. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lc.id_member)
  1984. WHERE lc.id_recipient = {int:selected_member}
  1985. AND lc.comment_type = {string:warning}
  1986. ORDER BY ' . $sort . '
  1987. LIMIT ' . $start . ', ' . $items_per_page,
  1988. array(
  1989. 'selected_member' => $memID,
  1990. 'warning' => 'warning',
  1991. )
  1992. );
  1993. $previous_warnings = array();
  1994. while ($row = $smcFunc['db_fetch_assoc']($request))
  1995. {
  1996. $previous_warnings[] = array(
  1997. 'issuer' => array(
  1998. 'id' => $row['id_member'],
  1999. 'link' => $row['id_member'] ? ('<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['member_name'] . '</a>') : $row['member_name'],
  2000. ),
  2001. 'time' => timeformat($row['log_time']),
  2002. 'reason' => $row['body'],
  2003. 'counter' => $row['counter'] > 0 ? '+' . $row['counter'] : $row['counter'],
  2004. 'id_notice' => $row['id_notice'],
  2005. );
  2006. }
  2007. $smcFunc['db_free_result']($request);
  2008. return $previous_warnings;
  2009. }
  2010. /**
  2011. * Get the number of warnings a user has.
  2012. *
  2013. * @param int $memID
  2014. * @return int Total number of warnings for the user
  2015. */
  2016. function list_getUserWarningCount($memID)
  2017. {
  2018. global $smcFunc;
  2019. $request = $smcFunc['db_query']('', '
  2020. SELECT COUNT(*)
  2021. FROM {db_prefix}log_comments
  2022. WHERE id_recipient = {int:selected_member}
  2023. AND comment_type = {string:warning}',
  2024. array(
  2025. 'selected_member' => $memID,
  2026. 'warning' => 'warning',
  2027. )
  2028. );
  2029. list ($total_warnings) = $smcFunc['db_fetch_row']($request);
  2030. $smcFunc['db_free_result']($request);
  2031. return $total_warnings;
  2032. }