36 changes: 24 additions & 12 deletions Sources/DbExtra-postgresql.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.0
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -254,28 +254,40 @@ function smf_db_table_sql($tableName)
$schema_create = substr($schema_create, 0, -strlen($crlf) - 1);

$result = $smcFunc['db_query']('', '
SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary, pg_get_indexdef(i.indexrelid) AS inddef
SELECT pg_get_indexdef(i.indexrelid) AS inddef
FROM pg_class AS c
INNER JOIN pg_index AS i ON (i.indrelid = c.oid)
INNER JOIN pg_class AS c2 ON (c2.oid = i.indexrelid)
WHERE c.relname = {string:table}',
WHERE c.relname = {string:table} AND i.indisprimary is {raw:pk}',
array(
'table' => $tableName,
'pk' => 'false',
)
);

while ($row = $smcFunc['db_fetch_assoc']($result))
{
if ($row['is_primary'])
{
if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0)
continue;
$index_create .= $crlf . $row['inddef'] . ';';
}

$index_create .= $crlf . 'ALTER TABLE ' . $tableName . ' ADD PRIMARY KEY ("' . $matches[1] . '");';
}
else
$index_create .= $crlf . $row['inddef'] . ';';
$smcFunc['db_free_result']($result);

$result = $smcFunc['db_query']('', '
SELECT pg_get_constraintdef(c.oid) as pkdef
FROM pg_constraint as c
WHERE c.conrelid::regclass::text = {string:table} AND
c.contype = {string:constraintType}',
array(
'table' => $tableName,
'constraintType' => 'p',
)
);

while ($row = $smcFunc['db_fetch_assoc']($result))
{
$index_create .= $crlf . 'ALTER TABLE ' . $tableName . ' ADD ' . $row['pkdef'] . ';';
}

$smcFunc['db_free_result']($result);

// Finish it off!
Expand Down
244 changes: 182 additions & 62 deletions Sources/DbPackages-mysql.php

Large diffs are not rendered by default.

328 changes: 181 additions & 147 deletions Sources/DbPackages-postgresql.php

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions Sources/DbSearch-mysql.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2023 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.4
*/

if (!defined('SMF'))
Expand All @@ -30,6 +30,10 @@ function db_search_init()
'db_create_word_search' => 'smf_db_create_word_search',
'db_support_ignore' => true,
);

db_extend();
$version = $smcFunc['db_get_version']();
$smcFunc['db_supports_pcre'] = version_compare($version, strpos($version, 'MariaDB') !== false ? '10.0.5' : '8.0.4', '>=');
}

/**
Expand Down
47 changes: 25 additions & 22 deletions Sources/DbSearch-postgresql.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2023 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.4
*/

if (!defined('SMF'))
Expand All @@ -34,17 +34,8 @@ function db_search_init()

db_extend();

//pg 9.5 got ignore support
$version = $smcFunc['db_get_version']();
// if we got a Beta Version
if (stripos($version, 'beta') !== false)
$version = substr($version, 0, stripos($version, 'beta')) . '.0';
// or RC
if (stripos($version, 'rc') !== false)
$version = substr($version, 0, stripos($version, 'rc')) . '.0';

if (version_compare($version, '9.5.0', '>='))
$smcFunc['db_support_ignore'] = true;
$smcFunc['db_support_ignore'] = true;
$smcFunc['db_supports_pcre'] = true;
}

