このページの本文へ

「あとで読む」の実装例で学ぶ!Ajaxを使ったWordPressプラグイン開発の始め方

2016年11月18日 06時43分更新

文●Sayed Rahman

  • この記事をはてなブックマークに追加
本文印刷
Ajaxを使ったWordPressプラグインの開発方法を学びましょう。独自の「あとで読む」機能を実装するプラグインの作り方を例に解説します。

急速に広まったWebテクノロジー「Ajax」は、ほとんどのWebサイトで使われています。Ajaxの重要な機能は、Webページをリロードせずにデータベースが操作できることです。つまり、ページを更新することなくデータベースからデータを取得してブラウザーに表示できるのです。

Ajaxは、素早くそしてスムーズにコンテンツを表示できるため、いまやさまざまな用途でWebサイトに使われるようになりました。たとえば、ブログコメントの投稿、記事間のリンク、ファイルのアップロードなどです。Webサイトを完全にAjax化して、サイトのどのページでも非同期で読み込めるようにすることさえも可能です。

この人気を受けて、大半の有力CMSがその仕組みにAjaxを取り入れています。WordPressも例外ではありません。実はWordPressは、Ajaxを強力でしかも手軽な形で取り入れています。今回は、WordPressでAjaxをどのように使用するのか、実例を挙げて説明します。jQueryのAjaxメソッドおよびWordPressのフックを使いますので、ある程度の知識が必要です。

もしWordPressの開発方法のチュートリアルを見たい場合は、『WordPress Plugin Development for Beginners(初心者のためのWordPressプラグイン開発)』を参照してください。

ここで作成するもの

すでに知っているかもしれませんが、WordPressでAjaxを使うには、WordPress以外でAjaxを使う場合とは若干異なります。次の2点を頭に入れてください。

  1. 処理するデータの送信先となるWordPressのadmin-ajax.phpファイルのURL
  2. Ajaxのアクションフックwp_ajax_。Ajaxがコールされたときに実行されるカスタム関数をフックしておく必要がある

それでは動作を理解するためにプラグインを作ります。作成するのは、読者が気に入ったブログ記事をあとで読むために、ある領域に保存できるプラグインです。この機能は、毎日たくさんのコンテンツを提供する雑誌スタイルのブログで便利です。ユーザーはログインして、おもしろいと思った投稿を専用エリアに保存し、あとから読めます。

「Read Me Later(あとで読む)」プラグインは次のようになります。

  • 最初に、すべてのブログ投稿の一番下にリンクを生成
  • ユーザーがリンクをクリックしたら、ページを更新することなく、リンク先の記事のIDを、ユーザーメタデータのデータベースに保存
  • 最後に、保存された投稿IDにもとづいて記事を表示するウィジェットを作成

分かりましたか? それでは少し面倒な作業から始めます。

プラグインファイルとフォルダーの準備

最初に、インストールされたWordPressのpluginフォルダーの中にread-me-laterフォルダーを作ります。このフォルダーに、プラグインに必要なファイルとサブフォルダーをすべて保存します。read-me-laterフォルダー内に、jscssという名前で2つのフォルダーを作成してください。

次に4つのファイルを作ります。拡張子まで含めたファイル名を以下に列挙します。

  • read-me-later.php
  • widget.php
  • read-me-later.js
  • read-me-later. css

最初の2つのファイルは、直接プラグインの主フォルダー内に置きます。jsファイルとcssファイルは、それぞれjsフォルダーとcssフォルダーに置きます。

それではread-me-later.phpファイルのコードを書いていきます。プラグインのヘッダーから記入します。

/**
 * Plugin Name: Read Me Later
 * Plugin URI: https://github.com/iamsayed/read-me-later
 * Description: This plugin allow you to add blog posts in read me later lists using Ajax
 * Version: 1.0.0
 * Author: Sayed Rahman
 * Author URI: https://github.com/iamsayed/
 * License: GPL3
 */

このコードは、WordPressのプラグインであるということを示しているので大切です。上のコードのあとに、ReadMeLaterというメインのプラグインクラスを作成します。

class ReadMeLater {}

必要なJavaScriptとCSSを読み込む

