ブログカード
最近あちこちのブログで見かける「はてなブログカード」と言う機能があります
WordPress で利用するには、このあたりの情報が大元のようです
はてなブログの記事を紹介しやすくしました。URLを貼るだけで、コンパクトに整ったブログカードを貼り付けることができます【追記あり】 – はてなブログ開発ブログ
はてなブログのブログカードがOGPに対応! はてなブログ以外もブログカードスタイルになるぞ
ここまで見て、あれ ブログカードがちょっと違うぞと気がついたと思いますが、これが今回作成したブログカードです。詳しくは後ほど紹介します。
はてなのブログカードは OGPにも対応しているようで、はてなブログ以外のサイトもブログカード形式で表示出来るようです
ちなみにOGPは、ツイッターやフェースブックで利用されている機能で記事の要約だけでなく画像等もメタデータとして含むことが出来ます
Open Graph protocol
OGPデータからカード作成するなら、プログラム組めば以外に簡単に出来るんじゃない?
と思ったのが今回のきっかけです (^^)
ということでちょっと調べると はてなのブログカードを iframe を使ってブックマークレットから簡単に生成する方法を公開してくれている方がいます
それを利用してブックマークレットからそのサイトのカードを作ってみます
最初はこの iframe をプログラムで処理しようとも思ったのですが、はてな経由のブログカードだと参照元やピンバックの問題があるようなので、単純にOGPを取得してリンクを はてなブログカードのように表示させてみました
OGPブログカード
ブログカード作成でどこを はてなブログカード に似せて、どこを変えるかがポイントです
- カードサイズ ー 少し大きめの最大幅 600px
- サムネイル画像 ー WordPress サムネイルのデフォルト 150px 想定
- 画像のキャッシュ ー サムネイルとアイコンは取得してキャッシュ化する
- はてブ数 ー 不要
レスポンシブ対応とするので、サムネイル画像がうまい具合に縮小するのはトリミングした場合なのですが、そこは選択できるように WordPress の設定に従っています
使い方は簡単で記事に URL を貼り付けるだけです
youtube 等を埋め込む場合と同じ要領です
ダウンロード
このプログラムを使いたい方、コードに興味がある方は、WordPress Plugin : Celtispack ページからダウンロードすることが出来ます
以下プログラム作成時のポイント等について少し紹介します
埋め込み oEmbed として作成するか?
wp_oembed_add_provider('http://*', 'http://hatenablog.com/oembed');
はてなのように独自のエンドポイントを決めてあたかも oEmbed 機能が動作してるがごとく実装しようと思ったのですが、エンドポイントのプロバイダーを選択するのに http://* というのはよろしくないです
例えば、上記の hatena のエンドポイントの登録では、記事中に埋め込まれた URL のどのようなパターンにもマッチするということなので、本当は他のエンドポイントを使いたいのに はてな が使われる可能性があるということになってしまうので wp_oembed_add_provider で登録する場合は ‘http://.+hate.+’ のように対象をしぼる必要があります
ということでプログラムとしては、エンドポイントを登録はせずに、該当するエンドポイントが見つからない場合の処理としてフィルターフックを使って実装しました
コードを調べると WP4.0 から追加された oembed_ttl というフックポイントが使えそうだったのでそこにフックして機能するようにしています
従って、このOGPブログカードが使えるのは WP4.0 以上となります
※はてなブログカードと併用したい場合は、はてなのエンドポイントの登録を 'http://*' から ‘http://.+hate.+’ にすれば独自ドメイン以外とは併用できます。また、Hatena ブログカードを直接 HTML に iframe で埋め込んでも併用できます
プログラム
oembed_ttl にフックする処理です
/**
* Filter the oEmbed TTL value (time to live).
*
* @since 4.0.0
*
* @param int $time Time to live (in seconds).
* @param string $url The attempted embed URL.
* @param array $attr An array of shortcode attributes.
* @param int $post_ID Post ID.
*/
// oEmbed 埋め込みURL で provider がマッチしない時に OGPブログカード html データを生成する
// また、記事更新時はキャッシュは使わないで強制的に記事内の全ての oEmbed も更新する
function oembed_ogp( $time, $url, $attr, $post_ID ) {
$option = $this->option;
if ( function_exists( '_wp_oembed_get_object' ) ){
$key_suffix = md5( $url . serialize( $attr ) );
$cachekey = '_oembed_' . $key_suffix;
$cachekey_time = '_oembed_time_' . $key_suffix;
$cache = get_post_meta( $post_ID, $cachekey, true );
$attr['discover'] = false;
$provider = '';
$html = '';
if ( !empty($option['oembed-blogcard'])){
$oembed = _wp_oembed_get_object();
$provider = $oembed->get_provider( $url, $attr );
}
if ( empty($GLOBALS['wp_embed']->usecache) ) {
$html = wp_oembed_get( $url, $attr );
if (empty($html) && empty($provider) && !empty($option['oembed-blogcard'])){
$html = $this->ogpcard_html_get($url);
}
}
elseif (empty($cache)){
if(empty($provider) && !empty($option['oembed-blogcard'])){
$html = $this->ogpcard_html_get($url);
}
}
//embed html 更新
if ( !empty($html) ) {
update_post_meta( $post_ID, $cachekey, $html );
update_post_meta( $post_ID, $cachekey_time, time() );
$cache = get_post_meta( $post_ID, $cachekey, true );
}
}
return($time);
}
oEmbed 処理は、通常埋め込む HTML を対象サイトから取得してデータベースにキャッシュとして保存しています
毎回、データを通信で取得するのはなかなかコストのかかる処理なのでキャッシュをうまく使わないと重くてどうにもならなくなってしまいますので注意です
次にOGPを使ってデータを取得する処理のメイン部分です
//OGPデータからブログカード用HTML生成
public function ogpcard_html_get( $url ) {
$html = '';
$args = array( 'timeout' => 10, 'httpversion' => '1.1' );
$response = wp_safe_remote_get( $url, $args );
//サイトによってはタイムアウトとなる場合あるのでデフォルト 5->10秒にして1回だけリトライ
if ( is_wp_error( $response ) || $response['response']['code'] !== 200 ) {
$response = wp_safe_remote_get( $url, $args );
}
if ( ! is_wp_error( $response ) && $response['response']['code'] === 200 ) {
$head = Celtis_lib::htmltagsplit($response['body'], '<head', '/head>');
if(!empty($head[1])){
//OGP パース
$ogp = ogp\Parser::parse( mb_convert_encoding($head[1], 'HTML-ENTITIES', 'UTF-8'));
$myurl = get_bloginfo('url');
//OGP には含まれていないが favicon を head から取得
$ogp['og:favicon'] = '';
preg_match('#<link.+icon.+href=.(.+\.ico|.+\.png)#', $head[1],$match);
if(!empty($match[1])){
$response = wp_safe_remote_get( $match[1] );
if ( ! is_wp_error( $response ) && $response['response']['code'] === 200 ) {
$type = $response['headers']['content-type'];
//アイコンはローカルにキャッシュする
$upload = wp_upload_dir();
$keyid = md5( 'icon_' . $match[1]);
//Hatena blog の icon は拡張子を ico にしないと IE で表示されない
$newext = ($type == 'image/x-icon' || $type == 'image/vnd.microsoft.icon')? 'ico' : 'png';
$fname = $upload['basedir'] . '/celtispack/icon/' . "{$keyid}.{$newext}";
$ifp = @ fopen( $fname, 'wb' );
if ( $ifp ){
@fwrite( $ifp, $response['body'] );
fclose( $ifp );
clearstatcache();
// Set correct file permissions
$stat = @ stat( dirname( $fname ) );
$perms = $stat['mode'] & 0007777;
$perms = $perms & 0000666;
@ chmod( $fname, $perms );
clearstatcache();
$imageurl = $upload['baseurl'] . '/celtispack/icon/' . "{$keyid}.{$newext}";
$ogp['og:favicon'] = '<img width="16" height="16" src="' . $imageurl . '" class="favicon" />';
}
}
}
$thumbnail = '';
if(!empty($ogp['og:image'])){
$img = (!is_array($ogp['og:image']))? $ogp['og:image'] : $ogp['og:image'][0];
$imgsize = array('width' => 150, 'height' => 150);
if(!empty($ogp['og:url']) && !preg_match("#$myurl#", $ogp['og:url'])){
//自サイト以外の画像はサムネイルを生成してキャッシュ
if(class_exists('Celtispack_thumbnail', FALSE)){
$module = Celtispack_thumbnail::thumbnail_module_instance();
$value = $module->getmake_thumbnail_size( $img, $imgsize, 'thumbnail');
if (false !== $value)
$img = $value['url'];
}
}
$default_attr = array(
'src' => $img,
'class' => "ogp-thumb",
'alt' => '',
);
$imgattr = wp_parse_args($imgsize, $default_attr);
$imgattr = array_map( 'esc_attr', $imgattr );
$thumbnail = rtrim("<img ");
foreach ( $imgattr as $name => $value ) {
$thumbnail .= " $name=" . '"' . $value . '"';
}
$thumbnail .= ' />';
}
if(!empty($ogp['og:title']) && !empty($ogp['og:url'])){
$og_title = (!is_array($ogp['og:title']))? $ogp['og:title'] : $ogp['og:title'][0];
$og_url = (!is_array($ogp['og:url']))? $ogp['og:url'] : $ogp['og:url'][0];
$grid = (empty($thumbnail)) ? 'no-thumb' : 'with-thumb';
$html .= '<div class="card-wrapper"><div class="card-wrapper-inner">';
//カードコンテント
$html .= '<div class="card-content ' .$grid .'">';
$html .= '<h2 class="card-title"><a href="' . $og_url . '" target="_blank">' . $og_title . '</a></h2>';
if(!empty($ogp['og:description'])){
$og_dsc = (!is_array($ogp['og:description']))? $ogp['og:description'] : $ogp['og:description'][0];
$html .='<div class="card-description">' . $og_dsc . '</div>';
}
$html .= '</div>';
if(!empty($thumbnail)){
$html .= '<div class="thumb-wrapper"><a href="' . $og_url . '" target="_blank">' . $thumbnail. '</a></div>';
}
//カードフッター
if(!empty($ogp['og:site_name'])){
$og_site = (!is_array($ogp['og:site_name']))? $ogp['og:site_name'] : $ogp['og:site_name'][0];
$html .= '<div class="card-footer"><a href="' . $og_url . '" target="_blank">' . $ogp['og:favicon'] . ' ' . $og_site . '</a></div>';
}
$html .= '</div></div>';
//html カスタマイズ用のフィルターフック
$html = apply_filters( 'oembed_ogp_dataparse', $html, $ogp, $url );
}
if(empty($html)){
//head からタイトルを取得してリンクを生成
preg_match('#<title>(.+)</title>#', $head[1],$match);
$title = (!empty($match[1]))? esc_html($match[1]) : esc_html($url);
$html = '<span class="embed-link"><a href="' . esc_url($url) . '">' . $title . '</a></span>';
}
}
}
return $html;
}
OGPが複数設定されている場合には、最初のデータを使用しています
OGPデータがない場合は title タグのデータを使って通常のリンクを作成します
カスタマイズを行いたい場合は、oembed_ogp_dataparse というフックポイントを設けていますので、はてブ数を取得するコード等を盛り込むことなどに使えると思います
※OGPデータのパース処理は GitHub で公開されていたコードを使わせて頂いてます
コンパクトでとても扱いやすいライブラリです
PHP Open Graph Library (/php-ogp)
ブログカードの CSS
CSSは私の作成している Celtis-one というテーマに合わせてありますが、他のテーマでも概ね問題なく表示できる思います
CSSは苦手なのですが、はてなブログカードぽく仕上がった気がします (^^)
CSSコード
/*
oEmbed OGP ブログカードのスタイル
カードサイズは一回り大きいが Hatenaブログカードに近いスタイルにする
*/
.card-wrapper:after,
.card-wrapper:before {
content: ' ';
display: table;
}
.card-wrapper:after {
zoom: 1;
clear: both;
}
.card-wrapper {
margin: 0px 0px 1.7rem;
overflow: hidden;
width:100%;
max-width:600px;
max-height:202px;
border: 1px solid;
border-color: #eaeaea #dddddd #d0d0d0;
border-radius: 5px;
background-color: #fff;
background-clip: padding-box;
}
.card-wrapper a,
.card-wrapper a:visited {
color: #369ecf;
border: none !important;
text-decoration: none;
}
.card-wrapper a:focus {
outline: thin dotted;
}
.card-wrapper a:hover {
text-decoration: underline;
}
.card-wrapper-inner {
padding: 12px;
}
.card-wrapper * {
word-wrap: break-word;
}
.card-content {
float: left;
padding-top: 0;
display: inline;
max-height: 150px;
overflow: hidden;
}
.card-wrapper .no-thumb {
width: 100%;
margin: 0 0 12px 0 !important;
}
.card-wrapper .with-thumb {
width: 72%;
margin-right: 1.8%;
margin-bottom: 12px;
}
.card-wrapper .thumb-wrapper {
float: left;
padding-top: 0;
width: 26.2%;
max-height: 150px;
margin: 0 0 12px 0 !important;
}
.card-content .card-title {
font-size: 16px;
line-height: 1.5;
max-height: 48px;
overflow: hidden;
margin: 0 0 3px;
padding: 0;
border: none;
background: none;
}
.card-content .card-title a {
color: #333333;
}
.card-content .card-description {
font-size: 12px;
line-height: 1.4;
max-height: 50px;
overflow: hidden;
}
.card-footer {
zoom: 1;
clear: both;
margin-top: 8px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
height: 15px;
position: relative;
font-size: 11px;
}
.card-footer img {
display: inline !important;
}
.card-footer a,
.card-footer a:visited,
.card-footer a:hover,
.card-footer a:focus,
.card-footer a:active {
color: #999999;
}
表示が崩れる場合は CSSで調整してみてください m(__)m
ちなみに記事作成時のビジュアルモードでもカード表示されるように TinyMCE エディターにもCSSをロードしていますので、こんな感じで表示されます

以上
WordPress 4.0 以上で使用できる「はてなブログカード」のような埋め込み機能のプログラムを紹介しました
まだ不具合等があるかもしれませんが、よろしければ試してみてください (^^)
“WordPressで「はてなブログカード」のような埋め込み機能作成” への2件のコメント