Manul Tech

WordPressにアップロードしたPDFにパスワードを設定/印刷不可にする

だいぶニッチなやつだけど、とある案件でやったのでせっかくなので残しておく。

2019/09/18 追記:この方法だとPDF ver1.4までしかパースできなかったので、こちらの記事でアップデートバージョンを紹介しています。

WordPressではPDFのサムネイル画像が表示されない

いきなり本題から外れるけど、WordPressではPDFをアップロードしてもサムネイル画像が表示されない。

ファイルアップロード スクリーンショット

まずはこれを表示させたい。
今回はPDFをアップロードした時点でImageMagickを使用してPDFの1ページ目のサムネイル画像を生成する。ホスティングサーバーがImagickは使えないけどImageMagickは使えるという仕様だったので、コマンドをexecで実行することに。

add_filter('wp_generate_attachment_metadata', 'generate_pdf_thumbnail', 10, 2);
function generate_pdf_thumbnail($meta, $id){
    $mime_type = get_post_mime_type($id);
    if($mime_type === 'application/pdf'){
        $convert_cmd_path = 'path/to/convert';
        $path = get_attached_file($id);
        $upload_dir = wp_upload_dir();
        $filename = basename($path);
        $filename = explode('.', $filename);
        $save = "{$upload_dir['basedir']}/PDF_thumbnails/{$filename[0]}.jpg";
        $convert = "{$convert_cmd_path} -background white -alpha remove -density 300 -geometry 500 {$path}[0] {$save}";
        exec($convert);

        update_post_meta($id, 'thumbnail', "{$upload_dir['baseurl']}/PDF_thumbnails/{$filename[0]}.jpg");
    }

    return $meta;
}

生成したサムネイル画像は/wp-content/upload/にPDF_thumbnailsというディレクトリを作成しておいて、そこにPDFファイルと同名で保存することにした。サムネイル画像のURLは元のPDFのメタ情報として保存。
convertコマンドのオプションはこちらが詳しい。

68user's page
UNIX/Linuxの部屋 convertコマンドの使い方

生成したサムネイル画像をデフォルトのPDFアイコンと差し替えるにはこちら。

add_filter('wp_mime_type_icon', 'change_pdf_icon', 10, 3);
function change_pdf_icon($icon, $mime, $post_id){
    if($mime === 'application/pdf'){
        if($thumbnail = get_post_meta($post_id, 'thumbnail', true)){
            $icon = $thumbnail;
        }
    }

    return $icon;
}

すると、

ファイルアップロード スクリーンショット2

表示された!
もう一仕事、元のPDFが削除された際にはサムネイル画像も削除したい。

add_action('delete_attachment', 'delete_pdf_thumbnail');
function delete_pdf_thumbnail($id){
    if($thumbnail = get_post_meta($id, 'thumbnail', true)){
        $upload_dir = wp_upload_dir();
        $filename = basename($thumbnail);
        $thumbnail = "{$upload_dir['basedir']}/PDF_thumbnails/{$filename}";
        if(file_exists($thumbnail)){
            unlink($thumbnail);
        }
    }
}

ここからが本題、アップロードしたPDFにパスワードと印刷不可設定をしたい

PHPでPDFを操作するにはFPDIとTCPDFというライブラリを使うといいそうな。
目的は違うけど、このライブラリを使用するにあたってこちらのポストを参考にした。

Qiita
TCPDF と FPDI で PDF を重ね合わせる

PDFにパスワードや印刷不可設定をする場合はTCPDFのSetProtectionメソッドでできるらしい。

WEBプログラミングやサーバ設定などのメモ場
PHP TCPDFでパスワード付PDF作成

SetProtectionメソッドのオプションの詳細はこちらが詳しい。

TCPDFマニュアル(勝手訳)
SetProtection

パスワードは特定の投稿タイプにアップしたPDFのみにしたかったから、save_post_{post_type}アクションに設定した。全てのPDFに設定したければ、上記のwp_generate_attachment_metadataフィルターで実行してもいいと思う。
また、当然PDFの内容には手を加えないわけなんだけど、アップロードしたPDFそのものにパスワード等の設定をしたら管理ページで内容を確認したい時とか煩わしくなりそうだったから、元PDFはそのままでパスワード等の設定をしたPDFのコピーを/wp-content/upload/に「lockPDF」というディレクトリを作成してそこに保存するようにした。加工後のPDFのURLは元PDFのメタデータとして保存する。