次に、WordPressのフックに、JavaScriptとCSSのファイルを登録し、読み込みキューに追加する必要があるので、複数のメソッドを作成します。ReadMeLaterクラスに以下のコードをコピーしてください。

/*
 * Action hooks
 */
public function run() {     
    // Enqueue plugin styles and scripts
    add_action( ‘plugins_loaded’, array( $this, 'enqueue_rml_scripts' ) );
    add_action( ‘plugins_loaded’, array( $this, 'enqueue_rml_styles' ) );      
}   
/**
 * Register plugin styles and scripts
 */
public function register_rml_scripts() {
    wp_register_script( 'rml-script', plugins_url( 'js/read-me-later.js', __FILE__ ), array('jquery'), null, true );
    wp_register_style( 'rml-style', plugins_url( 'css/read-me-later.css' ) );
}   
/**
 * Enqueues plugin-specific scripts.
 */
public function enqueue_rml_scripts() {        
    wp_enqueue_script( 'rml-script' );
}   
/**
 * Enqueues plugin-specific styles.
 */
public function enqueue_rml_styles() {         
    wp_enqueue_style( 'rml-style' ); 
}

このコードは比較的、理解しやすいでしょう。ここでregister_rml_scripts()というpublicメソッドを作ります。このメソッド内で、WordPressの関数を利用して先ほどのread-me-later.jsファイルとread-me-later.cssファイルを登録します。

次の2つのメソッドenqueue_rml_scripts()enqueue_rml_styles()は、先ほどのJavaScriptとスタイルシートを読み込みキューに追加しています。さらに、すべてのアクションフック(およびフィルターフック)を含むrunメソッドを作成しました。

もしもWordPressの経験がなければ、Younes Rafieの記事『え?このやり方間違ってたの? WordPressでJavaScriptやCSSを正しく読み込む方法』を参照するか、WordPressの解説(codex)を検索してJavaScriptとCSSのファイルを登録してキューに追加する方法の参考にしてください。

すべての投稿に「Read Me Later」リンクを作成する

次に、すべての投稿の下に「Read Me Later」リンクを作る必要があります。ユーザーはリンクをクリックすれば、記事を「Read Me Later」リストに保存できます。クリックされたらリンクは記事から消えて、投稿IDがデータベースに保存されます。リンクを作成するときには以下の2つに注意します。

  • ログインしたユーザーだけにリンクを表示する
  • リンクには、あとで使用する適切な投稿IDが含まれている

上の2つのために、ReadMeLaterクラスに次の関数を加えます。

/**
 * Adds a read me later button at the bottom of each post excerpt that allows logged in users to save those posts in their read me later list.
 *
 * @param string $content
 * @returns string
 */
public function rml_button( $content ) {   
    // Show read me later link only when the user is logged in
    if( is_user_logged_in() && get_post_type() == post ) {
        $html .= '<a href="#" class="rml_bttn" data-id="' . get_the_id() . '">Read Me Later</a>';
        $content .= $html;
    }
    return $content;       
}

ここではユーザーがログインしているかどうかと、投稿タイプが「post」になっているかどうかの両方をチェックして、リンクを作成します。ブログ投稿のIDはHTML5の属性を使って、get_the_id()関数で取得できることに注目してください。リンクはWordPressループ(投稿の表示ループ)の中に設置するので、この関数がまさに必要なのです。

リンクを各投稿の下に表示するため、runメソッドの中に以下のコードを加えてください。

// Setup filter hook to show Read Me Later link
add_filter( 'the_excerpt', array( $this, 'rml_button' ) );
add_filter( 'the_content', array( $this, 'rml_button' ) );

上のコードは、投稿の抜粋にフィルターフックを適用し、リンクをWordPressループに設置しています。これで、WordPressサイトにログインしてホームページ(もしくは記事のページ)を見ると、「Read Me Later」リンクがすべての投稿の下に表示されます。

AjaxのURLを指定する

Ajaxをコールするときには、WordPressのコアのひとつであるadmin-ajax.phpファイルにリクエストを送信しなければなりません。このファイルが、WordPressでのAjaxのリクエストの取り扱いと処理を担当します。このとき直接、ファイルパスをURLとして使ってはいけません。代わりにadmin_url('admin-ajax.php')を使えば、正しいファイルのURLが出力されます。唯一の問題は、JavaScriptの中ではPHPの関数が一切使えないということです。そこで、少し工夫が必要です。以下のコードを見てください。

