別の WordPress の新しい記事を表示するショートコード(REST API で取る方法と、データベースから取る方法)

概要説明

別の WordPress サイトの新しい記事を、自分のサイトに一覧で出すショートコード。パスワードの要らない REST API で取る方法と、同じサーバーのデータベースから取る方法を、キャッシュ・エスケープ付きで。前のコードの問題点も。

運営しているほかのサイトの新しい記事を、サイドバーや記事の下に並べたいことがあります。取る先が公開している WordPress なら、REST API(/wp-json/wp/v2/posts)で記事の一覧を取れます。データベースのパスワードは要りません。

まず、取ってきた記事を一覧の HTML にする関数です(下の 2 つのやり方で共通に使います)。

functions.php
/*----------------------------------------------------
 取ってきた記事を一覧の HTML にする(下の 2 つのやり方で共通)
----------------------------------------------------*/
if ( ! function_exists( 'fs_render_remote_posts' ) ) {
	function fs_render_remote_posts( $posts ) {
		if ( ! $posts ) {
			return '';
		}
		$html = '<ul class="fs-remote-posts">';
		foreach ( $posts as $p ) {
			$html .= '<li><a href="' . esc_url( $p['url'] ) . '">';
			if ( $p['image'] ) {
				$html .= '<img src="' . esc_url( $p['image'] ) . '" alt="" loading="lazy">';
			}
			$html .= '<span class="fs-remote-date">' . esc_html( mysql2date( 'Y.m.d', $p['date'] ) ) . '</span>';
			$html .= '<span class="fs-remote-title">' . esc_html( $p['title'] ) . '</span>';
			$html .= '</a></li>';
		}
		return $html . '</ul>';
	}
}
functions.php
/*----------------------------------------------------
 別の WordPress の新しい記事を出す(REST API で取る・1 時間ためておく)
 使い方:[fs_remote_posts url="https://example.com" count="5"]
----------------------------------------------------*/
if ( ! function_exists( 'fs_remote_posts_shortcode' ) ) {
	function fs_remote_posts_shortcode( $atts ) {
		$atts  = shortcode_atts( array( 'url' => '', 'count' => 5 ), $atts, 'fs_remote_posts' );
		$base  = untrailingslashit( esc_url_raw( $atts['url'] ) );
		$count = max( 1, min( 20, (int) $atts['count'] ) );
		if ( '' === $base ) {
			return '';
		}

		$cache_key = 'fs_remote_posts_' . md5( $base . '|' . $count );
		$posts     = get_transient( $cache_key );
		if ( false === $posts ) {
			$res = wp_remote_get( $base . '/wp-json/wp/v2/posts?_embed=wp:featuredmedia&per_page=' . $count, array( 'timeout' => 5 ) );
			if ( is_wp_error( $res ) || 200 !== wp_remote_retrieve_response_code( $res ) ) {
				set_transient( $cache_key, array(), 10 * MINUTE_IN_SECONDS ); // 取れなかったときは 10 分取りに行かない
				return '';
			}
			$posts = array();
			foreach ( (array) json_decode( wp_remote_retrieve_body( $res ), true ) as $p ) {
				$posts[] = array(
					'title' => isset( $p['title']['rendered'] ) ? wp_strip_all_tags( $p['title']['rendered'] ) : '',
					'url'   => isset( $p['link'] ) ? $p['link'] : '',
					'date'  => isset( $p['date'] ) ? $p['date'] : '',
					'image' => isset( $p['_embedded']['wp:featuredmedia'][0]['source_url'] ) ? $p['_embedded']['wp:featuredmedia'][0]['source_url'] : '',
				);
			}
			set_transient( $cache_key, $posts, HOUR_IN_SECONDS );
		}
		return fs_render_remote_posts( $posts );
	}
	add_shortcode( 'fs_remote_posts', 'fs_remote_posts_shortcode' );
}
  • 取った結果は 1 時間ためておく(transient)ので、ページを開くたびに相手のサイトへ取りに行きません。
  • 相手のサイトが止まっているときは、5 秒で諦めて何も出しません。10 分は取りに行かないので、自分のサイトが遅くなりません。
  • タイトル・URL・画像の URL は、出す前に esc_html・esc_url で安全な形にします。
  • 相手のサイトで REST API を止めている(401 などが返る)ときは、この方法では取れません。

データベースから取る(REST API が使えないとき)

相手のサイトの REST API が使えず、同じサーバー(または接続できるサーバー)にデータベースがあるときは、データベースから直接取ります。接続の情報は functions.php に書かず、wp-config.php に書きます。