/**
Expand Down Expand Up @@ -81,16 +72,28 @@ function smf_db_search_query($identifier, $db_string, $db_values = array(), $con
'~ENGINE=MEMORY~i' => '',
),
'insert_into_log_messages_fulltext' => array(
'~LIKE~i' => 'iLIKE',
'~NOT\sLIKE~i' => '~NOT iLIKE',
'~NOT\sRLIKE~i' => '!~*',
'~RLIKE~i' => '~*',
'/NOT\sLIKE/' => 'NOT ILIKE',
'/\bLIKE\b/' => 'ILIKE',
'/NOT RLIKE/' => '!~*',
'/RLIKE/' => '~*',
),
'insert_log_search_results_subject' => array(
'~LIKE~i' => 'iLIKE',
'~NOT\sLIKE~i' => 'NOT iLIKE',
'~NOT\sRLIKE~i' => '!~*',
'~RLIKE~i' => '~*',
'/NOT\sLIKE/' => 'NOT ILIKE',
'/\bLIKE\b/' => 'ILIKE',
'/NOT RLIKE/' => '!~*',
'/RLIKE/' => '~*',
),
'insert_log_search_topics' => array(
'/NOT\sLIKE/' => 'NOT ILIKE',
'/\bLIKE\b/' => 'ILIKE',
'/NOT RLIKE/' => '!~*',
'/RLIKE/' => '~*',
),
'insert_log_search_results_no_index' => array(
'/NOT\sLIKE/' => 'NOT ILIKE',
'/\bLIKE\b/' => 'ILIKE',
'/NOT RLIKE/' => '!~*',
'/RLIKE/' => '~*',
),
);

Expand All @@ -113,7 +116,7 @@ function smf_db_search_query($identifier, $db_string, $db_values = array(), $con

//fix double quotes
if ($identifier == 'insert_into_log_messages_fulltext')
$db_values = str_replace('"', "'", $db_values);
$db_string = str_replace('"', "'", $db_string);

$return = $smcFunc['db_query']('', $db_string,
$db_values, $connection
Expand Down
242 changes: 74 additions & 168 deletions Sources/Display.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2023 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.4
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -40,6 +40,7 @@ function Display()

// Load the proper template.
loadTemplate('Display');
loadCSSFile('attachments.css', array('minimize' => true, 'order_pos' => 450), 'smf_attachments');

// Not only does a prefetch make things slower for the server, but it makes it impossible to know if they read it.
if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch')
Expand Down Expand Up @@ -340,9 +341,6 @@ function Display()
// Create a previous next string if the selected theme has it as a selected option.
$context['previous_next'] = $modSettings['enablePreviousNext'] ? '<a href="' . $scripturl . '?topic=' . $topic . '.0;prev_next=prev#new">' . $txt['previous_next_back'] . '</a> - <a href="' . $scripturl . '?topic=' . $topic . '.0;prev_next=next#new">' . $txt['previous_next_forward'] . '</a>' : '';

// Check if spellchecking is both enabled and actually working. (for quick reply.)
$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && (function_exists('pspell_new') || (function_exists('enchant_broker_init') && ($txt['lang_character_set'] == 'UTF-8' || function_exists('iconv'))));

// Do we need to show the visual verification image?
$context['require_verification'] = !$user_info['is_mod'] && !$user_info['is_admin'] && !empty($modSettings['posts_require_captcha']) && ($user_info['posts'] < $modSettings['posts_require_captcha'] || ($user_info['is_guest'] && $modSettings['posts_require_captcha'] == -1));
if ($context['require_verification'])
Expand Down Expand Up @@ -481,14 +479,14 @@ function Display()
{
// No limit! (actually, there is a limit, but...)
$context['messages_per_page'] = -1;
$context['page_index'] .= empty($modSettings['compactTopicPagesEnable']) ? '<strong>' . $txt['all'] . '</strong> ' : '[<strong>' . $txt['all'] . '</strong>] ';
$context['page_index'] .= sprintf(strtr($settings['page_index']['current_page'], array('%1$d' => '%1$s')), $txt['all']);

// Set start back to 0...
$_REQUEST['start'] = 0;
}
// They aren't using it, but the *option* is there, at least.
else
$context['page_index'] .= '&nbsp;<a href="' . $scripturl . '?topic=' . $topic . '.0;all">' . $txt['all'] . '</a> ';
$context['page_index'] .= sprintf(strtr($settings['page_index']['page'], array('{URL}' => $scripturl . '?topic=' . $topic . '.0;all')), '', $txt['all']);
}

// Build the link tree.
Expand Down Expand Up @@ -860,171 +858,54 @@ function Display()
call_integration_hook('integrate_poll_buttons');
}

$limit = $context['messages_per_page'];
$start = $_REQUEST['start'];
$ascending = empty($options['view_newest_first']);
$firstIndex = 0;

// Check if we can use the seek method to speed things up
if (isset($_SESSION['page_topic']) && $_SESSION['page_topic'] == $topic && $_SESSION['page_ascending'] == $ascending)
{
// User moved to the next page
if (isset($_SESSION['page_next_start']) && $_SESSION['page_next_start'] == $start)
{
$start_char = 'M';
$page_id = $_SESSION['page_last_id'];
}
// User moved to the previous page
elseif (isset($_SESSION['page_before_start']) && $_SESSION['page_before_start'] == $start)
{
$start_char = 'L';
$page_id = $_SESSION['page_first_id'];
}
// User refreshed the current page
elseif (isset($_SESSION['page_current_start']) && $_SESSION['page_current_start'] == $start)
{
$start_char = 'C';
$page_id = $_SESSION['page_first_id'];
}
}
// Special case start page
elseif ($start == 0)
// Jump to page
// Calculate the fastest way to get the messages!
if ($start >= $context['total_visible_posts'] / 2 && $context['messages_per_page'] != -1)
{
$start_char = 'C';
$page_id = $ascending ? $context['topicinfo']['id_first_msg'] : $context['topicinfo']['id_last_msg'];
$DBascending = !$ascending;
$limit = $context['total_visible_posts'] <= $start + $limit ? $context['total_visible_posts'] - $start : $limit;
$start = $context['total_visible_posts'] <= $start + $limit ? 0 : $context['total_visible_posts'] - $start - $limit;
$firstIndex = empty($options['view_newest_first']) ? $start - 1 : $limit - 1;
}
else
$start_char = null;
$DBascending = $ascending;

$limit = $context['messages_per_page'];
// Get each post and poster in this topic.
$request = $smcFunc['db_query']('', '
SELECT id_msg, id_member, approved
FROM {db_prefix}messages
WHERE id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || $can_approve_posts ? '' : '
AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')') . '
ORDER BY id_msg ' . ($DBascending ? '' : 'DESC') . ($context['messages_per_page'] == -1 ? '' : '
LIMIT {int:start}, {int:max}'),
array(
'current_member' => $user_info['id'],
'current_topic' => $topic,
'is_approved' => 1,
'blank_id_member' => 0,
'start' => $start,
'max' => $limit,
)
);

$messages = array();
$all_posters = array();
$firstIndex = 0;

if (isset($start_char))
{
if ($start_char === 'M' || $start_char === 'C')
{
$DBascending = $ascending;
$page_operator = $ascending ? '>=' : '<=';
}
else
{
$DBascending = !$ascending;
$page_operator = $ascending ? '<=' : '>=';
}

if ($start_char === 'C')
$limit_seek = $limit;
else
$limit_seek = $limit + 1;

$request = $smcFunc['db_query']('', '
SELECT id_msg, id_member, approved
FROM {db_prefix}messages
WHERE id_topic = {int:current_topic}
AND id_msg ' . $page_operator . ' {int:page_id}' . (!$modSettings['postmod_active'] || $can_approve_posts ? '' : '
AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')') . '
ORDER BY id_msg ' . ($DBascending ? '' : 'DESC') . ($context['messages_per_page'] == -1 ? '' : '
LIMIT {int:limit}'),
array(
'current_member' => $user_info['id'],
'current_topic' => $topic,
'is_approved' => 1,
'blank_id_member' => 0,
'limit' => $limit_seek,
'page_id' => $page_id,
)
);

$found_msg = false;

// Fallback
if ($smcFunc['db_num_rows']($request) < 1)
unset($start_char);
else
{
while ($row = $smcFunc['db_fetch_assoc']($request))
{
// Check if the start msg is in our result
if ($row['id_msg'] == $page_id)
$found_msg = true;

// Skip the the start msg if we not in mode C
if ($start_char === 'C' || $row['id_msg'] != $page_id)
{
if (!empty($row['id_member']))
$all_posters[$row['id_msg']] = $row['id_member'];

$messages[] = $row['id_msg'];
}
}

// page_id not found? -> fallback
if (!$found_msg)
{
$messages = array();
$all_posters = array();
unset($start_char);
}
}

// Before Page bring in the right order
if (!empty($start_char) && $start_char === 'L')
krsort($messages);
}

// Jump to page
if (empty($start_char))
while ($row = $smcFunc['db_fetch_assoc']($request))
{
// Calculate the fastest way to get the messages!
if ($start >= $context['total_visible_posts'] / 2 && $context['messages_per_page'] != -1)
{
$DBascending = !$ascending;
$limit = $context['total_visible_posts'] <= $start + $limit ? $context['total_visible_posts'] - $start : $limit;
$start = $context['total_visible_posts'] <= $start + $limit ? 0 : $context['total_visible_posts'] - $start - $limit;
$firstIndex = empty($options['view_newest_first']) ? $start - 1 : $limit - 1;
}
else
$DBascending = $ascending;

// Get each post and poster in this topic.
$request = $smcFunc['db_query']('', '
SELECT id_msg, id_member, approved
FROM {db_prefix}messages
WHERE id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || $can_approve_posts ? '' : '
AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')') . '
ORDER BY id_msg ' . ($DBascending ? '' : 'DESC') . ($context['messages_per_page'] == -1 ? '' : '
LIMIT {int:start}, {int:max}'),
array(
'current_member' => $user_info['id'],
'current_topic' => $topic,
'is_approved' => 1,
'blank_id_member' => 0,
'start' => $start,
'max' => $limit,
)
);

while ($row = $smcFunc['db_fetch_assoc']($request))
{
if (!empty($row['id_member']))
$all_posters[$row['id_msg']] = $row['id_member'];
$messages[] = $row['id_msg'];
}

// Sort the messages into the correct display order
if (!$DBascending)
sort($messages);
if (!empty($row['id_member']))
$all_posters[$row['id_msg']] = $row['id_member'];
$messages[] = $row['id_msg'];
}

// Remember the paging data for next time
$_SESSION['page_first_id'] = $ascending ? reset($messages) : end($messages);
$_SESSION['page_before_start'] = $_REQUEST['start'] - $limit;
$_SESSION['page_last_id'] = $ascending ? end($messages) : reset($messages);
$_SESSION['page_next_start'] = $_REQUEST['start'] + $limit;
$_SESSION['page_current_start'] = $_REQUEST['start'];
$_SESSION['page_topic'] = $topic;
$_SESSION['page_ascending'] = $ascending;
// Sort the messages into the correct display order
if (!$DBascending)
sort($messages);

$smcFunc['db_free_result']($request);
$posters = array_unique($all_posters);
Expand Down Expand Up @@ -1159,8 +1040,13 @@ function Display()
'now' => time(),
)
);
$user_info['alerts'] = $user_info['alerts'] - max(0, $smcFunc['db_affected_rows']());
updateMemberData($user_info['id'], array('alerts' => $user_info['alerts']));
// If changes made, update the member record as well
if ($smcFunc['db_affected_rows']() > 0)
{
require_once($sourcedir . '/Profile-Modify.php');
$user_info['alerts'] = alert_count($user_info['id'], true);
updateMemberData($user_info['id'], array('alerts' => $user_info['alerts']));
}
}
}

Expand Down Expand Up @@ -1242,7 +1128,7 @@ function Display()

$context['jump_to'] = array(
'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
'board_name' => $smcFunc['htmlspecialchars'](strtr(strip_tags($board_info['name']), array('&amp;' => '&'))),
'board_name' => strtr($smcFunc['htmlspecialchars'](strip_tags($board_info['name'])), array('&amp;' => '&')),
'child_level' => $board_info['child_level'],
);

Expand Down Expand Up @@ -1278,7 +1164,6 @@ function Display()
'can_remove_poll' => 'poll_remove',
'can_reply' => 'post_reply',
'can_reply_unapproved' => 'post_unapproved_replies',
'can_view_warning' => 'profile_warning',
);
foreach ($anyown_permissions as $contextual => $perm)
$context[$contextual] = allowedTo($perm . '_any') || ($context['user']['started'] && allowedTo($perm . '_own'));
Expand Down Expand Up @@ -1326,7 +1211,7 @@ function Display()

// Check if the draft functions are enabled and that they have permission to use them (for quick reply.)
$context['drafts_save'] = !empty($modSettings['drafts_post_enabled']) && allowedTo('post_draft') && $context['can_reply'];
$context['drafts_autosave'] = !empty($context['drafts_save']) && !empty($modSettings['drafts_autosave_enabled']);
$context['drafts_autosave'] = !empty($context['drafts_save']) && !empty($modSettings['drafts_autosave_enabled']) && !empty($options['drafts_autosave_enabled']);
if (!empty($context['drafts_save']))
loadLanguage('Drafts');

Expand Down Expand Up @@ -1462,6 +1347,26 @@ function Display()
// Note: integrate_mod_buttons is no more necessary and deprecated, but is kept for backward compatibility with 2.0
call_integration_hook('integrate_mod_buttons', array(&$context['mod_buttons']));

// If any buttons have a 'test' check, run those tests now to keep things clean.
foreach (array('normal_buttons', 'mod_buttons') as $button_strip)
{
foreach ($context[$button_strip] as $key => $value)
{
if (isset($value['test']) && empty($context[$value['test']]))
{
unset($context[$button_strip][$key]);
}
elseif (isset($value['sub_buttons']))
{
foreach ($value['sub_buttons'] as $subkey => $subvalue)
{
if (isset($subvalue['test']) && empty($context[$subvalue['test']]))
unset($context[$button_strip][$key]['sub_buttons'][$subkey]);
}
}
}
}

// Load the drafts js file
if ($context['drafts_autosave'])
loadJavaScriptFile('drafts.js', array('defer' => false, 'minimize' => true), 'smf_drafts');
Expand Down Expand Up @@ -1566,7 +1471,7 @@ function prepareDisplayContext($reset = false)
else
{
// Define this here to make things a bit more readable
$can_view_warning = allowedTo('moderate_forum') || allowedTo('view_warning_any') || ($message['id_member'] == $user_info['id'] && allowedTo('view_warning_own'));
$can_view_warning = $user_info['is_mod'] || allowedTo('moderate_forum') || allowedTo('view_warning_any') || ($message['id_member'] == $user_info['id'] && allowedTo('view_warning_own'));

$memberContext[$message['id_member']]['can_view_profile'] = allowedTo('profile_view') || ($message['id_member'] == $user_info['id'] && !$user_info['is_guest']);
$memberContext[$message['id_member']]['is_topic_starter'] = $message['id_member'] == $context['topic_starter_id'];
Expand Down Expand Up @@ -1602,11 +1507,11 @@ function prepareDisplayContext($reset = false)
'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.png',
'subject' => $message['subject'],
'time' => timeformat($message['poster_time']),
'timestamp' => forum_time(true, $message['poster_time']),
'timestamp' => $message['poster_time'],
'counter' => $counter,
'modified' => array(
'time' => timeformat($message['modified_time']),
'timestamp' => forum_time(true, $message['modified_time']),
'timestamp' => $message['modified_time'],
'name' => $message['modified_name'],
'reason' => $message['modified_reason']
),
Expand Down Expand Up @@ -1671,7 +1576,7 @@ function prepareDisplayContext($reset = false)
'quick_edit' => array(
'label' => $txt['quick_edit'],
'class' => 'quick_edit',
'id' =>' modify_button_'. $output['id'],
'id' => 'modify_button_'. $output['id'],
'custom' => 'onclick="oQuickModify.modifyMsg(\''.$output['id'].'\', \''.!empty($modSettings['toggle_subject']).'\')"',
'icon' => 'quick_edit_button',
'show' => $output['can_modify']
Expand Down Expand Up @@ -1737,10 +1642,11 @@ function prepareDisplayContext($reset = false)
),
),
'quickmod' => array(
'class' => 'inline_mod_check',
'id' => 'in_topic_mod_check_'. $output['id'],
'custom' => 'style="display: none;"',
'content' => '',
'show' => !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $output['can_remove'],
'show' => !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $output['can_remove']
)
);

Expand Down
14 changes: 7 additions & 7 deletions Sources/Drafts.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.0
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -549,7 +549,7 @@ function showProfileDrafts($memID, $draft_type = 0)
// Get the count of applicable drafts on the boards they can (still) see ...
// @todo .. should we just let them see their drafts even if they have lost board access ?
$request = $smcFunc['db_query']('', '
SELECT COUNT(id_draft)
SELECT COUNT(*)
FROM {db_prefix}user_drafts AS ud
INNER JOIN {db_prefix}boards AS b ON (b.id_board = ud.id_board AND {query_see_board})
WHERE id_member = {int:id_member}
Expand Down Expand Up @@ -636,7 +636,7 @@ function showProfileDrafts($memID, $draft_type = 0)
),
'subject' => $row['subject'],
'time' => timeformat($row['poster_time']),
'timestamp' => forum_time(true, $row['poster_time']),
'timestamp' => $row['poster_time'],
'icon' => $row['icon'],
'id_draft' => $row['id_draft'],
'locked' => $row['locked'],
Expand Down Expand Up @@ -667,7 +667,7 @@ function showProfileDrafts($memID, $draft_type = 0)
$context[$context['profile_menu_name']]['tab_data'] = array(
'title' => $txt['drafts_show'],
'description' => $txt['drafts_show_desc'],
'icon_class' => 'pm_icons inbox'
'icon_class' => 'main_icons drafts'
);
$context['sub_template'] = 'showDrafts';
}
Expand Down Expand Up @@ -724,7 +724,7 @@ function showPMDrafts($memID = -1)

// Get the count of applicable drafts
$request = $smcFunc['db_query']('', '
SELECT COUNT(id_draft)
SELECT COUNT(*)
FROM {db_prefix}user_drafts
WHERE id_member = {int:id_member}
AND type={int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
Expand Down Expand Up @@ -829,7 +829,7 @@ function showPMDrafts($memID = -1)
'counter' => $counter,
'subject' => $row['subject'],
'time' => timeformat($row['poster_time']),
'timestamp' => forum_time(true, $row['poster_time']),
'timestamp' => $row['poster_time'],
'id_draft' => $row['id_draft'],
'recipients' => $recipients,
'age' => floor((time() - $row['poster_time']) / 86400),
Expand Down
57 changes: 49 additions & 8 deletions Sources/Errors.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2024 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.5
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -112,7 +112,7 @@ function log_error($error_message, $error_type = 'general', $file = null, $line
$tried_hook = true;
// Allow the hook to change the error_type and know about the error.
call_integration_hook('integrate_error_types', array(&$other_error_types, &$error_type, $error_message, $file, $line));
$known_error_types += $other_error_types;
$known_error_types = array_merge($known_error_types, $other_error_types);
}
// Make sure the category that was specified is a valid one
$error_type = in_array($error_type, $known_error_types) && $error_type !== true ? $error_type : 'general';
Expand All @@ -133,7 +133,7 @@ function log_error($error_message, $error_type = 'general', $file = null, $line
if (!isset($context['num_errors']))
{
$query = $smcFunc['db_query']('', '
SELECT COUNT(id_error)
SELECT COUNT(*)
FROM {db_prefix}log_errors',
array()
);
Expand Down Expand Up @@ -196,6 +196,9 @@ function fatal_lang_error($error, $log = 'general', $sprintf = array(), $status
global $txt, $language, $user_info, $context;
static $fatal_error_called = false;

// Ensure this is an array.
$sprintf = (array) $sprintf;

// Send the status header - set this to 0 or false if you don't want to send one at all
if (!empty($status))
send_http_status($status);
Expand All @@ -207,6 +210,17 @@ function fatal_lang_error($error, $log = 'general', $sprintf = array(), $status
loadTheme();
}

// Attempt to load the text string.
loadLanguage('Errors');
if (empty($txt[$error]))
$error_message = $error;
else
$error_message = empty($sprintf) ? $txt[$error] : vsprintf($txt[$error], $sprintf);

// Send a custom header if we have a custom message.
if (isset($_REQUEST['js']) || isset($_REQUEST['xml']) || isset($_RQEUEST['ajax']))
header('X-SMF-errormsg: ' . $error_message);

// If we have no theme stuff we can't have the language file...
if (empty($context['theme_loaded']))
die($error);
Expand All @@ -217,12 +231,15 @@ function fatal_lang_error($error, $log = 'general', $sprintf = array(), $status
{
loadLanguage('Errors', $language);
$reload_lang_file = $language != $user_info['language'];
$error_message = empty($sprintf) ? $txt[$error] : vsprintf($txt[$error], $sprintf);
if (empty($txt[$error]))
$error_message = $error;
else
$error_message = empty($sprintf) ? $txt[$error] : vsprintf($txt[$error], $sprintf);
log_error($error_message, $log);
}

// Load the language file, only if it needs to be reloaded
if ($reload_lang_file)
if ($reload_lang_file && !empty($txt[$error]))
{
loadLanguage('Errors');
$error_message = empty($sprintf) ? $txt[$error] : vsprintf($txt[$error], $sprintf);
Expand All @@ -246,7 +263,7 @@ function smf_error_handler($error_level, $error_string, $file, $line)
global $settings, $modSettings, $db_show_debug;

// Error was suppressed with the @-operator.
if (error_reporting() == 0)
if (error_reporting() == 0 || error_reporting() == (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR))
return true;

// Ignore errors that should should not be logged.
Expand Down Expand Up @@ -316,6 +333,28 @@ function smf_error_handler($error_level, $error_string, $file, $line)
die('No direct access...');
}

/**
* Generic handler for uncaught exceptions.
*
* Always ends execution.
*
* @param \Throwable $e The uncaught exception.
*/
function smf_exception_handler(\Throwable $e)
{
global $modSettings, $txt;

loadLanguage('Errors');

$message = $txt[$e->getMessage()] ?? $e->getMessage();

if (!empty($modSettings['enableErrorLogging'])) {
log_error($message, 'general', $e->getFile(), $e->getLine());
}

fatal_error($message, false);
}

