Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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[] = '({ci:' . implode('} LIKE {string:' . $param_name . '_normal} OR {ci:', $param_info['db_fields']) . '} LIKE {string:' . $param_name . '_normal})';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this use {ci_string}?


$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 {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 {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 {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 {ci_string: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 {ci:email_address} = {string:email_address}
OR {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 {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
13 changes: 13 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 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.
'~{(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] === '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 'ci_string':
return \sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $this->fix_mb4((string) $replacement)));

case 'array_int':
Expand Down
15 changes: 15 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 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.
'~{(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] === '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 {ci:} has folded.
case 'ci_string':
return \sprintf('LOWER(\'%1$s\')', pg_escape_string($this->connection, (string) $replacement));

case 'array_int':
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 {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[] = '{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[] = '{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 {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
19 changes: 9 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 {ci:} type folds the column for the engines that need it and
// leaves it alone for the ones that do not.
$member_name = '{ci:member_name}';
$real_name = '{ci:real_name}';

// Searches.
$member_name_search = $member_name . ' ' . $comparison . ' ' . implode(' OR ' . $member_name . ' ' . $comparison . ' ', $names_list);
Expand Down Expand Up @@ -5424,13 +5425,11 @@ 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'][] = '{ci:mem.member_name} IN ({array_string:users})';

// An array of values has no {ci:} of its own, so the names are
// folded here for the engines that compare them exactly.
$query_customizations['params']['users'] = Db::$db->case_sensitive ? array_map('strtolower', $users) : $users;

break;

Expand Down
Loading