この記事は公開または最後に更新されてから2116日が経過しています。情報が古くなっている可能性があるのでご注意下さい。

『全てのアップ画像によるスライドショーのjQuery 』において、画像のリストファイルを作成するにあたり、アクションフックsave_post を使っています。
アクションフックsave_post においては、公開や下書きのボタンを押した時だけではなく、Autosave におけるrevision の自動保存の時にも効力があります。
今回の画像のリストファイルの作成のように、意図的にしかも最終的な意味でボタンを押すなどした時に、その投稿において一度だけ動きさえすれば良い場合には、Autosave の時には動かないように、その保存がAutosave によるものか否かを判別する必要がありました。

通常であればsave_post ではなく投稿が公開されるときのフックであるpublish_post を使えばなんら苦労する必要はありません。がしかし、アップロードした画像を画像リストに登録したいという私的な場合において、予約投稿の画像においても即座にリストに加えたいといった場合では、publish_post は使えないのでいろいろと余計な事を考えなくてはならない状況に陥っているというわけなのです。

codex を探してみると、とりあえすこの三つが使えそうです。

  • if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
  • wp_is_post_revision()
  • wp_is_post_autosave()
・・・

《 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) 》

これはcodex 『関数リファレンス/add meta box 』のページに出ています。日本語ページにあるということは英語ページにも当然あります。
<用例>において 「データが入力された際 save_post アクションフックを使って何かを行う→投稿を保存した際、カスタムデータも保存する」の中で使用例が示されています。save_post 絡みであるわけです。
この定数”DOING_AUTOSAVE” がどこで定義されているかと探して見ると、「 /wp-admin/includes/post.php 」の ”function wp_autosave()” の中、1740行目あたりに出現します。ちなみに「 post.php 」は「 /wp-admin/post.php 」と「 /wp-includes/post.php 」というように同名ファイルが違うディレクトリに存在しています。紛らわしいですね。「 revision.php 」もまた同様です。

《 wp_is_post_revision() & wp_is_post_autosave() 》

この二つは「 /wp-includes/revision.php 」にあります。

<?php
	/* /wp-admin/includes/revision.php */
	/**
	 * Determines if the specified post is a revision.
	 *
	 * @since 2.6.0
	 *
	 * @param int|WP_Post $post Post ID or post object.
	 * @return false|int False if not a revision, ID of revision's parent otherwise.
	 */
	function wp_is_post_revision( $post ) {
		if ( !$post = wp_get_post_revision( $post ) )
			return false;

		return (int) $post->post_parent;
	}

	/**
	 * Determines if the specified post is an autosave.
	 *
	 * @since 2.6.0
	 *
	 * @param int|WP_Post $post Post ID or post object.
	 * @return false|int False if not a revision, ID of autosave's parent otherwise
	 */
	function wp_is_post_autosave( $post ) {
		if ( !$post = wp_get_post_revision( $post ) )
			return false;

		if ( false !== strpos( $post->post_name, "{$post->post_parent}-autosave" ) )
			return (int) $post->post_parent;

		return false;
	}

	/**
	 * Gets a post revision.
	 *
	 * @since 2.6.0
	 *
	 * @param int|WP_Post $post   The post ID or object.
	 * @param string      $output Optional. OBJECT, ARRAY_A, or ARRAY_N.
	 * @param string      $filter Optional sanitation filter. @see sanitize_post().
	 * @return WP_Post|array|null Null if error or post object if success.
	 */
	function wp_get_post_revision(&$post, $output = OBJECT, $filter = 'raw') {
		if ( !$revision = get_post( $post, OBJECT, $filter ) )
			return $revision;
		if ( 'revision' !== $revision->post_type )
			return null;

		if ( $output == OBJECT ) {
			return $revision;
		} elseif ( $output == ARRAY_A ) {
			$_revision = get_object_vars($revision);
			return $_revision;
		} elseif ( $output == ARRAY_N ) {
			$_revision = array_values(get_object_vars($revision));
			return $_revision;
		}

		return $revision;
	}
?>
PHP
CopyExpand

この二つの違いは、autosave の方がpost_name にautosave があるか判別している点です。

《 アクションフック save_post 》

アクションフックsave_post 自体は、これの場合は「 /wp-includes/post.php 」の中に記述してあって、 ”function wp_insert_post()”の一番最後の部分、3580行以降にあります。そのアクションフックの部分を抜き出しますと以下の様になってます。

