本文までスキップする 本文までスキップする

【サンプルコードあり】noteの投稿をRSSフィードを用いてWordPress内に表示させる方法

こんにちは。デザイナーの山田です。

私事ですが、最近テレビを購入しました。画質やらサイズやら、色々と悩みながら家電を探すのは楽しいですね。
今年はW杯もあるので、綺麗な映像で楽しみながら観戦したいと思います。

さて、今回のブログではnote記事をWordPressに表示させる方法について解説します。直近のお仕事の中で実装の機会があったため、その振り返りも兼ねてまとめました。
サンプルコードもありますので是非併せてお読みください。

※記事内で、いくつかnoteのサイト画面が出てきますが、執筆時現在(2022年11月)の画面で説明を進めさせていただきます。

【2通り】WordPressにnoteの記事情報を表示させる方法

まず前提として、WordPressにnoteの記事を表示させる方法は下記の2通りに分けられます。

1: iframe
2: RSS

公式からの引用ですが、「1: iframe」は以下の手順で表示させることができます。


この手法は、スポットでnoteの投稿を1記事だけ表示したい際には便利ですが、CSSでスタイルを整える・一度に数件表示させたい場合には不向きです。更新の手間もかかってしまうところもデメリットに挙げられるかと思います。

それに対してRSSを利用する方法は、更新の自動化やCSSによるデザインの拡張性を担保することが可能です。
今回のブログでは、noteのRSSを用いて記事を表示させる方法をご紹介させていただきます。

noteのRSSリンクを確認する

まずはnoteのRSSリンクを確認しましょう。
noteのRSSリンクは2種類ありますが、配信されている情報が違うだけなので、サイト上に表示させる方法に違いはありません。

1: RSS … 最新の更新情報をすべて配信している
2: マガジンRSS … マガジンの更新情報だけ配信(マガジンごとに存在)

各RSSは、noteのクリエイターページ、またはマガジンの一覧ページにて確認が可能です。


リンク先のページで下記のような画面が表示されれば、RSSリンクの確認はOKです。

ちなみにですが、noteには無料版とPRO版とあり、PRO版ならでは特徴があります。よくある実装ケースに大きく影響するものではないかと思いますが、覚えておくといいかもしれません。

PRO版の特徴
・記事の全文をRSSで取得できる …参考URL
・最新投稿からの最大50件取得できる(独自ドメインを適用している場合 | 無料版は最大25件) …参考URL

コーディング

取得元のRSSリンクを確認できたので、いよいよコーディングに移っていきましょう。
今回は次のようなデザインで、noteの投稿一覧を表示させていきたいと思います。

今回表示させる要素としては、以下の通りです。

  • タイトル
  • 記事URL
  • アイキャッチ
  • 抜粋文
  • 執筆者サムネイル
  • 執筆者名

HTML / CSS

RSSで持ってくるデータの出力先となるHTML / CSSはこちらになります(HMTLの内容はダミーのものです)。
アイキャッチ部分は、サイズがバラバラのものが表示されてしまっても問題ないように、[object-fit]を設定しています。

