Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions Sources/Actions/Admin/Members.php
Original file line number Diff line number Diff line change
Expand Up @@ -455,11 +455,7 @@ public function view(): void
// Replace the wildcard characters ('*' and '?') into MySQL ones.
$parameter = strtolower(strtr(Utils::htmlspecialchars($search_params[$param_name], ENT_QUOTES), ['%' => '\\%', '_' => '\\_', '*' => '%', '?' => '_']));

if (Db::$db->case_sensitive) {
$query_parts[] = '(LOWER(' . implode(') LIKE {string:' . $param_name . '_normal} OR LOWER(', $param_info['db_fields']) . ') LIKE {string:' . $param_name . '_normal})';
} else {
$query_parts[] = '(' . implode(' LIKE {string:' . $param_name . '_normal} OR ', $param_info['db_fields']) . ' LIKE {string:' . $param_name . '_normal})';
}
$query_parts[] = '({column_ci:' . implode('} LIKE {string:' . $param_name . '_normal} OR {column_ci:', $param_info['db_fields']) . '} LIKE {string:' . $param_name . '_normal})';

$where_params[$param_name . '_normal'] = '%' . $parameter . '%';
}
Expand Down
6 changes: 2 additions & 4 deletions Sources/Actions/AutoSuggest.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,11 @@ public function member(): array
$request = Db::$db->query(
'SELECT id_member, real_name
FROM {db_prefix}members
WHERE {raw:real_name} LIKE {string:search}' . (!empty($this->search_param['buddies']) ? '
WHERE {column_ci:real_name} LIKE {string:search}' . (!empty($this->search_param['buddies']) ? '
AND id_member IN ({array_int:buddy_list})' : '') . '
AND is_activated IN ({array_int:activated})
LIMIT ' . (Utils::entityStrlen($this->search) <= 2 ? '100' : '800'),
[
'real_name' => Db::$db->case_sensitive ? 'LOWER(real_name)' : 'real_name',
'buddy_list' => User::$me->buddies,
'search' => $this->search,
'activated' => [User::ACTIVATED, User::ACTIVATED_BANNED],
Expand Down Expand Up @@ -195,12 +194,11 @@ public function membergroups(): array
$request = Db::$db->query(
'SELECT id_group, group_name
FROM {db_prefix}membergroups
WHERE {raw:group_name} LIKE {string:search}
WHERE {column_ci:group_name} LIKE {string:search}
AND min_posts = {int:min_posts}
AND id_group NOT IN ({array_int:invalid_groups})
AND hidden != {int:hidden}',
[
'group_name' => Db::$db->case_sensitive ? 'LOWER(group_name)' : 'group_name',
'min_posts' => -1,
'invalid_groups' => [1, 3],
'hidden' => 2,
Expand Down
4 changes: 3 additions & 1 deletion Sources/Actions/Memberlist.php
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,8 @@ public function search(): void
$search_fields[] = 'email';
}

// These are expressions as well as plain columns, so they are
// folded here rather than through the {column_ci:} type.
if (Db::$db->case_sensitive) {
foreach ($fields as $key => $field) {
$fields[$key] = 'LOWER(' . $field . ')';
Expand All @@ -545,7 +547,7 @@ public function search(): void
ErrorHandler::fatalLang('invalid_search_string', false);
}

$query = $_POST['search'] == '' ? '= {string:blank_string}' : (Db::$db->case_sensitive ? 'LIKE LOWER({string:search})' : 'LIKE {string:search}');
$query = $_POST['search'] == '' ? '= {string:blank_string}' : 'LIKE {string_ci:search}';

$request = Db::$db->query(
'SELECT COUNT(*)
Expand Down
5 changes: 2 additions & 3 deletions Sources/Actions/Register2.php
Original file line number Diff line number Diff line change
Expand Up @@ -536,11 +536,10 @@ public static function registerMember(array &$reg_options, bool $return_errors =
$request = Db::$db->query(
'SELECT id_member
FROM {db_prefix}members
WHERE {raw:email_address_field} = {string:email_address}
OR {raw:email_address_field} = {string:username}
WHERE {column_ci:email_address} = {string:email_address}
OR {column_ci:email_address} = {string:username}
LIMIT 1',
[
'email_address_field' => Db::$db->case_sensitive ? 'LOWER(email_address)' : 'email_address',
'email_address' => $reg_options['email'],
'username' => $reg_options['username'],
],
Expand Down
3 changes: 1 addition & 2 deletions Sources/Actions/RequestMembers.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,11 @@ public function execute(): void
$request = Db::$db->query(
'SELECT real_name
FROM {db_prefix}members
WHERE {raw:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? '
WHERE {column_ci:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? '
AND id_member IN ({array_int:buddy_list})' : '') . '
AND is_activated IN ({array_int:activated})
LIMIT {int:limit}',
[
'real_name' => Db::$db->case_sensitive ? 'LOWER(real_name)' : 'real_name',
'buddy_list' => User::$me->buddies,
'search' => $this->search,
'activated' => [User::ACTIVATED, User::ACTIVATED_BANNED],
Expand Down
15 changes: 15 additions & 0 deletions Sources/Db/APIs/MySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ public function quote(string $db_string, array $db_values, ?object $connection =
[
// The literal type can have arbitrary content.
'~{(literal):([^}]*)}~',
// The column_ci type names a column inline rather than by key,
// so that the column a comparison is case folding is visible
// in the query itself. Only a column name, optionally
// qualified by a table alias, is accepted.
'~{(column_ci):([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)?)}~',
// Everything else needs to be a key in $db_values.
'~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~',
],
Expand Down Expand Up @@ -2753,6 +2758,12 @@ protected function replacement__callback(array $matches, array $db_values, objec
return '\'' . mysqli_real_escape_string($connection, $matches[2]) . '\'';
}

// MySQL folds case in the column's collation, so a case insensitive
// comparison is what a bare column already does.
if ($matches[1] === 'column_ci') {
return $matches[2];
}

if (!\array_key_exists($matches[2], $db_values)) {
$this->error_backtrace('The database value you\'re trying to insert does not exist: ' . Utils::htmlspecialchars($matches[2]), '', E_USER_ERROR, __FILE__, __LINE__);
}
Expand All @@ -2777,6 +2788,8 @@ protected function replacement__callback(array $matches, array $db_values, objec

case 'string':
case 'text':
// MySQL folds the case of the value in the collation as well.
case 'string_ci':
return \sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $this->fix_mb4((string) $replacement)));

case 'array_int':
Expand All @@ -2801,6 +2814,8 @@ protected function replacement__callback(array $matches, array $db_values, objec
break;

case 'array_string':
// As above, the collation folds each of these too.
case 'array_string_ci':
if (\is_array($replacement)) {
if (empty($replacement)) {
$this->error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
Expand Down
33 changes: 33 additions & 0 deletions Sources/Db/APIs/PostgreSQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ public function quote(string $db_string, array $db_values, ?object $connection =
[
// The literal type can have arbitrary content.
'~{(literal):([^}]*)}~',
// The column_ci type names a column inline rather than by key,
// so that the column a comparison is case folding is visible
// in the query itself. Only a column name, optionally
// qualified by a table alias, is accepted.
'~{(column_ci):([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)?)}~',
// Everything else needs to be a key in $db_values.
'~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~',
],
Expand Down Expand Up @@ -2684,6 +2689,12 @@ protected function replacement__callback(array $matches, array $db_values, objec
return '\'' . pg_escape_string($this->connection, $matches[2]) . '\'';
}

// PostgreSQL compares strings exactly, so a case insensitive comparison
// folds the column and pairs it with a value folded the same way.
if ($matches[1] === 'column_ci') {
return 'LOWER(' . $matches[2] . ')';
}

if (!\array_key_exists($matches[2], $db_values)) {
$this->error_backtrace('The database value you\'re trying to insert does not exist: ' . Utils::htmlspecialchars($matches[2]), '', E_USER_ERROR, __FILE__, __LINE__);
}
Expand All @@ -2710,6 +2721,10 @@ protected function replacement__callback(array $matches, array $db_values, objec
case 'text':
return \sprintf('\'%1$s\'', pg_escape_string($this->connection, (string) $replacement));

// Folded to match a column that {column_ci:} has folded.
case 'string_ci':
return \sprintf('LOWER(\'%1$s\')', pg_escape_string($this->connection, (string) $replacement));

case 'array_int':
if (\is_array($replacement)) {
if (empty($replacement)) {
Expand Down Expand Up @@ -2748,6 +2763,24 @@ protected function replacement__callback(array $matches, array $db_values, objec

break;

// Each of these folded to match a column that {column_ci:} has folded.
case 'array_string_ci':
if (\is_array($replacement)) {
if (empty($replacement)) {
$this->error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
}

foreach ($replacement as $key => $value) {
$replacement[$key] = \sprintf('LOWER(\'%1$s\')', pg_escape_string($this->connection, (string) $value));
}

return implode(', ', $replacement);
}

$this->error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);

break;

case 'array_uuid':
if (\is_array($replacement)) {
if (empty($replacement)) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/PersonalMessage/PM.php
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ public static function send(array $recipients, string $subject, string $message,
$request = Db::$db->query(
'SELECT id_member, member_name
FROM {db_prefix}members
WHERE ' . (Db::$db->case_sensitive ? 'LOWER(member_name)' : 'member_name') . ' IN ({array_string:usernames})',
WHERE {column_ci:member_name} IN ({array_string:usernames})',
[
'usernames' => array_keys($usernames),
],
Expand Down
9 changes: 2 additions & 7 deletions Sources/PersonalMessage/Search.php
Original file line number Diff line number Diff line change
Expand Up @@ -478,11 +478,7 @@ protected function setUserQuery(): void

foreach ($possible_users as $k => $v) {
$where_params['name_' . $k] = $v;
$where_clause[] = '{raw:real_name} LIKE {string:name_' . $k . '}';

if (!isset($where_params['real_name'])) {
$where_params['real_name'] = Db::$db->case_sensitive ? 'LOWER(real_name)' : 'real_name';
}
$where_clause[] = '{column_ci:real_name} LIKE {string:name_' . $k . '}';
}

// Who matches those criteria?
Expand All @@ -498,12 +494,11 @@ protected function setUserQuery(): void
if (Db::$db->num_rows($request) > $this->max_members_to_search) {
$this->user_query = '';
} else {
$this->searchq_parameters['real_name'] = Db::$db->case_sensitive ? 'LOWER(pm.from_name)' : 'pm.from_name';
$clauses = [];

foreach ($possible_users as $k => $v) {
$this->searchq_parameters['name_' . $k] = $v;
$clauses[] = '{raw:real_name} LIKE {string:name_' . $k . '}';
$clauses[] = '{column_ci:pm.from_name} LIKE {string:name_' . $k . '}';
}

if (Db::$db->num_rows($request) == 0) {
Expand Down
3 changes: 1 addition & 2 deletions Sources/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -1649,10 +1649,9 @@ public function validateEmail(string $email): bool|string
'SELECT id_member
FROM {db_prefix}members
WHERE id_member != {int:selected_member}
AND {raw:email_address_field} = {string:email_address}
AND {column_ci:email_address} = {string:email_address}
LIMIT 1',
[
'email_address_field' => Db::$db->case_sensitive ? 'LOWER(email_address)' : 'email_address',
'selected_member' => $this->id,
'email_address' => $email,
],
Expand Down
16 changes: 6 additions & 10 deletions Sources/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -3800,9 +3800,10 @@ public static function find(string|array $names, bool $use_wildcards = false, bo
$email_condition = '';
}

// Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise.
$member_name = Db::$db->case_sensitive ? 'LOWER(member_name)' : 'member_name';
$real_name = Db::$db->case_sensitive ? 'LOWER(real_name)' : 'real_name';
// The {column_ci:} type folds the column for the engines that need it and
// leaves it alone for the ones that do not.
$member_name = '{column_ci:member_name}';
$real_name = '{column_ci:real_name}';

// Searches.
$member_name_search = $member_name . ' ' . $comparison . ' ' . implode(' OR ' . $member_name . ' ' . $comparison . ' ', $names_list);
Expand Down Expand Up @@ -5424,13 +5425,8 @@ protected static function addQueryCustomizationsForLoadType(array &$query_custom
break;

case self::LOAD_BY_NAME:
if (Db::$db->case_sensitive) {
$query_customizations['where'][] = 'LOWER(mem.member_name) IN ({array_string:users})';
$query_customizations['params']['users'] = array_map('strtolower', $users);
} else {
$query_customizations['where'][] = 'mem.member_name IN ({array_string:users})';
$query_customizations['params']['users'] = $users;
}
$query_customizations['where'][] = '{column_ci:mem.member_name} IN ({array_string_ci:users})';
$query_customizations['params']['users'] = $users;

break;

Expand Down
Loading