Проверка обновления или создания нового поста в хуке save_post
Возможно ли в рамках хука save_post определить, создается ли новый пост или обновляется существующий?

Начиная с версии WordPress 3.7 (если я правильно помню), хук save_post
— подробнее о хуке и его использовании можно узнать в Code Reference: save_post
и Codex: save_post
— имеет третий параметр $update
, который можно использовать для определения, происходит ли обновление.
@param int $post_ID ID записи.
@param WP_Post $post Объект записи.
@param bool $update Указывает, обновляется ли существующая запись или создаётся новая.
Примечание:
$update
не всегда равен true
— вы можете проверить это самостоятельно с помощью приведённого ниже кода. Этот параметр недостаточно документирован, возможно, его название не самое удачное, что может вводить в заблуждение. Нижеприведённый код можно использовать для отладки — поэкспериментируйте с точками останова, иначе вы не увидите информацию/сообщения. Я думаю, причина обманчивого поведения кроется в обработке ревизий и автосохранений — их можно отключить, но я не рекомендую этого делать и сам не тестировал. Не уверен, что это требует создания Trac Ticket, поэтому не создавал его; если вы считаете иначе, перейдите по ссылке и сделайте это сами. Кроме того, как указано в комментариях, если у вас есть конкретная проблема, задайте новый вопрос.
add_action( 'save_post', 'debug_save_post_update', 10, 3 );
function debug_save_post_update( $ID, $post, $update ) {
echo '<pre>';
print_r( $post ); echo '<br>';
echo '$update == ';
echo $update ? 'true' : 'false';
// Условия
if( ! $update && $post->post_status == "auto-draft" ) {
// Применяется к новой записи
echo ' && $post->post_status == "auto-draft"';
//die();
} else if ( ! $update ) {
// Применяется к автосохранённой ревизии
//die();
} else {
// Применяется к обновлению опубликованной записи
// Если есть ревизия (что является стандартным поведением WordPress),
// это считается обновлением, что и вызывает путаницу
// Существуют другие методы, например проверка времени или статуса записи
// В зависимости от вашего случая может быть целесообразнее
// использовать один из этих альтернативных подходов
//die();
}
echo '</pre>';
//die();
}

Параметр $update
ВСЕГДА имеет значение true, даже при создании новой записи. Таким образом, этот параметр бесполезен. Не уверен, работал ли он вообще, но в последней версии WordPress 4.8 он точно не работает так, как описано в документации.

@SolomonClosson Если вы посмотрите на wp_publish_post
, то да. Но это не относится к его использованию в wp_insert_post
. Я написал функцию для отладки, добавил её в ответ.

@SolomonClosson Если у вас есть конкретная проблема, пожалуйста, задайте новый вопрос. Посмотрите историю изменений для функции отладки и объяснение.

Хук save_post
имеет третий параметр, который всегда установлен в TRUE, поэтому не совсем понятно, какое это имеет отношение к другим хукам, не говоря уже о них. Я говорю о хуке в вашем ответе. Это неверно.

@SolomonClosson Как я уже сказал, хук срабатывает дважды:
wp_insert_post()
, wp_publish_post()
. Последний только для запланированных записей, там $update
всегда установлен в true
. В случае с wp_insert_post()
, $update
не всегда true
.

Я опоздал на вечеринку, но эй, почему бы и нет, @Nicolai, то, что вы говорите, — это то, что можно было бы ожидать, но я не могу получить $update
равным false, даже когда подключаюсь к 'wp_insert_post'. Где бы я ни подключал хук, он всегда true. Я не понимаю, почему это не исправлено.

Мой способ проверки (внутри hooked-функции) — сравнение даты публикации и даты изменения (в GMT для стандартизации)
function check_new_vs_update( $post_id ){
$myPost = get_post($post_id);
$created = new DateTime( $myPost->post_date_gmt );
$modified = new DateTime( $myPost->post_modified_gmt );
$diff = $created->diff( $modified );
$seconds_difference = ((($diff->y * 365.25 + $diff->m * 30 + $diff->d) * 24 + $diff->h) * 60 + $diff->i)*60 + $diff->s;
if( $seconds_difference <= 1 ){
// Новая запись
}else{
// Обновлённая запись
}
}
add_action('save_post', 'check_new_vs_update' );
Это работает, потому что даже при создании записи к ней прикрепляется дата изменения, которая точно такая же, как и дата создания, но мы допускаем отклонение в 1 секунду на случай, если секунда изменится во время создания записи.

