WordPress как на ладони
Недорогой хостинг для сайтов на WordPress: wordpress.jino.ru

save_post хук-событие . WP 1.5.2

Событие срабатывающее всякий раз, когда запись (пост, страница) создается или обновляется, в том числе при публикации через импорт, xmlrpc или по email.

Данные записи передаются во втором параметре $post, но их также, как правило, можно получить через $_POST, $_GET или глобальную переменную global $post_data. Когда и что используется, зависит от того, как запись редактируется. Например, "быстрое редактирование" (quick edits) использует $_POST.

С версии 3.7 появился точно такой же хук save_post_(post_type). Где вместо post_type нужно указать название типа записи и событие будет срабатывать только при сохранении/добавлении записи этого типа. Выглядит хук так:

do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );

save_post - это копия хука wp_insert_post.

Хук edit_post - такой же как и save_post, но срабатывает только при обновлении записи.

Хук срабатывает в самом конце, после обновления данных поста, очистки кэша и прочих действий.

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

// очистим кэш поста
clean_post_cache( $post_ID );

Или нужно использовать хук wp_insert_post_data или wp_insert_attachment_data. Только эти хуки срабатывают до сброса кэша, но они не будут работать при вызове функции wp_publish_post().

Внимание! Зацикливание

В хуке save_post нужно аккуратно использовать функцию wp_update_post(). Потому что она повторно взывает этот хук и так код попадет в бесконечный цикл... Как этого избежать смотрите ниже в примере #3. Также об этом написано в описании функции wp_update_post().

Использование

add_action( 'save_post', 'action_function_name_85245', 10, 3 );
function action_function_name_85245( $post_ID, $post, $update ) {
	// Действие...
}
$post_ID(строка)
ID записи, которая обновляется.
$post(объект)
Объект записи, которая обновляется. Объект такой же как обычно в глобальной переменной $post.
$update(логический)
true — это обновление записи.
false — это добавление новой записи.

Примеры

#1 Отправка email при обновлении записи

Этот демонстративный пример, показывает как, всякий раз когда запись обновляется отправлять письмо администратору сайта с сообщением об обновлении:

add_action( 'save_post', 'my_project_updated_send_email' );
function my_project_updated_send_email( $post_id ) {

	// Если это ревизия, то не отправляем письмо
	if ( wp_is_post_revision( $post_id ) || get_post($post_id)->post_status != 'publish' )
		return;

	$post_title = get_the_title( $post_id );
	$post_url = get_permalink( $post_id );
	$subject = 'Запись была обновлена';

	$message = "На вашем сайте следующая запись была обновлена:\n\n";
	$message .= $post_title . ": " . $post_url;

	// Отправляем письмо.
	wp_mail( get_option('admin_email'), $subject, $message );
}

#2 Произвольный тип записи "book"

Предположим у вас есть тип поста book и вам нужно добавить к нему информацию об авторе, издателе и о печатной копии. Этот код показывает как можно сохранить эти данные в метаданные записи:

/**
 * Сохраняем метаданные записи при сохранении поста.
 */
add_action( 'save_post', 'save_book_meta' );
function save_book_meta( $post_id ) {
	// слаг лучше указать единожды и использовать во всех кодах 
	// связанных с типом записи, как это принято в классах
	$slug = 'book';

	// Проверяем тип записи, если не boo то выходим.
	if ( $slug != $_POST['post_type'] )
		return;

	// Обновляем метаданные записи.

	if ( isset( $_REQUEST['book_author'] ) ) {
		update_post_meta( $post_id, 'book_author', sanitize_text_field( $_REQUEST['book_author'] ) );
	}

	if ( isset( $_REQUEST['publisher'] ) ) {
		update_post_meta( $post_id, 'publisher', sanitize_text_field( $_REQUEST['publisher'] ) );
	}

	// inprint - это чекбокс.
	if ( isset( $_REQUEST['inprint'] ) ) {
		update_post_meta( $post_id, 'inprint', TRUE );
	} else {
		update_post_meta( $post_id, 'inprint', FALSE );
	}
}

В этом примере, проверку if ( $slug != $_POST['post_type'] ), можно заменить: вместо события save_post, можно использовать save_post_book.