/**
* It is called by {@link fatal_error()} and {@link fatal_lang_error()}.
*
Expand Down Expand Up @@ -348,6 +387,8 @@ function setup_fatal_error_context($error_message, $error_code = null)

$context['error_code'] = isset($error_code) ? 'id="' . $error_code . '" ' : '';

$context['error_link'] = isset($context['error_link']) ? $context['error_link'] : 'javascript:document.location=document.referrer';

if (empty($context['page_title']))
$context['page_title'] = $context['error_title'];

Expand Down Expand Up @@ -386,7 +427,7 @@ function setup_fatal_error_context($error_message, $error_code = null)
PROGRAM FLOW. Otherwise, security error messages will not be shown, and
your forum will be in a very easily hackable state.
*/
trigger_error('Hacking attempt...', E_USER_ERROR);
die('No direct access...');
}

/**
Expand Down
17 changes: 8 additions & 9 deletions Sources/Groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.3
*/

if (!defined('SMF'))
Expand All @@ -32,6 +32,8 @@ function Groups()
'requests' => array('GroupRequests', 'group_requests'),
);

call_integration_hook('integrate_manage_groups', array(&$subActions));

// Default to sub action 'index'.
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'index';

Expand All @@ -58,9 +60,6 @@ function Groups()
);
}

// CRUD $subActions as needed.
call_integration_hook('integrate_manage_groups', array(&$subActions));

// Call the actual function.
call_helper($subActions[$_REQUEST['sa']][0]);
}
Expand Down Expand Up @@ -285,13 +284,13 @@ function MembergroupMembers()
// Must be adding new members to the group...
elseif (isset($_REQUEST['add']) && (!empty($_REQUEST['toAdd']) || !empty($_REQUEST['member_add'])) && $context['group']['assignable'])
{
checkSession();
validateToken('mod-mgm');

// Demand an admin password before adding new admins -- every time, no matter what.
if ($context['group']['id'] == 1)
validateSession('admin', true);

checkSession();
validateToken('mod-mgm');

$member_query = array();
$member_parameters = array();

Expand Down Expand Up @@ -659,7 +658,7 @@ function GroupRequests()
array(
'position' => 'bottom_of_list',
'value' => '
<select name="req_action" onchange="if (this.value != 0 &amp;&amp; (this.value == \'reason\' || confirm(\'' . $txt['mc_groupr_warning'] . '\'))) this.form.submit();">
<select id="req_action" name="req_action" onchange="if (this.value != 0 &amp;&amp; (this.value == \'reason\' || confirm(\'' . $txt['mc_groupr_warning'] . '\'))) this.form.submit();">
<option value="0">' . $txt['with_selected'] . ':</option>
<option value="0" disabled>---------------------</option>
<option value="approve">' . $txt['mc_groupr_approve'] . '</option>
Expand Down
64 changes: 11 additions & 53 deletions Sources/Help.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.3
*/

if (!defined('SMF'))
Expand All @@ -30,7 +30,6 @@ function ShowHelp()

$subActions = array(
'index' => 'HelpIndex',
'rules' => 'HelpRules',
);

// CRUD $subActions as needed.
Expand Down Expand Up @@ -78,44 +77,6 @@ function HelpIndex()
$context['sub_template'] = 'manual';
}

/**
* Displays forum rules
*/
function HelpRules()
{
global $context, $txt, $boarddir, $user_info, $scripturl;

// Build the link tree.
$context['linktree'][] = array(
'url' => $scripturl . '?action=help',
'name' => $txt['help'],
);
$context['linktree'][] = array(
'url' => $scripturl . '?action=help;sa=rules',
'name' => $txt['terms_and_rules'],
);

// Have we got a localized one?
if (file_exists($boarddir . '/agreement.' . $user_info['language'] . '.txt'))
$context['agreement'] = parse_bbc(file_get_contents($boarddir . '/agreement.' . $user_info['language'] . '.txt'), true, 'agreement_' . $user_info['language']);
elseif (file_exists($boarddir . '/agreement.txt'))
$context['agreement'] = parse_bbc(file_get_contents($boarddir . '/agreement.txt'), true, 'agreement');
else
$context['agreement'] = '';

// Nothing to show, so let's get out of here
if (empty($context['agreement']))
{
// No file found or a blank file! Just leave...
redirectexit();
}

$context['canonical_url'] = $scripturl . '?action=help;sa=rules';

$context['page_title'] = $txt['terms_and_rules'];
$context['sub_template'] = 'terms';
}

/**
* Show some of the more detailed help to give the admin an idea...
* It shows a popup for administrative or user help.
Expand Down Expand Up @@ -148,20 +109,13 @@ function ShowAdminHelp()
// Allow mods to load their own language file here
call_integration_hook('integrate_helpadmin');

// Set the page title to something relevant.
$context['page_title'] = $context['forum_name'] . ' - ' . $txt['help'];

// Don't show any template layers, just the popup sub template.
$context['template_layers'] = array();
$context['sub_template'] = 'popup';

// What help string should be used?
if (isset($helptxt[$_GET['help']]))
$context['help_text'] = $helptxt[$_GET['help']];
elseif (isset($txt[$_GET['help']]))
$context['help_text'] = $txt[$_GET['help']];
else
$context['help_text'] = $_GET['help'];
fatal_lang_error('not_found', false, array(), 404);

switch ($_GET['help']) {
case 'cal_short_months':
Expand All @@ -171,10 +125,7 @@ function ShowAdminHelp()
$context['help_text'] = sprintf($context['help_text'], $txt['days_short'][1], $txt['days'][1]);
break;
case 'cron_is_real_cron':
$context['help_text'] = sprintf($context['help_text'], $boarddir, $boardurl);
break;
case 'enableSpellChecking':
$context['help_text'] = sprintf($context['help_text'], ((function_exists('pspell_new') || function_exists('enchant_broker_init')) ? $helptxt['enableSpellCheckingSupported'] : $helptxt['enableSpellCheckingUnsupported']));
$context['help_text'] = sprintf($context['help_text'], allowedTo('admin_forum') ? $boarddir : '[' . $txt['hidden'] . ']', $boardurl);
break;
case 'queryless_urls':
$context['help_text'] = sprintf($context['help_text'], (isset($_SERVER['SERVER_SOFTWARE']) && (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false || strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false) ? $helptxt['queryless_urls_supported'] : $helptxt['queryless_urls_unsupported']));
Expand All @@ -184,6 +135,13 @@ function ShowAdminHelp()
// Does this text contain a link that we should fill in?
if (preg_match('~%([0-9]+\$)?s\?~', $context['help_text'], $match))
$context['help_text'] = sprintf($context['help_text'], $scripturl, $context['session_id'], $context['session_var']);

// Set the page title to something relevant.
$context['page_title'] = $context['forum_name'] . ' - ' . $txt['help'];

// Don't show any template layers, just the popup sub template.
$context['template_layers'] = array();
$context['sub_template'] = 'popup';
}

?>
46 changes: 42 additions & 4 deletions Sources/Likes.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2024 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.5
*/

if (!defined('SMF'))
Expand All @@ -26,6 +26,11 @@ class Likes
*/
protected $_js = false;

/**
* @var string The sub action sent in $_GET['sa'].
*/
protected $_sa = null;

/**
* @var string If filled, its value will contain a string matching a key on a language var $txt[$this->_error]
*/
Expand Down Expand Up @@ -286,6 +291,39 @@ protected function delete()
// Are we calling this directly? if so, set a proper data for the response. Do note that __METHOD__ returns both the class name and the function name.
if ($this->_sa == __FUNCTION__)
$this->_data = __FUNCTION__;

// Check to see if there is an unread alert to delete as well...
$result = $smcFunc['db_query']('', '
SELECT id_alert, id_member FROM {db_prefix}user_alerts
WHERE content_id = {int:like_content}
AND content_type = {string:like_type}
AND id_member_started = {int:id_member_started}
AND content_action = {string:content_action}
AND is_read = {int:unread}',
array(
'like_content' => $this->_content,
'like_type' => $this->_type,
'id_member_started' => $this->_user['id'],
'content_action' => 'like',
'unread' => 0,
)
);
// Found one?
if ($smcFunc['db_num_rows']($result) == 1)
{
list($alert, $member) = $smcFunc['db_fetch_row']($result);

// Delete it
$smcFunc['db_query']('', '
DELETE FROM {db_prefix}user_alerts
WHERE id_alert = {int:alert}',
array(
'alert' => $alert,
)
);
// Decrement counter for member who received the like
updateMemberData($member, array('alerts' => '-'));
}
}

