以前、記事中の任意の文字列をブログカードに変換できるような仕組みを、プラグインを使わず実装しました。

ですが、外部サイトについては用意されているOGP(Open Graph Protocol)画像、いわゆるアイキャッチ画像への直リンクとなっていたので、WP_Filesystem()を使い、対象となる 外部リンクのアイキャッチ画像を自サーバーに取り込んでから使えるように改造 しました。

当サイトで実装しているブログカードは、以下のような仕様です。

2020年9月にWordPressからHugoに切り替えました。そのため、現環境ではブログカードへの自動変換機能は使っていません。参考までに情報を残しております。

投稿画面での編集中に、ブログカードにしたいリンク情報を、独自のclassを指定した<span>タグで以下のようにして囲んでおくと、

1
<span class="bcard ilink"><a href="/make-the-original-blog-card/" title="【WP】プラグインなしで内部リンクのブログカードを実装する方法 | 日なたの縁台" target="_blank">【WP】プラグインなしで内部リンクのブログカードを実装する方法 | 日なたの縁台</a></span>

以下のようなブログカードに公開時にはなっている。というものです。

WordPress以外の環境に移った場合でもリンク情報をそのまま使えるようにしておきたくてこのような仕様にしました。

ただ冒頭にも書いたとおり、外部サイトをブログカードにしたときのアイキャッチ画像だけは、そのままでは使用に問題がある状態になっていたので、そこをきちんと使えるように直しました。

以下、手直ししたプログラムです。

さらにキャッシュを利用しより高速に読み込めるよう改良したものはこちら▼
【WP】プラグインなしでの自作ブログカードの表示速度を上げるために取った方策

目次

実装した外部用ブログカードが抱えていた問題点

内部リンク(自分のサイト内でリンクを張ったもの)のブログカードについては、自分のサーバーからアイキャッチ用の画像を読み込ませるので、以前作ったプログラムで何の問題もありませんでした。

一方、外部リンクのブログカードについては、そのサイトで設定されているOGP画像への直リンクとなってしまう仕様になっており、外部サイトのサーバーに負荷をかけてしまうという、とてもヨロシクナイものとなっていました。

▼ 内部リンクのブログカード変換プログラムを作ったときの記事はこちら
【WP】プラグインなしで内部&外部リンクのブログカードを実装する方法

【直リンクNGな訳】
外部サイトのアイキャッチ画像が直リンクになっていると、外部サイトのブログカードが含まれた記事を開くときに、その記事が置かれているサーバーだけでなく、外部サイトが置かれているサーバーにも「情報を渡してください」という要求が出ることになり、相手方のサーバーにも負荷をかけてしまうことになるので良くないです。

なので、整備したブログカードへの変換機能を使うのは自分の記事に対してだけにし、外部サイトへのリンクはブログカード化せずに使っていました。

ですが、以前作ったプログラムでもすでに、外部リンクについてのOGP情報までは取得出来ているのだから、なんとか活用したい…!

ということで、色々調べて試してきた結果、ひとまず形になったので、プログラムをまとめました。

外部サイトのOGP画像を自サーバーに保存してから利用することについて、著作権的にどうなんだろう? とは気になり、ちょっとググって見たりしたのですが、しっかりリンクが張られ出典が明記されていればOKでは? という雰囲気のようです。

WP_Filesystem() で解決できた

PHPで外部サイトにある画像を保存する方法を調べてみると、「file_get_contents()でデータを取得し、file_put_contents()で保存すればよい」ということが分かりました。が、WordPress環境でそれらの関数を使おうすると警告が出るとのこと。

代わりに、セキュリティ面(ファイルのPermissionやOwnership)が考慮されたWP_Filesystem()を使うことが推奨されていました。

実装プログラム

そんな訳で、外部サイトのOGP画像(アイキャッチ画像)取得問題を解決するために、まずfunctions.php内に新たなオリジナル関数cb_fetch_ogp_image_file()を定義しました。

functions.phpの編集前は必ずバックアップを取り、いつでも元の状態に戻せるようにしておくことを忘れずに。