wp_localize_script( 'rml-script', 'readmelater_ajax', array( 'ajax_url' => admin_url('admin-ajax.php')) );

上のコードではwp_localize_script()という関数を使っています。この関数には3つの引数があります。

  1. rml-scriptは先のread-me-later.jsスクリプトの登録ハンドル名
  2. JavaScriptオブジェクトとして機能する、文字列(オブジェクト名)
  3. JavaScriptへ渡したい値を含んだ配列

これで、readmelater_ajax.ajax_urlと書けば、admin_url('admin-ajax.php')の値、言い換えればadmin-ajax.phpファイルのURLが出力されます。これをJavaScriptの中で使用します。

上のコードを、先に作成したenqueue_rml_scripts()メソッドの中に忘れずに書いてください。

JavaScriptと最初のAjaxのコールを作成する

それではAjaxのコールを作成します。先ほどのjsフォルダーからread-me-later.jsファイルを開き、以下のコードを追加してください。

jQuery(document).ready( function(){         
    jQuery('#content').on('click', 'a.rml_bttn', function(e) { 
        e.preventDefault();
        var rml_post_id = jQuery(this).data( 'id' );    
        jQuery.ajax({
            url : readmelater_ajax.ajax_url,
            type : 'post',
            data : {
                action : 'read_me_later',
                post_id : rml_post_id
            },
            success : function( response ) {
                jQuery('.rml_contents').html(response);
            }
        });
        jQuery(this).hide();            
    });     
});

上のコードでは、ユーザーが「Read Me Later」リンクをクリックしたときに呼ばれる関数を作成しました。関数の中でdataメソッドを用いて投稿IDを取得し、rml_post_id変数に入れています。その後、jQdueryの$.ajax()メソッドを使ってAjaxをコールしています。このメソッドには、これまで書いてきた複数のプロパティが使われています。1つずつ説明します。

urlにはadmin-ajax.phpファイルのURLが入っています。前のステップで、どのようにreadmelater_ajax.ajax_urlを定義したか覚えていますか? これが、URLの扱い方で、Ajaxリクエストが送信されて処理されます。

typeは、リクエストがHTTP‘$_GET[]’メソッドを使用するかHTTP‘$_POST[]’メソッドを使用するかを示します。ここはpostなので‘$_POST[]’メソッドを使用します。

dataにはAjaxのコールと一緒に送信するデータが入っています。データは、重要な対の値をもつオブジェクトです。post_idには投稿ID、actionにはwp_ajax_フックのサフィックスであるread_me_laterが入っています。次のステップで、Ajaxのアクションフックとそのコールバック関数を定義します。

最後の1つ、successには無名関数が入っています。Ajaxのコールが完了したら実行されます。

read me laterリンクが、#content id属性を付けたdivタグで囲まれていることを確認してください。そうしないとこのjQueryは動きません。

さて、ユーザーがRead Me Laterリンクをクリックした直後にこのリンクを消し、同じ投稿を2度保存することがないようにしなければなりません。jQuery.ajax()メソッドのあとに次のコードを加えました。

jQuery(this).hide();

これで、ユーザーがURLをクリックしたら「Read Me Later」リンクは消えます。

Ajaxアクションフック

さて、次が大事なところです。

ここまで、Read Me Laterリンクを作り、Ajaxと結び付けました。しかし、Ajaxリクエストに対するサーバー側の処理をなにも書いていないので、リンクをクリックしてもなにも起きません。ユーザーがリンクをクリックしたら、投稿のIDをデータベースに保存し、データベースの情報をもとにブラウザーに記事を表示しなければなりません。

サーバー側の処理を実行するために、WordPressでは2つのアクションフック、wp_ajax_my_actionwp_ajax_nopriv_my_actionが用意されています。前者はログインしたユーザーだけに実行され、後者はユーザーがログインしていない場合に役に立ちます。今回のサンプルプラグインはログイン済みのユーザー向けに作られているので、前者を使用します。ちなみにmy_actionは、wp_ajax_フックのサフィックスで、好きな名前をつけられます。

