Каждый разработчик WordPress рано или поздно сталкивается с валидацией и очисткой данных, но далеко не все понимают фундаментальную разницу между этими двумя процессами. Понимание этого различия не теоретическое — оно напрямую определяет, будет ли ваш плагин или тема обрабатывать пользовательский ввод безопасно или откроет уязвимость, компрометирующую весь сайт. Это руководство объясняет оба понятия с базовых принципов, проходит через все пять стандартных типов ввода, используемых в Settings API, и показывает, как создавать функции обратного вызова, которые одновременно функциональны и безопасны.
Определение валидации и очистки
Валидация и очистка служат разным целям и действуют на разных этапах жизненного цикла данных. Путаница между ними приводит либо к чрезмерно строгой обработке ввода, которая раздражает пользователей, либо к излишне разрешительному хранению данных, создающему риски безопасности.
Валидация: привратник
Валидация — это процесс проверки поступающих данных на соответствие заранее заданным критериям до их записи в базу данных. Она отвечает на вопрос: приемлемы ли эти данные? Валидированный адрес электронной почты содержит символ @ и домен. Валидированный URL начинается с http:// или https://. Валидированное целое число содержит только цифры. Если валидация не пройдена, данные отвергаются полностью — они никогда не достигают базы данных. Валидация происходит на границе ввода, до любого сохранения.
Очистка: фильтр
Очистка — это процесс обработки данных для удаления или кодирования потенциально опасного содержимого с сохранением предполагаемого смысла. Она отвечает на вопрос: могу ли я сделать эти данные безопасными? Очищенный текст лишён HTML-тегов. Очищенный email имеет обрезанные пробелы. Очищенные URL имеют закодированные недопустимые символы. В отличие от валидации, очистка не отвергает данные — она преобразует их в безопасную форму. Очистка применяется и до сохранения, и перед выводом, образуя двухслойную защиту.
Почему оба процесса критически важны
Сайт WordPress без надлежащей обработки данных — это открытая дверь. Представьте простое текстовое поле, где пользователи вводят отображаемое имя. Без очистки злоумышленник может ввести <script>alert('XSS')</script> в качестве имени. Когда это имя отображается в комментарии или списке пользователей, браузер каждого посетителя выполняет скрипт. Это хранимая межсайтовая скриптовая атака (XSS), и она является одной из самых распространённых уязвимостей в плагинах WordPress.
Защита требует совместной работы валидации и очистки. Валидация проверяет, что ввод имеет правильную форму (строка разумной длины). Очистка удаляет любое оставшееся опасное содержимое перед сохранением. А при выводе функции экранирования гарантируют, что даже если что-то проскользнуло, оно отображается как текст, а не выполняется как код.
| Процесс | Когда выполняется | Что делает | Последствия отказа |
|---|---|---|---|
| Валидация | Перед сохранением в БД | Проверяет соответствие данных требованиям формата | Некорректные данные сохраняются, повреждая базу или ломая функциональность |
| Очистка ввода | Перед сохранением в БД | Удаляет опасное содержимое из данных | Вредоносный код сохранён в базе, готов к выполнению XSS |
| Экранирование вывода | Перед отрисовкой в браузере | Кодирует вывод для безопасного отображения в HTML | Срабатывает хранимый XSS при загрузке страницы |
Settings API объединяет все три слоя. Когда вы регистрируете настройку через register_setting(), вы предоставляете колбэк очистки. Этот колбэк должен валидировать ввод, очистить его и вернуть очищенное значение. WordPress вызывает этот колбэк автоматически при отправке формы, перед записью в таблицу options. Для вывода вы используете функции экранирования — esc_html() и esc_attr() — в колбэках отрисовки страницы.
Функции очистки WordPress
WordPress предоставляет обширную библиотеку функций очистки, покрывающую наиболее распространённые типы данных. Использование этих встроенных функций всегда предпочтительнее написания собственной логики очистки, потому что они поддерживаются командой безопасности WordPress, протестированы на миллионах установок и обрабатывают краевые случаи, которые пользовательский код может пропустить.
| Функция | Тип ввода | Что удаляет | Пример |
|---|---|---|---|
| sanitize_text_field() | Обычный текст | HTML-теги, переносы строк, табуляции, лишние пробелы, проценты и октеты | "Привет <b>Мир</b>" становится "Привет Мир" |
| sanitize_email() | Адрес email | Недопустимые символы для формата email, лишние пробелы | " user@domain.com " становится "user@domain.com" |
| sanitize_url() / esc_url_raw() | URL | Недопустимые символы, удаляет опасные протоколы типа javascript: | "javascript:alert(1)" становится "" |
| sanitize_key() | Ярлык/ключ | Всё кроме строчных буквенно-цифровых символов, дефисов и подчёркиваний | "Мой Ключ!" становится "мойключ" |
| sanitize_title() | Ярлык записи | Приводит к нижнему регистру, заменяет пробелы на дефисы, удаляет HTML и спецсимволы | "Моя Запись" становится "моя-запись" |
| absint() | Положительное целое | Преобразует в абсолютное (неотрицательное) целое число | "-5abc" становится 5 |
| intval() | Целое число | Преобразует в целое с сохранением знака | "42px" становится 42 |
| sanitize_hex_color() | Шестнадцатеричный код цвета | Проверяет как 3- или 6-значный hex-цвет, возвращает пустую строку при невалидности | "#FF0000" проходит, "красный" возвращает "" |
Использование функций очистки в колбэках Settings API
Колбэк очистки получает весь массив отправленных значений и должен вернуть массив очищенных значений. Каждое поле в массиве должно быть обработано соответствующей функцией очистки WordPress:
function mytheme_sanitize_general_options($input) {
$output = array();
if (isset($input['site_title'])) {
$output['site_title'] = sanitize_text_field($input['site_title']);
}
if (isset($input['contact_email'])) {
$output['contact_email'] = sanitize_email($input['contact_email']);
}
if (isset($input['twitter_url'])) {
$output['twitter_url'] = esc_url_raw($input['twitter_url']);
}
if (isset($input['posts_per_page'])) {
$output['posts_per_page'] = absint($input['posts_per_page']);
}
if (isset($input['accent_color'])) {
$color = sanitize_hex_color($input['accent_color']);
if (!empty($color)) {
$output['accent_color'] = $color;
}
}
return $output;
}
[/codeblock]
Пять стандартных элементов ввода
Settings API WordPress поддерживает пять базовых типов ввода. Каждый тип служит определённой цели и требует особого подхода к отрисовке и обработке. Понимание того, когда использовать каждый тип, так же важно, как и умение их кодировать.
1. Text Input (однострочное текстовое поле)
Самый распространённый тип ввода. Используется для коротких строковых значений: заголовков, имён, ключей API и простых URL. Текстовые поля принимают свободный ввод и должны сочетаться с sanitize_text_field() для обычного текста или esc_url_raw() для URL.
function mytheme_text_field_callback() {
$options = get_option('mytheme_options');
$value = isset($options['site_title']) ? $options['site_title'] : '';
?>
Введите заголовок сайта для области шапки.
Атрибут name использует нотацию массива — mytheme_options[site_title] — что заставляет WordPress группировать все поля в единый ассоциативный массив в $_POST. Именно так колбэк очистки получает все значения сразу как единый параметр $input. Функция esc_attr() в атрибуте value предотвращает XSS, кодируя кавычки и спецсимволы, которые могли бы вырваться из контекста HTML-атрибута.2. Textarea (многострочное поле)
Используется для длинного текстового контента: описаний, пользовательского CSS, скриптов шапки/подвала и адресной информации. Textarea поддерживают переносы строк и должны очищаться в зависимости от ожидаемого содержимого: sanitize_textarea_field() для обычного текста или wp_kses_post() при допустимости ограниченного HTML.
function mytheme_textarea_callback() {
$options = get_option('mytheme_options');
$value = isset($options['footer_text']) ? $options['footer_text'] : '';
?>
Пользовательский текст для подвала сайта. Только обычный текст.
3. Radio Buttons (радиокнопки)
Радиокнопки позволяют выбрать один вариант из предопределённого набора. Они идеальны для взаимоисключающих выборов: режимов макета, цветовых схем или переключателей вкл/выкл, отображаемых парами. Функция checked() помогает сохранять состояние при перезагрузке страницы.
function mytheme_radio_callback() {
$options = get_option('mytheme_options');
$current = isset($options['layout']) ? $options['layout'] : 'default';
?>
Функция checked() сравнивает первый аргумент (значение опции) со вторым аргументом (текущее сохранённое значение). Когда они совпадают, она выводит checked='checked', гарантируя выбор правильной радиокнопки при загрузке страницы. Всегда оборачивайте группы радиокнопок в fieldset для семантической структуры и подписывайте каждую опцию через label для корректного поведения области клика.
4. Checkbox (одиночный флажок)
Одиночный чекбокс — это бинарный переключатель для булевых опций: включить/отключить функцию, показать/скрыть элемент. В отличие от радиокнопок, чекбокс может быть не отмечен, что означает, что имя поля вообще не отправляется. Это требует особой обработки в колбэке очистки.
function mytheme_checkbox_callback() {
$options = get_option('mytheme_options');
$checked = isset($options['enable_feature']) ? $options['enable_feature'] : 0;
?>
В колбэке очистки обработайте случай отсутствующего чекбокса:
function mytheme_sanitize_options($input) {
$output = array();
$output['enable_feature'] = isset($input['enable_feature'])
? absint($input['enable_feature'])
: 0;
return $output;
}
[/codeblock]
5. Select Dropdown (выпадающий список)
Выпадающие списки представляют варианты в компактной форме. Они уместны, когда у вас больше 3-4 вариантов или список может расти со временем. Функция selected(), подобно checked(), поддерживает выбранное состояние.
function mytheme_select_callback() {
$options = get_option('mytheme_options');
$current = isset($options['font_family']) ? $options['font_family'] : 'system';
$font_options = array(
'system' => 'Системный по умолчанию',
'georgia' => 'Georgia',
'helvetica' => 'Helvetica Neue',
'roboto' => 'Roboto',
'lora' => 'Lora',
);
?>
Выберите базовый шрифт для вашего сайта.
Шаблон с циклом foreach — стандартный подход для select с множеством опций. Определите варианты как ассоциативный массив пар value => label, затем итерируйтесь по ним, используя selected() для отметки текущего сохранённого значения. Это чище, чем писать отдельные теги <option>, и проще в поддержке при добавлении или удалении вариантов.Полный пример регистрации: все пять типов ввода
Объединим всё в полный рабочий пример. Он регистрирует секцию настроек с каждым типом ввода, включая очистку. Вы можете скопировать это в functions.php вашей темы или в основной файл плагина — всё будет работать сразу:
function mytheme_register_settings() {
register_setting(
'mytheme_options_group',
'mytheme_options',
'mytheme_sanitize_options'
);
add_settings_section(
'mytheme_main_section',
'Общие настройки',
'mytheme_section_description',
'mytheme-settings'
);
add_settings_field(
'site_title',
'Заголовок сайта',
'mytheme_text_field_callback',
'mytheme-settings',
'mytheme_main_section'
);
add_settings_field(
'footer_text',
'Текст подвала',
'mytheme_textarea_callback',
'mytheme-settings',
'mytheme_main_section'
);
add_settings_field(
'layout',
'Макет страницы',
'mytheme_radio_callback',
'mytheme-settings',
'mytheme_main_section'
);
add_settings_field(
'enable_feature',
'Расширенные функции',
'mytheme_checkbox_callback',
'mytheme-settings',
'mytheme_main_section'
);
add_settings_field(
'font_family',
'Базовый шрифт',
'mytheme_select_callback',
'mytheme-settings',
'mytheme_main_section'
);
}
add_action('admin_init', 'mytheme_register_settings');
function mytheme_section_description() {
echo 'Настройте общий внешний вид и поведение вашей темы.
'; } function mytheme_sanitize_options($input) { $output = array(); $output['site_title'] = isset($input['site_title']) ? sanitize_text_field($input['site_title']) : ''; $output['footer_text'] = isset($input['footer_text']) ? sanitize_textarea_field($input['footer_text']) : ''; $valid_layouts = array('default', 'wide', 'boxed'); $output['layout'] = isset($input['layout']) && in_array($input['layout'], $valid_layouts) ? $input['layout'] : 'default'; $output['enable_feature'] = isset($input['enable_feature']) ? absint($input['enable_feature']) : 0; $valid_fonts = array('system', 'georgia', 'helvetica', 'roboto', 'lora'); $output['font_family'] = isset($input['font_family']) && in_array($input['font_family'], $valid_fonts) ? $input['font_family'] : 'system'; return $output; } [/codeblock]Экранирование вывода в шаблонах административных страниц
Очистка данных перед сохранением — первый слой. Экранирование данных перед отображением — второй. Даже очищенные данные должны экранироваться при отрисовке в HTML, потому что требования кодирования различаются в зависимости от контекста. WordPress предоставляет семейство функций экранирования для разных HTML-контекстов:
- esc_html(): Для текстового содержимого между HTML-тегами. Преобразует <, >, & и кавычки в HTML-сущности.
- esc_attr(): Для значений HTML-атрибутов. Кодирует кавычки и спецсимволы, которые вырвались бы из атрибута.
- esc_url(): Для атрибутов href и src. Удаляет опасные протоколы типа javascript: и гарантирует корректный формат URL.
- esc_textarea(): Для содержимого textarea. Кодирует для контекста textarea, сохраняя переносы строк.
- wp_kses(): Для контента, который может содержать ограниченный набор разрешённых HTML-тегов. Используется с явным белым списком.
Вот как использовать их в шаблоне административной страницы, отображающем сохранённые опции:
function mytheme_page_callback() {
$options = get_option('mytheme_options');
?>
Типичные ошибки и их последствия
Использование esc_html() для URL в атрибутах href
esc_html() преобразует амперсанды в &, что ломает URL с параметрами запроса. Всегда используйте esc_url() для атрибутов href и src. esc_url() разработана специально для URL-контекста и обрабатывает кодирование корректно, удаляя опасные протоколы.
Забытая обработка неотмеченных чекбоксов
Когда чекбокс не отмечен, его имя не появляется в $_POST. Если ваш колбэк очистки обращается к $input['my_checkbox'] без проверки isset(), это вызывает PHP notice. Что хуже — опция никогда не обновляется, и старое отмеченное значение сохраняется бесконечно. Всегда задавайте значение по умолчанию 0 для неотмеченных чекбоксов.
Пропуск проверки по белому списку для Select и Radio
Никогда не предполагайте, что отправленное значение совпадает с одним из HTML-вариантов. Любой может отправить POST-запрос с произвольными данными через инструменты разработчика браузера или curl. Ваш колбэк очистки должен проверять, что отправленное значение существует в вашем утверждённом списке, прежде чем принять его.
Использование esc_textarea() для отрисовки обычного текста
esc_textarea() разработана только для использования внутри тегов <textarea>. Её применение для отрисовки текста в <p> или <div> не обеспечивает адекватного HTML-кодирования. Используйте esc_html() для текстового содержимого между HTML-тегами.
Чеклист безопасности для форм Settings API
Каждый раз при создании формы настроек проверяйте эти пункты перед тем, как считать работу завершённой:
- register_setting() включает колбэк очистки третьим аргументом
- Колбэк очистки проверяет isset() для каждого ключа массива перед обращением к нему
- Текстовые поля используют sanitize_text_field(), если им не требуется специально принимать HTML
- Поля URL используют esc_url_raw() в колбэке очистки
- Значения select и radio валидируются по белому списку через in_array()
- Опции чекбоксов предоставляют значение по умолчанию 0 при отсутствии ключа
- Все атрибуты value полей формы используют esc_attr() для экранирования вывода
- Всё текстовое содержимое использует esc_html() для экранирования вывода
- Все URL в атрибутах href или src используют esc_url() для экранирования вывода
- Содержимое textarea использует esc_textarea() внутри тега textarea
Часто задаваемые вопросы
В чём разница между валидацией и очисткой?
Валидация проверяет, соответствуют ли данные заданным критериям, и отвергает их при несоответствии. Представьте вышибалу: есть ли у этого человека действительное удостоверение? Очистка чистит данные, удаляя опасное содержимое с сохранением смысла. Представьте металлодетектор: пропустить людей, но изъять оружие. Валидация происходит первой, отсеивая явно неверные данные. Очистка следует за ней, очищая то, что прошло. Оба процесса необходимы для безопасной обработки данных.
Когда использовать sanitize_text_field() против sanitize_textarea_field()?
sanitize_text_field() для однострочных текстовых полей, где нужно удалить все HTML-теги, переносы строк и лишние пробелы. sanitize_textarea_field() для многострочных textarea, где нужно сохранить переносы строк, удаляя HTML-теги. Ключевое различие: sanitize_textarea_field() сохраняет переводы строк, делая её безопасной для многострочного контента без HTML.
Почему значение чекбокса никогда не сохраняется при снятии отметки?
HTML-формы не отправляют неотмеченные чекбоксы. Когда вы снимаете отметку с чекбокса, его имя вообще не появляется в $_POST. Ваш колбэк очистки должен проверять через isset() и предоставлять значение по умолчанию: $output['my_checkbox'] = isset($input['my_checkbox']) ? absint($input['my_checkbox']) : 0. Без этого fallback-значения состояние «не отмечен» никогда не сохраняется.
Как безопасно разрешить ограниченный HTML в textarea?
Используйте wp_kses() с явным белым списком. Пример: wp_kses($input['content'], array('strong' => array(), 'em' => array(), 'a' => array('href' => array(), 'title' => array()))). Это разрешает только теги strong, em и a с указанными атрибутами. Никогда не используйте wp_kses_post() с контекстом 'post' для настроек — он разрешает слишком много тегов. Определите минимальный набор тегов, необходимый вашему сценарию использования.
В чём разница между esc_url() и esc_url_raw()?
esc_url() для вывода — экранирует URL для использования в атрибутах href или src в HTML. esc_url_raw() для хранения — очищает URL перед сохранением в базу данных без добавления HTML-сущностей. Если использовать esc_url() в колбэке очистки, сохранённый URL будет содержать закодированные амперсанды (&), что ломает дальнейшее использование. Всегда используйте esc_url_raw() для очистки и esc_url() для вывода.
Как безопасно валидировать значение выпадающего списка?
Определите массив белого списка разрешённых значений. В колбэке очистки: $allowed = array('вариант1', 'вариант2', 'вариант3'); $output['select_field'] = in_array($input['select_field'], $allowed) ? $input['select_field'] : 'значение_по_умолчанию'. Это гарантирует, что даже если злоумышленник отправит значение, отсутствующее в HTML-вариантах select, только заведомо безопасные значения достигнут базы данных.
Нужны ли одновременно очистка и экранирование?
Да, абсолютно. Очистка чистит данные перед сохранением. Экранирование кодирует данные перед выводом. Они защищают разные этапы жизненного цикла данных. Даже правильно очищенные данные должны экранироваться при отрисовке, потому что контекст вывода может требовать другого кодирования. Например, очищенный текст безопасен в теге
, но может вырваться из HTML-атрибута без esc_attr(). Всегда очищайте на входе, экранируйте на выходе.
Какую функцию очистки использовать для целочисленных полей?
absint() для неотрицательных целых чисел: счётчиков, ID, лимитов. intval() когда допустимы отрицательные числа: смещения или значения температуры. Обе функции преобразуют строковый ввод в целые числа и безопасны против нечисловых инъекций. Для десятичных чисел используйте floatval(). Никогда не приводите напрямую к (int) без проверки — это может дать неожиданные результаты с нечисловыми строками.