Иногда post_date_gmt
имеет значение 2019-03-12 01:31:30
, а post_modified_gmt
— 2019-03-12 01:31:31
. :(

@HeYifei何一非 хорошее замечание, если обработка начинается в конце заданной секунды, такое может произойти. Я обновил свой ответ, спасибо

Ребята, просто информация. Хук срабатывает при восстановлении и удалении записи.

Не могу поверить, что это должно быть настолько сложно. WordPress никогда не перестает удивлять.

Если быть справедливым, у них буквально миллионы пользователей, и это проект с открытым исходным кодом. К сожалению, они не могут включить каждую функцию, и это, вероятно, не самая востребованная. В последние годы приоритетами были улучшение редактора и безопасности, и это хорошо! Даже jQuery только сейчас обновляется! До текущей версии использовалась 1.x!

В итоге я просто проверил наличие пользовательского значения перед его установкой. Таким образом, если это только что созданная запись, пользовательского значения ещё не будет существовать.
function attributes_save_postdata($post_id) {
// Если это автосохранение, ничего не делаем
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
// Проверяем nonce на безопасность
if (!wp_verify_nonce($_POST['_attributes_noncename'], plugin_basename(__FILE__))) return;
// Проверяем права пользователя для страниц и записей
if ('page' == $_POST['post_type']) {
if (!current_user_can('edit_page', $post_id)) return;
} else {
if (!current_user_can('edit_post', $post_id)) return;
}
// Получаем текущее значение метаполя
$termid = get_post_meta($post_id, '_termid', true);
if ($termid != '') {
// Если значение существует - помечаем как 'update'
$termid = 'update';
} else {
// Если значения нет - оставляем как есть
}
// Обновляем метаполе
update_post_meta($post_id, '_termid', $termid);
}
// Добавляем обработчик сохранения записи
add_action('save_post', 'attributes_save_postdata');

Для этого нужно сначала создать произвольное поле с помощью add_post_meta?

Согласно Codex: [update_post_meta] может использоваться вместо функции add_post_meta(). http://codex.wordpress.org/Function_Reference/update_post_meta

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

Лучшим способом структурирования будет либо поместить блок обновления первым, позволяя просто использовать if($update)
, либо оставить новый блок первым, но с использованием if( ! $update )
. Последний вариант привьёт OP лучшие практики и предпочтителен по стандартам кодирования WordPress в случаях, подобных использованию тернарного оператора

Я только что столкнулся с хуком save_post
при создании и обновлении записи. После изучения исходного кода, чтобы понять поток выполнения, я обнаружил, что следующий метод может быть полезен. Поскольку ранее он не упоминался, решил поделиться — вдруг кому-то пригодится. (Тестировалось на версии ядра 5.3.3)
Процесс создания записи примерно следующий:
- После нажатия "Добавить новую" (запись)
- Вызывается
$post = get_default_post_to_edit( $post_type, true );
, где get_default_post_to_edit()
получает аргумент$create_in_db = true
- Сразу же вызывается
wp_insert_post()
, создается запись со статусомauto-draft
, даже если она не сохранена. Каждый раз при нажатии "Добавить новую" создаетсяauto-draft
- При публикации новой записи
$update
всегда равенtrue
. То есть при публикации новой записи значение будетtrue
.
При сравнении объекта $_POST
для новой записи и обновления или повторной публикации записи, заметное отличие — это значение _wp_http_referer
. Для новой записи это /wp-admin/post-new.php
.
Предположение: предполагается, что запись публикуется/добавляется через интерфейс. Если это делается другим механизмом или кастомным кодом, проверку нужно адаптировать.
add_action( 'save_post', 'test_save_post_check', 0, 3 );
function test_save_post_check( $post_ID, $post, $update ) {
// другие проверки + эта
// проверяем наличие 'post-new' в '_wp_http_referer'
if( strpos( wp_get_raw_referer(), 'post-new' ) > 0 ) {
// новая запись
} else {
// обновление
}
}

Вот рабочий код, который я протестировал и использовал на своем сайте. Он решает две следующие проблемы, связанные с действием save_post:
Проблема проверки между обновлением и вставкой
Проблема двойной вставки из-за действия save_post
function save_annonces_callback($post_ID, $post, $update){ $post_type = get_post_type($post_ID); if ( $post_type === 'annonces' ){ //это предотвращает двойную вставку из-за действия save_post :) if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) { return; } else { //проверяем, является ли пост новым, чтобы выполнить вставку if( strpos( wp_get_raw_referer(), 'post-new' ) > 0 ){ //выполняем вставку }else{ //выполняем обновление } } } }add_action('save_post','save_annonces_callback', 10, 3);

Как намекнул Darshan Thanki (и более подробно объяснил Stephen Harris), вы можете использовать pre_post_update
в своих интересах.
global $___new_post;
$___new_post = true;
add_action(
'pre_post_update',
function() {
global $___new_post;
$___new_post = false;
},
0
);
function is_new_post() {
global $___new_post;
return $___new_post;
}
Причина, по которой я использовал глобальные переменные, заключается в том, что function is_new_post() use ( &$new_post )
недопустимо в PHP (удивительно...), поэтому передача этой переменной в область видимости функции не работает — отсюда и глобальная переменная.
Обратите внимание, что это действительно можно надежно использовать только внутри/после события save_post
(что обычно достаточно, по крайней мере, для того, что мы с этим делаем).

Когда срабатывает save_post, вся информация о записи уже доступна, поэтому теоретически вы можете использовать:
function f4553265_check_post() {
if (!get_posts($post_id)) {
// если это новая запись, get_posts($post_id) должен вернуть null
} else {
// $post_id уже существует в базе данных
}
}
add_action('save_post','f4553265_check_post');
однако это не тестировалось. =)

К моменту выполнения save_post
пост уже будет сохранен в базе данных - поэтому get_posts
вернет текущий пост.

Другой подход, который использует встроенную функцию и не требует изменений в базе данных, включает использование get_post_status()
.
$post_status = get_post_status();
if ( $post_status != 'draft' ) {
//черновик
} else {
//не черновик: может быть опубликован, ожидает проверки и т.д.
}
Однако обратите внимание, что этот метод может быть не совсем подходящим, если вы планируете позже снова установить статус "черновик" — ваши инструкции будут повторены в следующий раз при обновлении записи.
В зависимости от контекста, вы можете рассмотреть различные значения, возвращаемые функцией get_post_status()
, чтобы построить более подходящий сценарий.
См. Codex для get_post_status() и Статусы записей
Возможные значения:
- 'publish' - Опубликованная запись или страница
- 'pending' - Запись ожидает проверки
- 'draft' - Запись в статусе черновика
- 'auto-draft' - Новая созданная запись без содержимого
- 'future' - Запись, запланированная для публикации в будущем
- 'private' - Не видна пользователям, которые не авторизованы
- 'inherit' - Ревизия. См. get_children.
- 'trash' - Запись в корзине. Добавлено в версии 2.9.

Я не думаю, что это работает так, как было запрошено. Если я создаю новую запись и нажимаю 'Опубликовать', функция save_post()
выполняется в первый раз, но во время этого выполнения get_post_status()
уже возвращает 'publish', а не 'draft', несмотря на то, что запись находится только в процессе публикации.

Поскольку параметр $update
бесполезен, это самый быстрый способ, который я протестировал:
function wpse48678_check_is_post_new($post_id, $post, $update)
{
if (false !== strpos($_POST['_wp_http_referer'], 'post-new.php')) {
return true; // Или сделать что-то ещё.
} else {
return false; // Или сделать что-то ещё.
}
}
add_action('save_post_{$post_type}', 'wpse48678_check_is_post_new', 10, 3);

Я потратил на это часы
Сейчас я разрабатываю плагин и хотел отправлять уведомления всем авторам, когда на сайте моей Веб-дизайн компании создаётся страница.
Изначально я попробовал использовать следующий код:
add_action('save_post_page', function($post_id, $post, $update) {
if(!$update){
echo("Это новый пост!");
}else{
echo("Это старый пост!");
}
}
Но он постоянно выводил "Это старый пост!". Почему? После некоторых исследований выяснилось, что после нажатия кнопки "Добавить новую страницу" WordPress мгновенно создаёт автосохранённый черновик (auto-draft), из-за чего переменная $update устанавливается в true. Очень удобно.
После долгих поисков я наконец нашёл рабочее решение:
// Срабатывает при изменении статуса записи, например: pending → draft, published → scheduled.
add_action('transition_post_status', function($new_status, $old_status, $post) {
// Проверяем, переходит ли запись из auto-draft в любой другой статус (draft, pending, publish и т. д.)
if ('auto-draft' === $old_status && $new_status !== 'auto-draft'){
echo("Это новый пост!");
}else{
echo("Это старый пост!");
}
}
Примечание: В WordPress "draft" и "auto-draft" — это разные статусы. "Draft" — это ручной черновик, который пользователь сохраняет как незавершённую работу. "Auto-draft" же автоматически присваивается записи при создании, если она ещё не опубликована. По сути, это аналог автосохранения.
Надеюсь, это поможет кому-то сэкономить время... Теперь пойду потрачу ещё 4 часа на другую проблему, ха-ха.

Это должно сработать, ребята
add_action( 'wp_after_insert_post', 'clearCDNCache', 10, 4);
public function clearCDNCache($post_id, $post, $update, $post_before){
if ( ! empty($_POST['_wpnonce']) ) {
// Выполнить пользовательскую функцию только один раз
}
// Добавление нового режима.
if (!empty($_POST['_wp_http_referer']) && $_POST['_wp_http_referer'] == '/wp-admin/post-new.php') {
} else {
// Режим обновления
}
}