次のコードを、run()メソッドに追加してください。

// Setup Ajax action hook
add_action( 'wp_ajax_read_me_later', array( $this, 'read_me_later' ) );

上のコードで1つだけ気を付けることは、Ajaxフックのサフィックスが、jQuery.ajax()メソッド(前のステップ参照)のactionプロパティの値と一致していることです。気づいているかもしれませんが、ここでは覚えやすいようにコールバック関数と同じ名前(read_me_later)を付けています。それではコールバック関数を定義していきます。

public function read_me_later() {
    $rml_post_id = $_POST['post_id']; 
    $echo = array();       
    if(get_user_meta( wp_get_current_user()->ID, 'rml_post_ids', true ) !== null ) {
        $value = get_user_meta( wp_get_current_user()->ID, 'rml_post_ids', true );
    }

    if( $value ) {
        $echo = $value;
        array_push( $echo, $rml_post_id );
    }
    else {
        $echo = array( $rml_post_id );
    }

    update_user_meta( wp_get_current_user()->ID, 'rml_post_ids', $echo );
    $ids = get_user_meta( wp_get_current_user()->ID, 'rml_post_ids', true );
}

上のコードは、メインのプラグインクラスに配置します。なにをしているのか説明します。

最初に、投稿IDを$rml_post_id変数に入れ、空の配列である$echoを宣言します。

次に、rml_post_idsをキーに、データベースのusermetaテーブルに該当するフィールドがあるかをチェックします。もし見つかったら、WordPressの関数get_user_meta()を使ってメタデータを取得し、$valueに格納します。

再び、$valueの値が存在するかどうかチェックします。結果がtrueなら、その値を先ほど宣言した配列$echoに保存します。array_push()関数を使い、配列に$rml_post_idを追加します。$valueに値がなければ、単に$rml_post_id$echoに保存します。

update_user_meta()は、$echoに保存された値でメタデータを更新する(もしくはまだそのフィールドが存在しなければ新規作成する)ための関数です。

最後にget_user_meta()を使って、最新の更新されたメタデータを$idsに配列として保存します。

ユーザーが選んだ投稿IDを取得できたので、投稿を表示します。次のコードを加えてください。

// Query read me later posts
$args = array( 
    'post_type' => 'post',
    'orderby' => 'DESC', 
    'posts_per_page' => -1, 
    'numberposts' => -1,
    'post__in' => $ids
);

$rmlposts = get_posts( $args );
if( $ids ) :
    global $post;
    foreach ( $rmlposts as $post ) :
        setup_postdata( $post );
        $img = wp_get_attachment_image_src( get_post_thumbnail_id() ); 
    ?>          
        <div class="rml_posts">                 
            <div class="rml_post_content">
                <h5><a href="<?php echo get_the_permalink(); ?>"><?php the_title(); ?></a></h5>
                <p><?php the_excerpt(); ?></p>
            </div>
            <img src="<?php echo $img[0]; ?>" alt="<?php echo get_the_title(); ?>" class="rml_img">                    
        </div>
    <?php 
    endforeach; 
    wp_reset_postdata(); 
endif;      

// Always die in functions echoing Ajax content
die();

ユーザーの選択したすべての投稿を取得するためにWordPressのget_posts()関数を使いました。必要なパラメータは、投稿IDの入った配列post__inだけです。最後に、Ajaxのコンテンツを正しく出力するためにdie()が必要です。

read_me_later()関数のすべてのコードは以下です。

/**
 * Hook into wp_ajax_ to save post ids, then display those posts using get_posts() function
 */