#3 Как избежать зацикливания

Если вы вызываете функцию wp_update_post() внутри события save_post, то вы наткнетесь на зацикливание, потому что wp_update_post() вызывает save_post. Чтобы этого избежать, удалите добавленный хук, перед тем как вызывать wp_update_post(), а затем добавьте его снова:

// Эта функция делает все записи в дефолтной категории частными
add_action( 'save_post', 'set_private_categories' );
function set_private_categories( $post_id ){
	// Получим реальный ID поста, если это ревизия
	if ( $parent_id = wp_is_post_revision( $post_id ) ) 
		$post_id = $parent_id;

	// Получим ID категории по умолчанию из опций
	$defaultcat = get_option( 'default_category' );

	// Проверим находится ли пост в категории по умолчанию
	if ( in_category( $defaultcat, $post_id ) ) {
		// Удаляем хук, чтобы не было зацикливания
		remove_action( 'save_post', 'set_private_categories' );

		// обновляем запись. В это время срабатывает событие save_post
		wp_update_post( array( 'ID' => $post_id, 'post_status' => 'private' ) );

		// Ставим хук обратно
		add_action( 'save_post', 'set_private_categories' );
	}
}

Эта часть кода, должна быть обязательно:

// Удаляем хук, чтобы не было зацикливания
remove_action( 'save_post', 'set_private_categories' );

// обновляем запись. В это время срабатывает событие save_post
wp_update_post( array( 'ID' => $post_id, 'post_status' => 'private' ) );

// Ставим хук обратно
add_action( 'save_post', 'set_private_categories' );

Список изменений

С версии 1.5.0 Введена.

Где вызывается хук

wp_insert_post()
save_post
wp_publish_post()
save_post
WP_Customize_Manager::trash_changeset_post()
save_post
wp-includes/post.php 4153
do_action( 'save_post', $post_ID, $post, $update );
wp-includes/post.php 4286
do_action( 'save_post', $post->ID, $post, true );
wp-includes/class-wp-customize-manager.php 3087
do_action( 'save_post', $post->ID, $post, true );

Где используется хук в ядре WP