<?php
	/* function wp_insert_post() /wp-admin/includes/post.php */
	/**
	 * Fires once a post has been saved.
	 *
	 * The dynamic portion of the hook name, `$post->post_type`, refers to
	 * the post type slug.
	 *
	 * @since 3.7.0
	 *
	 * @param int     $post_ID Post ID.
	 * @param WP_Post $post    Post object.
	 * @param bool    $update  Whether this is an existing post being updated or not.
	 */
	do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );

	/**
	 * Fires once a post has been saved.
	 *
	 * @since 1.5.0
	 *
	 * @param int     $post_ID Post ID.
	 * @param WP_Post $post    Post object.
	 * @param bool    $update  Whether this is an existing post being updated or not.
	 */
	do_action( 'save_post', $post_ID, $post, $update );

	/**
	 * Fires once a post has been saved.
	 *
	 * @since 2.0.0
	 *
	 * @param int     $post_ID Post ID.
	 * @param WP_Post $post    Post object.
	 * @param bool    $update  Whether this is an existing post being updated or not.
	 */
	do_action( 'wp_insert_post', $post_ID, $post, $update );
?>
PHP
CopyExpand

post type 指定限定型のsave_post に通常のsave_post 、そしてwp_insert_post が並んでいます。これを見るとそれぞれ引数も全て同じ三つで、save_post wp_insert_post は名前が異なるだけのものです。
投稿タイプを限定できるようにpost_type 指定型があるのはこれを見て知りました。codex には通常型を使用して投稿タイプを判別する方法が書いてあって、これの使用方法は載っていないと思います。新しく追加されたんですね。
”function wp_insert_post()”の一番最後の部分に記述してあるということで、これらのフックは投稿のデータが全てデータベースに保存された後で実行されるものだということです。

・・・

ソースを読めばいかなるように動くのかはわかりそうなものですが、今一つ実感がつかめないので下記のようなコードを作って実験してみました。

<?php
	add_action( 'save_post', 'test_is_autosave', 10, 3 );

	function test_is_autosave( $post_id, $post, $update ) {
		$ptype = $post->post_status;
		$ans = wp_is_post_autosave( $post_id );

		if ( false === $ans ) {
			$ans = 'false';
		} elseif ( is_null( $ans ) ) {
			$ans = 'null';
		} 

		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			$comstr = 'autosave.' . $ptype . ' : ' . $ans;
			$mailtitle = 'autosave';
		} else {
			$comstr = 'success update images-list-file.' . $ptype . ' : ' . $ans;
			$mailtitle = 'success update';
		}
		wp_mail( 'admin@mymail.address', $mailtitle, $comstr );
	}
?>
PHP
CopyExpand
  • 投稿を新たに書き始める時に「新規追加」をクリックして何かしらを記入するためにフォーカスを移動などすると、すぐにその投稿用のデータが作成されます。このデータのその時のpost_status 「 auto-draft 」となっていて、自動的に作成されるデータではありますが DOING_AUTOSAVE は定義されておらず引っ掛かりません。wp_is_post_autosave() の返り値は「 false 」です(当然なのですが・・・後述)。
  • 記事を書き始めると時間が経過するにつれ、自動的に保存されるようになります。この時点では初めに作成された元のデータが上書きされて保存されていきます。自動保存が始まった時のpost_status 「 draft 」に変更されています。そしてこれらの自動保存は DOING_AUTOSAVE が定義され引っ掛かるようになります。しかし、wp_is_post_autosave() の返り値は「 false 」です(当然・・・)。
  • ここで”公開”や”下書き”ボタンをクリックして投稿を意図的に保存すると、当然のことながら DOING_AUTOSAVE には引っ掛からず、wp_is_post_autosave() の返り値も「 false 」です。そしてpost_status は”公開”であれば「 publish 」、”予約”であれば「 future 」に更新されます。そしてこの時に投稿の元データとは別のデータとして revision データが作成されます。こちらのpost_status 「 inherit 」
  • 改めて記事を更新するとして、投稿一覧から記事をクリックし、記事の編集を始めます。再び時間が経つにつれ自動保存されていくようになりますが、この場合は自動保存されるたびに revision として新しいデータが作成されて溜まっていきます。この場合は DOING_AUTOSAVE は定義されていて引っ掛かり、wp_is_post_autosave() の返り値は「 false 」ではなくなり初めて元の原稿データのpost_ID を返すようになります。
  • ”更新”などをクリックして意図的に保存した場合は、上記、”公開”や”下書き”ボタンをクリックした時と同じ結果となります。
・・・

自動保存におけるrevision データに関しては、codex 『リビジョン管理』のページに「WordPress のリビジョンシステムは、保存された下書きまたは公開済み投稿それぞれの更新記録を保存します。」と、書いてあるように、新規に記事を作成して一度意図的に保存したことのあるデータに対して作成されます。