/**
Expand All @@ -306,7 +344,7 @@ protected function insert()
call_integration_hook('integrate_issue_like_before', array(&$type, &$content, &$user, &$time));

// Insert the like.
$smcFunc['db_insert']('insert',
$smcFunc['db_insert']('ignore',
'{db_prefix}user_likes',
array('content_id' => 'int', 'content_type' => 'string-6', 'id_member' => 'int', 'like_time' => 'int'),
array($content, $type, $user['id'], $time),
Expand Down Expand Up @@ -344,7 +382,7 @@ protected function _count()
global $smcFunc;

$request = $smcFunc['db_query']('', '
SELECT COUNT(id_member)
SELECT COUNT(*)
FROM {db_prefix}user_likes
WHERE content_id = {int:like_content}
AND content_type = {string:like_type}',
Expand Down
670 changes: 447 additions & 223 deletions Sources/Load.php

Large diffs are not rendered by default.

129 changes: 119 additions & 10 deletions Sources/LogInOut.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.3
*/

if (!defined('SMF'))
Expand All @@ -31,15 +31,40 @@ function Login()

// You are already logged in, go take a tour of the boards
if (!empty($user_info['id']))
redirectexit();
{
// This came from a valid hashed return url. Or something that knows our secrets...
if (!empty($_REQUEST['return_hash']) && !empty($_REQUEST['return_to']) && hash_hmac('sha1', un_htmlspecialchars($_REQUEST['return_to']), get_auth_secret()) == $_REQUEST['return_hash'])
redirectexit(un_htmlspecialchars($_REQUEST['return_to']));
else
redirectexit();
}

// We need to load the Login template/language file.
loadLanguage('Login');
loadTemplate('Login');

$context['sub_template'] = 'login';

if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')
/* This is true when:
* We have a valid header indicating a JQXHR request. This is not sent during a cross domain request.
* OR we have found:
* 1. valid cors host
* 2. A header indicating a SMF request
* 3. The url has a ajax in either the GET or POST
* These are not intended for security, but ensuring the request is intended for a JQXHR response.
*/
if (
(
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
)
||
(
!empty($context['valid_cors_found'])
&& !empty($_SERVER['HTTP_X_SMF_AJAX'])
&& isset($_REQUEST['ajax'])
)
)
{
$context['from_ajax'] = true;
$context['template_layers'] = array();
Expand All @@ -60,6 +85,9 @@ function Login()
// Set the login URL - will be used when the login process is done (but careful not to send us to an attachment).
if (isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'dlattach') === false && preg_match('~(board|topic)[=,]~', $_SESSION['old_url']) != 0)
$_SESSION['login_url'] = $_SESSION['old_url'];
// This came from a valid hashed return url. Or something that knows our secrets...
elseif (!empty($_REQUEST['return_hash']) && !empty($_REQUEST['return_to']) && hash_hmac('sha1', un_htmlspecialchars($_REQUEST['return_to']), get_auth_secret()) == $_REQUEST['return_hash'])
$_SESSION['login_url'] = un_htmlspecialchars($_REQUEST['return_to']);
elseif (isset($_SESSION['login_url']) && strpos($_SESSION['login_url'], 'dlattach') !== false)
unset($_SESSION['login_url']);

Expand Down Expand Up @@ -90,7 +118,26 @@ function Login2()
// Load cookie authentication stuff.
require_once($sourcedir . '/Subs-Auth.php');