基本的には、file_get_contents()file_put_contents()で行える内容を、WP_Filesystem()によるものに置き換えるためのプログラムを作っています。あとはプラスαとして、画像ファイルの拡張子判定やリサイズなども行っています。(と、さらっと書いていますが、ここもだいぶ試行錯誤しました…笑)

具体的には、

  1. 外部サイトのOGP画像のURLを取得できていたら、そのURLから画像データを取得
  2. 拡張子判定やリサイズをした上で自サーバーに保存
  3. 最後に、自サーバーに保存した画像のURLを返す

という処理をさせるプログラムを書きました。

以下、そのコードです。

コードの流れや説明は、コメント部分に記述しています。また使用した関数のマニュアルへのリンクを、参考情報として @link: に書きました。
またコメント部分に記載すると、さすがに冗長になりすぎるかな? と判断した情報については、「コードの補足」として分けて書きました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* 2018/11/21: ファイル名を付ける部分のプログラムを変更(データのhashではなく、ファイルパスのhashへ) */
/**
* 最初のif文で、定義されている関数名に重複がないか確認します。
* 当サイトはドメインが「cosybench.com」なので、接頭辞的な「cb_」を関数の頭につけることで、重複避け対策をしています。
**/
if (!function_exists('cb_fetch_ogp_image_file')){ //子テーマを使っている場合、推奨

// ここから関数本体
/**
* 外部サイトのOGP:imageに設定されている画像を、自分のサーバーの任意のディレクトリに保存する
* @param   string   image_url (OGP:imageのURL)
* @return  string   path(自サーバーのファイルへのURLパス)
**/
function cb_fetch_ogp_image_file ( $image_url ) {
  /*
  * WP_Filesystem()を使うためには「file.php」が必要になるので、読み込みます。
  * 「image.php」は画像を編集するwp_get_image_editorで必要になるので、ここで一緒に読み込んでいます。
  */
  //WP_Filesystemを使用するためのファイルの読み込み
  require_once(ABSPATH . 'wp-admin/includes/file.php');
  require_once(ABSPATH . 'wp-admin/includes/image.php');

  // サーバーの画像を保存するディレクトリを設定
  /*
  * FTPソフトを使い、あらかじめ「uploads」フォルダ内に「elink-img」という専用フォルダを作っておく方式にしました。
  * フォルダのパーミッションは、自分のみ読み書きOK、その他は読み取り専用にします。
  */
  $dir = ABSPATH . 'wp-content/uploads/elink-img/'; //ABSPATHで、サーバー上の「public_html」フォルダまでの絶対パスが取得される(取得されない場合はwp-config.phpファイルの設定を確認)

  /* OGP画像のURLパスから、ファイル名を付けます。 */
  // @link: http://php.net/manual/ja/function.hash.php
  $image_name = hash('md5', $image_url, FALSE); // ファイルURLからファイル名を決める

  /* [elink-img」フォルダ内にある画像ファイルのリストを取得します */
  // @link: http://php.net/manual/ja/function.glob.php
  $image_lists =  glob($dir .'*', GLOB_NOSORT); // $dirでのファイルのリストを取得

  // @link: http://php.net/manual/ja/function.preg-grep.php
  $file_existance = preg_grep("/$image_name/", $image_lists); //ファイルの有無を確認

  if (!$file_existance): //ファイルがまだ作られていなかったら、画像を取得・保存する
      /*
      * WP_Filesystem()の認証に問題が発生しなければ、$wp_filesystemというグローバル変数を使えるようになります
      */
      if ( WP_Filesystem() ): //WP_Filesystemの初期化
          global $wp_filesystem; //$wp_filesystemオブジェクトの呼び出し

          /*
          * WP_Filesystem_Direct()クラスに用意されているメソッドを使います。
          * @link: https://developer.wordpress.org/reference/classes/wp_filesystem_direct/
          * == コードの補足(1) 参照==
          */
          $image_data = $wp_filesystem->get_contents($image_url); // 画像のバイナリデータを取得(PHPのfile_get_contents()と同じ)

          /*
          * $wp_filesystem->get_contents($image_url) で画像データが取得できたら、そのデータから拡張子の判定をしています。
          * == コードの補足(2) 参照==
          * そして最後に、取り込んだ画像のリサイズをしています。
          */
          if ($image_data): //画像データが取得できたら

              // MIME Typeをチェック
              // @link: http://php.net/manual/ja/function.finfo-open.php
              // @link: http://php.net/manual/ja/function.finfo-buffer.php
              // @link: http://php.net/manual/ja/fileinfo.constants.php
              $finfo = finfo_open(FILEINFO_MIME_TYPE);
              $mime = finfo_buffer($finfo, $image_data);
              finfo_close($finfo);

              switch ($mime) { //拡張子の決定、現在は以下の4タイプの画像のみ可(bmpはなくてもよいかもしれない)
                  case "image/jpeg":
                      $extension = '.jpg';
                      break;
                  case "image/png":
                      $extension = '.png';
                      break;
                  case "image/gif":
                      $extension = '.gif';
                      break;
                  case "image/bmp":
                      $extension = '.bmp';
                      break;
                  default:
                      echo "対応していない画像ファイルです";
                      break;
              }

              //画像を保存(PHPのfile_put_contents()と同じ)
              //ファイル名には、接頭辞的に「cb_」を付けています
              $wp_filesystem->put_contents($dir. "cb_" .$image_name. $extension, $image_data);

              //画像の編集(リサイズ)
              //@link: https://developer.wordpress.org/reference/classes/wp_image_editor/
              $image_editor = wp_get_image_editor($dir. "cb_" .$image_name. $extension);

              if ( !is_wp_error($image_editor) ): //画像編集オブジェクトに画像が正常に読み込めたら
                  // @link: https://developer.wordpress.org/reference/classes/wp_image_editor/resize/
                  $image_editor->resize(400, 400, false); //画像のリサイズ(W400×H400 cropせず との設定.ここは用途に応じてお好みでOK)
                  // @link: https://developer.wordpress.org/reference/classes/wp_image_editor/save/
                  $image_editor->save( $dir. "cb_" .$image_name. $extension ); //編集した画像の保存
              else:
                  echo "image_editorでエラーが発生しました";
              endif;

              $upload_dir = wp_upload_dir(); //「uploads」フォルダまでのURLパスを取得する
              // @link: https://developer.wordpress.org/reference/functions/wp_upload_dir/
              return $upload_dir['baseurl'] . "/elink-img/cb_" .$image_name. $extension;

          else:
              echo "画像データ取得できませんでした";
          endif;

  else: // ファイルがあったら、ファイル名を取得する

      //ファイル名を配列($file_existance)からを取り出す
      //@link: http://php.net/manual/ja/function.array-shift.php
      $filename = array_shift($file_existance); //ファイル名は、ローカルパス($dir/$image_name.$extension)形式 になっている

      //@link: http://php.net/manual/ja/function.str-replace.php
      $filename = str_replace($dir,"",$filename); //ファイル名から$dirの文字列を削除する

      //URL形式でファイルへのパスを設定する
      //@link: https://developer.wordpress.org/reference/functions/wp_upload_dir/
      $upload_dir = wp_upload_dir();
      return $upload_dir['baseurl'] . "/elink-img/" .$filename;  // https://example.com/wp-content/uploads/elink-img/filename となる

  endif; // WP_Filesystem() 終了
} // 「function cb_fetch_ogp_image_file($image_url)」 閉

} // 「if (!function_exists('cb_fetch_ogp_image_file')){」 閉

