Ошибки SQL при запросе чего-либо с апострофами

Вопрос или проблема

У меня есть следующая функция PHP, которая запрашивает некоторые пользовательские таблицы базы данных и выводит результат. Обычно это работает отлично, за исключением случаев, когда я вставляю апостроф в запрос; например, “Men’s” или “Women’s” вызывает такие ошибки, как ниже. Как я могу это исправить? Я немного новичок в SQL, поэтому, уверен, я делаю что-то явно неправильно.

[03-Oct-2024 16:12:10 UTC] Ошибка базы данных WordPress У вас ошибка в синтаксисе SQL; проверьте руководство, которое соответствует версии вашего сервера MySQL для правильного синтаксиса вблизи 's%' ИЛИ `description` LIKE '%Men\\'s%' ИЛИ `code` LIKE '%Men\\'s%') ORDER BY `titl' на строке 1 для запроса SELECT * FROM `83kg5a_activity_posts` WHERE ( `title` LIKE '%Men\\'s%' ИЛИ `description` LIKE '%Men\\'s%' ИЛИ `code` LIKE '%Men\\'s%') ORDER BY `title` ASC, `code` ASC выполнено do_action('wp_ajax_activity_list'), WP_Hook->do_action, WP_Hook->apply_filters, ActivityPlugin\ajax_activity_list, activity_include, include('/plugins/activity-plugin/views/activity-list.php'), ActivityPlugin\get_activities
/**
 * Получить активности, опционально с запросом
 *
 * @param array<mixed> $options
 * @return array|null
 */
function get_activities(array $options = []): ?array {
    global $wpdb;

    /**
     * Получить настройки
     */
    $settings = get_settings();

    /**
     * Конвертировать запрос
     */
    if (array_key_exists("q", $_GET) && $_GET["q"]) {
        $options[] = [
            [
                "key"      => "title",
                "operator" => "LIKE",
                "value"    => "%" . $wpdb->esc_like($_GET["q"]) . "%",
            ],
            [
                "key"      => "description",
                "operator" => "LIKE",
                "value"    => "%" . $wpdb->esc_like($_GET["q"]) . "%",
            ],
            [
                "key"      => "code",
                "operator" => "LIKE",
                "value"    => "%" . $wpdb->esc_like($_GET["q"]) . "%",
            ],
        ];
    }

    /**
     * Конвертировать термины
     */
    foreach ($_GET["terms"] as $data) {
        $options[] = [
            "key"      => $data["name"],
            "operator" => "=",
            "value"    => $data["value"],
        ];
    }

    /**
     * Конвертировать DOW
     */
    if (array_key_exists("dow", $_GET) && count($_GET["dow"]) < 7) {
        $options[] = array_map(function($day) use ($wpdb) {
            return [
                "key"      => "schedule",
                "operator" => "LIKE",
                "value"    => "%" . $wpdb->esc_like($day) . "%",
            ];
        }, $_GET["dow"]);
    }

    /**
     * Подготовить запрос
     */
    $query = "SELECT * FROM `{$wpdb->prefix}activity_posts`";

    /**
     * Добавить параметры к запросу
     */
    if ($options) {
        $query .= " WHERE";

        /**
         * Добавить параметры
         */
        foreach ($options as $data) {
            if (array_key_exists(0, $data)) {
                $query .= " (";

                foreach ($data as $value) {
                    $query .= " `{$value["key"]}` {$value["operator"]} '{$value["value"]}' OR";
                }

                $query = rtrim($query, " OR");

                $query .= ") AND";
            } else {
                $query .= " `{$data["key"]}` {$data["operator"]} '{$data["value"]}' AND";
            }
        }

        $query = rtrim($query, " AND");
    }

    /**
     * Конвертировать возраст
     */
    $queried_min = (array_key_exists("age_minimum", $_GET) ? intval($_GET["age_minimum"]) : $settings["age_limit_min"]) * 12;
    $queried_max = (array_key_exists("age_maximum", $_GET) ? intval($_GET["age_maximum"]) : $settings["age_limit_senior"]) * 12;

    $min_query = "";
    $max_query = "";

    if ($queried_min !== $settings["age_limit_min"] * 12) {
        $min_query = " `age_minimum` >= {$queried_min}";
    }

    if ($queried_min !== $settings["age_limit_min"] * 12 || $queried_max !== $settings["age_limit_senior"] * 12) {
        if ($queried_max >= $settings["age_limit_senior"] * 12) {
            $max_query .= " `age_maximum` >= {$queried_max}";
        } elseif ($queried_max >= $settings["age_limit_adult"] * 12) {
            $max_query .= " `age_maximum` >= {$queried_max} AND `age_maximum` <= " . ($settings["age_limit_senior"] * 12);
        } elseif ($queried_max > $settings["age_limit_min"] * 12) {
            $max_query .= " `age_maximum` <= {$queried_max}";
        }
    }

    if ($min_query || $max_query) {
        $query .= $options ? " AND" : " WHERE";

        if ($min_query && $max_query) {
            $query .= " ({$min_query} AND {$max_query})";
        } elseif ($min_query) {
            $query .= $min_query;
        } elseif ($max_query) {
            $query .= $max_query;
        }
    }

    /**
     * Установить порядок
     */
    $query .= " ORDER BY `title` ASC, `code` ASC";

    /**
     * Логировать запрос
     */
    if (WP_DEBUG_LOG) {
        error_log($query);
    }

    /**
     * Получить все кэшированные активности из базы данных
     */
    return $wpdb->get_results($query);
}

Рекомендуется использовать метод wpdb::prepare() для правильного создания запросов и предотвращения SQL-инъекций.

Для базового решения используйте функцию addslashes(), но этого недостаточно для предотвращения SQL-инъекций.

Смотрите https://stackoverflow.com/questions/4659879/mysql-php-with-special-characters-like-apostrophe-and-quotation-mark.

Ответ или решение

Чтобы устранить ошибки SQL, связанные с использованием апострофов в запросах, вы можете воспользоваться несколькими подходами. Ваша текущая реализация, похоже, не должным образом обрабатывает специальные символы, такие как апострофы, из-за чего возникает ошибка синтаксиса. Давайте рассмотрим, как можно это исправить.

1. Использование wpdb::prepare()

Рекомендованным методом для формирования запросов в WordPress является использование функции wpdb::prepare(). Этот метод не только эффективно обрабатывает апострофы и другие специальные символы, но и защищает от SQL-инъекций.

Вот примеры, как можно изменить ваш код:

if (array_key_exists("q", $_GET) && $_GET["q"]) {
    $search_query = $wpdb->esc_like($_GET["q"]);
    $options[] = [
        [
            "key"      => "title",
            "operator" => "LIKE",
            "value"    => "%$search_query%",
        ],
        [
            "key"      => "description",
            "operator" => "LIKE",
            "value"    => "%$search_query%",
        ],
        [
            "key"      => "code",
            "operator" => "LIKE",
            "value"    => "%$search_query%",
        ],
    ];
}

2. Формирования динамического запроса с wpdb::prepare()

В конце формирования вашего запроса вы можете использовать метод wpdb::prepare() чтобы избежать ошибок с синтаксисом и повысить безопасность:

if ($options) {
    $query .= " WHERE ";

    foreach ($options as $data) {
        if (array_key_exists(0, $data)) {
            $query .= " (";

            foreach ($data as $value) {
                // Использование prepare для обработки специальных символов
                $query .= " `{$value["key"]}` {$value["operator"]} % OR";
            }

            $query = rtrim($query, " OR");
            $query .= ") AND";
        } else {
            $query .= " `{$data["key"]}` {$data["operator"]} % AND";
        }
    }

    $query = rtrim($query, " AND");
}

// Подготовка запроса с использованием prepare
$prepared_query = $wpdb->prepare($query, ...array_map(function($data) {
    return $data["value"];
}, array_merge(...$options)));

// В итоге, выполняем запрос
return $wpdb->get_results($prepared_query);

3. Альтернативный метод: использование addslashes()

Если по каким-то причинам вы не можете использовать wpdb::prepare(), простой метод — это использовать функцию addslashes(), хотя это менее безопасный подход:

$value = addslashes($_GET["q"]);
$query .= " `{$value["key"]}` {$value["operator"]} '$value' AND";

Имейте в виду, что addslashes() происходит лишь экранирование специальных символов, и вы не защищены от SQL-инъекций, если не используете подготовленные выражения.

Заключение

Современные подходы к безопасности баз данных в PHP не оставляют места для сомнений в необходимости использования методов, подобных wpdb::prepare(). Применяя рекомендованные практики, вы не только устраняете ошибки типографии и специальный символов, но и обеспечиваете безопасность своего приложения. Следуя этим рекомендациям, вы можете избежать ошибок SQL и делать свои приложения более защищенными.

Оцените материал
Добавить комментарий

Капча загружается...