/ / / 【WordPress】プラグインなしで人気記事をランキング表示する方法

【WordPress】プラグインなしで人気記事をランキング表示する方法

2016083102

ブログ運営にWordPressをインストールしている方であれば、人気記事一覧の表示におおよそ「WordPress Popular Posts」を利用されていることかと思います。私もずっとWPPのお世話になっていました。

WPPは大変便利なのですが、デザイン面の細かい部分をカスタマイズができないので、せっかくだから自分で最初から人気記事表示機能を実装してみることにしました。できないとは少し違って、できはするんだけれど、WPPをアップデートすると編集したソースがすべて上書きされてしまうということなのですが…。

それはさておいて、早速本題に参りましょう。

DBに新規テーブルを作成

IT技術屋として恥ずかしながら、つい二日前くらいに初めてDBコンソールに接続したのですが、WordPressインストール時には作成されないはずのテーブルが二つほど存在していることに気付きました。それらがWPPインストール時に作成されるテーブルです。

WPPは、そのテーブルを使ってPV情報を管理しているのです。 改変する理由が特にないので、テーブル定義はWPPを流用します。二つのうち片方は必須ですが、もう片方はなくとも仕様は満たせるので、今回は省きます。

さて、それではまずDBコンソールである「phpMyAdmin」にアクセスしましょう。これは各々のレンタルサーバごとに方法が異なるようです。私はさくらインターネットを利用しているのですが、サーバコンソールに「phpMyAdmin」へのアクセスリンクが存在していました。

※≪サーバー名 phpMyAdmin≫といった具合に検索をかけると、すぐに引っ掛かると思います。 ブラウザでログイン画面を開いたら、ログイン情報を入力し、「実行する」ボタンを押下しましょう。

2016-02-13_175207

左側メニューにDB一覧があります。複数DBを利用されている方であれば、それらがすべて表示されるはずです。その場合は、今回の作業の対象となるブログに紐付くDB名を押下してください。単一DBの方も同様です。

2016-02-13_175344

メインページにDBのテーブル一覧が表示されます。正しくページが表示されましたら、次は上部タブから「SQL」を選択してください。

2016-02-13_175500

すると空白のテキストエリアが表示されますので、そこに下記SQLを貼り付けてください。しかし、実行はもう少し先ですのでお待ちあれ。