wp_is_post_autosave() においては、そのrevision データを元にして判別した結果なので、まだ一度も保存されていない新規作成の段階ではrevision データは存在せず、それゆえに返す結果は全て「 false 」であり、その段階でのautosave の判別には全く効力が無いということになります。これは基本的に同じ事をしているwp_is_post_revision() にしても同様です。

if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )においては、新規作成の画面を開いてすぐに作成されるこの投稿自体のデータ(post_status が ”auto-draft”)を除いて、それ以降に保存される元データの上書き時(post_status が ”draft”)においても、一度保存した後での revision が作成される時にも有効となります。
ちなみにアクションフックsave_post は、当然のことながら予約投稿が公開される時(post_status が future → publish に更新される)や、投稿が削除される時にもフックされます。

DOING_AUTOSAVE を使用すれば、とりあえず自動保存は避けられるとしても、最初にデータが作成される時、意図的に保存した時の最低でも二度は動いてしまうことになります。予約投稿であれば公開する時も入りますから3度ということです。データを更新して保存すればその都度起動するのは当然のこと。

・・・

さて、アクションフックsave_post で動かしたいコードを一度だけにするにはどうしたらいいかと?
そのコードを起動してやりたいことが、その時投稿する内容に関することを全く使用しないものであれば、迷うことなく最初にこのデータが作成される時を利用すれば良いという事になります。
パラメータから$post が得られるので、$post->post_status ”auto-draft” で限定すれば出来るはず。しかし、念を押すとこの時点では投稿の内容というものは全く無いに等しいです。
と、いうことであれば、特に autosave を検出しなくとも、得られるpost_status により判別すれば良いということになります。
「 publish 」「 future 」で限定すれば良いと。しかし、「 future 」の場合は、いずれ「 publish 」に更新する時に2度目の起動をしてしまいます。それをどうするかということになります。
投稿のステイタス( post_status )が変更される時のアクションフックが『 Post Status Transitions 』としてちゃんと用意されています。

  • transition_post_status Hook
  • {old_status}_to_{new_status} Hook
  • {status}_{post_type} Hook

このなかで、特定のステイタスの変移に絞ってフックできる形式が、
” {old_status}_to_{new_status} Hook ” です。
codex から抜粋すると以下のとおり。

An {old_status}_to_{new_status} action will execute when a post transitions from {old_status} to {new_status}. The action is accompanied by the $post object. In the add_action() function call, the action priority may be set between 0 and 20 (default is 10) and it is necessary to specify the number of arguments do_action() should pass to the callback function.

function on_publish_pending_post( $post ) {
    // A function to perform when a pending post is published.
}
add_action(  'pending_to_publish',  'on_publish_pending_post', 10, 1 );
PHP
CopyExpand

ちなみにこのフックは「 /wp-includes/post.php 」の” function wp_transition_post_status ”の中、4057行あたりに記述してあります。
これを使用して ” future_to_publish ” で限定してフックし、その時だけsave_post のアクションフックが作動しないように remove してしまえばいいんじゃないかと。

あと、たとえば予約投稿をいくつもまとめて行う場合など、アップロードディレクトリのスキャンは投稿するごとに毎回動かなくとも最後の投稿の時一度だけ動けば良いわけです。その場合にどうするかということになりますが、たとえばスキャンをキャンセルする投稿においては一度レビュー待ちで保存した後で予約投稿すれば、post_status はpending → future となるはずで、であればこの場合も同じように ” pending_to_future ” フックで remove すれば意図的に動かなくすることができるのではと。

ちなみにアクションフック save_postのパラメータで得られる$update 。これに関しては、既にそのデータが存在していればtrue を返します。よってまだ一度も投稿を保存していない状態では最初の” auto-draft ”の時だけがfalse ということになります。投稿を保存したことがあり、その編集時における autosave によって revision が作成される時には、各 revision データというのは新規作成になるので$update で得られる値は全てfalse になります。これは・・・?
と、いうことで以下がそうしてできたコードです。

元々のコードは恥ずかしながらここにあります。→『全てのアップ画像によるスライドショーのjQuery 』 尚、投稿タイプが” post ” の時だけ起動させたいので、post type 指定限定型のフック、save_post_{$post->post_type}を使っています。

