はじめに
以下をCocoonで実現します。
CocoonにはRSSカード表示用のショートコードがありますが、複数のRSSをまとめて表示することはできません。 そこで、複数サイトのRSS情報を取得・マージし、最新の更新日付順に並び替えて表示できる独自のショートコードを追加します。
この記事では、この方法について説明します。
完成イメージ
使用方法
URLオプションにカンマ区切りで複数のRSSを指定します。
[my_rss_merge url="https://site1.com/feed/, https://site2.com/feed/" site=1]
実装手順
以下のコードをfunctions.phpに追加します。
add_shortcode('my_rss_merge', function ($atts) {
// WordPressのRSS取得機能を読み込み
include_once(ABSPATH . WPINC . '/feed.php');
$atts = shortcode_atts([
'url' => '', // カンマ区切りのRSS URL
'count' => 5, // 表示件数
'img' => NO_IMAGE_RSS, // 代替画像
'target' => '_blank', // リンクのtarget属性
'cache_minute' => 60, // キャッシュ時間(分)
'desc' => 1, // 説明文表示
'date' => 1, // 日付表示
'bold' => 0, // タイトル太字表示
'arrow' => 0, // 矢印表示
'class' => null, // 拡張クラス
'site' => 0, // サイト名表示
], $atts, 'rss_merge');
if (empty($atts['url'])) {
return '';
}
// URL配列に変換
$feed_urls = array_filter(array_map('trim', explode(',', $atts['url'])), function ($url) {
return filter_var($url, FILTER_VALIDATE_URL) !== false;
});
if (empty($feed_urls)) {
return '';
}
// キャッシュ用IDを生成
$transient_id = 'rss_merge_' . md5(
implode('|', $feed_urls) . '_' . $atts['count'] . '_' . $atts['desc'] . '_' . $atts['date'] . '_' . $atts['site']
);
// キャッシュがある場合
$feed_contents = get_transient($transient_id);
if ($feed_contents !== false) {
return $feed_contents;
}
// SimplePieの内部キャッシュ・自動日付ソートを無効化
$feed_options_callback = function ($feed) {
$feed->enable_cache(false);
$feed->enable_order_by_date(false);
};
add_action('wp_feed_options', $feed_options_callback);
$items = [];
// 各RSSを取得
foreach ($feed_urls as $feed_url) {
$rss = fetch_feed($feed_url);
if (is_wp_error($rss)) {
continue;
}
// サイト名を取得
$site_title = trim((string) $rss->get_title());
if (empty($site_title)) {
$site_title = parse_url($feed_url, PHP_URL_HOST) ?? '';
}
// RSS内の全itemを取得
$rss_items = $rss->get_items();
if (is_array($rss_items)) {
foreach ($rss_items as $item) {
$items[] = [
'item' => $item,
'site' => $site_title,
];
}
}
}
// アクションを解除
remove_action('wp_feed_options', $feed_options_callback);
// 投稿日時で最新順にソート
usort($items, function ($a, $b) {
$date_a = $a['item']->get_date('c');
$date_b = $b['item']->get_date('c');
$time_a = $date_a ? strtotime($date_a) : 0;
$time_b = $date_b ? strtotime($date_b) : 0;
return $time_b - $time_a;
});
// 表示件数に制限
$items = array_slice($items, 0, (int) $atts['count']);
if (!empty($items)) {
ob_start();
// 日付フォーマットと抜粋長を取得
$date_format = get_site_date_format();
$excerpt_len = get_entry_card_excerpt_max_length();
foreach ($items as $row) {
$item = $row['item'];
$site_title = $row['site'];
// 記事本文を取得し空文字チェック
$item_content = $item->get_content();
if (!is_string($item_content)) {
$item_content = '';
}
// 本文内から最初の画像URLを抽出
$feed_img = $atts['img'];
if (preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $item_content, $matches)) {
$feed_img = $matches[1];
}
$feed_url = $item->get_permalink();
$feed_title = str_replace(["\r\n", "\r", "\n"], '', $item->get_title() ?? '');
$feed_date = $item->get_date($date_format);
$plain_text = wp_strip_all_tags($item_content);
$feed_text = get_content_excerpt($plain_text, $excerpt_len);
// Cocoonテンプレートに渡す引数を構築
$template_args = [
'url' => $feed_url,
'title' => $feed_title,
'target' => $atts['target'],
'img' => $feed_img,
'site' => $atts['site'],
'site_title' => $site_title,
'desc' => $atts['desc'],
'date' => $atts['date'],
'text' => $feed_text,
'date_str' => $feed_date,
'cache_minute' => $atts['cache_minute'],
];
cocoon_template_part('tmp/rss-card', null, $template_args);
}
$feed_content = ob_get_clean();
} else {
$domain_name = defined('THEME_NAME') ? THEME_NAME : 'default';
$feed_content = '<p>' . __('RSSフィードを取得できません', $domain_name) . '</p>';
}
// 追加クラスを生成
$card_class = get_additional_widget_entry_cards_classes([
'bold' => $atts['bold'],
'arrow' => $atts['arrow'],
'class' => $atts['class'],
]);
$feed_contents = '<div class="rss-entry-cards widget-entry-cards' . $card_class . ' no-icon">' . $feed_content . '</div>';
// キャッシュに保存
set_transient($transient_id, $feed_contents, 60 * (int) $atts['cache_minute']);
return $feed_contents;
});
参考:REST APIで情報を取得する方法
RSSは複数サイトを横断的に取得できる汎用性がありますが、アイキャッチ画像を取得できない、構造化データが不足しているといった課題があります。
そこで、対象サイトがWordPressに限定されるものの、REST APIを利用することで、投稿日やアイキャッチ画像を含む正確な投稿データを取得し、Cocoonのエントリーカード形式で安定した表示が可能になります。
RSSでは見た目や情報の精度に課題がある場合の代替手段として、REST APIは有効な選択肢となります。
完成イメージ
使用方法
以下にショートコードを示します。
[my_api_merge url="https://site1.com/,https://site2.com/"]
実装手順
以下のコードをfunctions.phpに追加します。
add_shortcode('my_api_merge', function ($atts) {
$atts = shortcode_atts([
'url' => '', // カンマ区切りのRSS URL
'count' => 5, // 表示件数
'img' => NO_IMAGE_RSS, // 画像がない場合の代替画像
'target' => '_blank', // リンクのtarget属性
'cache_minute' => 60, // キャッシュ時間(分)
'desc' => 1, // 説明文表示
'date' => 1, // 日付表示
'bold' => 0, // タイトル太字表示
'arrow' => 0, // 矢印表示
'class' => null, // 拡張クラス
'site' => 0, // サイト名表示
], $atts, 'api_merge');
if (empty($atts['url'])) {
return '';
}
// URL配列に変換
$site_urls = array_filter(array_map('trim', explode(',', $atts['url'])), function ($url) {
return filter_var($url, FILTER_VALIDATE_URL) !== false;
});
if (empty($site_urls)) {
return '';
}
// キャッシュ用IDを生成
$transient_id = 'api_merge_mod_' . md5(
implode('|', $site_urls) . '_' . (int) $atts['count'] . '_' . (int) $atts['site'] . '_' . (int) $atts['desc'] . '_' . (int) $atts['date']
);
// キャッシュがある場合
$feed_contents = get_transient($transient_id);
if ($feed_contents !== false) {
return $feed_contents;
}
$items = [];
// 各対象サイトからREST API経由でデータを取得
foreach ($site_urls as $site_url) {
$site_url = trailingslashit($site_url);
// 更新順で20件投稿を取得
$endpoint = $site_url . 'wp-json/wp/v2/posts?per_page=20&orderby=modified&_embed';
// APIリクエスト
$res = wp_remote_get($endpoint, ['timeout' => 10]);
if (is_wp_error($res)) {
continue;
}
// JSONデコード
$posts = json_decode(wp_remote_retrieve_body($res), true);
if (!is_array($posts)) {
continue;
}
// サイト名を取得
$site_title = '';
$info_res = wp_remote_get($site_url . 'wp-json/', ['timeout' => 10]);
if (!is_wp_error($info_res)) {
$info = json_decode(wp_remote_retrieve_body($info_res), true);
if (!empty($info['name'])) {
$site_title = trim((string) $info['name']);
}
}
if (empty($site_title)) {
$site_title = (string) parse_url($site_url, PHP_URL_HOST);
}
foreach ($posts as $post) {
if (!is_array($post)) {
continue;
}
$items[] = [
'post' => $post,
'site_url' => $site_url,
'site_title' => $site_title,
];
}
}
if (empty($items)) {
return '';
}
// 更新日時で降順ソート
$timezone = new DateTimeZone('Asia/Tokyo');
usort($items, function ($a, $b) use ($timezone) {
$modified_a = $a['post']['modified'] ?? '';
$modified_b = $b['post']['modified'] ?? '';
$date_a = !empty($modified_a) ? date_create($modified_a, $timezone) : false;
$date_b = !empty($modified_b) ? date_create($modified_b, $timezone) : false;
if ($date_a && $date_b) {
return $date_b->getTimestamp() - $date_a->getTimestamp();
}
return 0;
});
// 表示件数に制限
$items = array_slice($items, 0, (int) $atts['count']);
ob_start();
// 日付フォーマットと抜粋長を取得
$date_format = get_site_date_format();
$excerpt_len = (int) get_entry_card_excerpt_max_length();
foreach ($items as $row) {
$post = $row['post'];
$site_title = $row['site_title'];
$raw_title = isset($post['title']['rendered']) ? (string) $post['title']['rendered'] : '';
$title = trim(str_replace(["\r\n", "\r", "\n"], '', wp_strip_all_tags($raw_title)));
$link = isset($post['link']) ? (string) $post['link'] : '';
$modified_date = isset($post['modified']) ? (string) $post['modified'] : '';
$dateval = !empty($modified_date) ? mysql2date($date_format, $modified_date) : '';
$content = isset($post['content']['rendered']) ? (string) $post['content']['rendered'] : '';
$plain_text = wp_strip_all_tags($content);
$excerpt = get_content_excerpt($plain_text, $excerpt_len);
$feed_img = $atts['img'];
if (!empty($post['_embedded']['wp:featuredmedia'][0]['source_url'])) {
$feed_img = (string) $post['_embedded']['wp:featuredmedia'][0]['source_url'];
}
// Cocoonのテンプレート引数を構築
$template_args = [
'url' => esc_url($link),
'title' => $title,
'target' => $atts['target'],
'img' => $feed_img,
'site' => $atts['site'],
'site_title' => $site_title,
'desc' => $atts['desc'],
'date' => $atts['date'],
'text' => $excerpt,
'date_str' => $dateval,
'cache_minute' => (int) $atts['cache_minute'],
];
cocoon_template_part('tmp/rss-card', null, $template_args);
}
$feed_content = ob_get_clean();
// 追加クラスを生成
$card_class = get_additional_widget_entry_cards_classes([
'bold' => (int) $atts['bold'],
'arrow' => (int) $atts['arrow'],
'class' => $atts['class'],
]);
$output = '<div class="rss-entry-cards widget-entry-cards' . $card_class . ' no-icon">' . $feed_content . '</div>';
set_transient($transient_id, $output, 60 * (int) $atts['cache_minute']);
return $output;
});
参考:RSS表示の代替としてブログカードを利用する方法
RSSは複数サイトを横断的に取得できる汎用性がありますが、アイキャッチ画像を取得できない、構造化データが不足しているといった課題があります。
また、REST APIはこれらの問題を解消できる一方で、WordPressサイトに限定されるという制約があります。
そこで、幅広いサイトを扱える代替手段として、RSSフィードから投稿URLのみを取得し、ブログカード形式で表示する方法を用います。
この方法であれば、WordPress以外のブログサービスも含めて統一的に扱うことが可能です。
ただし、article:published_timeやarticle:modified_timeは任意情報のため取得していません。
そのため、外部ブログカードでは投稿日が表示されず、記事がいつ投稿されたものか分からない点には注意が必要です。
完成イメージ