これらの情報を総合してできたのがこちら。
※投稿タイプはlockPDFとし、アップロードしたPDFのattachment id$_POST['pdf']で取得できるものとする。

add_action('save_post_lockPDF', 'create_lock_PDF');
function create_lock_PDF($post_id){
    if(
        (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) ||
        !current_user_can('edit_post', $post_id) ||
        get_post_type($post_id) !== 'lockPDF'
    ) return $post_id;

    check_admin_referer('name_of_my_action', 'name_of_nonce_field');

    if(!empty($_POST['pdf'])){
        $pdf_id = filter_input(INPUT_POST, 'pdf');
        $source_file = get_attached_file($pdf_id);

        require_once 'path/to/FPDI/autoload.php';
        require_once 'path/to/TCPDF/tcpdf.php';

        $pdf = new TcpdfFpdi();
        $pdf->setPrintHeader(false);
        $pdf->setPrintFooter(false);
        $page_count = $pdf->setSourceFile($source_file);

        //  プリントアウト不可のほか、コピー禁止/編集禁止もついでに
        $pdf->SetProtection(['print', 'copy', 'modify'], 'password');

        for($i = 1; $i <= $page_count; $i++){
            $page = $pdf->importPage($i);
            $pdf->AddPage();
            $pdf->useTemplate($page, 0, 0);
        }

        $upload_dir = wp_upload_dir();
        $filename = basename($source_file);
        $filename = explode('.', $filename);
        $pdf->Output("{$upload_dir['basedir']}/lockPDF/{$filename[0]}.pdf", 'F');

        $lockfile = "{$upload_dir['baseurl']}/lockPDF/{$filename[0]}.pdf";
        update_post_meta($pdf_id, 'lockedPDF', $lockfile);

        //  元PDFのattachment idも投稿メタ情報として保存
        update_post_meta($post_id, 'pdf', $pdf_id);
    }
}

これでやりたいことはできた。
ループ中に加工後のPDFへのリンクを張るような場合はこんな感じで。

if(have_posts()){
    while(have_posts()){
        the_post();
        $pdf_id = get_post_meta(get_the_ID(), 'pdf', true);
        $lockedPDF = get_post_meta($pdf_id, 'lockedPDF', true);
        ?>
        <a href="<?=$lockedPDF?>" target="_blank">パスワード付きPDF</a>
        <?php
    }
}

最後に、PDFのサムネイルと同様に元PDFが削除された際には加工PDFも削除したいから、上記のコードに追加して。

add_action('delete_attachment', 'delete_pdf_thumbnail');
function delete_pdf_thumbnail($id){
    if($thumbnail = get_post_meta($id, 'thumbnail', true)){
        $upload_dir = wp_upload_dir();
        $filename = basename($thumbnail);
        $thumbnail = "{$upload_dir['basedir']}/PDF_thumbnails/{$filename}";
        if(file_exists($thumbnail)){
            unlink($thumbnail);
        }
    }

    if($lockfile = get_post_meta($id, 'lockedPDF', true)){
        $upload_dir = wp_upload_dir();
        $filename = basename($lockfile);
        $lockfile = "{$upload_dir['basedir']}/lockPDF/{$filename}";
        if(file_exists($lockfile)){
            unlink($lockfile);
        }
    }
}

しかし、こんなの今後またやるようなことあんのかな。。

ところで..

PDFにパスワードをつけられたのは良かったんだけど、印刷禁止とか、編集/コピー禁止の設定についてはFirefoxとか一部のPDFビュアーは無視するらしい。。

スラド
maiaの日記: FirefoxはPDFのセキュリティを無視する

Firefoxどうなってんだ。。
もしかしたらもっと厳密に禁止にする方法とかあるのかもしれないけど、俺はもう今回のでいいわ。

コメント0