Добавление валидации и обработки ошибок при сохранении произвольных полей?

9 дек. 2010 г., 23:57:18
Просмотры: 37.2K
Голосов: 31

У меня есть функция, которая определяет произвольное поле для типа записи. Допустим, поле называется "subhead".

Когда запись сохраняется, я хочу выполнить валидацию введенных данных и при необходимости отобразить сообщение об ошибке на странице редактирования записи. Что-то вроде:

// Обработка обновления записи
function wpse_update_post_custom_values($post_id, $post) {

    // Выполняем проверку...
    if($_POST['subhead'] != 'value i expect') {

        // Добавляем ошибку
        $errors->add('oops', 'Произошла ошибка.');

    }

    return $errors;

} 
add_action('save_post','wpse_update_post_custom_values',1,2);

Я пытаюсь привязать это к действию save_post, но не могу понять, как обрабатывать ошибки. Похоже, что объект ошибок не передается в функцию, а если я создаю свой собственный объект WP_Error и возвращаю его, он не учитывается механизмом, который выводит ошибки на странице редактирования записи.

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

Есть идеи?

ОБНОВЛЕНИЕ:

Основываясь на ответе @Denis, я попробовал несколько разных вариантов. Хранение ошибок как глобальной переменной не сработало, потому что WordPress выполняет перенаправление во время процесса save_post, что уничтожает глобальную переменную до того, как вы сможете её отобразить.

В итоге я сохранял их в meta-поле. Проблема в том, что вам нужно их очищать, иначе они не исчезнут при переходе на другую страницу, поэтому мне пришлось добавить еще одну функцию, привязанную к admin_footer, которая просто очищает ошибки.

Я не ожидал, что обработка ошибок для чего-то настолько распространенного (обновление записей) будет такой неуклюжей. Я что-то упускаю очевидное или это лучший подход?

// Обработка обновления записи
function wpse_5102_update_post_custom_values($post_id, $post) {

    // Для хранения ошибок
    $errors = false;

    // Выполняем валидацию...
    if($_POST['subhead'] != 'value i expect') {

        // Добавляем ошибку
        $errors .= 'Упс... произошла ошибка.';

    }

    update_option('my_admin_errors', $errors);

    return;

} 
add_action('save_post','wpse_5102_update_post_custom_values',1,2);


// Отображение ошибок
function wpse_5102_admin_notice_handler() {

    $errors = get_option('my_admin_errors');

    if($errors) {

        echo '<div class="error"><p>' . $errors . '</p></div>';

    }   

}
add_action( 'admin_notices', 'wpse_5102_admin_notice_handler' );


// Очистка ошибок
function wpse_5102__clear_errors() {

    update_option('my_admin_errors', false);

}
add_action( 'admin_footer', 'wpse_5102_clear_errors' );
6
Комментарии

Хороший вопрос. Думаю, вы могли бы избавиться от хука admin_footer, если очистите ошибки в конце вашей функции обработки уведомлений. Это немного упростит код.

Geert Geert
3 мая 2011 г. 09:54:21

Как вы обрабатываете повторное заполнение полей формы (с потенциально неверными данными)?

Geert Geert
3 мая 2011 г. 09:58:27

У меня базовый вопрос. В каком PHP-файле WordPress это находится?

User User
27 мая 2011 г. 02:25:20

@Karen Это должно быть в файле пользовательского плагина или в functions.php.

MathSmath MathSmath
27 мая 2011 г. 07:38:37

Возможно, я упускаю что-то очевидное, но будет ли немного эффективнее выполнить update_option('my_admin_errors', false); сразу после оператора if в конце wpse_5102_admin_notice_handler()?

Andrew Odri Andrew Odri
1 дек. 2012 г. 00:55:24

@AndrewOdri Да, думаю, это тоже сработает. Главное, чтобы вам не понадобилась информация об ошибке позже (например, для выделения элементов в форме или чего-то подобного). Хорошее замечание!

MathSmath MathSmath
5 дек. 2012 г. 18:19:40
Показать остальные 1 комментариев
Все ответы на вопрос 7
2

Храните ошибки в вашем классе или глобально, возможно, в транзиенте или мета-данных, и отображайте их в админ-уведомлениях при POST-запросах. В WordPress нет встроенного обработчика flash-сообщений.

10 дек. 2010 г. 00:02:19
Комментарии

Спасибо, что направили меня в этом направлении! В итоге я использовал мета-поле для хранения ошибок, так как у меня были проблемы с попытками сделать это через глобальную переменную или свойство. Сейчас обновляю свой ответ, чтобы объяснить, как я это делаю... дайте знать, если это то, что вы имели в виду, или есть более правильный способ, который я упускаю.

MathSmath MathSmath
10 дек. 2010 г. 02:15:03

Да, что-то в этом роде. Хотя, если подумать, возможно, лучше хранить это в переменной сессии. Это позволит нескольким авторам одновременно редактировать записи. :-) Также, насколько я знаю, нельзя сохранить false в опции. Вместо этого используйте пустую строку.