コードの補足(1): $wp_filesystemで使えるメソッド

WP_Filesystem()は、プラグイン開発をされている方々にはお馴染みのもののようですが、私は「お初お目にかかります」だったので、これで合ってるのかな? と、首をひねりつつ使っています。

特にプラグイン周りでこの関数を使う場合は、セキュリティを考慮し、SSHやFTPなど、どのような通信方式でファイルの移動や書き込みを行うかという設定をするそうなのですが、adminとしてファイル操作を行うならば、そのような認証は必要ないようです。

そしてその場合、WP_Filesystem()とコールして使えるようになるグローバル変数$wp_filesystemから使えるメソッド(methods)は、WP_Filesystem_Directクラスで用意されているものになるのだと思われます。
(特にこの部分の理解に自信がないのですが、デバッグ中に吐き出されるエラーメッセージを見ている限りでは、そのようでした)

▼ ちょっと情報は古いかもしれないですが、認証まわりに関することが書かれています。
ファイルシステムAPI – WordPress Codex 日本語版

WP_Filesystem_Directクラスと用意されているメソッド一覧
WP_Filesystem_Direct | Class | WordPress Developer Resources

▼ 他にも、WP_Filesystem_BaseWP_Filesystem_SSH2クラスなどがありますが、それらは使えませんでした。