if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')
/* This is true when:
* We have a valid header indicating a JQXHR request. This is not sent during a cross domain request.
* OR we have found:
* 1. valid cors host
* 2. A header indicating a SMF request
* 3. The url has a ajax in either the GET or POST
* These are not intended for security, but ensuring the request is intended for a JQXHR response.
*/
if (
(
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
)
||
(
!empty($context['valid_cors_found'])
&& !empty($_SERVER['HTTP_X_SMF_AJAX'])
&& isset($_REQUEST['ajax'])
)
)
{
$context['from_ajax'] = true;
$context['template_layers'] = array();
Expand All @@ -115,7 +162,10 @@ function Login2()
list (,, $timeout) = safe_unserialize($_SESSION['login_' . $cookiename]);

else
trigger_error('Login2(): Cannot be logged in without a session or cookie', E_USER_ERROR);
{
loadLanguage('Errors');
throw new \Exception('login_no_session_cookie');
}

$user_settings['password_salt'] = bin2hex($smcFunc['random_bytes'](16));
updateMemberData($user_info['id'], array('password_salt' => $user_settings['password_salt']));
Expand Down Expand Up @@ -145,7 +195,7 @@ function Login2()
redirectexit(empty($user_settings['tfa_secret']) ? '' : 'action=logintfa');
elseif (!empty($_SESSION['login_url']) && (strpos($_SESSION['login_url'], 'http://') === false && strpos($_SESSION['login_url'], 'https://') === false))
{
unset ($_SESSION['login_url']);
unset($_SESSION['login_url']);
redirectexit(empty($user_settings['tfa_secret']) ? '' : 'action=logintfa');
}
elseif (!empty($user_settings['tfa_secret']))
Expand Down Expand Up @@ -300,6 +350,7 @@ function Login2()
$other_passwords[] = md5($_POST['passwrd'] . strtolower($user_settings['member_name']));
$other_passwords[] = md5(md5($_POST['passwrd']));
$other_passwords[] = $_POST['passwrd'];
$other_passwords[] = crypt($_POST['passwrd'], $user_settings['passwd']);

// This one is a strange one... MyPHP, crypt() on the MD5 hash.
$other_passwords[] = crypt(md5($_POST['passwrd']), md5($_POST['passwrd']));
Expand All @@ -314,6 +365,19 @@ function Login2()
// APBoard 2 Login Method.
$other_passwords[] = md5(crypt($_POST['passwrd'], 'CRYPT_MD5'));
}
// If the salt is set let's try some other options
elseif (!empty($modSettings['enable_password_conversion']) && $user_settings['password_salt'] != '')
{
// PHPBB 3 check this function exists in PHP 5.5 or higher
if (function_exists('password_verify'))
$other_passwords[] = password_verify($_POST['passwrd'],$user_settings['password_salt']);

// PHP-Fusion
$other_passwords[] = hash_hmac('sha256', $_POST['passwrd'], $user_settings['password_salt']);

// MyBB
$other_passwords[] = md5(md5($user_settings['password_salt']) . md5($_POST['passwrd']));
}
// The hash should be 40 if it's SHA-1, so we're safe with more here too.
elseif (!empty($modSettings['enable_password_conversion']) && strlen($user_settings['passwd']) == 32)
{
Expand All @@ -337,6 +401,9 @@ function Login2()
if (!empty($modSettings['enable_password_conversion']))
$other_passwords[] = sha1($user_settings['password_salt'] . sha1($user_settings['password_salt'] . sha1($_POST['passwrd'])));

// PunBB
$other_passwords[] = sha1($user_settings['password_salt'] . sha1($_POST['passwrd']));

// Perhaps we converted to UTF-8 and have a valid password being hashed differently.
if ($context['character_set'] == 'UTF-8' && !empty($modSettings['previousCharacterSet']) && $modSettings['previousCharacterSet'] != 'utf8')
{
Expand Down Expand Up @@ -434,7 +501,26 @@ function LoginTFA()
$totp = new \TOTP\Auth($member['tfa_secret']);
$totp->setRange(1);

if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')
/* This is true when:
* We have a valid header indicating a JQXHR request. This is not sent during a cross domain request.
* OR we have found:
* 1. valid cors host
* 2. A header indicating a SMF request
* 3. The url has a ajax in either the GET or POST
* These are not intended for security, but ensuring the request is intended for a JQXHR response.
*/
if (
(
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
)
||
(
!empty($context['valid_cors_found'])
&& !empty($_SERVER['HTTP_X_SMF_AJAX'])
&& isset($_REQUEST['ajax'])
)
)
{
$context['from_ajax'] = true;
$context['template_layers'] = array();
Expand Down Expand Up @@ -539,7 +625,7 @@ function checkActivation()
// Standard activation?
elseif ($activation_status != 1)
{
log_error($txt['activate_not_completed1'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', false);
log_error($txt['activate_not_completed1'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', 'user');

$context['login_errors'][] = $txt['activate_not_completed1'] . ' <a href="' . $scripturl . '?action=activate;sa=resend;u=' . $user_settings['id_member'] . '">' . $txt['activate_not_completed2'] . '</a>';
return false;
Expand Down Expand Up @@ -648,8 +734,31 @@ function Logout($internal = false, $redirect = true)
{
global $sourcedir, $user_info, $user_settings, $context, $smcFunc, $cookiename, $modSettings;

// They decided to cancel a logout?
if (!$internal && isset($_POST['cancel']) && isset($_GET[$context['session_var']]))
redirectexit(!empty($_SESSION['logout_return']) ? $_SESSION['logout_return'] : '');
// Prompt to logout?
elseif (!$internal && !isset($_GET[$context['session_var']]))
{
loadLanguage('Login');
loadTemplate('Login');
$context['sub_template'] = 'logout';

// This came from a valid hashed return url. Or something that knows our secrets...
if (!empty($_REQUEST['return_hash']) && !empty($_REQUEST['return_to']) && hash_hmac('sha1', un_htmlspecialchars($_REQUEST['return_to']), get_auth_secret()) == $_REQUEST['return_hash'])
{
$_SESSION['logout_url'] = un_htmlspecialchars($_REQUEST['return_to']);
$_SESSION['logout_return'] = $_SESSION['logout_url'];
}
// Setup the return address.
elseif (isset($_SESSION['old_url']))
$_SESSION['logout_return'] = $_SESSION['old_url'];

// Don't go any further.
return;
}
// Make sure they aren't being auto-logged out.
if (!$internal)
elseif (!$internal && isset($_GET[$context['session_var']]))
checkSession('get');

require_once($sourcedir . '/Subs-Auth.php');
Expand Down
67 changes: 41 additions & 26 deletions Sources/Logging.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2024 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.5
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -52,14 +52,18 @@ function writeLog($force = false)

if (!empty($modSettings['who_enabled']))
{
$encoded_get = truncate_array($_GET) + array('USER_AGENT' => $_SERVER['HTTP_USER_AGENT']);
$encoded_get = truncate_array($_GET) + array('USER_AGENT' => mb_substr($_SERVER['HTTP_USER_AGENT'], 0, 128));

// In the case of a dlattach action, session_var may not be set.
if (!isset($context['session_var']))
$context['session_var'] = $_SESSION['session_var'];

unset($encoded_get['sesc'], $encoded_get[$context['session_var']]);
$encoded_get = $smcFunc['json_encode']($encoded_get);

// Sometimes folks mess with USER_AGENT & $_GET data, so one last check to avoid 'data too long' errors
if (mb_strlen($encoded_get) > 2048)
$encoded_get = '';
}
else
$encoded_get = '';
Expand Down Expand Up @@ -287,7 +291,7 @@ function displayDebug()
if ($_SESSION['view_queries'] == 1 && !empty($db_cache))
foreach ($db_cache as $q => $query_data)
{
$is_select = strpos(trim($query_data['q']), 'SELECT') === 0 || preg_match('~^INSERT(?: IGNORE)? INTO \w+(?:\s+\([^)]+\))?\s+SELECT .+$~s', trim($query_data['q'])) != 0;
$is_select = strpos(trim($query_data['q']), 'SELECT') === 0 || preg_match('~^INSERT(?: IGNORE)? INTO \w+(?:\s+\([^)]+\))?\s+SELECT .+$~s', trim($query_data['q'])) != 0 || strpos(trim($query_data['q']), 'WITH') === 0;
// Temporary tables created in earlier queries are not explainable.
if ($is_select)
{
Expand Down Expand Up @@ -349,7 +353,7 @@ function trackStats($stats = array())

$setStringUpdate = '';
$insert_keys = array();
$date = strftime('%Y-%m-%d', forum_time(false));
$date = smf_strftime('%Y-%m-%d', time());
$update_parameters = array(
'current_date' => $date,
);
Expand Down Expand Up @@ -401,7 +405,7 @@ function trackStats($stats = array())
*
* @return int The ID of the row containing the logged data
*/
function logAction($action, $extra = array(), $log_type = 'moderate')
function logAction($action, array $extra = array(), $log_type = 'moderate')
{
return logActions(array(array(
'action' => $action,
Expand All @@ -426,40 +430,39 @@ function logAction($action, $extra = array(), $log_type = 'moderate')
*
* @return int The last logged ID
*/
function logActions($logs)
function logActions(array $logs)
{
global $modSettings, $user_info, $smcFunc, $sourcedir;
global $modSettings, $user_info, $smcFunc, $sourcedir, $txt;

$inserts = array();
$log_types = array(
'moderate' => 1,
'user' => 2,
'admin' => 3,
);
$always_log = array('agreement_accepted', 'policy_accepted', 'agreement_updated', 'policy_updated');

// Make sure this particular log is enabled first...
if (empty($modSettings['modlog_enabled']))
unset ($log_types['moderate']);
if (empty($modSettings['userlog_enabled']))
unset ($log_types['user']);
if (empty($modSettings['adminlog_enabled']))
unset ($log_types['admin']);

call_integration_hook('integrate_log_types', array(&$log_types));
call_integration_hook('integrate_log_types', array(&$log_types, &$always_log));

foreach ($logs as $log)
{
if (!isset($log_types[$log['log_type']]))
return false;
if (!isset($log_types[$log['log_type']]) && (empty($modSettings[$log['log_type'] . 'log_enabled']) || !in_array($log['action'], $always_log)))
continue;

if (!is_array($log['extra']))
trigger_error('logActions(): data is not an array with action \'' . $log['action'] . '\'', E_USER_NOTICE);
{
loadLanguage('Errors');
throw new \TypeError(sprintf($txt['logActions_not_array'], $log['action']));
}

// Pull out the parts we want to store separately, but also make sure that the data is proper
if (isset($log['extra']['topic']))
{
if (!is_numeric($log['extra']['topic']))
trigger_error('logActions(): data\'s topic is not a number', E_USER_NOTICE);
{
loadLanguage('Errors');
throw new \TypeError($txt['logActions_topic_not_numeric']);
}
$topic_id = empty($log['extra']['topic']) ? 0 : (int) $log['extra']['topic'];
unset($log['extra']['topic']);
}
Expand All @@ -469,7 +472,10 @@ function logActions($logs)
if (isset($log['extra']['message']))
{
if (!is_numeric($log['extra']['message']))
trigger_error('logActions(): data\'s message is not a number', E_USER_NOTICE);
{
loadLanguage('Errors');
throw new \TypeError($txt['logActions_message_not_numeric']);
}
$msg_id = empty($log['extra']['message']) ? 0 : (int) $log['extra']['message'];
unset($log['extra']['message']);
}
Expand Down Expand Up @@ -502,12 +508,18 @@ function logActions($logs)
}

if (isset($log['extra']['member']) && !is_numeric($log['extra']['member']))
trigger_error('logActions(): data\'s member is not a number', E_USER_NOTICE);
{
loadLanguage('Errors');
throw new \TypeError($txt['logActions_member_not_numeric']);
}

if (isset($log['extra']['board']))
{
if (!is_numeric($log['extra']['board']))
trigger_error('logActions(): data\'s board is not a number', E_USER_NOTICE);
{
loadLanguage('Errors');
throw new \TypeError($txt['logActions_board_not_numeric']);
}
$board_id = empty($log['extra']['board']) ? 0 : (int) $log['extra']['board'];
unset($log['extra']['board']);
}
Expand All @@ -517,7 +529,10 @@ function logActions($logs)
if (isset($log['extra']['board_to']))
{
if (!is_numeric($log['extra']['board_to']))
trigger_error('logActions(): data\'s board_to is not a number', E_USER_NOTICE);
{
loadLanguage('Errors');
throw new \TypeError($txt['logActions_board_to_not_numeric']);
}
if (empty($board_id))
{
$board_id = empty($log['extra']['board_to']) ? 0 : (int) $log['extra']['board_to'];
Expand All @@ -528,7 +543,7 @@ function logActions($logs)
if (isset($log['extra']['member_affected']))
$memID = $log['extra']['member_affected'];
else
$memID = $user_info['id'];
$memID = $user_info['id'] ?? $log['extra']['member'] ?? 0;

if (isset($user_info['ip']))
$memIP = $user_info['ip'];
Expand Down
63 changes: 35 additions & 28 deletions Sources/ManageAttachments.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2024 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.5
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -112,6 +112,8 @@ function ManageAttachmentSettings($return_config = false)

// A bit of razzle dazzle with the $txt strings. :)
$txt['attachment_path'] = $context['attachmentUploadDir'];
if (empty($modSettings['attachment_basedirectories']) && $modSettings['currentAttachmentUploadDir'] == 1 && count($modSettings['attachmentUploadDir']) == 1)
$txt['attachmentUploadDir_path'] = $modSettings['attachmentUploadDir'][1];
$txt['basedirectory_for_attachments_path'] = isset($modSettings['basedirectory_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : '';
$txt['use_subdirectories_for_attachments_note'] = empty($modSettings['attachment_basedirectories']) || empty($modSettings['use_subdirectories_for_attachments']) ? $txt['use_subdirectories_for_attachments_note'] : '';
$txt['attachmentUploadDir_multiple_configure'] = '<a href="' . $scripturl . '?action=admin;area=manageattachments;sa=attachpaths">[' . $txt['attachmentUploadDir_multiple_configure'] . ']</a>';
Expand All @@ -136,7 +138,7 @@ function ManageAttachmentSettings($return_config = false)
array('select', 'automanage_attachments', array(0 => $txt['attachments_normal'], 1 => $txt['attachments_auto_space'], 2 => $txt['attachments_auto_years'], 3 => $txt['attachments_auto_months'], 4 => $txt['attachments_auto_16'])),
array('check', 'use_subdirectories_for_attachments', 'subtext' => $txt['use_subdirectories_for_attachments_note']),
(empty($modSettings['attachment_basedirectories']) ? array('text', 'basedirectory_for_attachments', 40,) : array('var_message', 'basedirectory_for_attachments', 'message' => 'basedirectory_for_attachments_path', 'invalid' => empty($context['valid_basedirectory']), 'text_label' => (!empty($context['valid_basedirectory']) ? $txt['basedirectory_for_attachments_current'] : sprintf($txt['basedirectory_for_attachments_warning'], $scripturl)))),
empty($modSettings['attachment_basedirectories']) && $modSettings['currentAttachmentUploadDir'] == 1 && count($modSettings['attachmentUploadDir']) == 1 ? array('json', 'attachmentUploadDir', 'subtext' => $txt['attachmentUploadDir_multiple_configure'], 40, 'invalid' => !$context['valid_upload_dir'], 'disabled' => true) : array('var_message', 'attach_current_directory', 'subtext' => $txt['attachmentUploadDir_multiple_configure'], 'message' => 'attachment_path', 'invalid' => empty($context['valid_upload_dir']), 'text_label' => (!empty($context['valid_upload_dir']) ? $txt['attach_current_dir'] : sprintf($txt['attach_current_dir_warning'], $scripturl))),
empty($modSettings['attachment_basedirectories']) && $modSettings['currentAttachmentUploadDir'] == 1 && count($modSettings['attachmentUploadDir']) == 1 ? array('var_message', 'attachmentUploadDir_path', 'subtext' => $txt['attachmentUploadDir_multiple_configure'], 40, 'invalid' => !$context['valid_upload_dir'], 'text_label' => $txt['attachmentUploadDir'], 'message' => 'attachmentUploadDir_path') : array('var_message', 'attach_current_directory', 'subtext' => $txt['attachmentUploadDir_multiple_configure'], 'message' => 'attachment_path', 'invalid' => empty($context['valid_upload_dir']), 'text_label' => (!empty($context['valid_upload_dir']) ? $txt['attach_current_dir'] : sprintf($txt['attach_current_dir_warning'], $scripturl))),
array('int', 'attachmentDirFileLimit', 'subtext' => $txt['zero_for_no_limit'], 6),
array('int', 'attachmentDirSizeLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
array('check', 'dont_show_attach_under_post', 'subtext' => $txt['dont_show_attach_under_post_sub']),
Expand All @@ -145,7 +147,7 @@ function ManageAttachmentSettings($return_config = false)
// Posting limits
array('int', 'attachmentPostLimit', 'subtext' => sprintf($txt['attachment_ini_max'], $post_max_kb . ' ' . $txt['kilobyte']), 6, 'postinput' => $txt['kilobyte'], 'min' => 1, 'max' => $post_max_kb, 'disabled' => empty($post_max_kb)),
array('int', 'attachmentSizeLimit', 'subtext' => sprintf($txt['attachment_ini_max'], $file_max_kb . ' ' . $txt['kilobyte']), 6, 'postinput' => $txt['kilobyte'], 'min' => 1, 'max' => $file_max_kb, 'disabled' => empty($file_max_kb)),
array('int', 'attachmentNumPerPostLimit', 'subtext' => $txt['zero_for_no_limit'], 6),
array('int', 'attachmentNumPerPostLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'min' => 0),
// Security Items
array('title', 'attachment_security_settings'),
// Extension checks etc.
Expand Down Expand Up @@ -400,28 +402,9 @@ function BrowseFiles()
// Attachments or avatars?
$context['browse_type'] = isset($_REQUEST['avatars']) ? 'avatars' : (isset($_REQUEST['thumbs']) ? 'thumbs' : 'attachments');

$titles = array(
'attachments' => array('?action=admin;area=manageattachments;sa=browse', $txt['attachment_manager_attachments']),
'avatars' => array('?action=admin;area=manageattachments;sa=browse;avatars', $txt['attachment_manager_avatars']),
'thumbs' => array('?action=admin;area=manageattachments;sa=browse;thumbs', $txt['attachment_manager_thumbs']),
);

$list_title = $txt['attachment_manager_browse_files'] . ': ';
foreach ($titles as $browse_type => $details)
{
if ($browse_type != 'attachments')
$list_title .= ' | ';

if ($context['browse_type'] == $browse_type)
$list_title .= '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;"> ';

$list_title .= '<a href="' . $scripturl . $details[0] . '">' . $details[1] . '</a>';
}

// Set the options for the list component.
$listOptions = array(
'id' => 'file_list',
'title' => $list_title,
'items_per_page' => $modSettings['defaultMaxListItems'],
'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=browse' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')),
'default_sort_col' => 'name',
Expand Down Expand Up @@ -577,7 +560,7 @@ function BrowseFiles()
),
'additional_rows' => array(
array(
'position' => 'above_table_headers',
'position' => 'above_column_headers',
'value' => '<input type="submit" name="remove_submit" class="button you_sure" value="' . $txt['quickmod_delete_selected'] . '" data-confirm="' . $txt['confirm_delete_attachments'] . '">',
),
array(
Expand All @@ -587,8 +570,29 @@ function BrowseFiles()
),
);

$titles = array(
'attachments' => array('?action=admin;area=manageattachments;sa=browse', $txt['attachment_manager_attachments']),
'avatars' => array('?action=admin;area=manageattachments;sa=browse;avatars', $txt['attachment_manager_avatars']),
'thumbs' => array('?action=admin;area=manageattachments;sa=browse;thumbs', $txt['attachment_manager_thumbs']),
);

$list_title = $txt['attachment_manager_browse_files'] . ': ';

// Does a hook want to display their attachments better?
call_integration_hook('integrate_attachments_browse', array(&$listOptions, &$titles, &$list_title));
call_integration_hook('integrate_attachments_browse', array(&$listOptions, &$titles));

foreach ($titles as $browse_type => $details)
{
if ($browse_type != 'attachments')
$list_title .= ' | ';

if ($context['browse_type'] == $browse_type)
$list_title .= '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;"> ';

$list_title .= '<a href="' . $scripturl . $details[0] . '">' . $details[1] . '</a>';
}

$listOptions['title'] = $list_title;

// Create the list.
require_once($sourcedir . '/Subs-List.php');
Expand Down Expand Up @@ -644,10 +648,12 @@ function list_getFiles($start, $items_per_page, $sort, $browse_type)
INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
WHERE a.attachment_type = {int:attachment_type}
AND a.id_member = {int:guest_id_member}
ORDER BY {raw:sort}
LIMIT {int:start}, {int:per_page}',
array(
'attachment_type' => $browse_type == 'thumbs' ? '3' : '0',
'guest_id_member' => 0,
'sort' => $sort,
'start' => $start,
'per_page' => $items_per_page,
Expand Down Expand Up @@ -1032,7 +1038,7 @@ function removeAttachments($condition, $query_type = '', $return_affected_messag
// Figure out the "encrypted" filename and unlink it ;).
if ($row['attachment_type'] == 1)
{
// if attachment_type = 1, it's... an avatar in a custom avatar directory.
// if attachment_type = 1, it's... an avatar in a custom avatars directory.
// wasn't it obvious? :P
// @todo look again at this.
@unlink($modSettings['custom_avatar_dir'] . '/' . $row['filename']);
Expand Down Expand Up @@ -2379,6 +2385,7 @@ function ManageAttachmentPaths()
'data' => array(
'db' => 'path',
'style' => 'width: 45%;',
'class' => 'word_break',
),
),
'num_dirs' => array(
Expand Down Expand Up @@ -2439,7 +2446,7 @@ function ManageAttachmentPaths()
*/
function list_getAttachDirs()
{
global $smcFunc, $modSettings, $context, $txt;
global $smcFunc, $modSettings, $context, $scripturl, $txt;

$request = $smcFunc['db_query']('', '
SELECT id_folder, COUNT(id_attach) AS num_attach, SUM(size) AS size_attach
Expand Down Expand Up @@ -2494,7 +2501,7 @@ function list_getAttachDirs()
'path' => $dir,
'current_size' => !empty($expected_size[$id]) ? comma_format($expected_size[$id] / 1024, 0) : 0,
'num_files' => comma_format($expected_files[$id] - $sub_dirs, 0) . ($sub_dirs > 0 ? ' (' . $sub_dirs . ')' : ''),
'status' => ($is_base_dir ? $txt['attach_dir_basedir'] . '<br>' : '') . ($error ? '<div class="error">' : '') . sprintf($txt['attach_dir_' . $status], $context['session_id'], $context['session_var']) . ($error ? '</div>' : ''),
'status' => ($is_base_dir ? $txt['attach_dir_basedir'] . '<br>' : '') . ($error ? '<div class="error">' : '') . sprintf($txt['attach_dir_' . $status], $context['session_id'], $context['session_var'], $scripturl) . ($error ? '</div>' : ''),
);
}

Expand Down
99 changes: 62 additions & 37 deletions Sources/ManageBans.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2024 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.5
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -45,12 +45,6 @@ function Ban()
'log' => 'BanLog',
);

// Default the sub-action to 'view ban list'.
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'list';

$context['page_title'] = $txt['ban_title'];
$context['sub_action'] = $_REQUEST['sa'];

// Tabs for browsing the different ban functions.
$context[$context['admin_menu_name']]['tab_data'] = array(
'title' => $txt['ban_title'],
Expand All @@ -60,29 +54,35 @@ function Ban()
'list' => array(
'description' => $txt['ban_description'],
'href' => $scripturl . '?action=admin;area=ban;sa=list',
'is_selected' => $_REQUEST['sa'] == 'list' || $_REQUEST['sa'] == 'edit' || $_REQUEST['sa'] == 'edittrigger',
),
'add' => array(
'description' => $txt['ban_description'],
'href' => $scripturl . '?action=admin;area=ban;sa=add',
'is_selected' => $_REQUEST['sa'] == 'add',
),
'browse' => array(
'description' => $txt['ban_trigger_browse_description'],
'href' => $scripturl . '?action=admin;area=ban;sa=browse',
'is_selected' => $_REQUEST['sa'] == 'browse',
),
'log' => array(
'description' => $txt['ban_log_description'],
'href' => $scripturl . '?action=admin;area=ban;sa=log',
'is_selected' => $_REQUEST['sa'] == 'log',
'is_last' => true,
),
),
);

call_integration_hook('integrate_manage_bans', array(&$subActions));

// Default the sub-action to 'view ban list'.
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'list';

// Mark the appropriate menu entry as selected
if (array_key_exists($_REQUEST['sa'], $context[$context['admin_menu_name']]['tab_data']['tabs']))
$context[$context['admin_menu_name']]['tab_data']['tabs'][$_REQUEST['sa']]['is_selected'] = true;

$context['page_title'] = $txt['ban_title'];
$context['sub_action'] = $_REQUEST['sa'];

// Call the right function for this sub-action.
call_helper($subActions[$_REQUEST['sa']]);
}
Expand Down Expand Up @@ -111,7 +111,6 @@ function BanList()

// Unban them all!
removeBanGroups($_POST['remove']);
removeBanTriggers($_POST['remove']);

// No more caching this ban!
updateSettings(array('banLastUpdated' => time()));
Expand Down Expand Up @@ -159,7 +158,7 @@ function BanList()
),
'data' => array(
'db' => 'notes',
'class' => 'smalltext',
'class' => 'smalltext word_break',
),
'sort' => array(
'default' => 'LENGTH(bg.notes) > 0 DESC, bg.notes',
Expand All @@ -172,7 +171,7 @@ function BanList()
),
'data' => array(
'db' => 'reason',
'class' => 'smalltext',
'class' => 'smalltext word_break',
),
'sort' => array(
'default' => 'LENGTH(bg.reason) > 0 DESC, bg.reason',
Expand Down Expand Up @@ -472,13 +471,13 @@ function BanEdit()
),
'additional_rows' => array(
array(
'position' => 'above_table_headers',
'position' => 'above_column_headers',
'value' => '
<input type="submit" name="remove_selection" value="' . $txt['ban_remove_selected_triggers'] . '" class="button"> <a class="button" href="' . $scripturl . '?action=admin;area=ban;sa=edittrigger;bg=' . $ban_group_id . '">' . $txt['ban_add_trigger'] . '</a>',
'style' => 'text-align: right;',
),
array(
'position' => 'above_table_headers',
'position' => 'above_column_headers',
'value' => '
<input type="hidden" name="bg" value="' . $ban_group_id . '">
<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '">
Expand Down Expand Up @@ -638,7 +637,7 @@ function list_getBanItems($start = 0, $items_per_page = 0, $sort = 0, $ban_group
SELECT
bi.id_ban, bi.hostname, bi.email_address, bi.id_member, bi.hits,
bi.ip_low, bi.ip_high,
bg.id_ban_group, bg.name, bg.ban_time, COALESCE(bg.expire_time, 0) AS expire_time, bg.reason, bg.notes, bg.cannot_access, bg.cannot_register, bg.cannot_login, bg.cannot_post,
bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time AS expire_time, bg.reason, bg.notes, bg.cannot_access, bg.cannot_register, bg.cannot_login, bg.cannot_post,
COALESCE(mem.id_member, 0) AS id_member, mem.member_name, mem.real_name
FROM {db_prefix}ban_groups AS bg
LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
Expand All @@ -662,7 +661,7 @@ function list_getBanItems($start = 0, $items_per_page = 0, $sort = 0, $ban_group
'id' => $row['id_ban_group'],
'name' => $row['name'],
'expiration' => array(
'status' => empty($row['expire_time']) ? 'never' : ($row['expire_time'] < time() ? 'expired' : 'one_day'),
'status' => $row['expire_time'] === null ? 'never' : ($row['expire_time'] < time() ? 'expired' : 'one_day'),
'days' => $row['expire_time'] > time() ? ($row['expire_time'] - time() < 86400 ? 1 : ceil(($row['expire_time'] - time()) / 86400)) : 0
),
'reason' => $row['reason'],
Expand Down Expand Up @@ -870,6 +869,9 @@ function banEdit2()

call_integration_hook('integrate_edit_bans', array(&$ban_info, empty($_REQUEST['bg'])));

// Limit 'reason' characters
$ban_info['reason'] = $smcFunc['truncate']($ban_info['reason'], 255);

// Adding a new ban group
if (empty($_REQUEST['bg']))
$ban_group_id = insertBanGroup($ban_info);
Expand All @@ -894,8 +896,11 @@ function banEdit2()
if (!empty($context['ban_errors']))
{
$context['ban_suggestions'] = !empty($saved_triggers) ? $saved_triggers : array();
$context['ban']['from_user'] = true;
$context['ban_suggestions'] = array_merge($context['ban_suggestions'], getMemberData((int) $_REQUEST['u']));
if (isset($_REQUEST['u']))
{
$context['ban']['from_user'] = true;
$context['ban_suggestions'] = array_merge($context['ban_suggestions'], getMemberData((int) $_REQUEST['u']));
}

// Not strictly necessary, but it's nice
if (!empty($context['ban_suggestions']['member']['id']))
Expand Down Expand Up @@ -933,7 +938,7 @@ function banEdit2()
*
* @return array|bool An array with the triggers if there were errors or false on success
*/
function saveTriggers($suggestions = array(), $ban_group, $member = 0, $ban_id = 0)
function saveTriggers(array $suggestions, $ban_group, $member = 0, $ban_id = 0)
{
global $context;

Expand Down Expand Up @@ -1103,7 +1108,7 @@ function removeBanTriggers($items_ids = array(), $group_id = false)
* Doesn't clean the inputs
*
* @param int[] $group_ids The IDs of the groups to remove
* @return bool Returns ture if successful or false if $group_ids is empty
* @return bool Returns true if successful or false if $group_ids is empty
*/
function removeBanGroups($group_ids)
{
Expand All @@ -1125,6 +1130,25 @@ function removeBanGroups($group_ids)
)
);

// Remove all ban triggers for these bans groups
$request = $smcFunc['db_query']('', '
SELECT id_ban
FROM {db_prefix}ban_items
WHERE id_ban_group IN ({array_int:ban_list})',
array(
'ban_list' => $group_ids,
)
);

$id_ban_triggers = array();
while ($row = $smcFunc['db_fetch_assoc']($request))
{
$id_ban_triggers[] = $row['id_ban'];
}
$smcFunc['db_free_result']($request);

removeBanTriggers($id_ban_triggers);

return true;
}

Expand Down Expand Up @@ -1180,7 +1204,7 @@ function validateTriggers(&$triggers)
global $context, $smcFunc;

if (empty($triggers))
$context['ban_erros'][] = 'ban_empty_triggers';
$context['ban_errors'][] = 'ban_empty_triggers';

$ban_triggers = array();
$log_info = array();
Expand All @@ -1197,7 +1221,7 @@ function validateTriggers(&$triggers)
$value = trim($value);
$ip_parts = ip2range($value);
if (!checkExistingTriggerIP($ip_parts, $value))
$context['ban_erros'][] = 'invalid_ip';
$context['ban_errors'][] = 'invalid_ip';
else
{
$ban_triggers['main_ip'] = array(
Expand All @@ -1209,7 +1233,7 @@ function validateTriggers(&$triggers)
elseif ($key == 'hostname')
{
if (preg_match('/[^\w.\-*]/', $value) == 1)
$context['ban_erros'][] = 'invalid_hostname';
$context['ban_errors'][] = 'invalid_hostname';
else
{
// Replace the * wildcard by a MySQL wildcard %.
Expand All @@ -1221,7 +1245,7 @@ function validateTriggers(&$triggers)
elseif ($key == 'email')
{
if (preg_match('/[^\w.\-\+*@]/', $value) == 1)
$context['ban_erros'][] = 'invalid_email';
$context['ban_errors'][] = 'invalid_email';

// Check the user is not banning an admin.
$request = $smcFunc['db_query']('', '
Expand All @@ -1236,7 +1260,7 @@ function validateTriggers(&$triggers)
)
);
if ($smcFunc['db_num_rows']($request) != 0)
$context['ban_erros'][] = 'no_ban_admin';
$context['ban_errors'][] = 'no_ban_admin';
$smcFunc['db_free_result']($request);

$value = substr(strtolower(str_replace('*', '%', $value)), 0, 255);
Expand All @@ -1258,14 +1282,14 @@ function validateTriggers(&$triggers)
)
);
if ($smcFunc['db_num_rows']($request) == 0)
$context['ban_erros'][] = 'invalid_username';
$context['ban_errors'][] = 'invalid_username';
list ($value, $isAdmin) = $smcFunc['db_fetch_row']($request);
$smcFunc['db_free_result']($request);

if ($isAdmin && strtolower($isAdmin) != 'f')
{
unset($value);
$context['ban_erros'][] = 'no_ban_admin';
$context['ban_errors'][] = 'no_ban_admin';
}
else
$ban_triggers['user']['id_member'] = $value;
Expand All @@ -1283,7 +1307,7 @@ function validateTriggers(&$triggers)
$val = trim($val);
$ip_parts = ip2range($val);
if (!checkExistingTriggerIP($ip_parts, $val))
$context['ban_erros'][] = 'invalid_ip';
$context['ban_errors'][] = 'invalid_ip';
else
{
$ban_triggers[$key][] = array(
Expand All @@ -1299,7 +1323,7 @@ function validateTriggers(&$triggers)
}
}
else
$context['ban_erros'][] = 'no_bantype_selected';
$context['ban_errors'][] = 'no_bantype_selected';

if (isset($value) && !is_array($value))
$log_info[] = array(
Expand Down Expand Up @@ -1474,6 +1498,8 @@ function updateBanGroup($ban_info = array())

if (empty($ban_info['name']))
$context['ban_errors'][] = 'ban_name_empty';
if ($smcFunc['strlen']($ban_info['name']) > 20)
$context['ban_errors'][] = 'ban_name_is_too_long';
if (empty($ban_info['id']))
$context['ban_errors'][] = 'ban_id_empty';
if (empty($ban_info['cannot']['access']) && empty($ban_info['cannot']['register']) && empty($ban_info['cannot']['post']) && empty($ban_info['cannot']['login']))
Expand Down Expand Up @@ -1563,6 +1589,8 @@ function insertBanGroup($ban_info = array())

if (empty($ban_info['name']))
$context['ban_errors'][] = 'ban_name_empty';
if ($smcFunc['strlen']($ban_info['name']) > 20)
$context['ban_errors'][] = 'ban_name_is_too_long';
if (empty($ban_info['cannot']['access']) && empty($ban_info['cannot']['register']) && empty($ban_info['cannot']['post']) && empty($ban_info['cannot']['login']))
$context['ban_errors'][] = 'ban_unknown_restriction_type';

Expand Down Expand Up @@ -1638,10 +1666,7 @@ function BanEditTrigger()
}
elseif (isset($_POST['edit_trigger']) && !empty($_POST['ban_suggestions']))
{
// The first replaces the old one, the others are added new (simplification, otherwise it would require another query and some work...)
saveTriggers(array_shift($_POST['ban_suggestions']), $ban_group, 0, $ban_id);
if (!empty($_POST['ban_suggestions']))
saveTriggers($_POST['ban_suggestions'], $ban_group);
saveTriggers($_POST['ban_suggestions'], $ban_group, 0, $ban_id);

redirectexit('action=admin;area=ban;sa=edit' . (!empty($ban_group) ? ';bg=' . $ban_group : ''));
}
Expand Down Expand Up @@ -2116,7 +2141,7 @@ function BanLog()
),
'additional_rows' => array(
array(
'position' => 'top_of_list',
'position' => 'after_title',
'value' => '
<input type="submit" name="removeSelected" value="' . $txt['ban_log_remove_selected'] . '" data-confirm="' . $txt['ban_log_remove_selected_confirm'] . '" class="button you_sure">
<input type="submit" name="removeAll" value="' . $txt['ban_log_remove_all'] . '" data-confirm="' . $txt['ban_log_remove_all_confirm'] . '" class="button you_sure">',
Expand Down
25 changes: 15 additions & 10 deletions Sources/ManageBoards.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.0
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -330,8 +330,8 @@ function EditCategory2()
if (isset($_POST['cat_order']))
$catOptions['move_after'] = (int) $_POST['cat_order'];

// Try and get any valid HTML to BBC first, add a naive attempt to strip it off, htmlspecialchars for the rest, parse it on display
$catOptions['cat_name'] = $smcFunc['htmlspecialchars'](strip_tags(html_to_bbc($_POST['cat_name'])));
// Try and get any valid HTML to BBC first, add a naive attempt to strip it off, htmlspecialchars for the rest
$catOptions['cat_name'] = $smcFunc['htmlspecialchars'](strip_tags($_POST['cat_name']));
$catOptions['cat_desc'] = $smcFunc['htmlspecialchars'](strip_tags(html_to_bbc($_POST['cat_desc'])));
$catOptions['is_collapsible'] = isset($_POST['collapse']);

Expand Down Expand Up @@ -668,8 +668,8 @@ function EditBoard2()
if (strlen(implode(',', $boardOptions['access_groups'])) > 255 || strlen(implode(',', $boardOptions['deny_groups'])) > 255)
fatal_lang_error('too_many_groups', false);

// Try and get any valid HTML to BBC first, add a naive attempt to strip it off, htmlspecialchars for the rest, parse it on display
$boardOptions['board_name'] = $smcFunc['htmlspecialchars'](strip_tags(html_to_bbc($_POST['board_name'])));
// Try and get any valid HTML to BBC first, add a naive attempt to strip it off, htmlspecialchars for the rest
$boardOptions['board_name'] = $smcFunc['htmlspecialchars'](strip_tags($_POST['board_name']));
$boardOptions['board_description'] = $smcFunc['htmlspecialchars'](strip_tags(html_to_bbc($_POST['desc'])));

$boardOptions['moderator_string'] = $_POST['moderators'];
Expand All @@ -679,6 +679,7 @@ function EditBoard2()
$moderators = array();
foreach ($_POST['moderator_list'] as $moderator)
$moderators[(int) $moderator] = (int) $moderator;

$boardOptions['moderators'] = $moderators;
}

Expand All @@ -693,35 +694,39 @@ function EditBoard2()
}

// Are they doing redirection?
$boardOptions['redirect'] = !empty($_POST['redirect_enable']) && isset($_POST['redirect_address']) && trim($_POST['redirect_address']) != '' ? trim($_POST['redirect_address']) : '';
$boardOptions['redirect'] = !empty($_POST['redirect_enable']) && isset($_POST['redirect_address']) && trim($_POST['redirect_address']) != '' ? normalize_iri(trim($_POST['redirect_address'])) : '';

// Profiles...
$boardOptions['profile'] = $_POST['profile'];
$boardOptions['profile'] = $_POST['profile'] == -1 ? 1 : $_POST['profile'];
$boardOptions['inherit_permissions'] = $_POST['profile'] == -1;

// We need to know what used to be case in terms of redirection.
if (!empty($_POST['boardid']))
{
$request = $smcFunc['db_query']('', '
SELECT redirect, num_posts
SELECT redirect, num_posts, id_cat
FROM {db_prefix}boards
WHERE id_board = {int:current_board}',
array(
'current_board' => $_POST['boardid'],
)
);
list ($oldRedirect, $numPosts) = $smcFunc['db_fetch_row']($request);
list ($oldRedirect, $numPosts, $old_id_cat) = $smcFunc['db_fetch_row']($request);
$smcFunc['db_free_result']($request);

// If we're turning redirection on check the board doesn't have posts in it - if it does don't make it a redirection board.
if ($boardOptions['redirect'] && empty($oldRedirect) && $numPosts)
unset($boardOptions['redirect']);

// Reset the redirection count when switching on/off.
elseif (empty($boardOptions['redirect']) != empty($oldRedirect))
$boardOptions['num_posts'] = 0;

// Resetting the count?
elseif ($boardOptions['redirect'] && !empty($_POST['reset_redirect']))
$boardOptions['num_posts'] = 0;

$boardOptions['old_id_cat'] = $old_id_cat;
}

// Create a new board...
Expand Down
14 changes: 7 additions & 7 deletions Sources/ManageCalendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.3
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -49,8 +49,6 @@ function ManageCalendar()
$default = 'settings';
}

$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : $default;

// Set up the two tabs here...
$context[$context['admin_menu_name']]['tab_data'] = array(
'title' => $txt['manage_calendar'],
Expand All @@ -69,6 +67,8 @@ function ManageCalendar()

call_integration_hook('integrate_manage_calendar', array(&$subActions));

$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : $default;

call_helper($subActions[$_REQUEST['sa']]);
}

Expand Down Expand Up @@ -216,7 +216,7 @@ function EditHoliday()
validateToken('admin-eh');

// Not too long good sir?
$_REQUEST['title'] = $smcFunc['substr']($_REQUEST['title'], 0, 60);
$_REQUEST['title'] = $smcFunc['substr']($smcFunc['normalize']($_REQUEST['title']), 0, 60);
$_REQUEST['holiday'] = isset($_REQUEST['holiday']) ? (int) $_REQUEST['holiday'] : 0;

if (isset($_REQUEST['delete']))
Expand All @@ -229,7 +229,7 @@ function EditHoliday()
);
else
{
$date = strftime($_REQUEST['year'] <= 1004 ? '1004-%m-%d' : '%Y-%m-%d', mktime(0, 0, 0, $_REQUEST['month'], $_REQUEST['day'], $_REQUEST['year']));
$date = smf_strftime($_REQUEST['year'] <= 1004 ? '1004-%m-%d' : '%Y-%m-%d', mktime(0, 0, 0, $_REQUEST['month'], $_REQUEST['day'], $_REQUEST['year']));
if (isset($_REQUEST['edit']))
$smcFunc['db_query']('', '
UPDATE {db_prefix}calendar_holidays
Expand Down Expand Up @@ -297,7 +297,7 @@ function EditHoliday()
}

// Last day for the drop down?
$context['holiday']['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $context['holiday']['month'] == 12 ? 1 : $context['holiday']['month'] + 1, 0, $context['holiday']['month'] == 12 ? $context['holiday']['year'] + 1 : $context['holiday']['year']));
$context['holiday']['last_day'] = (int) smf_strftime('%d', mktime(0, 0, 0, $context['holiday']['month'] == 12 ? 1 : $context['holiday']['month'] + 1, 0, $context['holiday']['month'] == 12 ? $context['holiday']['year'] + 1 : $context['holiday']['year']));
}

/**
Expand Down
10 changes: 5 additions & 5 deletions Sources/ManageErrors.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2024 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.5
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -95,7 +95,7 @@ function ViewErrorLog()
'value' => array(
'sql' => in_array($_GET['filter'], array('message', 'url', 'file')) ? base64_decode(strtr($_GET['value'], array(' ' => '+'))) : $smcFunc['db_escape_wildcard_string']($_GET['value']),
),
'href' => ';filter=' . $_GET['filter'] . ';value=' . $_GET['value'],
'href' => ';filter=' . $_GET['filter'] . ';value=' . urlencode($_GET['value']),
'entity' => $filters[$_GET['filter']]['txt']
);

Expand Down Expand Up @@ -137,7 +137,7 @@ function ViewErrorLog()
{
// We want all errors, not just the number of filtered messages...
$query = $smcFunc['db_query']('', '
SELECT COUNT(id_error)
SELECT COUNT(*)
FROM {db_prefix}log_errors',
array()
);
Expand Down Expand Up @@ -259,7 +259,7 @@ function ViewErrorLog()
{
$id = $filter['value']['sql'];
loadMemberData($id, false, 'minimal');
$context['filter']['value']['html'] = '<a href="' . $scripturl . '?action=profile;u=' . $id . '">' . $user_profile[$id]['real_name'] . '</a>';
$context['filter']['value']['html'] = '<a href="' . $scripturl . '?action=profile;u=' . $id . '">' . (isset($user_profile[$id]['real_name']) ? $user_profile[$id]['real_name'] : $txt['guest']) . '</a>';
}
elseif ($filter['variable'] == 'url')
$context['filter']['value']['html'] = '\'' . strtr($smcFunc['htmlspecialchars']((substr($filter['value']['sql'], 0, 1) == '?' ? $scripturl : '') . $filter['value']['sql']), array('\_' => '_')) . '\'';
Expand Down
31 changes: 18 additions & 13 deletions Sources/ManageLanguages.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2024 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.5
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -43,10 +43,6 @@ function ManageLanguages()
'editlang' => 'ModifyLanguage',
);

// By default we're managing languages.
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'edit';
$context['sub_action'] = $_REQUEST['sa'];

// Load up all the tabs...
$context[$context['admin_menu_name']]['tab_data'] = array(
'title' => $txt['language_configuration'],
Expand All @@ -55,6 +51,10 @@ function ManageLanguages()

call_integration_hook('integrate_manage_languages', array(&$subActions));

// By default we're managing languages.
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'edit';
$context['sub_action'] = $_REQUEST['sa'];

// Call the right function for this sub-action.
call_helper($subActions[$_REQUEST['sa']]);
}
Expand Down Expand Up @@ -269,7 +269,7 @@ function DownloadLanguage()
$extension = $pathinfo['extension'];

// Don't do anything with files we don't understand.
if (!in_array($extension, array('php', 'jpg', 'gif', 'jpeg', 'png', 'txt')))
if (!in_array($extension, array('php', 'jpg', 'gif', 'jpeg', 'png', 'txt', 'webp')))
continue;

// Basic data.
Expand Down Expand Up @@ -911,10 +911,13 @@ function ModifyLanguage()

if (!empty($context['possible_files'][$theme]['files']))
{
usort($context['possible_files'][$theme]['files'], function($val1, $val2)
{
return strcmp($val1['name'], $val2['name']);
});
usort(
$context['possible_files'][$theme]['files'],
function($val1, $val2)
{
return strcmp($val1['name'], $val2['name']);
}
);
}
}

Expand Down Expand Up @@ -988,7 +991,7 @@ function ModifyLanguage()
}

// Saving primary settings?
$primary_settings = array('native_name' => 'string', 'lang_character_set' => 'string', 'lang_locale' => 'string', 'lang_rtl' => 'bool', 'lang_dictionary' => 'string', 'lang_spelling' => 'string', 'lang_recaptcha' => 'string');
$primary_settings = array('native_name' => 'string', 'lang_character_set' => 'string', 'lang_locale' => 'string', 'lang_rtl' => 'string', 'lang_dictionary' => 'string', 'lang_recaptcha' => 'string');
$madeSave = false;
if (!empty($_POST['save_main']) && !$current_file)
{
Expand Down Expand Up @@ -1148,7 +1151,7 @@ function ModifyLanguage()
continue;

// These are arrays that need breaking out.
if (strpos($entryValue['entry'], 'array(') === 0 && strpos($entryValue['entry'], ')', -1) === strlen($entryValue['entry']) - 1)
if (strpos($entryValue['entry'], 'array(') === 0 && substr($entryValue['entry'], -1) === ')')
{
// No, you may not use multidimensional arrays of $txt strings. Madness stalks that path.
if (isset($entryValue['subkey']))
Expand Down Expand Up @@ -1656,6 +1659,8 @@ function cleanLangString($string, $to_display = true)
}
else
{
$string = $smcFunc['normalize']($string);

// Keep track of what we're doing...
$in_string = 0;
// This is for deciding whether to HTML a quote.
Expand Down
6 changes: 4 additions & 2 deletions Sources/ManageMail.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2020 Simple Machines and individual contributors
* @copyright 2024 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 RC2
* @version 2.1.5
*/

if (!defined('SMF'))
Expand Down Expand Up @@ -249,6 +249,8 @@ function list_getMailQueue($start, $items_per_page, $sort)
// Private PM/email subjects and similar shouldn't be shown in the mailbox area.
if (!empty($row['private']))
$row['subject'] = $txt['personal_message'];
else
$row['subject'] = mb_decode_mimeheader($row['subject']);

$mails[] = $row;
}
Expand Down
Loading