Denis de Bernardy Denis de Bernardy
10 дек. 2010 г. 11:35:09
4

Я рекомендую использовать сессии, так как это позволит избежать странных эффектов при одновременном редактировании несколькими пользователями. Вот что я делаю:

WordPress не запускает сессии самостоятельно. Поэтому вам нужно запустить сессию в вашем плагине, functions.php или даже wp-config.php:

if (!session_id())
  session_start();

При сохранении записи добавляем ошибки и уведомления в сессию:

function my_save_post($post_id, $post) {
   if($something_went_wrong) {
     //Добавляем сообщение об ошибке, если что-то пошло не так
     $_SESSION['my_admin_notices'] .= '<div class="error"><p>Возникла ошибка</p></div>';
     return false; //может остановить дальнейшую обработку
   }
   if($somthing_to_notice) {  //например, успешное сохранение
     //Добавляем уведомление, если что-то требует внимания
     $_SESSION['my_admin_notices'] .= '<div class="updated"><p>Запись обновлена</p></div>';
   }

   return true;
} 
add_action('save_post','my_save_post');

Выводим уведомления и ошибки, а затем очищаем сообщения в сессии:

function my_admin_notices(){
  if(!empty($_SESSION['my_admin_notices'])) print  $_SESSION['my_admin_notices'];
  unset ($_SESSION['my_admin_notices']);
}
add_action( 'admin_notices', 'my_admin_notices' );
14 июн. 2011 г. 13:36:04
Комментарии

исправление для версии с сессиями: при первом использовании переменной сессии не используйте .=, только =, если включить отладку, можно проверить почему...

User User
13 июл. 2012 г. 16:12:01

Я тоже так делал, но если выпускать плагин для широкой аудитории таким образом, люди в итоге возненавидят вас за это. WordPress не инициализирует сессии, потому что он разработан как stateless и не нуждается в них, а некоторые странные настройки серверов могут сломать это. Используйте Transients API - http://codex.wordpress.org/Transients_API вместо сессий, и вы сохраните совместимость. Просто подумал, что стоит отметить причину, почему не стоит так делать здесь.

pospi pospi
12 сент. 2012 г. 04:37:55

@pospi это, кажется, имеет схожие проблемы с исходным использованием функций get_option и update_option. Полагаю, решение состоит в добавлении ID текущего пользователя к ключу?

Gazillion Gazillion
24 окт. 2013 г. 19:55:28

