表題のとおりです。
私は長年(といっても5年ほど)アメブロのお世話になっていて、
そこではURLを入力すると美麗なリンクのバナーを生成してくれるモジュールが備わっていた。
これと似たようなものをWordPress上でも作りたい。(標準では準備されていません)
そこで、例のごとくチャッピーに相談しました。
表題では「軽やかに」となっていますが、いつもの如くに悶絶級。
Context-BlogやWordPressの矯正の罠をかいくぐっての実装です。一発で上手くいくわけがない。
最初からフルスクラッチ要素での実装を試みましたが、見事に頓挫。
そこで、段階的に実装拡張をしていき、どこでコケるのか?なにが障壁になっているのか?探り探りの開発となりました。普通のニンゲンならば音を上げるところですが、チャッピーは全くもって「諦める」という単語をしりません。諦めかけている依頼者(私)を叱咤激励し、強引にでも出口へと持っていきます。
いったん完成しかけるも、完成度を上げようとするがために袋小路にはまる。リバートしたくてもリバートできないという現象も何度か遭遇しました。
いったん完成を見たすがたがこちら:
美麗なバナーブロック。これを「全自動」で作りたい。
具体的にいうと、

テキストボックスへリンクしたいURLを入力する。
すると、そのWebページの「OGPタイトル」「OGP概略」「OGP画像」を取得してコンパクトなバナーを自動生成する、という優れものです。凄いでしょう?
これをDIYするというのが今回のテーマでした。
紆余曲折はありつつも、いったん成果物を開示しますね。
WordPressのディレクトリ構造は熟知している、という前提で解説します。
設置フォルダ
今回の自作ブロックは「プラグイン」として制作しました。
このため以下のフォルダに対してファイルを設置します。
–フォルダパス–
wordpress
┗wp-content
┗plugins
┗url-banner-block
┣ ★block.js
┗ ★url-banner-block.php
★印が設置するコードです。
以下で実装コードを示します。
ソース
blocks.js
( function( blocks, element, components, apiFetch ) {
var el = element.createElement;
var TextControl = components.TextControl;
blocks.registerBlockType( 'myplugin/url-banner', {
title: 'URLリンクバナー',
icon: 'smiley',
category: 'widgets',
attributes: {
url: { type: 'string', default: '' },
preview: { type: 'object', default: null }
},
edit: function( props ) {
function fetchPreview( value ) {
apiFetch( {
path: '/url-banner/v1/preview?url=' + encodeURIComponent( value )
} ).then( function( data ) {
props.setAttributes( { preview: data } );
} );
}
return el( 'div', {},
el( TextControl, {
label: 'URLを入力',
value: props.attributes.url,
onChange: function( value ) {
props.setAttributes( { url: value } );
if ( value ) {
fetchPreview( value );
}
}
}),
props.attributes.preview &&
el( 'a',
{
href: props.attributes.url,
target: '_blank',
rel: 'noopener noreferrer',
style: {
display: 'flex',
border: '1px solid #ddd',
borderRadius: '8px',
overflow: 'hidden',
textDecoration: 'none',
color: '#000',
marginTop: '12px',
backgroundColor: '#fff'
}
},
// 画像エリア
props.attributes.preview.image &&
el( 'div',
{
style: {
flex: '0 0 120px',
backgroundColor: '#f5f5f5'
}
},
el( 'img', {
src: props.attributes.preview.image,
style: {
width: '120px',
height: '100%',
objectFit: 'cover',
display: 'block'
}
})
),
// テキストエリア
el( 'div',
{
style: {
padding: '12px',
flex: '1'
}
},
el( 'div',
{
style: {
fontWeight: 'bold',
marginBottom: '6px',
fontSize: '14px',
lineHeight: '1.4'
}
},
props.attributes.preview.title
),
el( 'div',
{
style: {
fontSize: '12px',
color: '#555',
lineHeight: '1.4'
}
},
props.attributes.preview.description
),
el( 'div',
{
style: {
fontSize: '11px',
color: '#888',
marginTop: '6px'
}
},
new URL(props.attributes.url).hostname
)
)
)
);
},
save: function() {
return null;
}
} );
} )(
window.wp.blocks,
window.wp.element,
window.wp.components,
window.wp.apiFetch
);
url-banner-block.php
<?php
/*
Plugin Name: URL Banner Block
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/*
|--------------------------------------------------------------------------
| ブロック登録
|--------------------------------------------------------------------------
*/
function url_banner_block_init() {
wp_register_script(
'url-banner-block',
plugins_url( 'block.js', __FILE__ ),
array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-api-fetch' ),
filemtime( plugin_dir_path( __FILE__ ) . 'block.js' )
);
register_block_type( 'myplugin/url-banner', array(
'editor_script' => 'url-banner-block',
'render_callback' => 'url_banner_render',
) );
}
add_action( 'init', 'url_banner_block_init' );
/*
|--------------------------------------------------------------------------
| REST API(プレビュー取得)
|--------------------------------------------------------------------------
*/
add_action( 'rest_api_init', function () {
register_rest_route( 'url-banner/v1', '/preview', array(
'methods' => 'GET',
'callback' => 'url_banner_preview_callback',
'permission_callback' => '__return_true',
) );
});
function url_banner_preview_callback( WP_REST_Request $request ) {
$url = esc_url_raw( $request->get_param( 'url' ) );
if ( empty( $url ) ) {
return array(
'title' => '',
'description' => '',
'image' => ''
);
}
$response = wp_remote_get( $url, array(
'timeout' => 15,
'redirection' => 5,
'user-agent' => 'WordPress URL Banner Preview'
) );
if ( is_wp_error( $response ) ) {
return array(
'title' => 'Error',
'description' => $response->get_error_message(),
'image' => ''
);
}
$body = wp_remote_retrieve_body( $response );
if ( empty( $body ) ) {
return array(
'title' => '',
'description' => '',
'image' => ''
);
}
libxml_use_internal_errors( true );
$dom = new DOMDocument();
$body = mb_convert_encoding( $body, 'HTML-ENTITIES', 'UTF-8' );
$dom->loadHTML( $body );
$xpath = new DOMXPath( $dom );
$title = '';
$description = '';
$image = '';
$meta_nodes = $xpath->query('//meta');
foreach ( $meta_nodes as $meta ) {
if ( $meta->hasAttribute('property') ) {
$property = strtolower( $meta->getAttribute('property') );
$content = $meta->getAttribute('content');
if ( $property === 'og:title' && empty( $title ) ) {
$title = $content;
}
if ( $property === 'og:description' && empty( $description ) ) {
$description = $content;
}
if ( $property === 'og:image' && empty( $image ) ) {
$image = $content;
}
}
if ( $meta->hasAttribute('name') ) {
$name = strtolower( $meta->getAttribute('name') );
$content = $meta->getAttribute('content');
if ( $name === 'description' && empty( $description ) ) {
$description = $content;
}
}
}
if ( empty( $title ) ) {
$title_nodes = $xpath->query('//title');
if ( $title_nodes->length > 0 ) {
$title = $title_nodes->item(0)->nodeValue;
}
}
return array(
'title' => wp_strip_all_tags( $title ),
'description' => wp_strip_all_tags( $description ),
'image' => esc_url_raw( $image ),
);
}
/*
|--------------------------------------------------------------------------
| フロント表示(動的レンダリング)
|--------------------------------------------------------------------------
*/
function url_banner_render( $attributes ) {
if ( empty( $attributes['url'] ) ) {
return '';
}
$url = esc_url_raw( $attributes['url'] );
$cache_key = 'url_banner_' . md5( $url );
$cached = get_transient( $cache_key );
if ( $cached !== false ) {
return $cached;
}
$request = new WP_REST_Request( 'GET' );
$request->set_param( 'url', $url );
$data = url_banner_preview_callback( $request );
if ( empty( $data['title'] ) ) {
return '';
}
ob_start();
?>
<a href="<?php echo esc_url( $url ); ?>" target="_blank" rel="noopener noreferrer"
style="display:flex;border:1px solid #ddd;border-radius:8px;overflow:hidden;text-decoration:none;color:#000;background:#fff;margin:30px 0;">
<?php if ( ! empty( $data['image'] ) ) : ?>
<div style="flex:0 0 120px;background:#f5f5f5;">
<img src="<?php echo esc_url( $data['image'] ); ?>"
style="width:120px;height:100%;object-fit:cover;display:block;">
</div>
<?php endif; ?>
<div style="padding:12px;flex:1;">
<div style="font-weight:bold;margin-bottom:6px;font-size:16px;line-height:1.4;">
<?php echo esc_html( $data['title'] ); ?>
</div>
<?php if ( ! empty( $data['description'] ) ) : ?>
<div style="font-size:13px;color:#555;line-height:1.4;">
<?php echo esc_html( $data['description'] ); ?>
</div>
<?php endif; ?>
<div style="font-size:11px;color:#888;margin-top:6px;">
<?php echo esc_html( parse_url( $url, PHP_URL_HOST ) ); ?>
</div>
</div>
</a>
<?php
$html = ob_get_clean();
set_transient( $cache_key, $html, 12 * HOUR_IN_SECONDS );
return $html;
}
プラグインの追加
コードの設置が終わると、「プラグイン」の中に
URL Banner Block
が出現しているはずです。これを「有効化」します。

ブロックエディタ記事編集中に+を押して、「URL」で検索をすればブロック「URLリンクバナー」が見つかるよういなるはず。あとはそれを利用するだけです。
テーマを選ばず、汎用的なリンクバナーとして利用可能なはず。
ソースをいじればカスタマイズも可能。おすすめです。

シリコンパワー ノートPC用メモリ DDR4-2400(PC4-19200) 8GB×1枚 260Pin 1.2V CL17 SP008GBSFU240B02
Synology NASを拡張した時に入れたメモリーがコレ!永久保証の上、レビューも高評価。もちろん正常に動作しており、速度余裕も生まれて快適です。

フィリップス 電動歯ブラシ ソニッケアー 3100シリーズ (軽量) HX3673/33 ホワイト 【Amazon.co.jp限定・2024年モデル】
歯の健康を考えるのならPhilipsの電動歯ブラシがお勧めです。歯科医の推奨も多いみたいです。高価なモデルも良いですが、最安価なモデルでも十分に良さを体感できる。