<?php
	function imagefile_to_xml( $post_id, $post, $update ){
		$ptype = $post->post_status;
	
		//自動保存なら無効
		/*if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}*/

		//post_statusがpuslishかfutureの時だけ起動
		if ( 'publish' === $ptype or 'future' === $ptype ) {

			//新規保存の時に限る時は下を使用する
			//if ( ! $update ) {
				$nyear = date( 'Y' );
				$nmonth = date( 'm' );

				//二けたの数字をアルファベットに置き換えるための配列
				$numary = array(
					'01' => 'A',
					'02' => 'B',
					'03' => 'C',
					'04' => 'D',
					'05' => 'E',
					'06' => 'F',
					'07' => 'G',
					'08' => 'H',
					'09' => 'I',
					'10' => 'J',
					'11' => 'K',
					'12' => 'L',
					'13' => 'M',
					'14' => 'N',
					'15' => 'O',
					'16' => 'P',
					'17' => 'Q',
					'18' => 'R',
					'19' => 'S',
					'20' => 'T',
					'21' => 'U',
					'22' => 'V',
					'23' => 'W',
					'24' => 'X',
					'25' => 'Y',
				 );

				$tmpstr = array();
				//画像リストファイルの指定、パスは一部省略
				$xmlfile = '/home/~/wp-content/themes/first/ilist.php';

				//スキャンするアップ画像ディレクトリの指定
				$dirname = '/home/~/wp-content/uploads/';

				$filearrayasc=scandir($dirname);

				$i = 0;
				$fcount = 0;
	
				foreach ( $filearrayasc as $val ) {
					//年月で限定しない場合は下の if 文を使用
					/*if(strpos($val,'20')!==false){*/
					//年月で限定する場合は下の if 文を使用
					if ( $nyear === $val ) {
						$ynum = str_replace( '20', '', $val );
						$ynum = $numary[ $ynum ];
						$cdir=$dirname.$val."/";
						$cfile=scandir($cdir);
						foreach($cfile as $data){
							//年月で限定しない場合は下の if 文を使用
							/*if($data!=="." and $data!==".."){*/
							//年月で限定する場合は下の if 文を使用
							if ( $nmonth === $data ) {
								$mnum = $numary[ $data ];
								$gcdir=$cdir.$data."/";
								$gcfile=scandir($gcdir);
								foreach($gcfile as $fname){
									if($fname!=="." and $fname!==".." and strpos($fname,"x")===FALSE){
										$imgname = $ynum . $mnum . str_replace( '.jpg', '', $fname );
										$tmpstr[] = $imgname . ',';
										$fcount+=1;
									}
								}
							}
						}
					}
				}
	
				if ( file_exists( $xmlfile ) ) {
					// すでに存在するはずのリストを配列に読み込み
					$orilist = file( $xmlfile );
					// , 区切りのひとつながりのデータを , で分割し配列にする
					// データは配列の一番始めに入っているのでkeyで指定する必要あり
					$oriary = explode( ',', $orilist[0] );
					//元あるリストの配列に結合
					$result = array_merge( $oriary, $tmpstr );
					//重複するデータを削除
					$result = array_unique( $result );
				} else {
					$result = $tmpstr;
				}

				$outary = array();
				foreach($result as $val) {
					if ( strlen( $val ) > 6 ) {
						$outary[] = $val . ',';
					}
				}
				$success = file_put_contents( $xmlfile, $outary, LOCK_EX );

				//結果確認用のメール送信する場合
				/*if ( $success ) {
					$sendstr = 'update images-list-file by save_post ID : ' . $post_id;
					wp_mail( 'admin@mail.address', 'PoJB update imagefile', $sendstr );
				}*/
			/*}// ! $update */
		}
	}

	//引数を3ケ受け取る時は必ず四つ目の引数でその数を指定 デフォルトは1ケ
	//三つ目の引数は優先度 デフォルトは10
	add_action( 'save_post_post', 'imagefile_to_xml', 10, 3 );

	function on_future_publish_post( $post ) {
		remove_action( 'save_post_post', 'imagefile_to_xml' );
	}
	
	add_action(  'future_to_publish',  'on_future_publish_post', 10, 1 );
?>
PHP
CopyExpand

Leave a Reply!

JavaScript is necessary to send a comment.
You can edit and delete your comment if you input a edit key.
Edit key is necessary for attesting you when you edit and delete it.
The tag of HTML cannot be used in comment.
When you comment for the first time, it is displayed after the approval of the administrator.
Because I cannot speak English so much, it takes time to answer.
Required fields are marked *.

※Please enter more than 5 characters only alphabets.
※Edit or delete are possible for 2000 days after approval.

*

♠Simplistic Comment User Editable v4.0

♠When visitors leave comments on the site this site collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.
♠This site does not use cookie when visitors leave comments and commenter edit comment.
♠This site uses Akismet to reduce spam. Learn how your comment data is processed.

Comments feed

Trackback URL : https://strix.main.jp/wp-trackback.php?p=60332

Sanbanse Funabashi

Top

スクロールさせるか画像をクリックすると元に戻ります。