使用方法
以下にショートコードを示します。
[my_rss_merge_blogcard]
| オプション | 説明 | 値 | 初期値 |
|---|---|---|---|
| count | 表示数 | 5 | |
| url | RSSフィードのURL(必須) | カンマ区切りで指定 |
実装手順
以下のコードをfunctions.phpに追加します。
add_shortcode('my_rss_merge_blogcard', function ($atts) {
// WordPressのRSS取得機能を読み込み
include_once(ABSPATH . WPINC . '/feed.php');
$atts = shortcode_atts([
'url' => '', // カンマ区切りのRSS URL
'count' => 5, // 表示件数
'cache_minute' => 60, // キャッシュ時間(分)
], $atts, 'rss_merge_blogcard');
if (empty($atts['url'])) {
return '';
}
// URL配列に変換
$feed_urls = array_filter(array_map('trim', explode(',', $atts['url'])), function ($url) {
return filter_var($url, FILTER_VALIDATE_URL) !== false;
});
if (empty($feed_urls)) {
return '';
}
// キャッシュ用IDを生成
$transient_id = 'rss_merge_bc_' . md5(implode('|', $feed_urls) . '_' . (int) $atts['count']);
// キャッシュがある場合
$cached_output = get_transient($transient_id);
if ($cached_output !== false) {
return $cached_output;
}
// SimplePieの内部キャッシュ・自動日付ソートを無効化
$feed_options_callback = function ($feed) {
$feed->enable_cache(false);
$feed->enable_order_by_date(false);
};
add_action('wp_feed_options', $feed_options_callback);
$items = [];
// 各RSSを取得
foreach ($feed_urls as $feed_url) {
$rss = fetch_feed($feed_url);
if (is_wp_error($rss)) {
continue;
}
$rss_items = $rss->get_items();
if (is_array($rss_items)) {
foreach ($rss_items as $item) {
$items[] = $item;
}
}
}
// アクションを解除
remove_action('wp_feed_options', $feed_options_callback);
if (empty($items)) {
return '';
}
// 投稿日時で最新順にソート
usort($items, function ($a, $b) {
$date_a = $a->get_date('c');
$date_b = $b->get_date('c');
$time_a = $date_a ? strtotime($date_a) : 0;
$time_b = $date_b ? strtotime($date_b) : 0;
return $time_b - $time_a;
});
// 表示件数に制限
$items = array_slice($items, 0, (int) $atts['count']);
ob_start();
foreach ($items as $item) {
$post_url = $item->get_permalink();
if (!$post_url) {
continue;
}
echo do_shortcode('[blogcard url="' . esc_url($post_url) . '"]');
}
$output = ob_get_clean();
set_transient($transient_id, $output, 60 * (int) $atts['cache_minute']);
return $output;
});
RSS、REST API、OGPの制約と特徴まとめ
外部サイトの記事を取得・表示する方法として、RSS、REST API、OGPがありますが、それぞれ取得できる情報や制約が異なります。
以下の表に、実装時に特に重要となるポイントを整理します(表1参照)。
| 利用ライブラリ・手段 | 取得可能な主な情報 | 主な課題・制限 | |
|---|---|---|---|
| RSS | fetch_feed(WordPress標準) |
| 標準ではアイキャッチ画像が含まれない。 出力側のカスタマイズが必要。 |
| REST API | WordPress REST API |
| WordPressサイト限定。 相手側がAPIを公開(有効化)している必要がある。 |
| OGP | Open Graphライブラリ |
| 投稿日は任意項目。 サイトによって記述形式がバラバラで、取得精度が不安定。 |
Cocoon設定とRSSの課題について
Cocoon設定→インデックス→ 表示順を投稿日以外に変更した場合、その順序でRSSが生成されます。
しかし、RSSアイテムの<pubDate>は公開日であり、更新日ではありません。
そのため、リライトによって古い公開日の記事が再表示された場合でも、<pubDate>は過去日のままとなります。
この結果、複数サイトの情報をマージし、日付順にソートして最新数件をカード表示した際に、実際の表示順と日付情報が一致せず、正しく表示されない問題が発生します。
このような場合は、Cocoon標準のrssショートコードを複数指定し、各サイトごとに個別表示する必要があります。
さいごに
今回紹介した方法を使えば、複数のRSSフィードをまとめて取得し、CocoonのRSSカードデザインを維持したまま柔軟に表示できます。
キャッシュ制御や表示条件も細かく調整できるため、パフォーマンスと見た目の両立が可能です。