<ul class="list">
 <li class="col">
  <article class="card">
   <a href="" class="card-wrapper" target="_blank" rel="noopener noreferrer">
    <figure class="card-thumbnail"><img src="img/thumbnail01.jpg" alt=""></figure>
    <div class="card-contents">
      <h3 class="card-title">ここにタイトル01が入ります。ここにタイトル01が入ります。ここにタイトル01が入ります。</h3>
      <p class="card-description">ここに説明01が入ります。ここに説明01が入ります。ここに説明01が入ります。ここに説明01が入ります。ここに説明01が入ります。ここに説明01が入ります。</p>
      <time class="card-date">2022.11.01</time>
      <div class="card-author">
       <div class="card-author-img"><img src="img/author01.jpg" alt=""></div>
       <div class="card-author-name">執筆者01</div>
      </div>
    </div>
   </a>
  </article>
 </li>
 <li class="col">
  <article class="card">
   <a href="" class="card-wrapper" target="_blank" rel="noopener noreferrer">
    <figure class="card-thumbnail"><img src="img/thumbnail02.jpg" alt=""></figure>
    <div class="card-contents">
     <h3 class="card-title">ここにタイトル02が入ります。ここにタイトル02が入ります。ここにタイトル02が入ります。</h3>
     <p class="card-description">ここに説明02が入ります。ここに説明01が入ります。ここに説明02が入ります。ここに説明02が入ります。ここに説明02が入ります。ここに説明02が入ります。</p>
     <time class="card-date">2022.11.01</time>
     <div class="card-author">
      <div class="card-author-img"><img src="img/author02.jpg" alt=""></div>
      <div class="card-author-name">執筆者02</div>
     </div>
    </div>
   </a>
  </article>
 </li>
 <li class="col">
  <article class="card">
   <a href="" class="card-wrapper" target="_blank" rel="noopener noreferrer">
    <figure class="card-thumbnail"><img src="img/thumbnail03.jpg" alt=""></figure>
    <div class="card-contents">
     <h3 class="card-title">ここにタイトル03が入ります。ここにタイトル03が入ります。ここにタイトル03が入ります。</h3>
     <p class="card-description">ここに説明03が入ります。ここに説明03が入ります。ここに説明03が入ります。ここに説明03が入ります。ここに説明03が入ります。ここに説明03が入ります。</p>
     <time class="card-date">2022.11.01</time>
     <div class="card-author">
      <div class="card-author-img"><img src="img/author03.jpg" alt=""></div>
      <div class="card-author-name">執筆者03</div>
     </div>
    </div>
   </a>
  </article>
 </li>
</ul>
.list {
	margin-left: -10px;
	margin-right: -10px;
	display: flex;
	flex-wrap: wrap;
}

.col {
	flex: 0 0 33.3333%;
	padding-left: 10px;
	padding-right: 10px;
}

.card {
	width: 100%;
	height: 100%;
	.card-wrapper {
		display: block;
		text-decoration: none;
	}
	.card-thumbnail {
		padding-top: 52.34375%;
		position: relative;
		overflow: hidden;
		img {
			display: block;
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			object-fit: cover;
		}
	}
	.card-contents {
		margin-top: 20px;
	}
	.card-title {
		font-size: 18px;
		line-height: 1.4;
		font-weight: 700;
	}
	.card-description {
		font-size: 14px;
		margin-top: 10px;
		line-height: 1.5;
		color: #3e3b3f;
	}
	.card-date {
		display: block;
		font-size: 14px;
		margin-top: 10px;
		line-height: 1.5;
		color: #9a9a9a;
		font-weight: 700;
		position: relative;
		padding-left: 26px;
		&::before {
			content: '';
			width: 20px;
			height: 20px;
			position: absolute;
			top: 0;
			left: 0;
			background-image: url('../img/icon-date.svg');
			background-position: center center;
			background-repeat: no-repeat;
			background-size: contain;
		}
	}
	.card-author {
		margin-top: 10px;
		padding-top: 10px;
		border-top: 1px solid #f5f5f5;
		display: flex;
		flex-wrap: wrap;
		align-items: center;
	}
	.card-author-img {
		flex: 0 0 48px;
	}
	.card-author-name {
		font-size: 12px;
		color: #3e3b3f;
		padding-left: 8px;
	}
}

PHP(functions.php)

そして、メインとなるfunctions.phpの記述はこちらです。