CREATE TABLE IF NOT EXISTS `○○popularhistory` (
 `ID` bigint(20) NOT NULL AUTO_INCREMENT,
 `postid` bigint(20) NOT NULL,
 `pageviews` bigint(20) NOT NULL DEFAULT '1',
 `view_date` date NOT NULL DEFAULT '0000-00-00',
 `last_viewed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
 PRIMARY KEY (`ID`),
 UNIQUE KEY `ID_date` (`postid`,`view_date`),
 KEY `postid` (`postid`),
 KEY `view_date` (`view_date`),
 KEY `last_viewed` (`last_viewed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ;

2016-02-13_175555

CREATE TABLE IF NOT EXISTS~」の引用符で囲まれた部分がテーブル名となります。しかし、既存のテーブルと比較すると若干命名が異なっていると思います。WordPressインストール時にテーブル名に独自の接頭辞が付与されるためです。

先ほど貼り付けたCREATE文のテーブル名を、既存テーブルの命名に合わせましょう。既存テーブルの頭にくっついている文字列と同じ内容をCREATE文のテーブル名○○の部分と置き換えてください。確認して相違ないようでしたら、右下の「実行する」ボタンを押下しましょう。

SQLが正常実行された場合、このような画面になります。左側メニューに新たに作成されたテーブルが追加されていることを確認しましょう。

2016-02-13_175909

これにて前準備は完了です。

wp-load.php読み込み

次に、functions.phpに下記ソースを追記してください。

// $wpdbを使用するため、/wp-load.phpを読み込む
require_once( dirname(dirname(dirname(dirname(__FILE__)))) . '/wp-load.php' );

DBアクセスを行うためのおまじないです。

記事ごとのPV数を管理するため、以後記事が開かれたタイミング先ほど追加したテーブルへとデータを登録・更新することになりますが、その際DBアクセスを実現するのがwp-load.phpに定義されている$wpdbクラスなのです。

ですので、万が一wp-load.phpの読み込み前に$wpdbを使用してしまうとエラーが発生し、処理が正しく実施されません。そうした状況を避ける意図で、明示的にwp-load.phpを読み込んでおきましょう。

ひとつ注意があるので解説すると、__FILE__によってfunctions.phpのフルパスを取得し、dirname()によってその親ディレクトリのパスを取得しています。

2016-02-14_003445

私の場合、これによって得られるパスは「~/general21/wp/wp-content/themes/twentytwelve_general」となります。

ですが、これではwp-load.phpは読み込めません。functions.phpはテーマ内に存在しているので、WordPressの標準ファイルであるwp-load.phpとは異なった階層に位置しているためです。

2016-02-14_004852

ではどのようにすれば良いか。wp-load.phpはwpフォルダの直下に存在していますので、その分だけdirname()を入れ子にすれば、どんどん上位の階層に遡ってパスを取得することができます。

例に合わせて説明すると、「twentytwelve_general」から見て「wp」は三つ離れた階層ですので、dirname()を更に三つ実行すれば良いということになります。取得できた「~/general21/wp」に‘/wp-load.php’をくっつけて、require_once()によってようやく目当てのファイルを読み込めるわけです。

PV情報登録/更新機能の実装

DB登録・更新用関数の追加

functions.phpに下記ソースコードを追加してください。

/* PV記録をテーブルに反映する */
function add_posts_ranking($post_id) {
   global $wpdb;
   // 特殊文字をエスケープ
   $post_id = htmlspecialchars($post_id, ENT_QUOTES, 'UTF-8', false);
   // 対象記事のPV数と最終閲覧日付を更新する
   $sql = "UPDATE {$wpdb->prefix}popularpostssummary
            SET pageviews = pageviews + 1,
               last_viewed = current_timestamp()
            WHERE postid = {$post_id} 
            AND view_date = CURDATE()";
   $resultCnt = $wpdb->query($sql);
   // 更新に成功した(同日中既に閲覧されていた)場合は処理完了
   if ($resultCnt > 0) {
      return;
   }
   // 同日中始めての閲覧だった場合は、閲覧データを新規挿入
   $sql = "INSERT INTO {$wpdb->prefix}popularpostssummary
            (ID, postid, pageviews, view_date, last_viewed) VALUES
            (NULL, {$post_id}, 1, CURDATE(), current_timestamp())";
   $wpdb->query($sql);
}

記事閲覧時、DBへのPV登録・更新を実行する関数です。

データは、記事IDと日付で一意に絞り込めます。そのため、記事IDと日付を指定して更新処理を実施した際、更新対象が0件であればその日初めての閲覧とし、新規データを登録します。

逆にその日のデータが既に作成済みであればPV数をインクリメントし、最終閲覧日付を現在日付で更新します。

DBアクセス時のAjax通信設定

追記関数呼び出し用のcall_add_posts_ranking()を定義し、add_action()でAjax通信用のアクションフックとして登録します。functions.phpに下記ソースコードを追加してください。

function call_add_posts_ranking() {
   $post_id = $_POST['postid'];
   // 投稿IDが取得できない(記事ページではない)場合、処理を行わない
   if ( empty( $post_id ) ) {
      return;
   }
   add_posts_ranking( $post_id );
   die();
}
add_action( 'wp_ajax_nopriv_call_add_posts_ranking', 'call_add_posts_ranking' );

Ajax通信を行うアクション名の命名規約はwp_ajax_nopriv_[実行したい関数名]です。これによりWordPressにログインしていないユーザーの操作時のみ登録・更新処理が実行されます。

ちなみにwp_ajax_add_[実行したい関数名]という命名でアクションフックを登録すれば、ログイン済みユーザーの操作時に処理を実行できます。

  • wp_ajax_add~:ログイン済みユーザーの場合に実行
  • wp_ajax_nopriv~:ログインしていないユーザーの場合に実行

個別記事への投稿ID埋め込み

call_add_posts_ranking()に受け渡す投稿IDを設定するため、下記ソースコードをcontent-○○.phpに追記してください。

<input class="post-id" type="hidden" value="<?php the_ID(); ?>">

○○の部分は使用テーマによって異なります。content.php(トップページの投稿)、content-page.php(固定ページ)、content-single.php(個別記事)であるため、content-single.phpへの追記で問題ないはずです。

※content-page、content-singleがなければ、固定ページや個別記事の表示にはcontent.phpが呼び出されますので、その場合はcontent.phpに追記してください。

あまり綺麗な方法ではありませんが、Ajaxを経由すると別セッション扱いとなるのか$postなどのグローバル変数が参照できないため、やむなし。

PV登録・更新関数呼び出し

呼び出しスクリプト新規作成

上記で登録したアクションによりAjax通信を行うjQueryを定義します。script.jsを新規作成し、下記ソースコードを貼り付けてください。その後、使用中のテーマ直下にアップロードしてください。

jQuery(document).ready(function($){
   var postid = $(".post-id").val();
   $.ajax({
      type: 'POST',
      url: ajaxurl,
      data: { 'action': 'call_add_posts_ranking',
                'postid' : postid }
   });
});

Ajax通信ファイルのパス定義

Ajax通信時、urlパラメータにWordPress標準のAjax通信用PHPファイルを指定する必要があります。

管理画面では自動的にPHPファイルへのパスが変数として定義されていますが、通常画面では定義されていません。そのため、functions.phpに下記ソースコードを追記し、明示的にパスを定義しなければなりません。

function add_my_ajaxurl() {
   ?>
   <script>var ajaxurl = '<?php echo admin_url( 'admin-ajax.php'); ?>';</script>
   <?php
}
add_action( 'wp_head', 'add_my_ajaxurl', 1 );

呼び出しスクリプト読み込み

更に、functions.phpに下記ソースコードを追記してください。先ほど作成したjsファイルをブログ表示時に読み込みます。管理画面を表示する際には不必要であるため、その際はjsファイルを読み込まないように設定しています。

if (!is_admin()) {
  $cssdir = get_stylesheet_directory_uri();
  // 自作スクリプトをページにリンク
  wp_enqueue_script( 'theme-script', $cssdir.'/script.js', array('jquery'));
}

PV情報削除用関数の追加

続いて管理画面で記事が削除された場合に、DBからその記事に紐付くPV情報を全て削除するための関数を追加します。functions.phpに下記ソースコードを追記してください。

if (is_admin()) {
   /* PV記録をテーブルに反映する */
   function delete_posts_ranking($post_id) {
      global $wpdb;
      // 特殊文字をエスケープ
      $post_id = htmlspecialchars($post_id, ENT_QUOTES, 'UTF-8', false);
      // 削除された記事のPV記録を全削除
      $sql = "DELETE FROM {$wpdb->prefix}popularhistory WHERE postid = {$post_id} ";
      $wpdb->query($sql);
   }
   add_action('deleted_post', 'delete_posts_ranking');
}

削除されるのは、記事が完全削除された場合のみであることに注意。ゴミ箱に移動したタイミングではまだPV情報は保持されています。

人気記事取得用関数の追加

ブログ表示時、人気記事一覧の取得を行う関数を定義します。下記ソースコードを追記してください。

/* PV数を指定期間で合算し、人気記事一覧を取得する */
function get_popular_posts($limit, $last) {
   $limit = htmlspecialchars($limit, ENT_QUOTES, 'UTF-8', false);
   $last = htmlspecialchars($last, ENT_QUOTES, 'UTF-8', false);
   // $wpdbオブジェクトを宣言
   global $wpdb;
   $sql = "SELECT ID AS post_id,
            posts.post_title AS post_title,
            cats.name AS cat_name,
            concat('/category/', cats.slug) AS slug,
            ppsum.pvsum AS views
         FROM {$wpdb->prefix}posts posts
         -- 記事ごとのページビュー数の合計をインラインビュー化
         INNER JOIN 
            (SELECT postid, sum(pageviews) pvsum
             FROM {$wpdb->prefix}popularhistory
             WHERE last_viewed > current_timestamp() - INTERVAL {$last} DAY
             GROUP BY postid) AS ppsum
         ON posts.ID = ppsum.postid
         -- 記事カテゴリ取得(カテゴリが階層構造の場合は、最上級の親のみ取得)
         INNER JOIN
            (SELECT terms.name, terms.slug, rs.object_id
            FROM {$wpdb->prefix}terms terms
            INNER JOIN {$wpdb->prefix}term_taxonomy txnm
            ON terms.term_id = txnm.term_id
            INNER JOIN {$wpdb->prefix}term_relationships rs
            ON txnm.term_taxonomy_id = rs.term_taxonomy_id
            WHERE txnm.taxonomy = 'category'
            AND txnm.parent = 0
            GROUP BY rs.object_id) AS cats
         ON posts.ID = cats.object_id
         WHERE posts.post_status = 'publish'
         AND posts.post_type = 'post'
         ORDER BY ppsum.pvsum DESC
         LIMIT {$limit}";
   // SQLを発行し、結果セットを返す
   return $wpdb->get_results($sql);
}

個別記事テーブルと記事ごとにPV数を指定期間内で集約したPV管理テーブルを結合し、PV数の多い記事から順に、指定数だけ記事データを取得します。指定期間と指定数については後述します。

加えて、カテゴリー名を取得するため諸々のテーブルをインラインビュー化して、個別記事テーブル+PV管理テーブルにくっつけています。

ウィジェット定義

人気記事を表示するウィジェットの定義をfunctions.phpに追加。WordPress標準の「WP_Widget」クラスを継承すると、4つほどファンクションを記述するだけでウィジェットが出来上がってしまうのです。

/* 人気記事一覧ウィジェット */
class Popular_Posts extends WP_Widget {
   /*   コンストラクタ   */
   function Popular_Posts() {
      parent::WP_Widget(
         false,
         $name = 'Popular_Posts',
         array( 'description' => 'PV数の多い記事を一覧表示します。' )
      );
   }
   
   /* カスタマイズ画面で可変に設定したいパラメータをなどを記述 */
   function form($instance) {
      ?>
      <p>
         <label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('タイトル:'); ?></label>
         <input type="text" class="widefat" id="<?php echo $this->get_field_id('title'); ?>" 
            name="<?php echo $this->get_field_name('title'); ?>" 
            value="<?php echo esc_attr( $instance['title'] ); ?>">
      </p>
      <p>
         <label for="<?php echo $this->get_field_id('limit'); ?>"><?php _e('記事件数:'); ?></label>
         <input type="text" id="<?php echo $this->get_field_id('limit'); ?>" 
            name="<?php echo $this->get_field_name('limit'); ?>" 
            value="<?php echo esc_attr( $instance['limit'] ); ?>" size="3">
      </p>
      <p>
         <label for="<?php echo $this->get_field_id('last'); ?>"><?php _e('集計期間(日):'); ?></label>
         <input type="text" id="<?php echo $this->get_field_id('last'); ?>" 
            name="<?php echo $this->get_field_name('last'); ?>" 
            value="<?php echo esc_attr( $instance['last'] ); ?>" size="3">
      </p>
      <p>
         <input type="checkbox" id="<?php echo $this->get_field_id('pvdisp'); ?>" 
            name="<?php echo $this->get_field_name('pvdisp'); ?>" 
            value="1" <?php checked( $instance['pvdisp'], 1 ); ?>>
         <label for="<?php echo $this->get_field_id('pvdisp'); ?>"><?php _e('PV数を表示する'); ?></label>
      <?php
   }
   
   /* カスタマイズ画面の入力内容が変更された場合の処理 */
   function update($new_instance, $old_instance) {
      $instance = $old_instance;
      $instance['title'] = strip_tags($new_instance['title']);
      $instance['limit'] = is_numeric($new_instance['limit']) ? $new_instance['limit'] : 10;
      $instance['last'] = is_numeric($new_instance['last']) ? $new_instance['last'] : 1;
      $instance['pvdisp'] = strip_tags($new_instance['pvdisp']);
      return $instance;
   }

   /* ウィジェットの表示時のデザインを記述 */
   function widget($args, $instance) {
      extract($args);
      echo $before_widget;
      
      if(!empty($instance['title'])) {
         $title = apply_filters('widget_title', $instance['title']);
      }
      if ($title) {
         echo $before_title . $title . $after_title;
      } else {
         echo '人気記事';
      }
      $results = get_popular_posts($instance['limit'], $instance['last']);
      ?>
      <ul class="popular-list">
         <?php foreach ($results as $result) { ?>
         <li><a href="<?php echo get_permalink($result->post_id); ?>"
               title="<?php echo $result->post_title; ?>" target="_self">
               <?php if (has_post_thumbnail($result->post_id)) { echo get_the_post_thumbnail($result->post_id, 'thumbnail', 'class=popular-thumbnail'); } else { ?>
                  <img src="<?php echo my_catch_the_image($result->post_id); ?>" class="popular-thumbnail wp-post-image" width="100" height="100" alt="<?php echo $result->post_title; ?>">
               <?php } ?>
            </a>
            <a href="<?php echo get_permalink($result->post_id); ?>"
               class="popular-title" title="<?php echo $result->post_title; ?>" target="_self">
               <?php echo $result->post_title; ?>
            </a>
            <?php if (!Empty($instance['pvdisp'])) { ?><span class="popular-views"><?php echo $result->views. ' views'; ?></span><?php } ?>
            <span class="popular-category">
               <a href="<?php echo $result->slug; ?>"><?php echo $result->cat_name; ?></a>
            </span>
         </li>
         <?php } ?>
      </ul>
      <?php
      echo $after_widget;
   }
}
/* 自作ウィジェットを登録 */
register_widget('Popular_Posts');
  • Popular_Posts():コンストラクタ。このクラスが生成された際に読み込まれる部分。初期値の定義をしています。
  • form():カスタマイズ画面の入力項目。今回用意したパラメータはウィジェットのタイトル、記事表示数、集計期間です。後者二つをパラメータとして、DBから記事データを取得します。
  • update():管理画面の入力内容が変更された場合の振る舞いを記述しています。基本的には上書きですが、記事表示数と集計期間に関しては、数値以外の不正な内容が入力されると、デフォルト値に置き換えられるようにしています。
  • widget():ブログにウィジェットを表示した際のデザイン部分。SQL結果を元に、一覧を生成しています。

※get_the_post_thumbnail()の第二引数に、’thumbnail’を指定していますが、これはサムネイルサイズの名称です。WordPressではデフォルトで、下記サイズが選択できますのでお好みで。

サムネイルサイズ
  • thumbnail:デフォルト 150px × 150px
  • medium:デフォルト 300px × 300px
  • large:デフォルト 640px × 640px
  • full:アップロードした画像の元サイズ

register_widget():今回作成したウィジェットを、使用可能なウィジェットとして登録しています。

画像探索用ファンクション追加

記事にサムネイルが設定されていればそれを表示しますが、未設定の場合は記事内の一番先頭に存在する画像をサムネイルとして表示します。後者の場合に記事内の画像を探索する関数をfunctions.phpに追加しましょう。

function my_catch_the_image($post_id) {
   $post = get_post($post_id);
   $first_img = '';
   ob_start();
   ob_end_clean();
   $output = preg_match_all('/<img.+src=[\'"]([^\'"]+)[\'"].*>/i', $post->post_content, $matches);
   $first_img = $matches [1] [0];

   if (empty($first_img)) {
      $first_img = '[画像が見つからなかった場合に表示する画像パス.jpg]';
   }
   return $first_img;
}

万が一記事内に画像が存在しない場合は、$first_imgに設定したダミー画像を設定します。$first_imgの内容はご自由に設定ください。

これにてfunctions.phpの追記は終了です。

style.css追記

さてさて、機能は実装できましたが、このままだと見た目が大分しょぼいので、体裁を整えます。下記をstyle.cssに貼り付けてください。

/****************************************
 
          人気記事一覧
 
*****************************************/
.popular-list {
   counter-reset: popular-rank;
}

.popular-list li {
   display: block;
   overflow: hidden;
   position: relative;
   clear: both;
   margin: 0 0 10px 0 !important;
   border-bottom: 1px dashed #444;
}

.popular-list li:hover {
   filter: alpha(opacity=85);
   -moz-opacity: 0.85;
   opacity: 0.85;
   background-color: #fddac4 !important;
}

.popular-list li:before {
   color: #FFF;
   content: counter(popular-rank, decimal);
   counter-increment: popular-rank;
   text-align: center !important;
   position: absolute;
   top: 5px;
   left: 6px;
   z-index: 1;
   line-height:30px;
   width: 30px;
   height: 30px;
   background-color:#3E454C;
   border-radius: 2px;
   -moz-border-radius: 2px;
   filter:alpha(opacity=90);
   -moz-opacity: 0.90;
   opacity: 0.90;
}

.popular-thumbnail {
   float: left;
   margin: 10px 10px 10px 10px !important;
}

.popular-title {
   color: #f22d30 !important;
   display: block;
   font-size: 15px;
   font-weight: 600;
   margin: 10px 10px 0 120px !important;
}

.popular-views {
   text-align: center;
   font-size: 12px;
   font-weight: 600;
   background-color: #F7E8E8;
   color: #FF0000;
   padding: 1px 8px;
}

.popular-category {
   display: block;
   text-align: right;
   font-size: 12px;
   font-weight: 600;
   background-color: #DD4453;
   color: #fff;
   margin: 15px 10px 10px 0;
   padding: 2px 15px;
   float: right;
}

.popular-category a,
.popular-category a:hover {
   color: #fff !important;
}

ウィジェット表示

それでは登録したウィジェットをサイドバーに表示しましょう。まずダッシュボードにログインします。

2016-02-14_020156

左側メニューの外観>ウィジェットを選択します。

2016-02-14_020258

利用できるウィジェット」に表示されている「Popular_Posts」をメインサイドバーにドラッグ&ドロップします。追加位置はお好きな場所で大丈夫です。

2016-02-14_020348

下記パラメータをお好みで設定し、「保存」ボタンを押下します。

  • タイトル:ウィジェットの表示タイトル。
  • 記事件数:一覧に表示する記事件数。
  • 集計期間(日):PV数の集計期間。当日から過去何日までを範囲とするか入力してください。
  • PV数を表示する:集計期間中のPV数を表示するかどうか。チェックがあれば表示します。

2016-03-01_220957

ブログのトップページを表示し、サイドバーにウィジェットが表示されていることを確認します。デザインはご自分のブログに合わせて設定して頂ければよろしいかと。

実装直後からPV情報の蓄積が開始されるため、ウィジェットを表示しても最初は記事が数件しか出力されない可能性があります。実装からある程度時間を置いてからのウィジェット表示をオススメします。この点はWPPと同様。

2016-02-16_003823

まとめ

ウィジェットを自作化することで、デザイン面のカスタマイズ性がグッと向上しました。機能実装がようやく一息ついたところなので、デザインは全然いじっていませんが…。パフォーマンスを考慮して、なるべくプラグインはインストールしたくないという方は、どうぞご参考になさって頂ければと思います。

この記事をシェアする
このブログをフォローする