public function read_me_later() {

    $rml_post_id = $_POST['post_id']; 
    $echo = array();

    if( get_user_meta( wp_get_current_user()->ID, 'rml_post_ids', true ) !== null ) {
        $value = get_user_meta( wp_get_current_user()->ID, 'rml_post_ids', true );
    }

    if( $value ) {
        $echo = $value;
        array_push( $echo, $rml_post_id );
    }
    else {
        $echo = array( $rml_post_id );
    }

    update_user_meta( wp_get_current_user()->ID, 'rml_post_ids', $echo );
    $ids = get_user_meta( wp_get_current_user()->ID, 'rml_post_ids', true );

    // Query read me later posts
    $args = array( 
        'post_type' => 'post',
        'orderby' => 'DESC', 
        'posts_per_page' => -1, 
        'numberposts' => -1,
        'post__in' => $ids
    );

    $rmlposts = get_posts( $args );
    if( $ids ) :
        global $post;
        foreach ( $rmlposts as $post ) :
            setup_postdata( $post );
            $img = wp_get_attachment_image_src( get_post_thumbnail_id() ); 
        ?>          
            <div class="rml_posts">                 
                <div class="rml_post_content">
                    <h5><a href="<?php echo get_the_permalink(); ?>"><?php the_title(); ?></a></h5>
                    <p><?php the_excerpt(); ?></p>
                </div>
                <img src="<?php echo $img[0]; ?>" alt="<?php echo get_the_title(); ?>" class="rml_img">                    
            </div>
        <?php 
        endforeach; 
        wp_reset_postdata(); 
    endif;      

    // Always die in functions echoing Ajax content
    die();

}

あとで読む記事を表示するウィジェットを作る

次に、ユーザーが保存した投稿を表示するウィジェットが必要です。簡略化のため、ごく基本的なウィジェットを作ります。カスタムウィジェットを作るためにはWordPressのWP_Widgetクラスを拡張すればよいだけですので、詳細に説明はしません。さっそく作成してみましょう。widget.phpファイルを開き、WP_Widgetクラスを継承した子クラスRML_Widgetを作成してください。

class RML_Widget extends WP_Widget {}

ウィジェットを初期化するためにクラスの中に__construct()マジックメソッドを作ります。

function __construct() {
    parent::__construct(
       'rml_widget', // Base ID
        __( 'Read Me Later', 'text_domain' ), // Name
        array( 'classname' => 'rml_widgt', 'description' => __( 'Read Me Later widget for displaying saved posts', 'text_domain' ), ) // Args
    );
}

ダッシュボードのウィジェット領域に表示されるウィジェットの名前と説明がセットされました。

バックエンドのウィジェット設定は、以下のようにform()メソッドで作成できます。

public function form( $instance ) {
    if ( isset( $instance['title'] ) ) {
        $title = $instance['title'];
    } else {
        $title = __( 'Read Me Later Posts', 'text_domain' );
    }
    ?>
    <p>
        <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
        <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>"
            name="<?php echo $this->get_field_name( 'title' ); ?>" type="text"
            value="<?php echo esc_attr( $title ); ?>">
        </p>
    <?php
}

見てのとおり、設定画面にはウィジェットのタイトルを入れたテキストフィールドがあります。タイトルは変数$titleに格納しました。get_field_id()およびget_field_name()はそれぞれ、テキストフィールドに一意のIDと名称を付加しています。

update()メソッドは、ユーザーが入力した値のサニタイジングと更新をしています。

public function update( $new_instance, $old_instance ) {
    $instance          = array();
    $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';

    return $instance;
}

この関数は2つの引数を取ります。

  1. $new_instanceには、先のform()メソッドで作成した設定画面でユーザーが入力した値が入っている
  2. $old_instanceはその反対で、以前の値が入っている

それでは「Read Me Later」投稿をブラウザーに表示するため、widget()メソッドを作成します。

public function widget( $args, $instance ) {      
    $title = apply_filters( 'widget_title', $instance['title'] ); 
    echo $args['before_widget'];
    if ( ! empty( $title ) ) {
        echo $args['before_title'] . $title . $args['after_title'];
    }

    echo '<div class="rml_contents">';

        $ids = get_user_meta( wp_get_current_user()->ID, 'rml_post_ids', true );

        $args = array( 
            'post_type' => 'post',
            'orderby' => 'DESC', 
            'posts_per_page' => -1, 
            'numberposts' => -1,
            'post__in' => $ids
        );

        $rmlposts = get_posts( $args );
        if( $ids ) :
            global $post;
            foreach ( $rmlposts as $post ) :
                setup_postdata( $post );
                $img = wp_get_attachment_image_src( get_post_thumbnail_id() ); 
                ?>          
                <div class="rml_posts">                 
                    <div class="rml_post_content">
                        <h4><a href="<?php echo get_the_permalink(); ?>"><?php the_title(); ?></a></h4>
                        <p><?php the_excerpt; ?></p>
                    </div>
                    <img src="<?php echo $img[0]; ?>" alt="<?php echo get_the_title(); ?>" class="rml_img">                    
                </div>
            <?php 
            endforeach;
            wp_reset_postdata();
        else :
        echo '<p>You have no saved posts now.</p>';
        endif;  

    echo '</div>';      
    echo $args['after_widget'];
}

