未公開のページへアクセスがあった時の動きについて
-
お世話になってます。
未公開のページ(ステータスが future のページ)へ管理者がアクセスすると、公開日前でも内容を表示することができますが、寄稿者などの管理者以外の人がアクセスした時は、404ページが表示されてしまいます。
404ページの表示は、削除済みのページへのアクセスでも未公開のページへのアクセスでも全部一緒ですが、表示をわけることはできるでしょうか。
例えば、未公開のページに寄稿者などの権限を持つ人がアクセスしたら、「ただいま準備中です」という表示をさせたいのです。
公開日を取得して条件分岐できないものかと思い、以下のようなものを single.php や 404.php に記述してみました。
しかし、404.php の内容が表示されるだけで、うまくいきませんでした。<?php $id = $post->ID; $page = get_page($id); if ($page->post_status == 'future'): global $current_user; get_currentuserinfo(); if ($current_user->user_level == 10) {//管理者だったら the_content();//本文表示 } else {//管理者以外の一般ユーザーだったら $today = date("Ymd");//今日の日付取得 if(the_time('Ymd') <= $today){//公開日を過ぎていたら the_content();//本文表示 }else{//公開日前だったら echo "ただいま編集中です。".the_time('Ymd')."に公開予定です。"; } } else: echo "404エラー 見つかりませんでした。"; endif; ?>
アドバイスいただければと思います。
よろしくお願いします。
-
こんにちは、
404.php の場合は、$post はセットされていないです。
https://codex.wordpress.org/Function_Reference/url_to_postid
みたいな関数も、0が返るだけなので、提示されたコードの if文は、成立しません。
試してはいないですが、$_SERVER[‘REQUEST_URI’]; 等から、自前で IDを取り出して、
get_page()は、非推奨なので、get_post() 等で、必要な情報を取得する事になるんじゃないかと思います。
横から失礼します。
404.php
の中でグローバル変数$wp_query
を表示させてみると$wp_query->get('name')
にスラッグが入り、投稿があって非公開の場合は$wp_query->found_posts
が1
になり、一致するスラッグがないとfound_posts
は0
だったので、これで判別できるんじゃないでしょうか。@gblsm さん
var_dump()等では、確かにそう見えますが、実際にコード化できますか?
if ( $wp_query->query_vars['name'] ){ echo $wp_query->query_vars['name']; } if ( $wp_query->query_vars['found_posts'] ) { echo $wp_query->query_vars['found_posts']; }
nobitaさん
テーマ Twenty Twelve の 404.php で試してみました。
非公開と公開予定を判別する方法は思いつかないのですが。<?php get_header(); ?> <div id="primary" class="site-content"> <div id="content" role="main"> <?php if ( $wp_query->found_posts != 0 ): ?> <div>非公開または公開予定の投稿です。</div> <?php else: ?> <article id="post-0" class="post error404 no-results not-found"> // 元々の404ページの表示内容 </article><!-- #post-0 --> <?php endif; ?> </div><!-- #content --> </div><!-- #primary --> <?php get_footer(); ?>
試した結果から推測なんですが、メンバー変数 posts に入った投稿の数(ループで表示する件数)が post_count で、非公開や公開予定の投稿を除外する前の件数が found_posts みたいです。コアのコードを読んで確かめればよいんですが未だです。
nobitaさん
コアのコードを粗くですが眺めてみました。WP_Queryクラスのget_postsメソッドです。- まずURLからクエリーを組み立てますが、このとき非公開や公開予定も含む(除外しない)クエリーが組み立てられます。そのクエリーで投稿を取得して posts プロパティーに入れます。同時に、取得された投稿の件数が found_posts プロパティーに入ります。
- 次に、投稿が見つかっていて is_single または is_page の場合(表示する投稿が一つのみ)、その投稿が public でなければ posts プロパティーを空の配列に変えてしまいます(表示する投稿が無い状態)。
- 最後に、表示する投稿の件数(posts プロパティーの要素数)をもう一度数えなおして post_count プロパティーに入れます。
こういう訳で、404.php が表示されるときに found_posts がゼロでなければ、public ではない単一投稿が表示されようとしたと思って良さそうです。
@gblsm さん
403を返すことが出来そうで、面白いと思います。
ただ、この結果が意図的なものかどうかという点が気になるところです。
WordPressが、これをバグとして認識するのか、意図的なものなのか、チケットを切ってみてはどうですか?
@gblsm さん
WP_Query のデータについては、整合性がとれているように見えるんですけど…
found_posts はもっぱらページング情報を作るための変数なので、is_single や is_page など、ID で指定するページや、ページングをしない場合 (SQL に LIMIT 句がない場合)、ほとんど意味のない値です。逆にページングをする場合は、found_posts と post_count との差異が重要な意味を持つことになります。
もうご存知でしょうけれども、found_posts 生成の過程は、LIMIT 句があるときは、
SELECT SQL_CALC_FOUND_ROWS ... LIMIT ...
を実行する- 結果のデータを
count($this->posts)
でカウントし、post_count にセットする SELECT FOUND_ROWS()
を実行して戻り値を found_posts にセットする
となり、ない場合は (あるいは明示的にユーザがページングを抑制する場合)、
SELECT ...
を実行するcount($this->posts)
の値を found_posts にセットする- 投稿データが公開すべきでないものの場合は、クエリの結果から post データを削除する
- post_count に0をセットする
となります。count() をやり直すというのはたぶん読み間違いでしょう。どちらの場合も共通なのは、found_posts は、データベースに該当のデータが何件存在するかを示す値だということで、両者の一貫性は保たれているように見えるんですよね。
そう考えると、通常の404なら、
found_posts = 0 post_count = 0
ユーザに閲覧権限がない、あるいは公開すべきでない場合。
found_posts = 1 post_count = 0
となるのは、それぞれの変数の役割りどおりということになりませんか? 強いて突っ込みどころを探すと、found_posts の doc string で、
<blockqoute>
* The amount of found posts for the current query.
*
* If limit clause was not used, equals $post_count.
</blockqoute>equals になってないぞ、というくらいでしょうか。意図してないのにそうなっちまった、という風には読めなくて、不具合になるという場面が思いつきません。
公開データかそうでないかの判定を SQL 文に含めないのは、WHERE 句の書き換えをするより、取得したデータで判定した方がメンテナンスが楽ということもあるかもしれません。投稿データが1件しかないというのが保証されてもいるので、ここはすっきりしたコードですよね。これをバグと呼ぶには、2つの値が合ってないと困る例があるといいかもしれないと思ったのですが、結局見つけられませんでした。バグリポート入口は以下です。
https://make.wordpress.org/core/reports/
@jan202020 さん
もうスレッドを見てないかもしれませんが、おもしろそうだったので、考えてみました。表示を変えたいというだけなので、template_include フィルタフックを使って、テンプレートを変更するのはどうでしょうか? 404.php は外部に表示するので、そのままにしておいて、表示用のテンプレートを別にひとつ作り、ログインユーザにはそちらを使うという考え方です。条件は future だけにしましたが、pending や private を加える可能性もありということで、配列にしてあります。
テンプレートにデータを渡す関係で、グローバル変数をひとつ作っているのが間抜けですが、$wp_query を強引に書き換えるよりはマシではないかということで、お許しを。
functions.php
function custom_template_include($template) { if (!is_user_logged_in()) return $template; if (is_404()) { global $wpdb; $posts_to_display = array('future'); $ID = get_query_var('p'); $result = $wpdb->get_row("SELECT post_status, post_date, post_content FROM wp_posts WHERE ID = $ID"); if ($result && in_array($result->post_status, $posts_to_display) { global $my_private_post; $my_private_post = $result; $new_template = locate_template(array('custom.php')); if ('' != $new_template) { $template = $new_template; } } } return $template; } add_filter('template_include', 'custom_template_include');
custom.php
global $my_private_post; if ($my_private_post) { if ($my_private_post->post_status == 'future') { echo 'ふふ、ひみつ...公開予定日' . '<br>'; echo date_i18n(get_option('date_format'), strtotime($my_private_post->post_date)); } else { echo '投稿内容を見せちゃいます' . '<br>'; echo $my_private_post->post_content; } } else { echo 'そんな投稿ありません'; }
@gblsm さん
私のがん違いだったみたいです。ごめんなさい。
チケットの件は、@kjmtshさんが示してくれたURLのMeの項目のところで作れます。
他にも、WordPressのテーマディレクトリがおかしいとかの指摘なども行うことが出来ます。
https://meta.trac.wordpress.org/
@kjmtsh さん
お手数おかけしました ありがとうございます。
夢の中をふらふらしていたみたいです。せっかくなので、@gblsmさんの すごいアイディアで、私もコードを書いてみました
Twentytwelve functions.php
function my_get_404_id( ){ global $wp_query,$wpdb; if ( is_404() ) { if ( $wp_query->found_posts == 0 ) { return false; } /* 固定ページ */ if ( isset( $wp_query->queried_object->ID ) ) { return absint( $wp_query->queried_object->ID ); } /* 下書き */ if ( isset( $wp_query->query['page_id'] ) ) { return absint( $wp_query->query['page_id'] ); } /* 投稿 非ログイン固定ページ*/ if ( isset( $wp_query->request ) ) { return absint( $wpdb->get_var( $wp_query->request ) ); } } } function my_get_404_status( ) { global $wp_query; if ( is_404() ) { if ( $wp_query->found_posts == 0 ) { return false; } /* page */ if( isset( $wp_query->queried_object->post_status ) ) { return $wp_query->queried_object->post_status; } /* 下書き */ if ( isset( $wp_query->query['page_id'] ) ) { $id = my_get_404_id(); return get_post_status( $id ); } /* post */ if ( isset( $wp_query->request ) ) { $id = my_get_404_id(); return get_post_status( $id ); } } }
Twentytwelve 404.php
<?php /** * The template for displaying 404 pages (Not Found) * * @package WordPress * @subpackage Twenty_Twelve * @since Twenty Twelve 1.0 */ get_header(); ?> <div id="primary" class="site-content"> <div id="content" role="main"> <?php $my_status_html = '<h3>%1$s</h3>'; if ( $status = my_get_404_status() ) { switch( $status ) { case( 'draft'): printf( $my_status_html, 'この投稿は、草稿です。公開していません' ); break; case( 'pending'): printf( $my_status_html, 'この投稿は、レビュー待ちです。公開していません' ); break; case( 'future'): printf( $my_status_html, 'この投稿は、近日公開予定です' ); break; case( 'private'): printf( $my_status_html, 'この投稿は、非公開です' ); break; } } else { ?> <article id="post-0" class="post error404 no-results not-found"> // 元々の404ページの表示内容 </article><!-- #post-0 --> <?php } ?> </div><!-- #content --> </div><!-- #primary --> <?php get_footer(); ?>
@kjmtsh さん
詳しい説明をありがとうございます。きっかけを頂いたので WP_Query クラスの get_posts メソッドをもう少し読んでみました。自分の頭を整理するために書き残しておきます。もうご存知でしょうけれども、found_posts 生成の過程は、LIMIT 句があるときは、
(中略)
となり、ない場合は (あるいは明示的にユーザがページングを抑制する場合)、- SELECT … を実行する
- count($this->posts) の値を found_posts にセットする
- 投稿データが公開すべきでないものの場合は、クエリの結果から post データを削除する
- post_count に0をセットする
となります。count() をやり直すというのはたぶん読み間違いでしょう。
上記ステップ 1 の SELECT … を実行する前に、「post_status 指定がなく」かつ「is_sigular ではない」場合には「publish な投稿のみ取得する」という条件をクエリに追加しています。ですので普通のアーカイブページの場合は、上記ステップ 2 の時点で、公開すべきでない投稿は除外が済んでいます。反対に、上記のステップ 3 は「is_single または is_page」の場合のみ実行されます(この部分は @kjmtsh さんが説明して下さったとおり)。
最後に count() をやり直す話ですが、上記ステップ 3 で $this->posts に array() をセットするので、(ご指摘どおり)get_posts メソッドの最後で count() をやり直すのではなく 0 をセットしていました。count() をやり直すのは $this->posts が空でない時(フィルターフックにより投稿の数が変えられたら再計算が必要)のみでした。
- トピック「未公開のページへアクセスがあった時の動きについて」には新たに返信することはできません。