function note_feed_display($feedURL, $num, $length) {
	if(!$feedURL) {
        return false;
    } 
	if(!$num) {
        $num = 3;
    }
	if(!$length) {
        $length = 20;
    }

   	include_once( ABSPATH . WPINC . '/feed.php' );
	$rss = fetch_feed( $feedURL );
	if ( !is_wp_error( $rss ) ) { 
        // 取得するフィードのアイテム数を設定
	    $maxitems = $rss->get_item_quantity($num);
        // 最新記事から◯件のアイテムを取得
	    $rss_items = $rss->get_items( 0, $maxitems );
	} 
	if ( !empty( $maxitems ) ) {
	if ($maxitems == 0){
		echo '<!-- RSSデータがありません -->'; 
	} else {
		echo '<ul class="list">';	
		foreach ( $rss_items as $item ) {
			$hash = substr($item->get_link(), strrpos($item->get_link(), '/') + 1);
			// localで確認する際に必要
			// $options = stream_context_create(array('ssl' => array(
			// 	'verify_peer'      => false,
			// 	'verify_peer_name' => false__
			// )));
			$api_data = file_get_contents('https://note.mu/api/v1/' . 'notes/' . $hash);
			// localで確認する際、false / $options($context)が必要
			// $api_data = file_get_contents('https://note.mu/api/v1/' . 'notes/' . $hash, false, $options);

			$eyecatch = json_decode($api_data, true)['data']['eyecatch'];
			$creatorImg = json_decode($api_data, true)['data']['user']['user_profile_image_path'];
			$creatorName = json_decode($api_data, true)['data']['user']['nickname'];

			$first_img = '';
			if ( preg_match('/<img.+?src=[\'"]([^\'"]+?)[\'"].*?>/msi', $item->get_content(), $matches) ) {
				$first_img = $matches[1];
			}

			$description = $item->get_description();
			$description = str_replace("続きをみる", "", $description);
			$description = strip_tags($description);
			if($length != 0) {
				$description = mb_strimwidth($description, 0, $length, "...", 'utf-8');
			}

            echo '<li class="col">';
            echo '<article class="card">';
            echo '<a href="'.$item->get_permalink().'" class="card-wrapper" target="_blank" rel="noopener noreferrer">';

			/**
			 * アイキャッチ
			 * ないときは、投稿内の1枚目の画像を
			 * それもないときは、ダミー画像を表示させる
			 */
            if (!empty($eyecatch)){
				echo '<figure class="card-thumbnail"><img src="'.$eyecatch.'" alt="'.$item->get_title().' eyecatch"></figure>';
			} elseif (empty($eyecatch) && !empty($first_img)) {
				echo '<figure class="card-thumbnail"><img src="'.esc_attr($first_img).'" alt="'.$item->get_title().'" /></figure>'; 
			} else {
				echo '<figure class="card-thumbnail"><img src="<!-- ダミー画像のパス -->" alt="'.$item->get_title().'" /></figure>'; 
			}

            echo '<div class="card-contents">';
            echo '<h3 class="card-title">'.$item->get_title().'</h3>';
            echo '<p class="card-description">'.$description.'</p>';
            echo '<time class="card-date">'.$item->get_date('Y.m.d').'</time>';
            echo '<div class="card-author">';
            echo '<div class="card-author-img"><img src="'.$creatorImg.'" alt="'.$creatorName.'"></div>';
            echo '<div class="card-author-name">'.$creatorName.'</div>';
            echo '</div>';
            echo '</div>';

            echo '</a>';
            echo '</article>';
            echo '</li>';

		}
		echo '</ul>'; 
	  } 
	}
}

出力する際は、下記のように引数を入れて呼び出してください。
今回は、「フィードのURL / 出力する記事数 / 抜粋文の表示文字数(バイトによる計算)」を設定しています。

<?php note_feed_display('https://note.com/info/rss','3', 120); ?>

いくつかポイントに触れていきます。

まず、[fetch feed]でRSSフィードを取得しています。
[fetch_feed]はfeed.php内にあるため、事前に[include_once]で読み込んでおきましょう。

include_once( ABSPATH . WPINC . '/feed.php' );
$rss = fetch_feed( $feedURL );

取得したRSSフィードはSimplePieによって表示されるため、メソッドで情報を呼び出すことができます。

[get_item_quantity() / get_items()]も、メソッドにあたります。[get_item_quantity()]で取得するフィードのアイテム数を設定し、[get_items()]で最新の投稿から指定した個数分だけアイテムを取得している、といった流れです。

if ( !is_wp_error( $rss ) ) { 
    // 取得するフィードのアイテム数を設定
    $maxitems = $rss->get_item_quantity($num);
    // 最新記事から◯件のアイテムを取得
    $rss_items = $rss->get_items( 0, $maxitems );
}

