WordPressには投稿を保護する機能として、「パスワード保護」と「非公開」の2つの方法が用意されています。しかしながら、このどちらでも添付したメディア(画像等のファイル)はURLがばれるとアクセス出来てしまいます。(添付ファイルには保護が掛からない)

このファイルを保護してほしいと言われることが多いため、実現方法を試行錯誤してみました。(有識者の方もしもっと良い方法があったら教えてください)

以下実現方法です。

例えば、secureposts投稿タイプの投稿画面からのアップロードの場合、ログイン必須の画像として扱うようにする

1.フォルダへのアクセスをすべてPHPへ転送します。

/wp-content/uploads/secureフォルダを作成して、.htaccessファイルを設置、このフォルダのアクセスをAPIへ

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteRule ^index\.php$ - [L]
  RewriteRule (.*) /wp-admin/admin-ajax.php?action=mysite_secure&path=$1
</IfModule>

nginxの場合はこんな感じ

location /wp-content/uploads/secure {
  return 301 /wp-admin/admin-ajax.php?action=mysite_secure&path=$uri;
}

2.フックを使って、対象の投稿タイプからのファイルアップロードの場合はsecureフォルダへ保存場所を変更

add_filter( 'upload_dir', 'mysite_upload_dir' );
function mysite_upload_dir( $uploads ) {
  if ( isset( $_REQUEST['action'] ) && 'upload-attachment' == $_REQUEST['action'] ) {
  // ファイルのアップロード
    $is_secure = false;
    if ( isset( $_REQUEST[‘post_id’] ) && ( ‘’ != $_REQUEST[‘post_id’] ) ) {
      $post = get_post( intval( $_REQUEST[‘post_id’] ) );
      if ( ‘secureposts’ == $post->post_type ) {
        $is_secure = true;
      }
    } elseif ( isset( $_REQUEST[‘post’] ) && ( ‘’ != $_REQUEST[‘post’] ) ) {
      // 投稿編集画面
      $post = get_post( intval( $_REQUEST[‘post’] ) );
      if ( ‘secureposts’ == $post->post_type ) {
       $is_secure = true;
      }
    } elseif ( isset( $_REQUEST[‘post_type’] ) && ( ‘secureposts’ == $_REQUEST[‘post_type’] ) ) {
      // 新規投稿画面
      $is_secure = true;
    }
    // 紐付く投稿あり
    if ( $is_secure ) {
      $uploads['subdir'] = '/secure' . $uploads['subdir'];
      $uploads['path'] = $uploads['basedir'] . $uploads['subdir'];
      $uploads['url'] = $uploads['baseurl'] . $uploads['subdir'];
      if ( ! file_exists( $uploads['path'] ) ) {
        mkdir( $uploads['path'], 0750, true );
      }
   }
  }
  return $uploads;
}

こんな風に変わります。

3.メディア上ではどれがセキュアな画像かわからないのでタイトルに[secure]を自動追加

add_filter( 'mysite_secure_file_prefix', 'mysite_secure_file_prefix', 10 , 1);
  function mysite_secure_file_prefix($prefix) {
  return '[secure]';
}

add_filter( 'wp_insert_attachment_data', 'mysite_wp_insert_attachment_data', 10, 2 );
function mysite_wp_insert_attachment_data( $data, $postarr ) {
  if ( isset( $_REQUEST['action'] ) && 'upload-attachment' == $_REQUEST['action'] ) {
    $is_secure = false;
    if ( isset( $_REQUEST['post_id'] ) && ( '' != $_REQUEST['post_id'] ) ) {
      $post = get_post( intval( $_REQUEST['post_id'] ) );
      if ( 'secureposts' == $post->post_type ) {
        $is_secure = true;
      }
    } elseif ( isset( $_REQUEST['post'] ) && ( '' != $_REQUEST['post'] ) ) {
      $post = get_post( intval( $_REQUEST['post'] ) );
      if ( 'secureposts' == $post->post_type ) {
        $is_secure = true;
      }
    } elseif ( isset( $_REQUEST['post_type'] ) && ( 'secureposts' == $_REQUEST['post_type'] ) ) {
      $is_secure = true;
    }

    if ( $is_secure ) {
      $data['post_title'] = apply_filters( 'mysite_secure_file_prefix', '' ) . $data['post_title'];
    }
  }
  return $data;
}

こんな風に変わります。

4.admin-ajaxでファイルへのアクセス (本当はREST APIで作り直したいけど、時間が無かったので、)

add_action( ‘wp_ajax_mysite_secure’, ‘mysite_secure’ );
add_action( ‘wp_ajax_nopriv_mysite_secure’, ‘mysite_secure’ );
function mysite_secure() {
  if ( is_user_logged_in() === false ) {
    wp_die( ‘アクセス権がありません。' );
  }
  $path = ABSPATH . $_REQUEST['path'];
  $filename = basename( $path );
  // ファイル名から投稿を抽出
  $file_meta_value = substr( $_REQUEST['path'], strpos( $_REQUEST['path'], '/secure/' ) + 1 );
  $att_posts = new WP_Query( array(
    'post_type' => 'attachment',
    'post_status' => 'inherit',
    'meta_query' => array(
      array(
        'key' => '_wp_attached_file',
        'value' => $file_meta_value
      )
    )
  ) );
  if ( $att_posts->have_posts() ) {
    $att_posts->the_post();
    $array_meta_data = get_post_meta( get_the_ID(), '_wp_attachment_metadata', true );
    $ext = pathinfo( get_attached_file( get_the_ID() ), PATHINFO_EXTENSION );
    $filename = substr( get_the_title(), strlen( apply_filters( 'mysite_secure_file_prefix', '' ) ) ) . '.' . $ext;

    if ( file_exists( $path ) ) {
      $filetype = wp_check_filetype( $filename );
      header( "Content-Type: " . $filetype['type'] );
      header( 'Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode( $filename ) );
      header( "Content-Length: " . filesize( $path ) );
      ob_end_clean();
      readfile( $path );
    }
  }
  exit;
}

これでsecurepostsという投稿タイプからアップロードされたファイルはログインしないと見れないようになりました。他の方法も検討してますが、DBに画像自体を保存するとかしないと難しそうな気がします。もし思いついた方是非教えてください。

 

関連したLTを先日のWordBenchでしたのでスライドを公開