Да, это точно сработает! Главное — добавить что-то для уникальной идентификации пользователя, чтобы сообщения не перемешивались между авторизованными пользователями (:

pospi pospi
28 окт. 2013 г. 02:23:34
2

Основываясь на предложении pospi использовать транзиенты, я придумал следующее решение. Единственная проблема в том, что нет хука для размещения сообщения под h2, где отображаются другие сообщения, поэтому мне пришлось использовать jQuery-костыль для этого.

Сначала сохраняем сообщение об ошибке в обработчике save_post (или аналогичном). Я задаю короткое время жизни — 60 секунд, чтобы сообщение сохранялось ровно на время редиректа.

if($has_error)
{
  set_transient( "acme_plugin_error_msg_$post_id", $error_msg, 60 );
}

Затем просто извлекаем это сообщение при следующей загрузке страницы и выводим его. Также удаляем сообщение, чтобы оно не отображалось повторно.

add_action('admin_notices', 'acme_plugin_show_messages');

function acme_plugin_show_messages()
{
  global $post;
  if ( false !== ( $msg = get_transient( "acme_plugin_error_msg_{$post->ID}" ) ) && $msg) {
    delete_transient( "acme_plugin_error_msg_{$post->ID}" );
    echo "<div id=\"acme-plugin-message\" class=\"error below-h2\"><p>$msg</p></div>";
  }
}

Поскольку хук admin_notices срабатывает до генерации основного контента страницы, уведомление не попадает в место, где отображаются другие сообщения при редактировании записи, поэтому я использовал этот jQuery-код для перемещения:

jQuery('h2').after(jQuery('#acme-plugin-message'));

Поскольку ID записи является частью имени транзиента, это решение должно работать в большинстве многопользовательских сред, за исключением случаев, когда несколько пользователей одновременно редактируют одну и ту же запись.

16 сент. 2012 г. 00:55:49
Комментарии

Можешь подробнее объяснить фразу "Поскольку ID записи является частью имени транзиента"? Я создал класс для обработки сообщений об ошибках, используя этот метод, но мне нужно передавать user_ID в конструктор. Использует ли Transient API user_id при хешировании ключа? (Спрашиваю, потому что в кодексе это, кажется, не упоминается)

Gazillion Gazillion
24 окт. 2013 г. 20:37:01

Нет, но ты можешь добавить его вручную. В приведенном мной коде имя транзиента — acme_plugin_error_msg_POSTID. Ты можешь просто добавить ID пользователя, например acme_plugin_error_msg_POSTID_USERID.

Josh Coady Josh Coady
30 янв. 2015 г. 06:34:21
1

Когда выполняется save_post, запись уже сохранена в базе данных.

Анализируя исходный код WordPress, в частности функцию update_post() в файле wp-includes/post.php, нет встроенного способа перехватить запрос перед сохранением в базу данных.

Однако мы можем использовать хук pre_post_update с функциями header() и get_post_edit_link(), чтобы предотвратить сохранение записи.

<?php

/**
*   Выполняет валидацию перед сохранением/вставкой пользовательского типа записи
*/
function custom_post_site_save($post_id, $post_data) {
    // Если это просто ревизия, ничего не делаем.
    if (wp_is_post_revision($post_id))
        return;

    if ($post_data['post_type'] == 'my_custom_post_type') {
        // Запрещаем заголовки записей короче 5 символов
        if (strlen($post_data['post_title'] < 5)) {
            header('Location: '.get_edit_post_link($post_id, 'redirect'));
            exit;
        }
    }
}
add_action( 'pre_post_update', 'custom_post_site_save', 10, 2);

Если нужно уведомить пользователя об ошибке, проверьте этот gist: https://gist.github.com/Luc45/09f2f9d0c0e574c0285051b288a0f935

30 июл. 2018 г. 00:54:58
Комментарии

Спасибо! Отлично справляется с валидацией, как при первой публикации, так и при обновлении поста. Вы только что сэкономили мне массу времени и усилий.

Zade Zade
15 дек. 2018 г. 10:31:52
2

Почему бы вам не проверить ваше поле с помощью JavaScript? Я думаю, это будет лучший подход в данном случае.

10 дек. 2010 г. 10:10:10
Комментарии

Спасибо за предложение! Что я упустил в вопросе (для простоты) — это то, что мне нужно обрабатывать ошибки загрузки файлов на стороне сервера. Все равно спасибо за предложение!

MathSmath MathSmath
10 дек. 2010 г. 18:33:45

Валидация на javascript не защищает от некоторых атак, только серверная валидация является действительно безопасной. Кроме того, WordPress предлагает хорошие инструменты для проверки пользовательских данных. Но вы правы, если это просто проверка значений перед отправкой данных на сервер, это может сэкономить время при небольшой нагрузке на сервер ^^

nderambure nderambure
15 мар. 2011 г. 01:50:40
2

Пытаясь использовать приведённый выше скрипт, я столкнулся со странной проблемой. На экране редактирования отображаются два сообщения после обновления записи. Одно показывает состояние содержимого из предыдущего сохранения, а другое — из текущего. Например, если я правильно сохраняю запись, а затем делаю ошибку, первое сообщение — "ошибка", а второе — "ок", хотя они генерируются в одно и то же время. Если я изменю скрипт и добавлю только одно сообщение (например, "ошибка"), инициирую одно обновление с "ошибка" и затем другое с "ок", сообщение "ошибка" остаётся (отображается во второй раз). Мне приходится сохранять с "ок" ещё раз, чтобы избавиться от него. Я действительно не понимаю, в чём проблема, я проверил это на трёх разных локальных серверах, и на каждом из них наблюдается та же самая ошибка. Если у кого-то есть идеи или предложения, пожалуйста, помогите!

8 сент. 2011 г. 19:57:15
Комментарии

Я провел дополнительные тесты более простой, второй версии скрипта, о которой упоминал выше, и кажется, что если сообщение об "ошибке" действительно добавляется в массив сессии, оно отображается на экране редактирования. Если сообщения нет (все "ок") и предыдущее сообщение было об ошибке, оно появляется на экране. Что странно, оно генерируется в момент сохранения (не кэшируется) - я проверил это, используя date() в теле сообщения об ошибке. Сейчас я совершенно сбит с толку.

jlub jlub
8 сент. 2011 г. 20:55:32

Ладно, на случай если кто-то еще рвет на себе волосы - оказалось, что проблема была в системе ревизий Wordpress (вероятно, какой-то баг?). Я отключил ее, и теперь все работает как надо.

User User
12 сент. 2011 г. 12:41:27
1

Я разработал плагин, который добавляет мгновенную обработку ошибок на экранах редактирования записей и предотвращает публикацию постов до тех пор, пока не будут заполнены обязательные поля:

https://github.com/interconnectit/required-fields

Он позволяет сделать любые поля записи обязательными, но вы также можете использовать предоставляемый API, чтобы сделать обязательными любые пользовательские поля с настраиваемым сообщением об ошибке и функцией валидации. По умолчанию проверяется, пустое поле или нет.

19 нояб. 2012 г. 12:00:22
Комментарии

Не стесняйтесь добавлять любые проблемы на GitHub, если вы их обнаружите. Мне также нужно немного улучшить документацию API, так как есть несколько дополнительных фильтров, которые вы можете использовать.

sanchothefat sanchothefat
7 дек. 2012 г. 16:13:53