wp-config.php
// 取る先のデータベースの接続情報(functions.php ではなく wp-config.php に書く)
define( 'FS_REMOTE_DB', array(
	'host'   => 'localhost',
	'name'   => 'データベース名',
	'user'   => 'ユーザー名',
	'pass'   => 'パスワード',
	'prefix' => 'wp_',
	'url'    => 'https://example.com', // 取る先のサイトの URL
) );
functions.php
/*----------------------------------------------------
 別の WordPress の新しい記事を出す(データベースから取る・1 時間ためておく)
 使い方:[fs_remote_posts_db count="5"]
----------------------------------------------------*/
if ( ! function_exists( 'fs_remote_posts_db_shortcode' ) ) {
	function fs_remote_posts_db_shortcode( $atts ) {
		if ( ! defined( 'FS_REMOTE_DB' ) ) {
			return '';
		}
		$atts      = shortcode_atts( array( 'count' => 5 ), $atts, 'fs_remote_posts_db' );
		$count     = max( 1, min( 20, (int) $atts['count'] ) );
		$cache_key = 'fs_remote_posts_db_' . $count;
		$posts     = get_transient( $cache_key );

		if ( false === $posts ) {
			$c      = FS_REMOTE_DB;
			$remote = new wpdb( $c['user'], $c['pass'], $c['name'], $c['host'] );
			$p      = preg_replace( '/[^A-Za-z0-9_]/', '', $c['prefix'] );
			$url    = untrailingslashit( $c['url'] );
			$rows   = $remote->get_results( $remote->prepare(
				"SELECT p.ID, p.post_title, p.post_date, f.meta_value AS file
				FROM {$p}posts AS p
				LEFT JOIN {$p}postmeta AS t ON t.post_id = p.ID AND t.meta_key = '_thumbnail_id'
				LEFT JOIN {$p}postmeta AS f ON f.post_id = t.meta_value AND f.meta_key = '_wp_attached_file'
				WHERE p.post_status = 'publish' AND p.post_type = 'post'
				ORDER BY p.post_date DESC
				LIMIT %d",
				$count
			) );
			$posts = array();
			foreach ( (array) $rows as $r ) {
				$posts[] = array(
					'title' => $r->post_title,
					'url'   => $url . '/?p=' . (int) $r->ID,
					'date'  => $r->post_date,
					'image' => $r->file ? $url . '/wp-content/uploads/' . $r->file : '',
				);
			}
			set_transient( $cache_key, $posts, HOUR_IN_SECONDS );
		}
		return fs_render_remote_posts( $posts );
	}
	add_shortcode( 'fs_remote_posts_db', 'fs_remote_posts_db_shortcode' );
}
  • 新しい順に、指定した数だけ取ります(LIMIT)。全部の記事は読みません。
  • アイキャッチ画像は、画像の情報(_wp_attached_file)からアドレスを作ります。
  • 接続の情報が間違っていると、表示のたびにエラーになることがあります。作業環境で確かめてから本番に入れてください。

[fs_remote_posts url=”https://example.com” count=”5″] REST API で、https://example.com の新しい記事を 5 件出す

[fs_remote_posts_db count=”5″] wp-config.php の FS_REMOTE_DB のデータベースから、新しい記事を 5 件出す

見た目は CSS で整えます(例)。

style.css(例)
.fs-remote-posts { list-style: none; margin: 0; padding: 0; }
.fs-remote-posts li { border-bottom: 1px solid #ddd; }
.fs-remote-posts a { display: flex; gap: 12px; align-items: center; padding: 10px 0; }
.fs-remote-posts img { width: 80px; height: 80px; object-fit: cover; }
.fs-remote-date { color: #888; font-size: 12px; }

前に載せていたコードの問題と、確かめた結果

以前この記事に載せていたコードには、次の問題がありました。

  • 本文を $post->content で取っていた(正しくは post_content)ので、本文はいつも空だった。
  • 件数の上限も並び順もなく、ページを開くたびに相手のサイトの公開記事を全部読んでいた(記事が多いと重い)。
  • タイトルや URL を、エスケープせずにそのまま HTML に入れていた。
  • データベースのパスワードを functions.php に書いていた。
  • アイキャッチ画像のアドレスに guid を使っていた(サイトを引っ越すと古いアドレスのままになる)。

確かめた結果

作業環境(WordPress 7.1)で確かめました。

やり方 結果
REST API(make.wordpress.org/core から 3 件) 3 件出た。1 回目 1.28 秒、2 回目はためておいた分で 0 秒
データベース(別の WordPress のデータベースから 3 件) 3 件・画像 3 枚が出た。1 回目 0.07 秒、2 回目 0 秒
REST API で、ないサイトを指定 何も出さずに終わる(自分のページは表示される)

このサイトのフッターの「運営サイトの一覧と記事数」も、同じように別の WordPress から取って、1 日ためておく作りにしています。

参考

関連機能

  1. 編集画面にnoindex設定を追加 ( カスタム投稿に対応 )

  2. WordPress お問合せフォームの郵便番号に入力したら住所を自動で入力する機能を追加 ( Contact Form 7 )

  3. WordPress の head タグの中に style や script を書き足す:wp_head の echo と、決まりの書き方(wp_add_inline_style)

サイドバー

よく使うカスタマイズ

最近の記事

Profile

PAGE TOP