コードの補足(2): ファイルの名前付けや拡張子の判定について

当初は、外部サイトのOGP画像に元々付けられているファイル名から、PHP関数のsub_str()strpos()などを利用してファイル名の取得と拡張子の判定を行っていたのですが、まれに拡張子が付けられていないOGP画像があったり、ファイル名に特色のないものもあったりして。

なので、元のファイル名をそのまま使っていると、近い将来、ファイル名が重複してしまう日が来るかも? と思ったので、外部リンクのOGP画像のURLからファイル名を設定するようにしました。ファイル名はhash(ハッシュ)化しているため、無味乾燥な文字列になります。

実装済だったブログカード変換用プログラムの微修正

あとは、過去記事で実装していたプログラム内で定義していたoriginal_blog_card()関数を、1箇所だけ修正しました。

修正箇所は、プログラム内の「外部リンクの場合」部分で、opengraph.phpというオープンソースを利用して、外部サイトのOGP情報から画像へのリンクを取得していたところです。

以下、該当部分のみ抜粋しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(省略)
/* 該当部分のみの抜粋 */
elseif ($m[1] == 'e') : //外部リンクの場合(external)
  //設定されているOGP情報を利用する
  //@link https://github.com/scottmac/opengraph
  require_once('opengraph.php');

  $graph = OpenGraph::fetch($url);
  if ($graph) : //OGP情報が設定されていたら
      $title = $graph->title ? $graph->title : $m[3];
      $excerpt = $graph->description ? $graph->description : '';
      /** ここ($src = ~)を修正↓ **/
      // 修正前: $src = $graph->image ? $graph->image : get_template_directory_uri().'/images/ogp_no_image.jpg';
      // 修正後:
      $src = $graph->image ? cb_fetch_ogp_image_file($graph->image) : get_template_directory_uri().'/images/ogp_no_image.jpg';
      $thumbnail = '<div class="blog-card-thumbnail"><img src="' .$src. '"alt="' .$m[3]. '"></div>';
  endif;
  $date       = ''; //外部リンクは投稿日なし
  $target     = 'target="_blank"'; //外部リンクは新規タブで

endif;
(省略)

旧バージョンのプログラムでは$graph->imageで外部サイトのOGP画像への直リンクになってしまっていた部分を、外部サイト用のブログカードのアイキャッチ画像のリンクを、自サーバーに保存された画像データのリンクに置き換えます。

具体的には、今回新しく定義したcb_fetch_ogp_image_file()関数に、$graph->imageで取得される外部サイトのOGP画像のURLを引数として処理させています。

▼ 今回の実装で、下のブログカードに使用している画像が自サーバーに保存されたものになっています。

まとめ

そんなこんなで、オリジナルブログカードの実装例をまとめた記事を公開してから4ヶ月以上経ってしまいましたが、ようやく、相手方(外部サイト)のサーバーに負荷をかけることなく、外部リンク先で設定されていたOGP画像を自サーバーに保存して、ブログカードのアイキャッチ画像として使えるようになりました。

いまのところ、特に目立った不具合は生じていませんが、もしかすると迂遠なコードになっていたり、盛大に間違えていたりする部分もあるかもしれませんので、参考にされる場合はどうか自己責任でよろしくお願いいたします。

以上、外部サイトをブログカード化するとき、外部サイトのアイキャッチ画像をどのように処理すればいいか問題の解決策でした。

今回自分が実装しようとして検索してみた限りでは、WP_Filesystem()を使ってアレコレやっている日本語で書かれた事例もまだまだ少ないようだったので、どなたかの参考になれば幸いです。

さらに改造

ブログカードの表示に必要なデータの読み込みに時間がかかっていたので、キャッシュを利用するようにしました。 set_transient()、get_transient() 関数を使用。

以下の記事もぜひご覧ください。