投稿を表示するためにget_posts()関数を使います。read_me_later()メソッドとほとんど同じです。

read-me-later.phpファイルの先頭に次のコードを加えて、widget.phpファイルの読み込みを忘れないでください。

require(plugin_dir_path( __FILE__ ).'widget.php');

安全にAjaxをコールする

Ajaxの開発をするときには、より安全なコードになるようにします。ユーザーからデータを受け取るときには、どのようなデータであれデータベースに保存する前にサニタイジングしてください。リクエストが正しい場所から来たこと、認証されたユーザーによってなされていることをチェックするために、nonceを使います。ここでは、AjaxのコールにおいてWordPressのnonceをどのように使うのか説明します。

最初に、wp_create_nonce()メソッドを用いてnonceを作成し、JavaScriptへ渡します。そのためにはenqueue_rml_scripts()メソッドのコードを使います。

wp_localize_script( 'read-me-later', 'readmelater_ajax', array( 'ajax_url' => admin_url('admin-ajax.php') ) );

これを以下のコードと入れ替えてください。

wp_localize_script( 'read-me-later', 'readmelater_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'check_nonce' => wp_create_nonce('rml-nonce') ) ); 

これで、readmelater_ajax.check_nonceによって、JavaScriptからnonceの値にアクセスできるようになりました。以下のように、JavaScriptファイルのjQuery.ajax()メソッドにsecurityプロパティを加えます。

security : readmelater_ajax.check_nonce

最終的なJavaScriptは次のようになります。

jQuery(document).ready( function(){ 

    jQuery('#content').on('click', 'a.rml_bttn', function(e) { 
        e.preventDefault();

        var rml_post_id = jQuery(this).data( 'id' );

        jQuery.ajax({
            url : readmelater_ajax.ajax_url,
            type : 'post',
            data : {
                action : 'read_me_later',
                security : readmelater_ajax.check_nonce,               
                post_id : rml_post_id
            },
            success : function( response ) {
                jQuery('.rml_contents').html(response);
            }
        });

        jQuery(this).hide();
    }); 

});

最後に、Ajaxのコールバックの中でnonceの値をチェックするため、check_ajax_referer()関数を利用します。先に作成したread_me_later()メソッドの先頭に以下のコードを加えてください。

check_ajax_referer( 'rml-nonce', 'security' );

この関数は2つの引数をとります。最初の引数は、先にwp_create_nonce()関数で作ったキーです。次の引数は、JavaScriptから渡されたsecurityプロパティです。

もしnonceが正しくない、もしくは存在しない場合、Ajaxのコールを終了することで、スクリプトは不正なAjaxリクエストを防いでいます。

最後に

この記事では、ユーザーが気に入った記事をリストに保存してあとで読めるシステムを作りました。機能はいつでも追加できます。たとえば、保存されたすべての投稿を表示する別ページを作ったり、作成した別ページをリストに追加したり、カスタム投稿タイプから投稿を追加したりする機能などです。ダッシュボードに設定ページを作って、すべてのオプション設定ができるようにすることさえできるのです。読者のために作りたい機能を考えてください。

見てのとおり、WordPressでAjaxを使うのは難しくありません。最初は怖じ気づいてしまうかもしれませんが、一度、方法を理解すればうまく動くし格好良いですね。WordPressのフックはいたるところで使われていて、本当に便利です。この記事で、役に立つテクニックを学んでもらえたのなら嬉しいことです。さあ、Ajaxを楽しんで、作りたいものを作りましょう!

(原文:How to Use Ajax in WordPress – a Real World Example

[翻訳:西尾健史/編集:Livit

Web Professionalトップへ

WebProfessional 新着記事