diff --git a/admin/members.php b/admin/members.php index 11b7f051..5e3e1433 100644 --- a/admin/members.php +++ b/admin/members.php @@ -99,52 +99,23 @@ if (noSubmit('membersubmit')) { if (!$members) { $body = $template->process('admin_members_search.php'); - } else if ($members == "search") { + } elseif ($members == "search") { $template->token = $token->create('Control Panel/Members', 'mass-edit', $vars::NONCE_FORM_EXP); $body = $template->process('admin_members_edit_start.php'); $query = $db->query("SELECT * FROM " . $vars->tablepre . "members $where ORDER BY username"); - while($member = $db->fetch_array($query)) { + while ($member = $db->fetch_array($query)) { $template->member = $member; $template->userLink = recodeOut($member['username']); $template->statusAttr = attrOut($member['customstatus']); - $template->sadminselect = ''; - $template->adminselect = ''; - $template->smodselect = ''; - $template->modselect = ''; - $template->memselect = ''; - $template->banselect = ''; + $template->userStatus = $core->userStatusControl("status{$member['uid']}", $member['status']); $template->noban = ''; $template->u2uban = ''; $template->postban = ''; $template->bothban = ''; - switch($member['status']) { - case 'Super Administrator': - $template->sadminselect = $vars::selHTML; - break; - case 'Administrator': - $template->adminselect = $vars::selHTML; - break; - case 'Super Moderator': - $template->smodselect = $vars::selHTML; - break; - case 'Moderator': - $template->modselect = $vars::selHTML; - break; - case 'Member': - $template->memselect = $vars::selHTML; - break; - case 'Banned': - $template->banselect = $vars::selHTML; - break; - default: - $template->memselect = $vars::selHTML; - break; - } - switch($member['ban']) { case 'u2u': $template->u2uban = $vars::selHTML; @@ -180,9 +151,9 @@ $template->srchstatus = $srchstatus; $body .= $template->process('admin_members_edit_end.php'); } -} else if (onSubmit('membersubmit')) { +} elseif (onSubmit('membersubmit')) { $core->request_secure('Control Panel/Members', 'mass-edit', error_header: true); - $query = $db->query("SELECT uid, username, password, status FROM " . $vars->tablepre . "members $where"); + $query = $db->query("SELECT uid, username, status FROM " . $vars->tablepre . "members $where"); // Guarantee this request will not remove all Super Administrators. if (X_SADMIN && $db->num_rows($query) > 0) { @@ -190,7 +161,7 @@ $sa_count = (int) $db->result($saquery, 0); $db->free_result($saquery); - while($mem = $db->fetch_array($query)) { + while ($mem = $db->fetch_array($query)) { if ($mem['status'] == 'Super Administrator' && $core->postedVar('status'.$mem['uid']) != 'Super Administrator') { $sa_count--; } @@ -202,7 +173,7 @@ } // Now execute this request - while($mem = $db->fetch_array($query)) { + while ($mem = $db->fetch_array($query)) { $origstatus = $mem['status']; $status = $core->postedVar('status'.$mem['uid']); if ($status == '') { @@ -234,9 +205,17 @@ $db->query("DELETE FROM " . $vars->tablepre . "u2u WHERE owner='{$mem['username']}'"); $db->query("UPDATE " . $vars->tablepre . "whosonline SET username='xguest123' WHERE username='{$mem['username']}'"); } else { - $db->query("UPDATE " . $vars->tablepre . "members SET ban='$banstatus', status='$status', postnum='$postnum', customstatus='$cusstatus'$queryadd WHERE uid={$mem['uid']}"); - if ('' != $queryadd) { - $session->logoutAll($mem['username']); + $db->query("UPDATE " . $vars->tablepre . "members SET ban='$banstatus', status='$status', postnum='$postnum', customstatus='$cusstatus' WHERE uid={$mem['uid']}"); + + if (getRawString('pw' . $mem['uid']) != '') { + $newPass = $core->assertPasswordPolicy('pw' . $mem['uid'], 'pw' . $mem['uid']); + $passMan = new \XMB\Password($sql); + $passMan->changePassword($mem['username'], $newPass); + unset($newPass, $passMan); + + // Force logout and delete cookies. + $sql->deleteWhosonline($mem['username']); + $session->logoutAll($mem['username'], isSelf: false); } } } diff --git a/buddy.php b/buddy.php index 674f5475..18dfeefa 100644 --- a/buddy.php +++ b/buddy.php @@ -34,8 +34,6 @@ $template = \XMB\Services\template(); $vars = \XMB\Services\vars(); -require XMB_ROOT . 'include/buddy.inc.php'; - header('X-Robots-Tag: noindex'); if (X_GUEST) { diff --git a/editprofile.php b/editprofile.php index c7d11108..6691ed4f 100644 --- a/editprofile.php +++ b/editprofile.php @@ -22,6 +22,7 @@ * along with this program. If not, see . */ +$core = \XMB\Services\core(); $session = \XMB\Services\session(); $sql = \XMB\Services\sql(); $theme = \XMB\Services\theme(); @@ -67,69 +68,20 @@ $js_https_only = $https_only ? 'true' : 'false'; if (noSubmit('editsubmit')) { - $sadminselect = $adminselect = $smodselect = ''; - $modselect = $memselect = $banselect = ''; - switch($member['status']) { - case 'Super Administrator': - $sadminselect = $selHTML; - break; - case 'Administrator': - $adminselect = $selHTML; - break; - case 'Super Moderator': - $smodselect = $selHTML; - break; - case 'Moderator': - $modselect = $selHTML; - break; - case 'Member': - $memselect = $selHTML; - break; - case 'Banned': - $banselect = $selHTML; - break; - default: - $memselect = $selHTML; - break; - } + $form = new \XMB\UserEditForm($member, $vars->self, $core, $theme, $tran, $vars); + $form->setOptionSelectors(); + $form->setCallableElements(); + $form->setAvatar(); + $form->setBirthday(); + $form->setStatus(); + $form->setTextFields(); + $form->setNumericFields(); + $form->setMiscFields(); + + $subTemplate = $form->getTemplate(); $custout = attrOut($member['customstatus']); - $checked = ''; - if ($member['showemail'] == 'yes') { - $checked = $cheHTML; - } - - $subschecked = ''; - if ($member['sub_each_post'] == 'yes') { - $subschecked = $cheHTML; - } - - $newschecked = ''; - if ($member['newsletter'] == 'yes') { - $newschecked = $cheHTML; - } - - $uou2uchecked = ''; - if ($member['useoldu2u'] == 'yes') { - $uou2uchecked = $cheHTML; - } - - $ogu2uchecked = ''; - if ($member['saveogu2u'] == 'yes') { - $ogu2uchecked = $cheHTML; - } - - $eouchecked = ''; - if ($member['emailonu2u'] == 'yes') { - $eouchecked = $cheHTML; - } - - $invchecked = ''; - if ('1' === $member['invisible']) { - $invchecked = $cheHTML; - } - $registerdate = gmdate($dateformat, core()->timeKludge((int) $member['regdate'])); if (0 == (int) $member['lastvisit']) { @@ -170,56 +122,6 @@ $currdate = gmdate($timecode, $vars->onlinetime + ($SETTINGS['addtime'] * 3600)); $textoffset = str_replace('$currdate', $currdate, $lang['evaloffset']); - $themelist = $theme->selector( - nameAttr: 'thememem', - selection: (int) $member['theme'], - ); - - $langfileselect = $tran->createLangFileSelect($member['langfile']); - - $day = intval(substr($member['bday'], 8, 2)); - $month = intval(substr($member['bday'], 5, 2)); - $year = substr($member['bday'], 0, 4); - - for($i = 0; $i <= 12; $i++) { - $sel[$i] = ''; - } - $sel[$month] = $selHTML; - - $dayselect = array(); - $dayselect[] = ''; - $dayselect = implode("\n", $dayselect); - - $u2uasel0 = $u2uasel1 = $u2uasel2 = ''; - switch($member['u2ualert']) { - case 2: - $u2uasel2 = $selHTML; - break; - case 1: - $u2uasel1 = $selHTML; - break; - case 0: - default: - $u2uasel0 = $selHTML; - break; - } - - $check12 = $check24 = ''; - if ('24' === $member['timeformat']) { - $check24 = $cheHTML; - } else { - $check12 = $cheHTML; - } - if ($SETTINGS['sigbbcode'] == 'on') { $bbcodeis = $lang['texton']; } else { @@ -228,139 +130,27 @@ $htmlis = $lang['textoff']; - $avatar = ''; - null_string($member['avatar']); - if ($SETTINGS['avastatus'] == 'on') { - if ($https_only && strpos($member['avatar'], ':') !== false && substr($member['avatar'], 0, 6) !== 'https:') { - $member['avatar'] = ''; - } - eval('$avatar = "'.template('memcp_profile_avatarurl').'";'); - } - - if ($SETTINGS['avastatus'] == 'list') { - $avatars = ''; - $dir1 = opendir(XMB_ROOT.'images/avatars'); - while($avFile = readdir($dir1)) { - if (is_file(XMB_ROOT.'images/avatars/'.$avFile) && $avFile != '.' && $avFile != '..' && $avFile != 'index.html') { - $avatars .= ''; - } - } - $avatars = str_replace('value="'.$member['avatar'].'"', 'value="'.$member['avatar'].'" selected="selected"', $avatars); - $avatarbox = ''; - eval('$avatar = "'.template('memcp_profile_avatarlist').'";'); - closedir($dir1); - } - $lang['searchusermsg'] = str_replace('*USER*', $member['username'], $lang['searchusermsg']); - $member['bio'] = decimalEntityDecode($member['bio']); - $member['location'] = decimalEntityDecode($member['location']); - $member['mood'] = decimalEntityDecode($member['mood']); - $member['sig'] = decimalEntityDecode($member['sig']); - $userrecode = recodeOut($member['username']); $template = template_secure('admintool_editprofile', 'Edit User Account', $member['uid'], $vars::NONCE_FORM_EXP); eval('$editpage = "'.$template.'";'); } else { request_secure('Edit User Account', $member['uid']); - $status = postedVar('status'); - $origstatus = $member['status']; - $query = $db->query("SELECT COUNT(uid) FROM ".X_PREFIX."members WHERE status='Super Administrator'"); - $sa_count = (int) $db->result($query, 0); - $db->free_result($query); - if ($origstatus == 'Super Administrator' && $status != 'Super Administrator' && $sa_count == 1) { - error($lang['lastsadmin']); - } - $cusstatus = postedVar('cusstatus', '', FALSE); - $langfilenew = postedVar('langfilenew'); - $result = $db->query("SELECT devname FROM ".X_PREFIX."lang_base WHERE devname='$langfilenew'"); - if ($db->num_rows($result) == 0) { - $langfilenew = $SETTINGS['langfile']; - } - $timeoffset1 = isset($_POST['timeoffset1']) && is_numeric($_POST['timeoffset1']) ? $_POST['timeoffset1'] : 0; - $thememem = formInt('thememem'); - $tppnew = isset($_POST['tppnew']) ? (int) $_POST['tppnew'] : $SETTINGS['topicperpage']; - $pppnew = isset($_POST['pppnew']) ? (int) $_POST['pppnew'] : $SETTINGS['postperpage']; - - $dateformatnew = postedVar('dateformatnew', '', FALSE, TRUE); - $dateformattest = attrOut($dateformatnew, 'javascript'); // NEVER allow attribute-special data in the date format because it can be unescaped using the date() parser. - if (strlen($dateformatnew) == 0 || $dateformatnew !== $dateformattest) { - $dateformatnew = $SETTINGS['dateformat']; - } - unset($dateformattest); + $form = new \XMB\UserEditForm($member, $vars->self, $core, $theme, $tran, $vars); + $form->readBirthday(); + $form->readAvatar(); + $form->readCallables(); + $form->readTextFields(); + $form->readOptions(); + $form->readNumericFields(); + $form->readMiscFields(); - $timeformatnew = formInt('timeformatnew'); - if ($timeformatnew != 12 && $timeformatnew != 24) { - $timeformatnew = $SETTINGS['timeformat']; - } + $cusstatus = postedVar('cusstatus', '', FALSE); - $newsubs = formYesNo('newsubs'); - $saveogu2u = formYesNo('saveogu2u'); - $emailonu2u = formYesNo('emailonu2u'); - $useoldu2u = formYesNo('useoldu2u'); - $invisible = formInt('newinv'); - $showemail = formYesNo('newshowemail'); - $newsletter = formYesNo('newnewsletter'); - $u2ualert = formInt('u2ualert'); - $year = formInt('year'); - $month = formInt('month'); - $day = formInt('day'); - // For year of birth, reject all integers from 100 through 1899. - if ($year >= 100 && $year <= 1899) $year = 0; - $bday = iso8601_date($year, $month, $day); - $location = postedVar('newlocation', 'javascript', TRUE, TRUE, TRUE); $email = postedVar('newemail', 'javascript', TRUE, TRUE, TRUE); - $site = postedVar('newsite', 'javascript', TRUE, TRUE, TRUE); - $bio = postedVar('newbio', 'javascript', TRUE, TRUE, TRUE); - $mood = postedVar('newmood', 'javascript', TRUE, TRUE, TRUE); - $sig = postedVar('newsig', 'javascript', true, true, true); - - if ($SETTINGS['avastatus'] == 'on') { - $avatar = postedVar('newavatar', 'javascript', TRUE, TRUE, TRUE); - $rawavatar = postedVar('newavatar', '', FALSE, FALSE); - - $newavatarcheck = postedVar('newavatarcheck'); - - $max_size = explode('x', $SETTINGS['max_avatar_size']); - - if (preg_match('/^' . get_img_regexp($https_only) . '$/i', $rawavatar) == 0) { - $avatar = ''; - } elseif (ini_get('allow_url_fopen')) { - if ((int) $max_size[0] > 0 && (int) $max_size[1] > 0 && strlen($rawavatar) > 0) { - $size = @getimagesize($rawavatar); - if ($size === FALSE) { - $avatar = ''; - } elseif (($size[0] > (int) $max_size[0] || $size[1] > (int) $max_size[1]) && !X_SADMIN) { - error($lang['avatar_too_big'] . $SETTINGS['max_avatar_size'] . 'px'); - } - } - } elseif ($newavatarcheck == "no") { - $avatar = ''; - } - unset($rawavatar); - } elseif ($SETTINGS['avastatus'] == 'list') { - $rawavatar = postedVar('newavatar', '', FALSE, FALSE); - $dirHandle = opendir(XMB_ROOT.'images/avatars'); - $filefound = FALSE; - while($avFile = readdir($dirHandle)) { - if ($rawavatar == './images/avatars/'.$avFile) { - if (is_file(XMB_ROOT.'images/avatars/'.$avFile) && $avFile != '.' && $avFile != '..' && $avFile != 'index.html') { - $filefound = TRUE; - } - } - } - closedir($dirHandle); - unset($rawavatar); - if ($filefound) { - $avatar = postedVar('newavatar', 'javascript', TRUE, TRUE, TRUE); - } else { - $avatar = ''; - } - } else { - $avatar = ''; - } $db->query("UPDATE ".X_PREFIX."members SET status='$status', customstatus='$cusstatus', email='$email', site='$site', location='$location', bio='$bio', sig='$sig', showemail='$showemail', timeoffset='$timeoffset1', avatar='$avatar', @@ -368,14 +158,15 @@ dateformat='$dateformatnew', mood='$mood', invisible='$invisible', saveogu2u='$saveogu2u', emailonu2u='$emailonu2u', useoldu2u='$useoldu2u', u2ualert=$u2ualert, sub_each_post='$newsubs' WHERE username='$user'"); - $newpassword = $_POST['newpassword']; - if ($newpassword) { - $newpassword = md5($newpassword); - $db->query("UPDATE ".X_PREFIX."members SET password='$newpassword' WHERE username='$user'"); + if (getRawString('newpassword') != '') { + $newPass = $core->assertPasswordPolicy('newpassword', 'newpassword'); + $passMan = new \XMB\Password($sql); + $passMan->changePassword($rawuser, $newPass); + unset($newPass, $passMan); // Force logout and delete cookies. - $query = $db->query("DELETE FROM ".X_PREFIX."whosonline WHERE username='$user'"); - $session->logoutAll($rawuser); + $sql->deleteWhosonline($rawuser); + $session->logoutAll($rawuser, isSelf: false); } $unlock = formYesNo('unlock'); diff --git a/header.php b/header.php index 8fdcd886..c3c303c1 100644 --- a/header.php +++ b/header.php @@ -54,6 +54,7 @@ require XMB_ROOT . 'include/BBCode.php'; require XMB_ROOT . 'include/Bootup.php'; require XMB_ROOT . 'include/BootupLoader.php'; +require XMB_ROOT . 'include/buddy.inc.php'; require XMB_ROOT . 'include/captcha.inc.php'; require XMB_ROOT . 'include/debug.inc.php'; require XMB_ROOT . 'include/format.php'; @@ -61,6 +62,7 @@ require XMB_ROOT . 'include/functions.inc.php'; require XMB_ROOT . 'include/Login.php'; require XMB_ROOT . 'include/Observer.php'; +require XMB_ROOT . 'include/Password.php'; require XMB_ROOT . 'include/schema.inc.php'; require XMB_ROOT . 'include/services.php'; require XMB_ROOT . 'include/sessions.inc.php'; @@ -71,6 +73,7 @@ require XMB_ROOT . 'include/ThemeManager.php'; require XMB_ROOT . 'include/tokens.inc.php'; require XMB_ROOT . 'include/translation.inc.php'; +require XMB_ROOT . 'include/UserEditForm.php'; require XMB_ROOT . 'include/validate.inc.php'; diff --git a/include/Login.php b/include/Login.php index 505138df..df143e0d 100644 --- a/include/Login.php +++ b/include/Login.php @@ -90,7 +90,6 @@ function elevateUser(bool $force_inv = false) // $xmbuser is often used as a raw value in queries and should be sql escaped. // $self['username'] had been a good alternative for template/HTML use when it was a global variable. // Now the best practice is to keep the $self array isolated and use a template alias of the $self['username'] value for output. - // $xmbpw was historically abused and will no longer contain a value. if ('good' == $state || 'already-logged-in' == $state) { // 'good' means normal login or resumed session. @@ -101,7 +100,6 @@ function elevateUser(bool $force_inv = false) $vars->self = ['status' => '']; $vars->xmbuser = ''; } - $vars->self['password'] = ''; // Initialize the new translation system if (! defined('XMB_UPGRADE')) { diff --git a/include/Password.php b/include/Password.php new file mode 100644 index 00000000..01cea526 --- /dev/null +++ b/include/Password.php @@ -0,0 +1,139 @@ +. + */ + +declare(strict_types=1); + +namespace XMB; + +use InvalidArgumentException; +use SensitiveParameter; + +/** + * User password handler and abstraction. + * + * @since 1.10.00 + */ +class Password +{ + public const int MAX_LENGTH = 72; + + private const string ALGO = PASSWORD_DEFAULT; + + /** + * When creating a Password object, the stored credentials are required. + * + * The $storedHash value may be empty for the sake of new users and lost password situations only. + * + * @param string $storedHash Must be the members.password value. + * @param SQL $sql + */ + public function __construct(private SQL $sql) + { + // Property promotion + } + + private function isObsolete(string $storedHash): bool + { + return password_needs_rehash($storedHash, $this::ALGO); + } + + /** + * Determine if password input was valid for a new session. + * + * Automatically regenerates the stored hash of an existing password as needed. + * This method is binary-safe. + * + * @param string $rawPass Must be the raw input. + * @return bool + */ + public function checkInput( + #[\SensitiveParameter] + string $rawPass, + string $storedHash, + ): bool { + if (strlen($rawPass) == 0) throw new InvalidArgumentException('The XMB Password class does not accept empty inputs.'); + + if ($this->isObsolete($storedHash)) { + if (strlen($storedHash) == 32) { + // Use MD5. + $result = $storedHash === md5($rawPass); + } else { + // Use the modern system. + $result = password_verify($rawPass, $storedHash); + } + if ($result && strlen($rawPass) <= $this::MAX_LENGTH) { + // Automatically regenerate the hash. + $this->changePassword($rawPass); + } + } else { + $result = password_verify($rawPass, $storedHash); + } + if ($result && strlen($rawPass) > $this::MAX_LENGTH) { + // TODO: Force a manual password change. + } + return $result; + } + + /** + * Save a new password for the specified user. + * + * The caller is responsible for checking user or admin authorization to make this change. + * This method is binary-safe. + * + * @param string $username Must be HTMl encoded, as in members.username. + * @param string $rawPass Must be the raw input. + */ + public function changePassword( + string $username, + #[\SensitiveParameter] + string $rawPass, + ) { + if (strlen($username) == 0) throw new InvalidArgumentException('The XMB Password class does not accept empty inputs.'); + if (strlen($rawPass) == 0) throw new InvalidArgumentException('The XMB Password class does not accept empty inputs.'); + if (strlen($rawPass) > $this::MAX_LENGTH) throw new InvalidArgumentException('The password input was invalid due to excessive length.'); + + // Save modern hash. + $newHash = $this->hashPassword($rawPass); + $this->sql->setNewPassword($username, $newHash); + } + + /** + * Hash a raw password. + * + * This method is binary-safe. + * + * @param string $rawPass Must be the raw input. + * @return string The hash, expected to be 60 to 128 chars in length. + */ + public function hashPassword( + #[\SensitiveParameter] + string $rawPass, + ): string { + if (strlen($rawPass) == 0) throw new InvalidArgumentException('The XMB Password class does not accept empty inputs.'); + if (strlen($rawPass) > $this::MAX_LENGTH) throw new InvalidArgumentException('The password input was invalid due to excessive length.'); + + // Compute modern hash and return. + return password_hash($rawPass, $this::ALGO); + } +} diff --git a/include/ThemeManager.php b/include/ThemeManager.php index ea22f348..d7e0eec8 100644 --- a/include/ThemeManager.php +++ b/include/ThemeManager.php @@ -57,7 +57,7 @@ public function setTheme() $fid = getInt('fid', 'r'); $tid = getInt('tid', 'r'); if ($tid > 0) { - $forum = $this->sql->getFIDFromTID($tid); + $forum = $this->sql->getFIDFromTID($tid, getThemeIDToo: true); if (count($forum) == 0) { $tid = 0; $fid = 0; diff --git a/include/UserEditForm.php b/include/UserEditForm.php new file mode 100644 index 00000000..6712eb77 --- /dev/null +++ b/include/UserEditForm.php @@ -0,0 +1,513 @@ +. + */ + +declare(strict_types=1); + +namespace XMB; + +/** + * Provides common form logic for user self-registration, self-editing, and admin-editing. + * + * @since 1.10.00 + */ +class UserEditForm +{ + private Template $template; + + private string $formMode; + + private array $edits = []; + + /** + * @param array @targetUser The record being edited. May be empty for new users. + * @param array @editorUser The record of the user doing the editing. May be empty for new users. + */ + public function __construct( + private array $targetUser, + private array $editorUser, + private Core $core, + private DBStuff $db, + private SQL $sql, + private Theme\Manager $theme, + private Translation $tran, + private Variables $vars, + ) { + $this->template = new \XMB\Template($vars); + $this->template->addRefs(); + + if (! isset($targetUser['username'])) { + $this->formMode = 'new'; + } elseif ($targetUser['username'] === $editorUser['username']) { + $this->formMode = 'self'; + } else { + $this->formMode = 'admin'; + } + } + + public function getTemplate(): Template + { + return $this->template; + } + + public function getEdits(): array + { + return $this->edits; + } + + public function setOptions() + { + $template = $this->template; + $member = &$this->targetUser; + $vars = $this->vars; + + $template->check12 = ''; + $template->check24 = ''; + $template->u2uasel0 = ''; + $template->u2uasel1 = ''; + $template->u2uasel2 = ''; + + if ($this->formMode == 'new') { + // From template member_reg + $template->checked = ''; + $template->subschecked = ''; + $template->newschecked = $vars::cheHTML; + $template->uou2uchecked = ''; + $template->ogu2uchecked = $vars::cheHTML; + $template->eouchecked = ''; + $template->invchecked = ''; + if ('24' === $this->vars->settings['timeformat']) { + $template->check24 = $vars::cheHTML; + } else { + $template->check12 = $vars::cheHTML; + } + + } else { + // From memcp.php + $template->checked = $member['showemail'] == 'yes' ? $vars::cheHTML : ''; + $template->subschecked = $member['sub_each_post'] == 'yes' ? $vars::cheHTML : ''; + $template->newschecked = $member['newsletter'] == 'yes' ? $vars::cheHTML : ''; + $template->uou2uchecked = $member['useoldu2u'] == 'yes' ? $vars::cheHTML : ''; + $template->ogu2uchecked = $member['saveogu2u'] == 'yes' ? $vars::cheHTML : ''; + $template->eouchecked = $member['emailonu2u'] == 'yes' ? $vars::cheHTML : ''; + $template->invchecked = $member['invisible'] === '1' ? $vars::cheHTML : ''; + + switch ($member['u2ualert']) { + case '2': + $template->u2uasel2 = $vars::selHTML; + break; + case '1': + $template->u2uasel1 = $vars::selHTML; + break; + case '0': + default: + $template->u2uasel0 = $vars::selHTML; + } + + if ('24' === $member['timeformat']) { + $template->check24 = $vars::cheHTML; + } else { + $template->check12 = $vars::cheHTML; + } + } + } + + public function readOptions() + { + $timeformatnew = formInt('timeformatnew'); + if ($timeformatnew != 12 && $timeformatnew != 24) { + $timeformatnew = $this->vars->settings['timeformat']; + } + if ($this->targetUser['timeformat'] != $timeformatnew) { + $this->edits['timeformat'] = $timeformatnew; + } + + $u2ualert = formInt('u2ualert'); + if ($this->targetUser['u2ualert'] != $u2ualert) { + $this->edits['u2ualert'] = $u2ualert; + } + + $showemail = formYesNo('newshowemail'); + if ($this->targetUser['showemail'] != $showemail) { + $this->edits['showemail'] = $showemail; + } + + $newsletter = formYesNo('newsletter'); + if ($this->targetUser['newsletter'] != $newsletter) { + $this->edits['newsletter'] = $newsletter; + } + + $useoldu2u = formYesNo('useoldu2u'); + if ($this->targetUser['useoldu2u'] != $useoldu2u) { + $this->edits['useoldu2u'] = $useoldu2u; + } + + $saveogu2u = formYesNo('saveogu2u'); + if ($this->targetUser['saveogu2u'] != $saveogu2u) { + $this->edits['saveogu2u'] = $saveogu2u; + } + + $emailonu2u = formYesNo('emailonu2u'); + if ($this->targetUser['emailonu2u'] != $emailonu2u) { + $this->edits['emailonu2u'] = $emailonu2u; + } + + + if ($this->formMode != 'new') { + $newsubs = formYesNo('newsubs'); + if ($this->targetUser['sub_each_post'] != $newsubs) { + $this->edits['sub_each_post'] = $newsubs; + } + + $invisible = getPhpInput('newinv') === '1' ? '1' : '0'; + if ($this->targetUser['invisible'] != $invisible) { + $this->edits['invisible'] = $invisible; + } + } + } + + public function setCallables() + { + $template = $this->template; + $member = &$this->targetUser; + + if ($this->formMode == 'new') { + $timeOffset = $this->vars->settings['def_tz']; + $theme = null; + $langfile = $this->vars->settings['langfile']; + } else { + $timeOffset = $member['timeoffset']; + $theme = (int) $member['theme']; + $langfile = $member['langfile']; + } + + $template->timezones = $this->core->timezone_control($timeOffset); + + $template->themelist = $this->theme->selector( + nameAttr: 'thememem', + selection: $theme, + ); + + $template->langfileselect = $this->tran->createLangFileSelect($langfile); + + if ($this->formMode == 'admin') { + $template->userStatus = $this->core->userStatusControl( + statusField: 'status', + currentStatus: $member['status'], + ); + } + } + + public function readCallables() + { + $timeoffset = getPhpInput('timeoffset1'); + if (! is_numeric($timeoffset)) $timeoffset = '0'; + if ($this->formMode == 'new' || $this->targetUser['timeoffset'] != $timeoffset) { + $this->edits['timeoffset'] = $timeoffset; + } + + $thememem = formInt('thememem'); + if ($this->formMode == 'new' || $this->targetUser['theme'] != $thememem) { + $this->edits['theme'] = $thememem; + } + + $langfilenew = $this->core->postedVar('langfilenew'); + if (! $this->tran->langfileExists($langfilenew)) { + $langfilenew = $this->vars->settings['langfile']; + } + if ($this->formMode == 'new' || $this->targetUser['langfile'] != $langfilenew) { + $this->edits['langfile'] = $langfilenew; + } + + if ($this->formMode == 'admin') { + $status = $this->core->postedVar('status', dbescape: false); + $origstatus = $this->targetUser['status']; + if ($origstatus == 'Super Administrator') { + $query = $this->db->query("SELECT COUNT(uid) FROM " . $this->vars->tablepre . "members WHERE status = 'Super Administrator'"); + $sa_count = (int) $this->db->result($query); + $this->db->free_result($query); + if ($status != 'Super Administrator' && $sa_count == 1) { + $this->core->error($this->vars->lang['lastsadmin']); + } + } + if ($this->targetUser['status'] != $status) { + $this->edits['status'] = $status; + } + } + } + + private function setAvatar() + { + $template = $this->template; + $member = &$this->targetUser; + + $httpsOnly = 'on' == $this->vars->settings['images_https_only']; + $template->js_https_only = $httpsOnly ? 'true' : 'false'; + + if ($this->vars->settings['avastatus'] == 'on') { + if ($this->formMode == 'new') { + $template->avatar = $template->process('member_reg_avatarurl.php'); + } else { + null_string($member['avatar']); + if ($httpsOnly && strpos($member['avatar'], ':') !== false && substr($member['avatar'], 0, 6) !== 'https:') { + $member['avatar'] = ''; + } + $template->member = $member; + $template->avatar = $template->process('memcp_profile_avatarurl.php'); + } + } elseif ($this->vars->settings['avastatus'] == 'list') { + $avatars = ['']; + $dir1 = opendir(XMB_ROOT . 'images/avatars'); + while ($avFile = readdir($dir1)) { + if (is_file(XMB_ROOT . 'images/avatars/' . $avFile) && $avFile != '.' && $avFile != '..' && $avFile != 'index.html') { + $avatars[] = ''; + } + } + closedir($dir1); + if ($this->formMode != 'new') { + null_string($member['avatar']); + $avatars = str_replace('value="'.$member['avatar'].'"', 'value="'.$member['avatar'].'" selected="selected"', $avatars); + } + $template->avatars = implode("\n", $avatars); + $template->avatar = $template->process('member_reg_avatarlist.php'); + unset($avatars, $template->avatars); + } else { + $template->avatar = ''; + } + } + + private function readAvatar(): string + { + $httpsOnly = 'on' == $this->vars->settings['images_https_only']; + + if ($this->vars->settings['avastatus'] == 'on') { + $avatar = $this->core->postedVar('newavatar', 'javascript', dbescape: false, quoteencode: true); + $rawavatar = getPhpInput('newavatar'); + $newavatarcheck = getPhpInput('newavatarcheck'); + + $max_size = explode('x', $this->vars->settings['max_avatar_size']); + + if (preg_match('/^' . get_img_regexp($httpsOnly) . '$/i', $rawavatar) == 0) { + $avatar = ''; + } elseif (ini_get('allow_url_fopen')) { + if ((int) $max_size[0] > 0 && (int) $max_size[1] > 0 && strlen($rawavatar) > 0) { + $size = getimagesize($rawavatar); + if ($size === false) { + $avatar = ''; + } elseif (($size[0] > (int) $max_size[0] || $size[1] > (int) $max_size[1]) && ! X_SADMIN) { + $this->core->error($this->vars->lang['avatar_too_big'] . $this->vars->settings['max_avatar_size'] . 'px'); + } + } + } elseif ($newavatarcheck == 'no') { + $avatar = ''; + } + } elseif ($this->vars->settings['avastatus'] == 'list') { + $rawavatar = getPhpInput('newavatar'); + $dirHandle = opendir(XMB_ROOT . 'images/avatars'); + $filefound = false; + while ($avFile = readdir($dirHandle)) { + if ($rawavatar == $this->vars->full_url . 'images/avatars/' . $avFile) { + if (is_file(XMB_ROOT . 'images/avatars/' . $avFile) && $avFile != '.' && $avFile != '..' && $avFile != 'index.html') { + $filefound = true; + break; + } + } + } + closedir($dirHandle); + $avatar = $filefound ? $this->core->postedVar('newavatar', 'javascript', dbescape: false, quoteencode: true) : ''; + } else { + $avatar = ''; + } + + return $avatar; + } + + public function setBirthday() + { + $template = $this->template; + $member = &$this->targetUser; + + if ($this->formMode == 'new') { + $day = ''; + $month = 0; + $template->year = ''; + } else { + $day = intval(substr($member['bday'], 8, 2)); + $month = intval(substr($member['bday'], 5, 2)); + $template->year = substr($member['bday'], 0, 4); + } + + $dayselect = [ + "'; + $template->dayselect = implode("\n", $dayselect); + + $sel = array_fill(start_index: 0, count: 13, value: ''); + $sel[$month] = $this->vars::selHTML; + $template->sel = $sel; + } + + public function readBirthday() + { + $year = formInt('year'); + $month = formInt('month'); + $day = formInt('day'); + // For year of birth, reject all integers from 100 through 1899. + if ($year >= 100 && $year <= 1899) $year = 0; + $bday = iso8601_date($year, $month, $day); + + if ($this->formMode == 'new' || $this->targetUser['bday'] != $bday) { + $this->edits['bday'] = $bday; + } + } + + public function setOptionalFields() + { + $member = &$this->targetUser; + + if ($this->formMode == 'admin') { + $member['bio'] = decimalEntityDecode($member['bio']); + $member['location'] = decimalEntityDecode($member['location']); + $member['mood'] = decimalEntityDecode($member['mood']); + $member['sig'] = decimalEntityDecode($member['sig']); + } else { + $member['bio'] = $this->core->rawHTMLsubject($member['bio']); + $member['location'] = $this->core->rawHTMLsubject($member['location']); + $member['mood'] = $this->core->rawHTMLsubject($member['mood']); + $member['sig'] = $this->core->rawHTMLsubject($member['sig']); + } + + $this->template->member = $member; + + $this->setAvatar(); + } + + public function readOptionalFields() + { + $anyEdit = 'on' == $this->vars->settings['regoptional']; + + $selfEdit = $this->formMode == 'self' && ( + 'off' == $this->vars->settings['quarantine_new_users'] + || ((int) $this->vars->self['postnum'] > 0 && 'no' == $this->vars->self['waiting_for_mod']) + || X_STAFF + ); + + $adminEdit = $this->formMode == 'admin'; + + if ($anyEdit || $selfEdit || $adminEdit) { + $location = $this->core->postedVar('newlocation', 'javascript', dbescape: false, quoteencode: true); + $site = $this->core->postedVar('newsite', 'javascript', dbescape: false, quoteencode: true); + $bio = $this->core->postedVar('newbio', 'javascript', dbescape: false, quoteencode: true); + $mood = $this->core->postedVar('newmood', 'javascript', dbescape: false, quoteencode: true); + $sig = $this->core->postedVar('newsig', 'javascript', dbescape: false, quoteencode: true); + $avatar = $this->readAvatar(); + } else { + $location = ''; + $site = ''; + $bio = ''; + $mood = ''; + $sig = ''; + $avatar = ''; + } + if ($this->formMode == 'new' || $this->targetUser['location'] != $location) { + $this->edits['location'] = $location; + } + if ($this->formMode == 'new' || $this->targetUser['site'] != $site) { + $this->edits['site'] = $site; + } + if ($this->formMode == 'new' || $this->targetUser['bio'] != $bio) { + $this->edits['bio'] = $bio; + } + if ($this->formMode == 'new' || $this->targetUser['mood'] != $mood) { + $this->edits['mood'] = $mood; + } + if ($this->formMode == 'new' || $this->targetUser['sig'] != $sig) { + $this->edits['sig'] = $sig; + if ($this->formMode != 'new' && $this->vars->settings['resetsigs'] == 'on') { + if (strlen(trim($this->targetUser['sig'])) == 0) { + if (strlen(trim($sig)) > 0) { + $this->sql->setPostSigsByAuthor(true, $this->vars->self['username']); + } + } elseif (strlen(trim($sig)) == 0) { + $this->sql->setPostSigsByAuthor(false, $this->vars->self['username']); + } + } + } + if ($this->formMode == 'new' || $this->targetUser['avatar'] != $avatar) { + $this->edits['avatar'] = $avatar; + } + } + + public function setNumericFields() + { + if ($this->formMode == 'new') { + $this->template->tpp = $this->vars->settings['topicperpage']; + $this->template->ppp = $this->vars->settings['postperpage']; + } else { + $this->template->tpp = $this->targetUser['tpp']; + $this->template->ppp = $this->targetUser['ppp']; + } + } + + public function readNumericFields() + { + $tpp = formInt('tpp'); + $ppp = formInt('ppp'); + if ($tpp < 5) $tpp = $this->vars->settings['topicperpage']; + if ($ppp < 5) $ppp = $this->vars->settings['postperpage']; + if ($this->formMode == 'new' || $this->targetUser['tpp'] != $tpp) { + $this->edits['tpp'] = (int) $tpp; + } + if ($this->formMode == 'new' || $this->targetUser['ppp'] != $ppp) { + $this->edits['ppp'] = (int) $ppp; + } + } + + public function setMiscFields() + { + if ($this->formMode == 'new') { + $this->template->dateformat = $this->vars->settings['dateformat']; + } else { + $this->template->dateformat = $this->targetUser['dateformat']; + } + } + + public function readMiscFields() + { + $dateformat = getPhpInput('dateformatnew'); + $dateformattest = attrOut($dateformat, 'javascript'); + // Never allow attribute-special data in the date format because it can be unescaped using the date() parser. + if (strlen($this->targetUser['dateformat']) == 0 || $this->targetUser['dateformat'] !== $dateformattest) { + $dateformat = $this->vars->settings['dateformat']; + } + if ($this->formMode == 'new' || $this->targetUser['dateformat'] != $dateformat) { + $this->edits['dateformat'] = $dateformat; + } + } +} diff --git a/include/Variables.php b/include/Variables.php index 11706f88..079043c0 100644 --- a/include/Variables.php +++ b/include/Variables.php @@ -33,6 +33,10 @@ class Variables public const int NONCE_MAX_AGE = 43200; // CAPTCHA expiration, in seconds. public const int NONCE_KEY_LEN = 12; // Size of captchaimages.imagestring. public const int ONLINE_TIMER = 600; // Visitors are offline after this many seconds. + public const int PASS_MIN_LENGTH = 8; // New passwords may not be shorter than this. + public const int PASS_MAX_LENGTH = 72; // Hash input maximum to avoid truncation. + public const int USERNAME_MIN_LENGTH = 3; // New usernames may not be shorter than this. + public const int USERNAME_MAX_LENGTH = 32; // Size of members.username. // permissions constants public const int PERMS_COUNT = 4; //Number of raw bit sets stored in postperm setting. // indexes used in permissions arrays diff --git a/include/admin.inc.php b/include/admin.inc.php index e3eb1e04..9c6dd8b4 100644 --- a/include/admin.inc.php +++ b/include/admin.inc.php @@ -35,8 +35,14 @@ */ class admin { - public function __construct(private Core $core, private DBStuff $db, private SessionMgr $session, private SQL $sql, private Template $template, private Variables $vars) - { + public function __construct( + private Core $core, + private DBStuff $db, + private SessionMgr $session, + private SQL $sql, + private Template $template, + private Variables $vars + ) { // Property promotion. } @@ -53,7 +59,7 @@ public function rename_user(string $userfrom, string $userto): string $db = $this->db; $lang = &$this->vars->lang; - if (strlen($userto) < 3 || strlen($userto) > 32) { + if (strlen($userto) < $vars::USERNAME_MIN_LENGTH || strlen($userto) > $vars::USERNAME_MAX_LENGTH) { return $lang['username_length_invalid']; } diff --git a/include/format.php b/include/format.php index 0bdc9f7d..02fb6962 100644 --- a/include/format.php +++ b/include/format.php @@ -87,12 +87,8 @@ function MakeTime() /** * @since 1.9.4 */ -function iso8601_date($year=0, $month=0, $day=0) +function iso8601_date(int $year, int $month, int $day) { - $year = (int) $year; - $month = (int) $month; - $day = (int) $day; - if ($year < 1 || $month < 1 || $day < 1) { return '0000-00-00'; } @@ -109,7 +105,7 @@ function iso8601_date($year=0, $month=0, $day=0) $day = 1; } - return str_pad($year, 4, '0', STR_PAD_LEFT).'-'.str_pad($month, 2, '0', STR_PAD_LEFT).'-'.str_pad($day, 2, '0', STR_PAD_LEFT); + return str_pad((string) $year, 4, '0', STR_PAD_LEFT).'-'.str_pad((string) $month, 2, '0', STR_PAD_LEFT).'-'.str_pad((string) $day, 2, '0', STR_PAD_LEFT); } /** diff --git a/include/functions.inc.php b/include/functions.inc.php index f3fb0e79..daf52299 100644 --- a/include/functions.inc.php +++ b/include/functions.inc.php @@ -28,6 +28,11 @@ use InvalidArgumentException; +/** + * High-level and general-purpose methods which depend on XMB's shared services. + * + * @since 1.10.00 + */ class Core { public function __construct( @@ -1336,8 +1341,8 @@ public function printGmDate($timestamp=null, $altFormat=null, $altOffset=0) $startStr = substr($altFormat, 0, $pos); $endStr = substr($altFormat, $pos+1); $month = gmdate('m', intval($timestamp + ($timeoffset*3600)+(($altOffset+$SETTINGS['addtime'])*3600))); - $textM = month2text($month); - return printGmDate($timestamp, $startStr, $altOffset).substr($textM,0, ($f ? strlen($textM) : 3)).printGmDate($timestamp, $endStr, $altOffset); + $textM = $this->month2text($month); + return $this->printGmDate($timestamp, $startStr, $altOffset) . substr($textM,0, ($f ? strlen($textM) : 3)) . $this->printGmDate($timestamp, $endStr, $altOffset); } else { return gmdate($altFormat, intval($timestamp + ($timeoffset * 3600) + (($altOffset+$SETTINGS['addtime']) * 3600))); } @@ -2117,6 +2122,54 @@ function timezone_control(string $offset): string return $template->process('timezone_control.php'); } + /** + * Generates HTML for a user status (role) select element. + * + * @since 1.10.00 + * @param string $statusField The name attribute for the select element. + * @param string $currentStatus The previously saved value of members.status. + * @return string + */ + public function userStatusControl(string $statusField, string $currentStatus): string + { + $template = new \XMB\Template($this->vars); + $template->addRefs(); + + $template->statusField = $statusField; + + // From editprofile.php + $template->sadminselect = ''; + $template->adminselect = ''; + $template->smodselect = ''; + $template->modselect = ''; + $template->memselect = ''; + $template->banselect = ''; + switch ($currentStatus) { + case 'Super Administrator': + $template->sadminselect = $this->vars::selHTML; + break; + case 'Administrator': + $template->adminselect = $this->vars::selHTML; + break; + case 'Super Moderator': + $template->smodselect = $this->vars::selHTML; + break; + case 'Moderator': + $template->modselect = $this->vars::selHTML; + break; + case 'Member': + $template->memselect = $this->vars::selHTML; + break; + case 'Banned': + $template->banselect = $this->vars::selHTML; + break; + default: + $template->memselect = $this->vars::selHTML; + } + + return $template->process('admin_members_status_field.php'); + } + /** * Checks if guest recently tried to register and disclosed age < 13 * @@ -2144,6 +2197,35 @@ public function assertAdminOnly() } } + /** + * Checks a new password against requirements. + * + * @since 1.10.00 + * @param string $passInputName + * @param string $passConfirmName + * @return string The raw text of the new password. + */ + public function assertPasswordPolicy(string $passInputName, string $passConfirmName): string + { + $password1 = getRawString($passInputName); + $password2 = getRawString($passConfirmName); + + if ('' == $password1) { + $this->error($this->vars->lang['textnopassword']); + } + if (strlen($password1) < $this->vars::PASS_MIN_LENGTH) { + $core->error($this->vars->lang['pwtooshort']); + } + if (strlen($password1) > $this->vars::PASS_MAX_LENGTH) { + $core->error($this->vars->lang['pwtoolong']); + } + if ($password1 !== $password2) { + $this->error($this->vars->lang['pwnomatch']); + } + + return $password1; + } + /** * Process and return the post_attachmentbox template. * diff --git a/include/sessions.inc.php b/include/sessions.inc.php index 4031d3e2..8de89ed5 100644 --- a/include/sessions.inc.php +++ b/include/sessions.inc.php @@ -28,11 +28,12 @@ use RuntimeException; use XMB\Core; +use XMB\Password; use XMB\SQL; use function XMB\formYesNo; use function XMB\getPhpInput; -use function XMB\postedVar; +use function XMB\getRawString; /** * Session Data objects are used to pass results between functions. @@ -161,22 +162,20 @@ public function logoutByLists(array $selection) /** * Deletes all tokens for all sessions after the user sets new credentials. * - * @param string $username If not specified, logs out the member linked to the current session. + * @param string $username + * @param bool $isSelf Whether the client should be cleared also. For example, not when the admin is editing someone else's password. */ - public function logoutAll(string $username = '') + public function logoutAll(string $username = '', bool $isSelf = true) { if ('' == $username) { - $current_client = true; if ('good' == $this->status) { $username = $this->saved->member['username']; } else { return; } - } else { - $current_client = false; } foreach($this->mechanisms as $session) { - $session->logoutAll($username, $current_client); + $session->logoutAll($username, $isSelf); } } @@ -486,14 +485,14 @@ public function checkUsername(): Data if (strlen($uinput) < self::USER_MIN_LEN) { return $data; } - + $member = $this->sql->getMemberByName($uinput); - + if (empty($member)) { $data->status = 'bad'; return $data; } - + $data->member = &$member; $data->status = 'good'; $data->permanent = formYesNo('trust') == 'yes'; @@ -502,22 +501,22 @@ public function checkUsername(): Data public function checkPassword(Data $data): Data { - $pinput = $_POST['password']; + $pinput = getRawString('password'); if (empty($pinput)) { return new Data(); } - - $pinput = md5($pinput); - if ($data->member['password'] !== $pinput) { + $passMan = new Password($this->sql); + $storedPass = $data->member['password'] !== '' ? $data->member['password'] : $data->member['password2']; + + if (! $passMan->checkInput($pinput, $storedPass)) { $this->core->auditBadLogin($data->member); $data = new Data(); $data->status = 'bad'; return $data; } - - $data->member['password'] = ''; + return $data; } diff --git a/include/sql.inc.php b/include/sql.inc.php index e2d50c6e..199076d3 100644 --- a/include/sql.inc.php +++ b/include/sql.inc.php @@ -186,7 +186,7 @@ public function addMember(array $values): int if (! isset($values['waiting_for_mod'])) $values['waiting_for_mod'] = 'no'; // Required values: - $req = ['username', 'password', 'email', 'status', 'regip', 'regdate']; + $req = ['username', 'password2', 'email', 'status', 'regip', 'regdate']; // Types: $ints = ['regdate', 'postnum', 'theme', 'tpp', 'ppp', 'timeformat', 'lastvisit', 'pwdate', 'u2ualert', 'bad_login_date', @@ -196,7 +196,7 @@ public function addMember(array $values): int $strings = ['username', 'password', 'email', 'site', 'status', 'location', 'bio', 'sig', 'showemail', 'avatar', 'customstatus', 'bday', 'langfile', 'newsletter', 'regip', 'ban', 'dateformat', 'ignoreu2u', 'mood', 'invisible', - 'u2ufolders', 'saveogu2u', 'emailonu2u', 'useoldu2u', 'sub_each_post', 'waiting_for_mod']; + 'u2ufolders', 'saveogu2u', 'emailonu2u', 'useoldu2u', 'sub_each_post', 'waiting_for_mod', 'password2']; $sql = []; @@ -232,6 +232,52 @@ public function addMember(array $values): int return $this->db->insert_id(); } + /** + * SQL command + * + * @since 1.10.00 + * @param string $username The user being updated, must be HTML encoded. + * @param array $values Field name & value list. + */ + public function updateMember(string $username, array $values) + { + $this->db->escape_fast($username); + + // Types: + $ints = ['regdate', 'postnum', 'theme', 'tpp', 'ppp', 'timeformat', 'lastvisit', 'pwdate', 'u2ualert', 'bad_login_date', + 'bad_login_count', 'bad_session_date', 'bad_session_count']; + + $numerics = ['timeoffset']; + + $strings = ['password', 'email', 'site', 'status', 'location', 'bio', 'sig', 'showemail', 'avatar', + 'customstatus', 'bday', 'langfile', 'newsletter', 'regip', 'ban', 'dateformat', 'ignoreu2u', 'mood', 'invisible', + 'u2ufolders', 'saveogu2u', 'emailonu2u', 'useoldu2u', 'sub_each_post', 'waiting_for_mod', 'password2']; + + $sql = []; + + foreach($ints as $field) { + if (isset($values[$field])) { + if (! is_int($values[$field])) throw new InvalidArgumentException("Type mismatch for $field"); + $sql[] = "$field = {$values[$field]}"; + } + } + foreach($numerics as $field) { + if (isset($values[$field])) { + if (! is_numeric($values[$field])) throw new InvalidArgumentException("Type mismatch for $field"); + $sql[] = "$field = {$values[$field]}"; + } + } + foreach($strings as $field) { + if (isset($values[$field])) { + if (! is_string($values[$field])) throw new InvalidArgumentException("Type mismatch for $field"); + $this->db->escape_fast($values[$field]); + $sql[] = "$field = '{$values[$field]}'"; + } + } + + $this->db->query("UPDATE " . $this->tablepre . "members SET " . implode(', ', $sql) . " WHERE username = '$username'"); + } + /** * SQL command * @@ -413,10 +459,10 @@ public function endMemberQuarantine(string $username) */ public function setNewPassword(string $username, string $password) { - $sqlpass = $this->db->escape($password); - $sqluser = $this->db->escape($username); + $this->db->escape_fast($password); + $this->db->escape_fast($username); - $this->db->query("UPDATE " . $this->tablepre . "members SET password = '$sqlpass', bad_login_count = 0 WHERE username = '$sqluser'"); + $this->db->query("UPDATE " . $this->tablepre . "members SET password2 = '$password', password = '', bad_login_count = 0 WHERE username = '$username'"); } /** @@ -1126,6 +1172,23 @@ public function addFavoriteIfMissing(int $tid, string $username, string $type, b $this->db->free_result($query); } + /** + * SQL command + * + * @since 1.10.00 + */ + public function deleteFavorites(array $tids, string $username, string $type, bool $quarantine = false) + { + $sqluser = $this->db->escape($username); + $sqltype = $this->db->escape($type); + $tids = array_map('intval', $tids); + $tids = implode(',', $tids); + + $table = $quarantine ? $this->tablepre . 'hold_favorites' : $this->tablepre . 'favorites'; + + $query = $this->db->query("DELETE FROM $table WHERE tid IN ($tids) AND username = '$sqluser' AND type = '$sqltype'"); + } + /** * Retrieve the list of favorite threads for a user, sorted by recent posts. * @@ -1784,6 +1847,19 @@ public function deleteOldWhosonline(string $address, string $username, int $time $this->db->query("DELETE FROM " . $this->tablepre . "whosonline WHERE ((ip='$address' AND username='xguest123') OR (username='$username') OR (time < $timelimit))"); } + /** + * Remove a user's records upon logout. + * + * @since 1.10.00 + * @param string $username Current user. + */ + public function deleteWhosonline(string $username) + { + $this->db->escape_fast($username); + + $this->db->query("DELETE FROM " . $this->tablepre . "whosonline WHERE username = '$username'"); + } + /** * Add a Who's Online Record * @@ -2019,11 +2095,16 @@ public function getIPFromPost(int $pid): string * * @since 1.10.00 * @param int $tid The thread ID number. + * @param boool $getThemeIDToo Should the result contain the theme field or not? * @return array The forum ID and theme. */ - public function getFIDFromTID(int $tid): array + public function getFIDFromTID(int $tid, bool $getThemeIDToo = false): array { - $query = $this->db->query("SELECT f.fid, f.theme FROM " . $this->tablepre . "forums f RIGHT JOIN " . $this->tablepre . "threads t USING (fid) WHERE t.tid = $tid"); + if ($getThemeIDToo) { + $query = $this->db->query("SELECT f.fid, f.theme FROM " . $this->tablepre . "forums f RIGHT JOIN " . $this->tablepre . "threads t USING (fid) WHERE t.tid = $tid"); + } else { + $query = $this->db->query("SELECT fid FROM " . $this->tablepre . "threads WHERE tid = $tid"); + } if ($this->db->num_rows($query) === 0) { $forum = []; // Post not found. diff --git a/include/translation.inc.php b/include/translation.inc.php index ffa1b13f..649dd36e 100644 --- a/include/translation.inc.php +++ b/include/translation.inc.php @@ -34,6 +34,8 @@ class Translation private array $dirCache = []; private array $langCache = []; + private bool $dirCacheStatus = false; + public function __construct(private Variables $vars) { // Property promotion } @@ -78,21 +80,7 @@ public function loadPhrases(array $langkeys = []): array $langkeys = array_unique(array_merge($langkeys, ['charset', 'language'])); // First, cache the file list. - if (count($this->dirCache) == 0) { - $languages = scandir(XMB_ROOT . 'lang/'); - - if (false === $languages) { - $msg = 'Unable to read the /lang/ directory. '; - if ($this->vars->debug) { - $msg .= 'See error log for details.'; - } else { - $msg .= 'Enable debug mode in XMB conf.php for details.'; - } - throw new RuntimeException($msg); - } - - $this->dirCache = $languages; - } + if (! $this->dirCacheStatus) $this->initDirCache(); // Second, cache the needed parts of each file. foreach ($this->dirCache as $filename) { @@ -214,4 +202,41 @@ public function createLangFileSelect(string $currentLangFile): string } return ''; } + + /** + * Checks if the specified language is installed by confirming the file exists. + * + * @since 1.10.00 + */ + public function langFileExists(string $langfile): bool + { + if (! $this->dirCacheStatus) $this->initDirCache(); + + return false !== array_search("$langfile.lang.php", $this->dirCache); + } + + /** + * Sets up the lang directory cache. + * + * @since 1.10.00 + */ + private function initDirCache() + { + if ($this->dirCacheStatus) return; + + $languages = scandir(XMB_ROOT . 'lang/'); + + if (false === $languages) { + $msg = 'Unable to read the /lang/ directory. '; + if ($this->vars->debug) { + $msg .= 'See error log for details.'; + } else { + $msg .= 'Enable debug mode in XMB conf.php for details.'; + } + throw new RuntimeException($msg); + } + + $this->dirCache = $languages; + $this->dirCacheStatus = true; + } } diff --git a/include/validate-email.inc.php b/include/validate-email.inc.php index 7cadad67..0e662603 100644 --- a/include/validate-email.inc.php +++ b/include/validate-email.inc.php @@ -22,6 +22,10 @@ * along with this program. If not, see . */ +declare(strict_types=1); + +namespace XMB; + /** * EmailAddressValidator Class * https://code.google.com/archive/p/php-email-address-validation/ diff --git a/include/validate.inc.php b/include/validate.inc.php index 529fe728..f3895ff5 100644 --- a/include/validate.inc.php +++ b/include/validate.inc.php @@ -73,6 +73,24 @@ function getPhpInput(string $varname, string $sourcearray = 'p'): string return $retval; } +/** + * Retrieves raw user input. + * + * This is rarely useful and should be limited to things like passwords and binary data. + * + * @since 1.10.00 + */ +function getRawString(string $varname, string $sourcearray = 'p'): string +{ + $retval = getRawInput($varname, $sourcearray); + + if (! is_string($retval)) { + $retval = ''; + } + + return $retval; +} + /** * Retrieve a string or array input without filtering. * diff --git a/install/cinst.php b/install/cinst.php index e7275405..fbbfd0ab 100644 --- a/install/cinst.php +++ b/install/cinst.php @@ -88,17 +88,27 @@ function rmFromDir($path) } ob_implicit_flush(1); +require XMB_ROOT . 'db/DBStuff.php'; +require XMB_ROOT . 'include/Bootup.php'; require XMB_ROOT.'include/global.inc.php'; -require_once XMB_ROOT.'config.php'; require_once XMB_ROOT.'db/'.$database.'.php'; +require XMB_ROOT . 'include/Password.php'; require XMB_ROOT.'include/schema.inc.php'; require XMB_ROOT.'include/sql.inc.php'; +require XMB_ROOT . 'include/Template.php'; require XMB_ROOT.'include/translation.inc.php'; +require XMB_ROOT . 'include/Variables.php'; -define('X_PREFIX', $tablepre); +$vars = new \XMB\Variables(); +$template = new \XMB\Template($vars); +$boot = new \XMB\Bootup($template, $vars); +$boot->loadConfig(); -$db = new dbstuff; -$db->connect($dbhost, $dbuser, $dbpw, $dbname, $pconnect, true); + +define('X_PREFIX', $vars->tablepre); + +$db = $boot->connectDB(); +$sql = new \XMB\SQL($db, $vars->tablepre); show_act("Checking Super Administrator Account"); $vUsername = trim($frmUsername); @@ -138,7 +148,8 @@ function rmFromDir($path) } // these two are used waaaaay down below. -$vPassword = md5($frmPassword); +$passMan = new \XMB\Password($sql); +$vPassword = $passMan->hashPassword($frmPassword); $myDate = time(); show_result(X_INST_OK); @@ -389,9 +400,9 @@ function rmFromDir($path) show_result(X_INST_OK); show_act("Creating Super Administrator Account"); -\XMB\SQL\addMember([ +$sql->addMember([ 'username' => $vUsername, - 'password' => $vPassword, + 'password2' => $vPassword, 'pwdate' => $myDate, 'regdate' => $myDate, 'regip' => $_SERVER['REMOTE_ADDR'], diff --git a/lang/English.lang.php b/lang/English.lang.php index da2d6ee4..39a5c22e 100644 --- a/lang/English.lang.php +++ b/lang/English.lang.php @@ -317,7 +317,7 @@ $lang['evalindexstats'] = "\$threads topics / \$posts posts / \$members members"; $lang['evalipmask'] = "
This IP is currently banned with a mask (\$ipmask).
If you click on Unban IP, you will also unban all IP addresses included in this mask."; $lang['evalnobestmember'] = "Nobody has posted today, and as such there is no best member."; -$lang['evaloffset'] = "Time Offset (GMT time is currently \$currdate)"; +$lang['evaloffset'] = "Time Offset (UTC is currently \$currdate)"; $lang['evalstats1'] = "On the \$bbname, there are:"; $lang['evalstats10'] = "\$forumpost posts per forum"; $lang['evalstats11'] = "\$threadreply replies per thread"; @@ -654,6 +654,8 @@ $lang['pwchange'] = "Change Password"; $lang['pwnomatch'] = "Passwords do not match"; $lang['pwnote'] = "Enter new password if changing, otherwise leave blank."; +$lang['pwtoolong'] = "The new password was too long. Recommended password length is between 20 and 70 characters."; +$lang['pwtooshort'] = "The new password was too short. Recommended password length is between 20 and 70 characters."; $lang['queries'] = "Queries"; $lang['quickjump_status'] = "Quick Jump Status:
This option enables/disables the Quick Jump feature when selecting forums and categories in the header and footer."; $lang['quickreply'] = 'Quick Reply'; @@ -1222,43 +1224,43 @@ $lang['tickercontents'] = "News In Newsticker:"; $lang['tickername'] = "News & Updates"; $lang['timemsg'] = "Processed in"; -$lang['timezone1'] = "(GMT -12:00) Kwajalein Island"; -$lang['timezone10'] = "(GMT -3:30) Newfoundland"; -$lang['timezone11'] = "(GMT -3:00) Brasilia, Buenos Aires, Rio de Janeiro, São Paulo"; -$lang['timezone12'] = "(GMT -2:00) Mid-Atlantic, Fernando de Noronha"; -$lang['timezone13'] = "(GMT -1:00) Azores, Cape Verde Islands"; -$lang['timezone14'] = "(GMT) Dublin, Edinburgh, Lisbon, London, Monrovia"; -$lang['timezone15'] = "(GMT +1:00) Amsterdam, Berlin, Brussels, Madrid, Paris, Rome"; -$lang['timezone16'] = "(GMT +2:00) Cairo, Helsinki, Kaliningrad, South Africa"; -$lang['timezone17'] = "(GMT +3:00) Baghdad, Riyadh, Moscow, Nairobi"; -$lang['timezone18'] = "(GMT +3:30) Tehran"; -$lang['timezone19'] = "(GMT +4:00) Abu Dhabi, Baku, Muscat, Tbilisi"; -$lang['timezone2'] = "(GMT -11:00) Midway Island, Samoa"; -$lang['timezone20'] = "(GMT +4:30) Kabul"; -$lang['timezone21'] = "(GMT +5:00) Ekaterinburg, Islamabad, Karachi, Tashkent"; -$lang['timezone22'] = "(GMT +5:30) Bombay, Calcutta, Madras, New Delhi"; -$lang['timezone23'] = "(GMT +5:45) Kathmandu"; -$lang['timezone24'] = "(GMT +6:00) Almaty, Bishkek, Dhaka, Omsk, Thimphu"; -$lang['timezone25'] = "(GMT +6:30) Rangoon"; -$lang['timezone26'] = "(GMT +7:00) Bangkok, Hanoi, Jakarta, Krasnoyarsk"; -$lang['timezone27'] = "(GMT +8:00) Beijing, Hong Kong, Perth, Singapore, Taipei"; -$lang['timezone28'] = "(GMT +9:00) Osaka, Sapporo, Seoul, Tokyo, Yakutsk"; -$lang['timezone29'] = "(GMT +9:30) Adelaide, Darwin"; -$lang['timezone3'] = "(GMT -10:00) Hawaii"; -$lang['timezone30'] = "(GMT +10:00) Canberra, Guam, Melbourne, Sydney, Vladivostok"; -$lang['timezone31'] = "(GMT +11:00) Magadan, New Caledonia, Solomon Islands"; -$lang['timezone32'] = "(GMT +12:00) Auckland, Fiji, Kamchatka Krai, Marshall Islands"; -$lang['timezone33'] = "(GMT +13:00) Kingdom of Tonga"; -$lang['timezone34'] = "(GMT +14:00) Christmas Island"; -$lang['timezone35'] = "(GMT +12:45) Chatham Islands"; -$lang['timezone36'] = "(GMT +10:30) Lord Howe Island"; -$lang['timezone37'] = "(GMT -9:30) Marquesas Islands"; -$lang['timezone4'] = "(GMT -9:00) Anchorage, Fairbanks"; -$lang['timezone5'] = "(GMT -8:00) Pacific Time (US & Canada), Tijuana"; -$lang['timezone6'] = "(GMT -7:00) Mountain Time (US & Canada), Arizona"; -$lang['timezone7'] = "(GMT -6:00) Central Time (US & Canada), Mexico City, Central America"; -$lang['timezone8'] = "(GMT -5:00) Eastern Time (US & Canada), Bogotá, Lima, Quito"; -$lang['timezone9'] = "(GMT -4:00) Atlantic Time (Canada), Caracas, La Paz, Santiago"; +$lang['timezone1'] = "(UTC -12:00) Kwajalein Island"; +$lang['timezone10'] = "(UTC -3:30) Newfoundland"; +$lang['timezone11'] = "(UTC -3:00) Brasilia, Buenos Aires, Rio de Janeiro, São Paulo"; +$lang['timezone12'] = "(UTC -2:00) Mid-Atlantic, Fernando de Noronha"; +$lang['timezone13'] = "(UTC -1:00) Azores, Cape Verde Islands"; +$lang['timezone14'] = "(UTC) Dublin, Edinburgh, Lisbon, London, Monrovia"; +$lang['timezone15'] = "(UTC +1:00) Amsterdam, Berlin, Brussels, Madrid, Paris, Rome"; +$lang['timezone16'] = "(UTC +2:00) Cairo, Helsinki, Kaliningrad, South Africa"; +$lang['timezone17'] = "(UTC +3:00) Baghdad, Riyadh, Moscow, Nairobi"; +$lang['timezone18'] = "(UTC +3:30) Tehran"; +$lang['timezone19'] = "(UTC +4:00) Abu Dhabi, Baku, Muscat, Tbilisi"; +$lang['timezone2'] = "(UTC -11:00) Midway Island, Samoa"; +$lang['timezone20'] = "(UTC +4:30) Kabul"; +$lang['timezone21'] = "(UTC +5:00) Ekaterinburg, Islamabad, Karachi, Tashkent"; +$lang['timezone22'] = "(UTC +5:30) Bombay, Calcutta, Madras, New Delhi"; +$lang['timezone23'] = "(UTC +5:45) Kathmandu"; +$lang['timezone24'] = "(UTC +6:00) Almaty, Bishkek, Dhaka, Omsk, Thimphu"; +$lang['timezone25'] = "(UTC +6:30) Rangoon"; +$lang['timezone26'] = "(UTC +7:00) Bangkok, Hanoi, Jakarta, Krasnoyarsk"; +$lang['timezone27'] = "(UTC +8:00) Beijing, Hong Kong, Perth, Singapore, Taipei"; +$lang['timezone28'] = "(UTC +9:00) Osaka, Sapporo, Seoul, Tokyo, Yakutsk"; +$lang['timezone29'] = "(UTC +9:30) Adelaide, Darwin"; +$lang['timezone3'] = "(UTC -10:00) Hawaii"; +$lang['timezone30'] = "(UTC +10:00) Canberra, Guam, Melbourne, Sydney, Vladivostok"; +$lang['timezone31'] = "(UTC +11:00) Magadan, New Caledonia, Solomon Islands"; +$lang['timezone32'] = "(UTC +12:00) Auckland, Fiji, Kamchatka Krai, Marshall Islands"; +$lang['timezone33'] = "(UTC +13:00) Kingdom of Tonga"; +$lang['timezone34'] = "(UTC +14:00) Christmas Island"; +$lang['timezone35'] = "(UTC +12:45) Chatham Islands"; +$lang['timezone36'] = "(UTC +10:30) Lord Howe Island"; +$lang['timezone37'] = "(UTC -9:30) Marquesas Islands"; +$lang['timezone4'] = "(UTC -9:00) Anchorage, Fairbanks"; +$lang['timezone5'] = "(UTC -8:00) Pacific Time (US & Canada), Tijuana"; +$lang['timezone6'] = "(UTC -7:00) Mountain Time (US & Canada), Arizona"; +$lang['timezone7'] = "(UTC -6:00) Central Time (US & Canada), Mexico City, Central America"; +$lang['timezone8'] = "(UTC -5:00) Eastern Time (US & Canada), Bogotá, Lima, Quito"; +$lang['timezone9'] = "(UTC -4:00) Atlantic Time (Canada), Caracas, La Paz, Santiago"; $lang['tocont'] = "to continue."; $lang['todaydays'] = "days"; $lang['todaygo'] = "Go!"; diff --git a/lost.php b/lost.php index 786b0770..0c6bf6ae 100644 --- a/lost.php +++ b/lost.php @@ -50,28 +50,27 @@ } elseif ($valid_post) { // New password from posted form received. $username = postedVar('username', '', true, false); - $password1 = postedVar('password1', '', false, false); - $password2 = postedVar('password2', '', false, false); if ('' == $username) { error($lang['textnousername']); } - if (strlen($username) < 3 || strlen($username) > 32) { + if (strlen($username) < $vars::USERNAME_MIN_LENGTH || strlen($username) > $vars::USERNAME_MAX_LENGTH) { error($lang['username_length_invalid']); } - if ('' == $password1) { - error($lang['textnopassword']); - } - if ($password1 !== $password2) { - error($lang['pwnomatch']); - } + + $newPass = $core->assertPasswordPolicy('password1', 'password2'); // Inputs look reasonable. Check the token. if (! \XMB\Token\consume($token2, 'Lost Password', $username)) { error($lang['lostpw_bad_token']); } - $newpassword = md5($password1); - sql()->setNewPassword($username, $newpassword); + $passMan = new \XMB\Password($sql); + $passMan->changePassword($username, $newPass); + unset($newPass, $passMan); + + $sql->deleteWhosonline($username); + $session->logoutAll($username); + message($lang['lostpw_success']); } else { diff --git a/member.php b/member.php index 271f9e15..105e08f8 100644 --- a/member.php +++ b/member.php @@ -221,7 +221,7 @@ $self = []; $self['username'] = trim(postedVar('username', '', TRUE, FALSE)); - if (strlen($self['username']) < 3 || strlen($self['username']) > 32) { + if (strlen($self['username']) < $vars::USERNAME_MIN_LENGTH || strlen($self['username']) > $vars::USERNAME_MAX_LENGTH) { error($lang['username_length_invalid']); } @@ -276,23 +276,17 @@ } if ($SETTINGS['emailcheck'] == 'on') { - $self['password'] = ''; + $newPass = ''; $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; $get = strlen($chars) - 1; for($i = 0; $i < 10; $i++) { - $self['password'] .= $chars[random_int(0, $get)]; + $newPass .= $chars[random_int(0, $get)]; } - $password2 = $self['password']; - } elseif (!isset($_POST['password']) || !isset($_POST['password2'])) { - error($lang['textpw1']); } else { - $self['password'] = $_POST['password']; - $password2 = $_POST['password2']; - } - - if ($self['password'] !== $password2) { - error($lang['pwnomatch']); + $newPass = $core->assertPasswordPolicy('password', 'password2'); } + $passMan = new \XMB\Password($sql); + $self['password2'] = $passMan->hashPassword($newPass); $fail = false; $efail = false; @@ -341,51 +335,18 @@ error($lang['bademail']); } - if ($self['password'] == '') { - error($lang['textpw1']); - } - - $self['langfile'] = postedVar('langfilenew', '', false, false); - $langfilenew = $db->escape($self['langfile']); - $result = $db->query("SELECT devname FROM ".X_PREFIX."lang_base WHERE devname='$langfilenew'"); - if ($db->num_rows($result) == 0) { - $self['langfile'] = $SETTINGS['langfile']; - } + $form = new \XMB\UserEditForm([], [], $core, $theme, $tran, $vars); + $form->readBirthday(); + $form->readAvatar(); + $form->readCallables(); + $form->readTextFields(); + $form->readOptions(); + $form->readNumericFields(); + $form->readMiscFields(); $count1 = $sql->countMembers(); $self['status'] = ($count1 != 0) ? 'Member' : 'Super Administrator'; - $self['timeoffset'] = isset($_POST['timeoffset1']) && is_numeric($_POST['timeoffset1']) ? $_POST['timeoffset1'] : 0; - $self['theme'] = formInt('thememem'); - $self['tpp'] = formInt('tpp'); - $self['ppp'] = formInt('ppp'); - $self['showemail'] = formYesNo('showemail'); - $self['newsletter'] = formYesNo('newsletter'); - $self['saveogu2u'] = formYesNo('saveogu2u'); - $self['emailonu2u'] = formYesNo('emailonu2u'); - $self['useoldu2u'] = formYesNo('useoldu2u'); - $self['u2ualert'] = formInt('u2ualert'); - - // For year of birth, reject all integers from 100 through 1899. - $year = formInt('year'); - $month = formInt('month'); - $day = formInt('day'); - if ($year >= 100 && $year <= 1899) $year = 0; - $self['bday'] = iso8601_date($year, $month, $day); - - $self['dateformat'] = postedVar('dateformatnew', '', false, false); - $dateformattest = attrOut($self['dateformat'], 'javascript'); // NEVER allow attribute-special data in the date format because it can be unescaped using the date() parser. - if (strlen($self['dateformat']) == 0 || $self['dateformat'] !== $dateformattest) { - $self['dateformat'] = $SETTINGS['dateformat']; - } - unset($dateformattest); - - $self['timeformat'] = formInt('timeformatnew'); - if ($self['timeformat'] != 12 && $self['timeformat'] != 24) { - $self['timeformat'] = $SETTINGS['timeformat']; - } - - $self['password'] = md5($self['password']); $self['regdate'] = $vars->onlinetime; if (strlen($onlineip) > 15 && ((int) $SETTINGS['schema_version'] < 9 || strlen($onlineip) > 39)) { $self['regip'] = ''; @@ -393,59 +354,6 @@ $self['regip'] = $onlineip; } - if ('on' == $SETTINGS['regoptional']) { - $self['location'] = postedVar('location', 'javascript', true, false, true); - $self['site'] = postedVar('site', 'javascript', true, false, true); - $self['bio'] = postedVar('bio', 'javascript', true, false, true); - $self['mood'] = postedVar('mood', 'javascript', true, false, true); - $self['sig'] = postedVar('sig', 'javascript', true, false, true); - - if ($SETTINGS['avastatus'] == 'on') { - $self['avatar'] = postedVar('newavatar', 'javascript', true, false, true); - $rawavatar = postedVar('newavatar', '', FALSE, FALSE); - - $newavatarcheck = postedVar('newavatarcheck'); - - $max_size = explode('x', $SETTINGS['max_avatar_size']); - - if (preg_match('/^' . get_img_regexp($https_only) . '$/i', $rawavatar) == 0) { - $self['avatar'] = ''; - } elseif (ini_get('allow_url_fopen')) { - if ((int) $max_size[0] > 0 && (int) $max_size[1] > 0 && strlen($rawavatar) > 0) { - $size = @getimagesize($rawavatar); - if ($size === FALSE) { - $self['avatar'] = ''; - } elseif ($size[0] > (int) $max_size[0] || $size[1] > (int) $max_size[1]) { - error($lang['avatar_too_big'] . $SETTINGS['max_avatar_size'] . 'px'); - } - } - } elseif ($newavatarcheck == "no") { - $self['avatar'] = ''; - } - unset($rawavatar); - } elseif ($SETTINGS['avastatus'] == 'list') { - $rawavatar = postedVar('newavatar', '', FALSE, FALSE); - $dirHandle = opendir(XMB_ROOT.'images/avatars'); - $filefound = FALSE; - while($avFile = readdir($dirHandle)) { - if ($rawavatar == './images/avatars/'.$avFile) { - if (is_file(XMB_ROOT.'images/avatars/'.$avFile) && $avFile != '.' && $avFile != '..' && $avFile != 'index.html') { - $filefound = TRUE; - } - } - } - closedir($dirHandle); - unset($rawavatar); - if ($filefound) { - $self['avatar'] = $core->postedVar('newavatar', 'javascript', true, false, true); - } else { - $self['avatar'] = ''; - } - } else { - $self['avatar'] = ''; - } - } - $sql->addMember($self); $lang2 = $tran->loadPhrases([ @@ -477,14 +385,13 @@ $username = trim(postedVar('username', '', FALSE, FALSE)); $rawbbname = htmlspecialchars_decode($bbname, ENT_NOQUOTES); $subject = "[$rawbbname] {$translate['textyourpw']}"; - $body = "{$translate['textyourpwis']} \n\n{$translate['textusername']} $username\n{$translate['textpassword']} $password2\n\n$full_url"; + $body = "{$translate['textyourpwis']} \n\n{$translate['textusername']} $username\n{$translate['textpassword']} $newPass\n\n$full_url"; xmb_mail($rawemail, $subject, $body, $translate['charset']); } else { $session->newUser($self); } - $self['password'] = ''; - $password2 = ''; + unset($newPass, $passMan); break; } @@ -555,28 +462,21 @@ if (4 == $stepout) { // Display new user form - $captcharegcheck = ''; + $form = new \XMB\UserEditForm([], [], $core, $theme, $tran, $vars); + $form->setOptionSelectors(); + $form->setCallableElements(); + $form->setAvatar(); + $form->setBirthday(); + $form->setNumericFields(); + $form->setMiscFields(); + + $subTemplate = $form->getTemplate(); + $token = \XMB\Token\create('Registration', (string) $stepout, $vars::NONCE_FORM_EXP, true); $currdate = gmdate($vars->timecode, $core->standardTime($vars->onlinetime)); $textoffset = str_replace('$currdate', $currdate, $lang['evaloffset']); - $themelist = $theme->selector( - nameAttr: 'thememem', - selection: null, - ); - - $langfileselect = $tran->createLangFileSelect($langfile); - - $dayselect = array(); - $dayselect[] = ''; - $dayselect = implode("\n", $dayselect); - if ($SETTINGS['sigbbcode'] == 'on') { $bbcodeis = $lang['texton']; } else { @@ -590,41 +490,11 @@ eval('$pwtd = "'.template('member_reg_password').'";'); } - if ('24' === $SETTINGS['timeformat']) { - $timeFormat12Checked = ''; - $timeFormat24Checked = $cheHTML; - } else { - $timeFormat12Checked = $cheHTML; - $timeFormat24Checked = ''; - } - - $timezones = timezone_control($SETTINGS['def_tz']); - - $avatd = ''; - if ($SETTINGS['avastatus'] == 'on') { - eval('$avatd = "'.template('member_reg_avatarurl').'";'); - } else if ($SETTINGS['avastatus'] == 'list') { - $avatars = array(); - $avatars[] = ''; - $dirHandle = opendir(XMB_ROOT.'images/avatars'); - while($avFile = readdir($dirHandle)) { - if (is_file(XMB_ROOT.'images/avatars/'.$avFile) && $avFile != '.' && $avFile != '..' && $avFile != 'index.html') { - $avatars[] = ''; - } - } - closedir($dirHandle); - $avatars = implode("\n", str_replace('value="'.$member['avatar'].'"', 'value="'.$member['avatar'].'" selected="selected"', $avatars)); - eval('$avatd = "'.template('member_reg_avatarlist').'";'); - } - - $dformatorig = $SETTINGS['dateformat']; - $regoptional = ''; if ($SETTINGS['regoptional'] == 'on') { eval('$regoptional = "'.template('member_reg_optional').'";'); } - $captcharegcheck = ''; eval('$memberpage = "'.template('member_reg').'";'); } diff --git a/memcp.php b/memcp.php index 7260619a..a342b3c9 100644 --- a/memcp.php +++ b/memcp.php @@ -35,6 +35,7 @@ $sql = \XMB\Services\sql(); $template = \XMB\Services\template(); $theme = \XMB\Services\theme(); +$token = \XMB\Services\token(); $tran = \XMB\Services\translation(); $vars = \XMB\Services\vars(); $lang = &$vars->lang; @@ -55,21 +56,36 @@ case 'profile': $core->nav(''.$lang['textusercp'].''); $core->nav($lang['texteditpro']); + if ($SETTINGS['subject_in_title'] == 'on') { + $template->threadSubject = $lang['texteditpro'] . ' - '; + } break; case 'subscriptions': $core->nav(''.$lang['textusercp'].''); $core->nav($lang['textsubscriptions']); + if ($SETTINGS['subject_in_title'] == 'on') { + $template->threadSubject = $lang['textsubscriptions'] . ' - '; + } break; case 'favorites': $core->nav(''.$lang['textusercp'].''); $core->nav($lang['textfavorites']); + if ($SETTINGS['subject_in_title'] == 'on') { + $template->threadSubject = $lang['textfavorites'] . ' - '; + } break; case 'devices': $core->nav(''.$lang['textusercp'].''); $core->nav($lang['devices']); + if ($SETTINGS['subject_in_title'] == 'on') { + $template->threadSubject = $lang['devices'] . ' - '; + } break; default: $core->nav($lang['textusercp']); + if ($SETTINGS['subject_in_title'] == 'on') { + $template->threadSubject = $lang['textusercp'] . ' - '; + } break; } @@ -85,196 +101,76 @@ $header .= $template->process('memcp_nav.php'); if (noSubmit('editsubmit')) { - $member = $vars->self; - - $template->checked = $member['showemail'] == 'yes' ? $vars::cheHTML : ''; - $template->subschecked = $member['sub_each_post'] == 'yes' ? $vars::cheHTML : ''; - $template->newschecked = $member['newsletter'] == 'yes' ? $vars::cheHTML : ''; - $template->uou2uchecked = $member['useoldu2u'] == 'yes' ? $vars::cheHTML : ''; - $template->ogu2uchecked = $member['saveogu2u'] == 'yes' ? $vars::cheHTML : ''; - $template->eouchecked = $member['emailonu2u'] == 'yes' ? $vars::cheHTML : ''; - $template->invchecked = $member['invisible'] === '1' ? $vars::cheHTML : ''; - - $currdate = gmdate($vars->timecode, $core->standardTime($vars->onlinetime)); - $template->textoffset = str_replace('$currdate', $currdate, $lang['evaloffset']); - - $template->timezones = $core->timezone_control($member['timeoffset']); - - $template->u2uasel0 = ''; - $template->u2uasel1 = ''; - $template->u2uasel2 = ''; - switch ($member['u2ualert']) { - case '2': - $template->u2uasel2 = $vars::selHTML; - break; - case '1': - $template->u2uasel1 = $vars::selHTML; - break; - case '0': - default: - $template->u2uasel0 = $vars::selHTML; - break; - } - - $template->themelist = $theme->selector( - nameAttr: 'thememem', - selection: (int) $member['theme'], - ); - - $template->langfileselect = $tran->createLangFileSelect($member['langfile']); - - $day = intval(substr($member['bday'], 8, 2)); - $month = intval(substr($member['bday'], 5, 2)); - $template->year = substr($member['bday'], 0, 4); - - $sel = array_fill(start_index: 0, count: 13, value: ''); - $sel[$month] = $vars::selHTML; - $template->sel = $sel; - - $template->dayselect = [ - "'; - $template->dayselect = implode("\n", $dayselect); - - $template->check12 = ''; - $template->check24 = ''; - if ('24' === $member['timeformat']) { - $template->check24 = $vars::cheHTML; - } else { - $template->check12 = $vars::cheHTML; - } - - if ($SETTINGS['sigbbcode'] == 'on') { - $template->bbcodeis = $lang['texton']; - } else { - $template->bbcodeis = $lang['textoff']; - } - - $template->htmlis = $lang['textoff']; - - null_string($member['avatar']); - if ($SETTINGS['avastatus'] == 'on') { - if ($https_only && strpos($member['avatar'], ':') !== false && substr($member['avatar'], 0, 6) !== 'https:') { - $member['avatar'] = ''; - } - $template->member = $member; - $template->avatar = $template->process('memcp_profile_avatarurl.php'); - } elseif ($SETTINGS['avastatus'] == 'list') { - $avatars = ''; - $dir1 = opendir(XMB_ROOT . 'images/avatars'); - while ($avFile = readdir($dir1)) { - if (is_file(XMB_ROOT . 'images/avatars/' . $avFile) && $avFile != '.' && $avFile != '..' && $avFile != 'index.html') { - $avatars .= ''; - } - } - closedir($dir1); - $avatars = str_replace('value="'.$member['avatar'].'"', 'value="'.$member['avatar'].'" selected="selected"', $avatars); - $template->avatarbox = ''; - $template->avatar = $template->process('memcp_profile_avatarlist.php'); - unset($avatars, $template->avatarbox); - } else { - $template->avatar = ''; - } - - $member['bio'] = $core->rawHTMLsubject($member['bio']); - $member['location'] = $core->rawHTMLsubject($member['location']); - $member['mood'] = $core->rawHTMLsubject($member['mood']); - $member['sig'] = $core->rawHTMLsubject($member['sig']); - - $template->member = $member; + $form = new \XMB\UserEditForm($vars->self, $vars->self, $core, $db, $sql, $theme, $tran, $vars); + $form->setOptions(); + $form->setCallables(); + $form->setBirthday(); + $form->setNumericFields(); + $form->setMiscFields(); + if ('on' == $SETTINGS['regoptional'] || 'off' == $SETTINGS['quarantine_new_users'] || ((int) $vars->self['postnum'] > 0 && 'no' == $vars->self['waiting_for_mod']) || X_STAFF) { - $template->optional = $template->process('memcp_profile_optional.php'); + $form->setOptionalFields(); + $subTemplate = $form->getTemplate(); + $subTemplate->bbcodeis = $SETTINGS['sigbbcode'] == 'on' ? $lang['texton'] : $lang['textoff']; + $subTemplate->htmlis = $lang['textoff']; + $subTemplate->optional = $subTemplate->process('memcp_profile_optional.php'); } else { - $template->optional = ''; + $subTemplate = $form->getTemplate(); + $subTemplate->optional = ''; } - $template->hUsername = $vars->self['username']; - $template->token = $token->create('User Control Panel/Edit Profile', $vars->self['uid'], $vars::NONCE_FORM_EXP); + $currdate = gmdate($vars->timecode, $core->standardTime($vars->onlinetime)); + $subTemplate->textoffset = str_replace('$currdate', $currdate, $lang['evaloffset']); - $mempage = $template->process('memcp_profile.php'); + $subTemplate->hUsername = $vars->self['username']; + $subTemplate->token = $token->create('User Control Panel/Edit Profile', $vars->self['uid'], $vars::NONCE_FORM_EXP); + + $mempage = $subTemplate->process('memcp_profile.php'); } if (onSubmit('editsubmit')) { $core->request_secure('User Control Panel/Edit Profile', $vars->self['uid'], error_header: true); - if (! empty($_POST['newpassword'])) { - if (empty($_POST['oldpassword'])) { - error($lang['textpwincorrect']); - } - $member = $sql->getMemberByName($vars->self['username']); - if ($member['password'] !== md5($_POST['oldpassword'])) { - error($lang['textpwincorrect']); - } - unset($member); - if (empty($_POST['newpasswordcf'])) { - error($lang['pwnomatch']); + + if (getRawString('newpassword') != '') { + $storedPass = $vars->self['password'] !== '' ? $vars->self['password'] : $vars->self['password2']; + $passMan = new \XMB\Password($sql); + $oldPass = getRawString('oldpassword'); + if ($oldPass == '') { + $core->error($lang['textnopassword']); } - if ($_POST['newpassword'] !== $_POST['newpasswordcf']) { - error($lang['pwnomatch']); + if (! $passMan->checkInput($oldPass, $storedPass)) { + $core->auditBadLogin($vars->self); + $core->error($lang['textpwincorrect']); } - - $newpassword = md5($_POST['newpassword']); - - $pwtxt = "password='$newpassword',"; + $newPass = $core->assertPasswordPolicy('newpassword', 'newpasswordcf'); + $passMan->changePassword($vars->self['username'], $newPass); + unset($newPass, $passMan, $oldPass, $storedPass); // Force logout and delete cookies. - $query = $db->query("DELETE FROM " . $vars->tablepre . "whosonline WHERE username='$xmbuser'"); + $sql->deleteWhosonline($vars->self['username']); $session->logoutAll(); - } else { - $pwtxt = ''; } - $langfilenew = postedVar('langfilenew'); - $result = $db->query("SELECT devname FROM " . $vars->tablepre . "lang_base WHERE devname='$langfilenew'"); - if ($db->num_rows($result) == 0) { - $langfilenew = $SETTINGS['langfile']; - } - - $timeoffset1 = isset($_POST['timeoffset1']) && is_numeric($_POST['timeoffset1']) ? $_POST['timeoffset1'] : 0; - $thememem = formInt('thememem'); - $tppnew = isset($_POST['tppnew']) ? (int) $_POST['tppnew'] : $SETTINGS['topicperpage']; - $pppnew = isset($_POST['pppnew']) ? (int) $_POST['pppnew'] : $SETTINGS['postperpage']; + $form = new \XMB\UserEditForm($vars->self, $vars->self, $core, $db, $sql, $theme, $tran, $vars); + $form->readBirthday(); + $form->readCallables(); + $form->readOptionalFields(); + $form->readOptions(); + $form->readNumericFields(); + $form->readMiscFields(); - $dateformatnew = postedVar('dateformatnew', '', FALSE, TRUE); - $dateformattest = attrOut($dateformatnew, 'javascript'); // NEVER allow attribute-special data in the date format because it can be unescaped using the date() parser. - if (strlen($dateformatnew) == 0 || $dateformatnew !== $dateformattest) { - $dateformatnew = $SETTINGS['dateformat']; - } - unset($dateformattest); - - $timeformatnew = formInt('timeformatnew'); - if ($timeformatnew != 12 && $timeformatnew != 24) { - $timeformatnew = $SETTINGS['timeformat']; - } + $edits = $form->getEdits(); - $newsubs = formYesNo('newsubs'); - $saveogu2u = formYesNo('saveogu2u'); - $emailonu2u = formYesNo('emailonu2u'); - $useoldu2u = formYesNo('useoldu2u'); - $invisible = formInt('newinv'); - $showemail = formYesNo('newshowemail'); - $newsletter = formYesNo('newnewsletter'); - $u2ualert = formInt('u2ualert'); - $year = formInt('year'); - $month = formInt('month'); - $day = formInt('day'); - // For year of birth, reject all integers from 100 through 1899. - if ($year >= 100 && $year <= 1899) $year = 0; - $bday = iso8601_date($year, $month, $day); - $email = postedVar('newemail', 'javascript', TRUE, TRUE, TRUE); + $email = $core->postedVar('newemail', 'javascript', dbescape: false, quoteencode: true); - if ($email !== $db->escape($vars->self['email'])) { + if ($email !== $vars->self['email']) { if ($SETTINGS['doublee'] == 'off' && false !== strpos($email, "@")) { - $query = $db->query("SELECT COUNT(uid) FROM " . $vars->tablepre . "members WHERE email = '$email' AND username != '$xmbuser'"); - $count1 = (int) $db->result($query,0); + $sqlEmail = $db->escape($email); + $query = $db->query("SELECT COUNT(uid) FROM " . $vars->tablepre . "members WHERE email = '$sqlEmail' AND username != '" . $vars->xmbuser . "'"); + $count1 = (int) $db->result($query); $db->free_result($query); if ($count1 != 0) { - error($lang['alreadyreg']); + $core->error($lang['alreadyreg']); } } @@ -300,137 +196,57 @@ $db->free_result($query); if ($efail) { - error($lang['emailrestricted']); + $core->error($lang['emailrestricted']); } - require XMB_ROOT.'include/validate-email.inc.php'; + require XMB_ROOT . 'include/validate-email.inc.php'; $test = new EmailAddressValidator(); - $rawemail = postedVar('newemail', '', FALSE, FALSE); + $rawemail = getPhpInput('newemail'); if (false === $test->check_email_address($rawemail)) { - error($lang['bademail']); + $core->error($lang['bademail']); } } - - if ($SETTINGS['avastatus'] == 'on') { - $avatar = postedVar('newavatar', 'javascript', TRUE, TRUE, TRUE); - $rawavatar = postedVar('newavatar', '', FALSE, FALSE); - - $newavatarcheck = postedVar('newavatarcheck'); - - $max_size = explode('x', $SETTINGS['max_avatar_size']); - - if (preg_match('/^' . get_img_regexp($https_only) . '$/i', $rawavatar) == 0) { - $avatar = ''; - } elseif (ini_get('allow_url_fopen')) { - if ((int) $max_size[0] > 0 && (int) $max_size[1] > 0 && strlen($rawavatar) > 0) { - $size = @getimagesize($rawavatar); - if ($size === FALSE) { - $avatar = ''; - } elseif (($size[0] > (int) $max_size[0] || $size[1] > (int) $max_size[1]) && !X_SADMIN) { - error($lang['avatar_too_big'] . $SETTINGS['max_avatar_size'] . 'px'); - } - } - } elseif ($newavatarcheck == "no") { - $avatar = ''; - } - unset($rawavatar); - } elseif ($SETTINGS['avastatus'] == 'list') { - $rawavatar = postedVar('newavatar', '', FALSE, FALSE); - $dirHandle = opendir(XMB_ROOT.'images/avatars'); - $filefound = FALSE; - while($avFile = readdir($dirHandle)) { - if ($rawavatar == './images/avatars/'.$avFile) { - if (is_file(XMB_ROOT.'images/avatars/'.$avFile) && $avFile != '.' && $avFile != '..' && $avFile != 'index.html') { - $filefound = TRUE; - } - } - } - closedir($dirHandle); - unset($rawavatar); - if ($filefound) { - $avatar = postedVar('newavatar', 'javascript', TRUE, TRUE, TRUE); - } else { - $avatar = ''; - } - } else { - $avatar = ''; + + if ($vars->self['email'] != $email) { + $edits['email'] = $email; + } + + if (count($edits) > 0) { + $sql->updateMember($vars->self['username'], $edits); } - if ('on' == $SETTINGS['regoptional'] || 'off' == $SETTINGS['quarantine_new_users'] || ((int) $vars->self['postnum'] > 0 && 'no' == $vars->self['waiting_for_mod']) || X_STAFF) { - $location = postedVar('newlocation', 'javascript', TRUE, TRUE, TRUE); - $site = postedVar('newsite', 'javascript', TRUE, TRUE, TRUE); - $bio = postedVar('newbio', 'javascript', TRUE, TRUE, TRUE); - $mood = postedVar('newmood', 'javascript', TRUE, TRUE, TRUE); - $sig = postedVar('newsig', 'javascript', TRUE, TRUE, TRUE); - - if ($SETTINGS['resetsigs'] == 'on') { - if (strlen(trim($vars->self['sig'])) == 0) { - if (strlen(trim($sig)) > 0) { - $sql->setPostSigsByAuthor(true, $vars->self['username']); - } - } elseif (strlen(trim($sig)) == 0) { - $sql->setPostSigsByAuthor(false, $vars->self['username']); - } - } - } else { - $avatar = ''; - $location = ''; - $site = ''; - $bio = ''; - $mood = ''; - $sig = ''; - } - - $db->query("UPDATE " . $vars->tablepre . "members SET $pwtxt email='$email', site='$site', location='$location', bio='$bio', sig='$sig', showemail='$showemail', - timeoffset='$timeoffset1', avatar='$avatar', theme='$thememem', bday='$bday', langfile='$langfilenew', tpp='$tppnew', ppp='$pppnew', - newsletter='$newsletter', timeformat='$timeformatnew', dateformat='$dateformatnew', mood='$mood', invisible='$invisible', saveogu2u='$saveogu2u', - emailonu2u='$emailonu2u', useoldu2u='$useoldu2u', u2ualert=$u2ualert, sub_each_post='$newsubs' WHERE username='$xmbuser'" - ); - - message($lang['usercpeditpromsg'], TRUE, '', '', $vars->full_url . 'memcp.php', true, false, true); + $core->message($lang['usercpeditpromsg'], redirect: $vars->full_url . 'memcp.php'); } } elseif ($action == 'favorites') { $header = $template->process('header.php'); $header .= $template->process('memcp_nav.php'); - $favadd = getInt('favadd'); + $favadd = onSubmit('favadd'); if (noSubmit('favsubmit') && $favadd) { - if ($favadd == 0) { - error($lang['generic_missing']); - } + $favadd = getInt('favadd'); - $query = $db->query("SELECT fid FROM " . $vars->tablepre . "threads WHERE tid=$favadd"); - if ($db->num_rows($query) == 0) { - error($lang['privforummsg']); + $row = $sql->getFIDfromTID($favadd); + if (count($row) == 0) { + $core->error($lang['privforummsg']); } - $row = $db->fetch_array($query); $forum = $forums->getForum((int) $row['fid']); - $perms = checkForumPermissions($forum); - if (!($perms[$vars::PERMS_VIEW] && $perms[$vars::PERMS_PASSWORD])) { - error($lang['privforummsg']); + $perms = $core->checkForumPermissions($forum); + if (! ($perms[$vars::PERMS_VIEW] && $perms[$vars::PERMS_PASSWORD])) { + $core->error($lang['privforummsg']); } if ($forum['type'] == 'sub') { $perms = $core->checkForumPermissions($forums->getForum((int) $forum['fup'])); - if (!($perms[$vars::PERMS_VIEW] && $perms[$vars::PERMS_PASSWORD])) { - error($lang['privforummsg']); + if (! ($perms[$vars::PERMS_VIEW] && $perms[$vars::PERMS_PASSWORD])) { + $core->error($lang['privforummsg']); } } - $query = $db->query("SELECT tid FROM " . $vars->tablepre . "favorites WHERE tid=$favadd AND username='$xmbuser' AND type='favorite'"); - $favthread = $db->fetch_array($query); - $db->free_result($query); - - if ($favthread) { - error($lang['favonlistmsg']); - } - - $db->query("INSERT INTO " . $vars->tablepre . "favorites (tid, username, type) VALUES ($favadd, '$xmbuser', 'favorite')"); - message($lang['favaddedmsg'], TRUE, '', '', $vars->full_url . 'memcp.php?action=favorites', true, false, true); - } + $sql->addFavoriteIfMissing($favadd, $vars->self['username'], 'favorite'); - if (!$favadd && noSubmit('favsubmit')) { + $core->message($lang['favaddedmsg'], redirect: $vars->full_url . 'memcp.php?action=favorites'); + } elseif (! $favadd && noSubmit('favsubmit')) { $favnum = 0; - $favs = ''; + $template->favs = ''; $fids = $core->permittedFIDsForThreadView(); if (count($fids) != 0) { $query = $sql->getFavorites($vars->self['username'], $fids, limit: null); @@ -446,38 +262,37 @@ $lastpostname = $lang['textanonymous']; } - $lastreplydate = gmdate($dateformat, $core->timeKludge((int) $lastpost[0])); - $lastreplytime = gmdate($timecode, $core->timeKludge((int) $lastpost[0])); - $lastpost = $lang['lastreply1'].' '.$lastreplydate.' '.$lang['textat'].' '.$lastreplytime.' '.$lang['textby'].' '.$lastpostname; - $fav['subject'] = rawHTMLsubject(stripslashes($fav['subject'])); + $lastreplydate = gmdate($vars->dateformat, $core->timeKludge((int) $lastpost[0])); + $lastreplytime = gmdate($vars->timecode, $core->timeKludge((int) $lastpost[0])); + $template->lastpost = $lang['lastreply1'].' '.$lastreplydate.' '.$lang['textat'].' '.$lastreplytime.' '.$lang['textby'].' '.$lastpostname; + $fav['subject'] = $core->rawHTMLsubject(stripslashes($fav['subject'])); if ($fav['icon'] != '') { - $fav['icon'] = ''; + $fav['icon'] = ''; } else { $fav['icon'] = ''; } + $template->fav = $fav; + $template->forum = $forum; $favnum++; - eval('$favs .= "'.template('memcp_favs_row').'";'); + $template->favs .= $template->process('memcp_favs_row.php'); } unset($query); } - $favsbtn = ''; if ($favnum != 0) { - eval('$favsbtn = "'.template('memcp_favs_button').'";'); + $template->favsbtn = $template->process('memcp_favs_button.php'); + } else { + $template->favsbtn = ''; + $template->favs = $template->process('memcp_favs_none.php'); } - if ($favnum == 0) { - eval('$favs = "'.template('memcp_favs_none').'";'); - } - eval('$mempage = "'.template('memcp_favs').'";'); - } - - if (!$favadd && onSubmit('favsubmit')) { - $query = $db->query("SELECT tid FROM " . $vars->tablepre . "favorites WHERE username='$xmbuser' AND type='favorite'"); - $tids = array(); - while($fav = $db->fetch_array($query)) { + $mempage = $template->process('memcp_favs.php'); + } elseif (! $favadd && onSubmit('favsubmit')) { + $query = $db->query("SELECT tid FROM " . $vars->tablepre . "favorites WHERE username = '" . $vars->xmbuser . "' AND type = 'favorite'"); + $tids = []; + while ($fav = $db->fetch_array($query)) { $delete = formInt('delete'.$fav['tid']); if ($delete == intval($fav['tid'])) { $tids[] = $delete; @@ -485,13 +300,12 @@ } $db->free_result($query); if (count($tids) > 0) { - $tids = implode(', ', $tids); - $db->query("DELETE FROM " . $vars->tablepre . "favorites WHERE username='$xmbuser' AND tid IN ($tids) AND type='favorite'"); + $sql->deleteFavorites($tids, $vars->self['username'], 'favorite'); } - message($lang['favsdeletedmsg'], TRUE, '', '', $vars->full_url . 'memcp.php?action=favorites', true, false, true); + $core->message($lang['favsdeletedmsg'], redirect: $vars->full_url . 'memcp.php?action=favorites'); } } elseif ($action == 'subscriptions') { - $subadd = getInt('subadd'); + $subadd = onSubmit('subadd'); if (! $subadd && noSubmit('subsubmit')) { $fids = $core->permittedFIDsForThreadView(); $num = $sql->countSubscriptionsByUser($vars->self['username'], $fids); @@ -530,6 +344,7 @@ $fav['icon'] = ''; } $template->fav = $fav; + $template->forum = $forum; $template->subscriptions .= $template->process('memcp_subscriptions_row.php'); } @@ -542,29 +357,40 @@ $mempage = $template->process('memcp_subscriptions.php'); } elseif ($subadd && noSubmit('subsubmit')) { - $query = $db->query("SELECT COUNT(tid) FROM " . $vars->tablepre . "favorites WHERE tid='$subadd' AND username='$xmbuser' AND type='subscription'"); - if ((int) $db->result($query, 0) == 1) { - $db->free_result($query); - error($lang['subonlistmsg'], TRUE); - } else { - $db->query("INSERT INTO " . $vars->tablepre . "favorites (tid, username, type) VALUES ('$subadd', '$xmbuser', 'subscription')"); - message($lang['subaddedmsg'], TRUE, '', '', $vars->full_url . 'memcp.php?action=subscriptions', true, false, true); + $tid = getInt('subadd'); + + $row = $sql->getFIDfromTID($tid); + if (count($row) == 0) { + $core->error($lang['privforummsg']); + } + $forum = $forums->getForum((int) $row['fid']); + $perms = $core->checkForumPermissions($forum); + if (! ($perms[$vars::PERMS_VIEW] && $perms[$vars::PERMS_PASSWORD])) { + $core->error($lang['privforummsg']); } + if ($forum['type'] == 'sub') { + $perms = $core->checkForumPermissions($forums->getForum((int) $forum['fup'])); + if (! ($perms[$vars::PERMS_VIEW] && $perms[$vars::PERMS_PASSWORD])) { + $core->error($lang['privforummsg']); + } + } + + $sql->addFavoriteIfMissing($tid, $vars->self['username'], 'subscription'); + $core->message($lang['subaddedmsg'], redirect: $vars->full_url . 'memcp.php?action=subscriptions'); } elseif (! $subadd && onSubmit('subsubmit')) { - $query = $db->query("SELECT tid FROM " . $vars->tablepre . "favorites WHERE username='$xmbuser' AND type='subscription'"); - $tids = array(); + $query = $db->query("SELECT tid FROM " . $vars->tablepre . "favorites WHERE username = '" . $vars->xmbuser . "' AND type = 'subscription'"); + $tids = []; while ($sub = $db->fetch_array($query)) { - $delete = formInt('delete'.$sub['tid']); + $delete = formInt('delete' . $sub['tid']); if ($delete == intval($sub['tid'])) { $tids[] = $delete; } } $db->free_result($query); if (count($tids) > 0) { - $tids = implode(', ', $tids); - $db->query("DELETE FROM " . $vars->tablepre . "favorites WHERE username='$xmbuser' AND tid IN ($tids) AND type='subscription'"); + $sql->deleteFavorites($tids, $vars->self['username'], 'subscription'); } - message($lang['subsdeletedmsg'], TRUE, '', '', $vars->full_url . 'memcp.php?action=subscriptions', true, false, true); + $core->message($lang['subsdeletedmsg'], redirect: $vars->full_url . 'memcp.php?action=subscriptions'); } } elseif ($action == 'devices') { if (onSubmit('devicesubmit')) { @@ -613,7 +439,6 @@ $mempage = $template->process('memcp_devices.php'); } else { - require XMB_ROOT . 'include/buddy.inc.php'; $buddy = new \XMB\BuddyManager($core, $db, $sql, $template, $vars); $header = $template->process('header.php'); diff --git a/misc.php b/misc.php index a30df63d..0fe4e7b3 100644 --- a/misc.php +++ b/misc.php @@ -127,8 +127,8 @@ case 'logout': if ('logged-out' == $session->getStatus()) { $gone = $session->getMember(); - $query = $db->query("DELETE FROM " . $vars->tablepre . "whosonline WHERE username='{$gone['username']}'"); - $core->redirect($vars->full_url, 0); + $sql->deleteWhosonline($gone['username']); + $core->redirect($vars->full_url, timeout: 0); } else { $core->message($lang['notloggedin']); } diff --git a/templates/admin_members_edit_row.php b/templates/admin_members_edit_row.php index 7b98759f..e4d1f1ac 100644 --- a/templates/admin_members_edit_row.php +++ b/templates/admin_members_edit_row.php @@ -8,19 +8,14 @@ - + + + diff --git a/templates/admin_members_status_field.php b/templates/admin_members_status_field.php new file mode 100644 index 00000000..6ad0b17f --- /dev/null +++ b/templates/admin_members_status_field.php @@ -0,0 +1,8 @@ + diff --git a/templates/admintool_editprofile.php b/templates/admintool_editprofile.php index 1b2614ea..535c825b 100644 --- a/templates/admintool_editprofile.php +++ b/templates/admintool_editprofile.php @@ -17,14 +17,9 @@ - + + + - @@ -98,18 +93,18 @@ - + - + />
/>
- />
+ />
/>
/>
/>
diff --git a/templates/member_reg.php b/templates/member_reg.php index 28d4f515..76342301 100644 --- a/templates/member_reg.php +++ b/templates/member_reg.php @@ -53,11 +53,11 @@ - + - + @@ -93,7 +93,7 @@ -/>   />  +/>   />  diff --git a/templates/member_reg_avatarlist.php b/templates/member_reg_avatarlist.php index 95dd9ac9..b918c8d8 100644 --- a/templates/member_reg_avatarlist.php +++ b/templates/member_reg_avatarlist.php @@ -1,5 +1,5 @@ -   -* +   +* diff --git a/templates/memcp_favs.php b/templates/memcp_favs.php index 25febf93..43d437ca 100644 --- a/templates/memcp_favs.php +++ b/templates/memcp_favs.php @@ -1,4 +1,4 @@ -
+ diff --git a/templates/memcp_favs_row.php b/templates/memcp_favs_row.php index 1357765b..eed7e077 100644 --- a/templates/memcp_favs_row.php +++ b/templates/memcp_favs_row.php @@ -1,8 +1,8 @@ - - + + - + diff --git a/templates/memcp_profile.php b/templates/memcp_profile.php index 4ea805b9..46b74068 100644 --- a/templates/memcp_profile.php +++ b/templates/memcp_profile.php @@ -64,11 +64,11 @@ - + - + @@ -84,7 +84,7 @@ - + diff --git a/templates/memcp_profile_avatarlist.php b/templates/memcp_profile_avatarlist.php deleted file mode 100644 index 4277d483..00000000 --- a/templates/memcp_profile_avatarlist.php +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/viewthread.php b/viewthread.php index 472199da..a6e4e1f6 100644 --- a/viewthread.php +++ b/viewthread.php @@ -267,8 +267,8 @@ $quickTemplate->allowbbcode = ($forum['allowbbcode'] == 'yes') ? $lang['texton']:$lang['textoff']; if (X_MEMBER) { - $quickTemplate->usesigcheck = ($vars->self['sig'] != '') ? $cheHTML : ''; - $quickTemplate->subcheck = ('yes' == $vars->self['sub_each_post']) ? $cheHTML : ''; + $quickTemplate->usesigcheck = ($vars->self['sig'] != '') ? $vars::cheHTML : ''; + $quickTemplate->subcheck = ('yes' == $vars->self['sub_each_post']) ? $vars::cheHTML : ''; } else { $quickTemplate->usesigcheck = ''; $quickTemplate->subcheck = '';
/> />
 *