次に、取得したアイテムを[foreach]にかけて取り出していきます。
$hashの処理で記事のハッシュを取得し、それをもとに[file_get_contents]で文字列に組み込みます。
あとは取り出したい値を指定しながらデコードすればOKです。

$hash = substr($item->get_link(), strrpos($item->get_link(), '/') + 1);
$api_data = file_get_contents('https://note.mu/api/v1/' . 'notes/' . $hash);

$eyecatch = json_decode($api_data, true)['data']['eyecatch'];
$creatorImg = json_decode($api_data, true)['data']['user']['user_profile_image_path'];
$creatorName = json_decode($api_data, true)['data']['user']['nickname'];

パーマリンクやタイトルなどは先述したSimplePieのメソッドで呼び出せるため、そちらを用いると楽かと思います。

// パーマリンク
echo '<a href="'.$item->get_permalink().'" class="card-wrapper" target="_blank" rel="noopener noreferrer">...</a>';

// タイトル
echo '<h3 class="card-title">'.$item->get_title().'</h3>';

画像を表示させたい場合は、アイキャッチが空きの場合でも問題がないような処理しておくと安心です。
ここでは、「アイキャッチ->記事内の1番目の画像->ダミー画像」といった優先度で設定しています。

$first_img = '';
if ( preg_match('/<img.+?src=[\'"]([^\'"]+?)[\'"].*?>/msi', $item->get_content(), $matches) ) {
    $first_img = $matches[1];
}

/**
* アイキャッチ
* ないときは、投稿内の1枚目の画像を
* それもないときは、ダミー画像を表示させる
*/
if (!empty($eyecatch)){
    echo '<figure class="card-thumbnail"><img src="'.$eyecatch.'" alt="'.$item->get_title().' eyecatch"></figure>';
} elseif (empty($eyecatch) && !empty($first_img)) {
    echo '<figure class="card-thumbnail"><img src="'.esc_attr($first_img).'" alt="'.$item->get_title().'" /></figure>'; 
} else {
    echo '<figure class="card-thumbnail"><img src="<!-- ダミー画像のパス -->" alt="'.$item->get_title().'" /></figure>'; 
}

descriptionの表示方法だけ、少し工夫が必要です。
[get_description()]で取り出した値には、デフォルトで「続きをみる」やタグが含まれているので、それらを取り除いた上で再出力する必要があります。

$description = $item->get_description();
$description = str_replace("続きをみる", "", $description);
$description = strip_tags($description);
if($length != 0) {
	$description = mb_strimwidth($description, 0, $length, "...", 'utf-8');
}

echo '<p class="card-description">'.$description.'</p>';

下記のように表示されれば成功です!

local(旧local by flywheel)で表示がエラーになる場合

具体的な表示方法は以上になりますが、構築中に躓いたことがあったため併せてご紹介させていただきます。
ローカル環境で構築する際、local(旧local by flywheel)を用いる方もいらっしゃるかと思いますが、上記コードでそのまま表示させようとするとエラーが発生してしまいます。

Warning: file_get_contents(): SSL operation failed with code 1. OpenSSL Error messages: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed in [パス]

どうやら自己証明書環境下では、[file_get_contents()]がエラーで動かないみたいです。
エラー回避のために、今回は下記の手順でlocal上に表示させました。

/**
* localでSSL化しておく
* 参考:https://localwp.com/help-docs/ssl/managing-local-sites-ssl-certificate-in-macos/
*/

$options = stream_context_create(array('ssl' => array(
	'verify_peer'      => false,
	'verify_peer_name' => false
)));

$api_data = file_get_contents('https://note.mu/api/v1/' . 'notes/' . $hash, false, $options);

※あくまでlocal内で確認する際にだけ用いる設定になるかと思います。
※他にもっと良い方法などあればご教授いただきますと幸いです。。

まとめ

noteの投稿一覧は最近のサイトでよく見かけるものですが、どうやって実装しているのか分からなかったので、よい学びとなりました。
今回の記事が、皆様の参考になれば幸いです。

この記事の執筆者

Masaki Yamada
Masaki Yamada
大阪・京都にあるWebサイト制作の株式会社TANE-beのデザイナー・エンジニアです。