wp-includes/default-filters.php 466
add_action( 'save_post', 'delete_get_calendar_cache' );
22 коммента
Полезные 1 Вопросы 2 Все
  • @ campusboy3542 www.youtube.com/c/wpplus

    Интересная штука, если прописать вот так:

    function save_postratings_for_post( $post_id ) {
    	var_dump($post_id);
    	exit;
    }
    add_action( 'save_post', 'save_postratings_for_post' );

    То мы сможем наблюдать, что при заходе на форму создания поста, он уже имеет ID. Если честно, я удивлен. По идее, зачем это делать?

    Если использовать первый пример, то при заходе на /wp-admin/post-new.php будет сразу отослано письмо.

    P.S.: В базе также появляется auto-draft

    1
    Ответить9.Дек.2016 03:02 #
    • Kama7751

      Там сложная система на самом деле! При создании записи, сразу создается черновик. А нужно это чтобы была запись в БД, и был ID к которому сразу можно прилепить метаполя и т.д. А иначе придется куда-то это все сохранять, а там система то гибкая. Получается для этого нужно очень многое учесть и по сути нужно написать огромный скрипт, который будет хранить все что может быть привязано к записи. А таким подходом решается сразу все! Потом при публикации остается только изменить статус с draft на publish и готово!

      П.С. Первый пример дополнил, спасибо!

      Ответить9.Дек.2016 04:12 #
      • @ campusboy3542 www.youtube.com/c/wpplus

        Это я понимаю smile Я не понимаю зачем создавать в БД черновик, который потом не виден. К примеру, я захожу, чтобы написать пост. Открыл страницу, потом подумал - нет вдохновения, закрыл страницу. Я не написал ни 1 символа, просто зашёл на страницу и ушёл, а в БД запись есть под статусом auto-draft. И в списке записей нет поста "Черновик" или ещё что-либо, чтобы можно было удалить или отредактировать. То есть эта запись в БД осталась мёртвым грузом, никому не нужная, мусором. Меня не устраивает лишь это и больше ничего smile Это можно почистить только зайдя в БД, через админку добраться до неё уже никак.

        P.S.: Спасибо за правки в коде!

        Ответить10.Дек.2016 12:51 #
  • Дмитрий

    Добрый день, а как быть с отложенными записями? Как я понимаю тут уже этот хук не работает?

    Ответить26.Янв.2017 16:42 #
    • Kama7751

      Если нужно делать что-то именно при публикации записи, то читай wp_transition_post_status(). save_post срабатывает только при обновлении записи или публикации так сказать вручную. Но если ВП автоматом меняет статус поста с, например, «черновик» на «опубликован», то save_post не сработает. Поэтому тут нужны другие хуки основанные на функции что я дал выше, там все написано...

      Ответить26.Янв.2017 19:00 #
  • @ campusboy3542 www.youtube.com/c/wpplus

    А во втором примере есть очистка sanitize_text_field. Есть ли в ней смысл? Вроде как в add_metadata есть строка:

    $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
    Ответить11.Фев.2017 00:23 #
    • Kama7751

      Она не очищает, а включает фильтры, а в том примере мы именно чистим... Т.е. результат если их убрать будет другой...

      1
      Ответить11.Фев.2017 02:19 #
  • Юрий

    У меня вот такой вопрос. Я сделал php скрипт, который никак не связан с wordpress, но он создает статьи в БД WordPress, т.е вставляется строка в таблицу wp_posts и прописывается категория в wp_term_relationships.
    Всё хорошо, за исключением того, что в этом случае стандартный поиск не работает.
    Подскажите, куда еще нужно внести изменения, чтобы работал поиск по сайту.
    Если созданную таким образом статью открыть и сохранить, то поиск начинает работать.

    Ответить3.Окт.2018 13:41 #
    • @ campusboy3542 www.youtube.com/c/wpplus

      А с каким статусом создаётся статья по вашему методу?

      Ответить3.Окт.2018 15:48 #
      • Юрий

        В запросе стоит 'publish', она сразу видна на сайте

        Ответить3.Окт.2018 16:24 #
  • Эдуард atox.pro

    Доброго времени суток.
    Нужна помощь.
    Сделал я агрегатор новостей на базе плагина WPGrabber с последующим постингом в соцсети плагином SNAP. Вроде все работает, но остаются лишние пробелы, переводы строк и прочий мусор. Верстка постов в самой WP выглядит нормально, но при попытке публикации в соцсети выглядит все просто ужасно.
    Применил я следующую конструкцию:

    //=============================== Удаление лишних пробелов
    
    add_action( 'save_post', 'remove_empty_p' );
    function remove_empty_p( $content ) {
    	$content = get_the_content();
    	$content = force_balance_tags( $content );
    	$content = preg_replace( '#<p>\s*+(<br\s*/*>)?\s*</p>#i', '', $content );
    	$content = preg_replace( '~\s?<p>(\s| )+</p>\s?~', '', $content );
    	$content = preg_replace( '#\s*?\r?\n\s*?(?=\r\n|\n)#s' , '', $content );
    	return $content;
    	{
    		// Удаляем хук, чтобы не было зацикливания
    		remove_action( 'save_post', 'remove_empty_p' );
    
    		// обновляем запись. В это время срабатывает событие save_post
    		wp_update_post( array( 'ID' => $post_id, 'post_content' => $content ) );
    
    		// Ставим хук обратно
    		add_action( 'save_post', 'remove_empty_p' );
    	}
    }

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

    Именно в таком виде потом это попадает в ленту ВК, например, разумеется, без HTML, но с несуразными пробелами.
    Как заставить работать вышеприведенную функцию или ее аналог в абсолютно автоматическом режиме и только по ID свежесграбленного и опубликованного поста?

    Ответить14.Фев.2019 19:27 #
  • @ Дмитрий

    добрый день

    // Проверим находится ли пост в категории по умолчанию
    	if ( in_category( $defaultcat, $post_id ) ) {
    
    		// Удаляем хук, чтобы не было зацикливания
    		remove_action( 'save_post', 'set_private_categories' );
    
    		// обновляем запись. В это время срабатывает событие save_post
    		wp_update_post( array( 
    			'ID'=> $post_ID, 
    			'post_content' => $post->post_content . '-1',
    		) );
    
    		// Ставим хук обратно
    		add_action( 'save_post', 'set_private_categories' );
    	}

    в итоге сохраненный контент
    2-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1

    в чем может быть причина зацикленности?

    Ответить1.Май.2020 10:57 #
  • Здравствуйте.

    Интересная штука. На хук save_post при условии if ($update == 0) вешаю создание мета-поля через update_post_meta. Все отлично, поле создается, но не у записи, а у ее ревизии первой. То есть, до нажатия "Опубликовать" происходит создание мета-поля, видимо, есть какое-то автосохранение, на которое save_post тоже реагирует.

    Такое может быть или я в своем коде начал путаться? Вроде бы 3 раза проверил.

    --

    Хмм... Странно, что при условии else и все еще не нажатой кнопки Опубликовать от этого хука ко мне письма идут, которые должны идти только если нажата кнопка Обновить. И если эту ситуацию я могу победить, добавив проверку if (get_post_status($post_ID) == 'publish'), то в случае если это не апдейт проверка не прокатит.

    Ответить6.Июл.2020 20:16 #
    • Ну, а теперь фокус!

      Оказывается, при нажатии в админке кнопки "Опубликовать" технически происходит не публикация, а обновление записи. При этом меняется ее статус с draft на publish.

      Я бы до этого не допер, если бы не ошибка в моем же коде, когда именно после нажатия "Опубликовать" в админке должно мета-поле создаваться. Оказывается, мета-поле уже было создано.

      Публикацией же (технически, то есть программно) считается попадание первой записи в таблицу. Это тот волшебный момент, когда вы только открыли редактор записи и кнопка "Опубликовать" становится на секунду неактивной.

      --

      Я может чего не понимаю, но передо мной открытый phpMyAdmin и все происходит именно так.

      --

      WordPress не такой уж и безгрешный, как я думал раньше. Нафига так называть хук и передавать третий параметр $update, если работает все равно не как ожидаешь, да?

      Ответить6.Июл.2020 22:03 #
      • В общем, остается вопрос: как отправить письмо именно после первого нажатия кнопки Опубликовать?

        Не подходит:

        1. if ( wp_is_post_revision($post_ID) ) - потому что ничего не дает по факту для проверки на первое появление в БД и как ее привязать не знаю.
        2. if (get_post_status($post_ID) == 'publish') - потому что в момент нажатия запись еще не в статусе опубликовано
        3. add_action( 'draft_to_publish') - потому что в этом случае будет отправка каждый раз, когда запись вернется из Черновика в Опубликовано.

        Пока на ум приходит создание еще одного мета-поля email_already_sending и использовать его вместе с add_action( 'draft_to_publish')

        Ответить6.Июл.2020 22:28 #
        • @ campusboy3542 www.youtube.com/c/wpplus

          Привет. Первое создание поста идёт в файле post-new.php с помощью функции get_default_post_to_edit().

          Первое что пришло на ум именно, что и ты придумал с меткой email_already_sending.

          1
          Ответить6.Июл.2020 23:00 #
        • Kama7751

          как отправить письмо именно после первого нажатия кнопки Опубликовать?

          add_action( 'draft_to_publish' ) - Этот хук должен тебе подойти, он сработает один раз, при публикации поста (когда статус измениться с draft на publish). Что тебе когерентно нужно сделать непонятно до конца.

          Ознакомься с хуками: wp_transition_post_status() там по сути можно сделать все что тебе может понадобится, используя проверки.

          1
          Ответить7.Июл.2020 02:23 #
    • Kama7751

      поле создается, но не у записи, а у ее ревизии первой

      Ревизия - это по сути и есть запись, при публикации у нее меняется только статус. ВП сразу создает запись в БД просто когда ты заходишь на страницу создания записи. Это нужно чтобы при написании поста (еще до публикации) у записи уже был свой ID в бд и к нему можно было прикрепить метаполя и медиафайлы. Все дальнейшие действия с публикацие и т.д. - это работа со статусами поста. Обычно когда пост публикуется (получает статус publish), этот статут уже больше не меняется - было бы странно сначала опубликовать, а потом сунуть обратно в черновики или на проверку.

      1
      Ответить7.Июл.2020 02:27 #