以前書いた所からもう少し先に進んで、やってみたかった「AMP用のファイルをフォルダにまとめる」というものを作ったのでメモとして。
色々勉強になった反面、どういう形が正解なのかわからず、動きはすれど不安は消えず、という感じです。
なお、プラグインの作成はほとんど初めてに近く、敬遠していたphpのクラスも試したりと、コード的にはおかしな作りになっているかもしれません。
また記事も相当に長いので、その意味でもあまりお勧め出来ない記事です。
実現したいこと
実現したいことは以下の通り。
- WordPressのAMP対応
- AMP用のファイルをまとめる
- エンドポイントで対応
- プラグインで対応
- カスタムのベースとなるように簡易な形で構築
- AMP化の対象は投稿ページと固定ページ
当初はプラグインにしなければならないと考えていたのですが、調べるうちにプラグインの形では無くとも実現できたのでは、と思っています。
とはいえテーマを触らずに済むという点で、プラグイン化は理にかなっているとも考えています。
以前のAMP化手法に関しては以下をご覧下さい。
参考資料
本題のAMPからはズレますが、プラグイン作成の勉強用兼参照用資料として以下の「サイトの拡張性を飛躍的に高める WordPressプラグイン開発のバイブル」を挙げておきます。
AMP対応に関する具体的な仕組みは以下のプラグインの中を見て参考にしました。
プラグインのファイル構造
冒頭に述べましたが、プラグイン作成は素人なので、以降の内容の正確性はまったく保証できません。
まず初めに構造ですが、のっけから「これでいいのか?」と悩んでいる部分です。
- amp_plugin(プラグイン用のディレクトリ)
- – amp_plugin.php(プラグインの名称がついたファイル)
- – inc(設定などファイルが入ったフォルダ)
- – amp-function.php(主にコンテンンツ内の置換と余計な読み込みの解除)
- – amp-temp-setting.php(ampテンプレートの読み込み用設定)
- – temp(AMP用のテンプレート)
- – amp-header.php(amp用のheadなど)
- – amp-footer.php(amp用のfooterなど)
- – amp-single.php(amp用の投稿ページ)
- – amp-page.php(amp用の固定ページ)
- – amp-sidebar.php(amp用のサイドバー)
- – images(amp用の画像)
基本的に、AMPのためにフックや正規表現でいろいろ設定するのはincフォルダにまとめて、出力用のsingle.phpやpage.php的なテンプレートはtempフォルダにまとめる形です。
投稿ページも固定ページも一つのindex.phpで対応は可能ですし、サイドバーが不要なら削除は可能ですし、この辺りは各サイトごとに自由に変更できます。
コード
自分用のメモなので今回もサクサク進めますが、コードを記載します。
amp_plugin.php
if ( !defined('ABSPATH') )
defined( 'ABSPATH' ) or die( 'Nope, not accessing this' );
define( 'AMP_PLUGIN', __FILE__ );
define( 'AMP_DIR', untrailingslashit( dirname( AMP_PLUGIN) ) );
define( 'AMP_DIR_PATH', trim( plugin_dir_url( __FILE__ ), '/' ) );
define( 'AMP_QUERY_VAR', 'amp' );
//ampのエンドポイント追加とamp用セッティング
add_action( 'init', 'amp_init' );
function amp_init() {
add_rewrite_endpoint( AMP_QUERY_VAR, EP_PERMALINK | EP_PAGES );
add_action( 'wp', 'amp_render' );
}
//プラグイン有効化の際にamp_initのアクションでエンドポイント追加
register_activation_hook( __FILE__, 'amp_activate' );
function amp_activate() {
if ( ! did_action( 'amp_init' ) ) {
amp_init();
}
flush_rewrite_rules();
}
//プラグインの無効化でエンドポイント削除
register_deactivation_hook( __FILE__, 'amp_deactivate' );
function amp_deactivate() {
global $wp_rewrite;
foreach ( $wp_rewrite->endpoints as $index => $endpoint ) {
if ( AMP_QUERY_VAR === $endpoint[1] ) {
unset( $wp_rewrite->endpoints[ $index ] );
break;
}
}
flush_rewrite_rules();
}
//ampの判定用関数
function is_amp_endpoint()
{
return false !== get_query_var( AMP_QUERY_VAR, false );
}
//amp用セッティング
function amp_render()
{
$is_amp_endpoint = is_amp_endpoint();
if ($is_amp_endpoint){
require_once( AMP_DIR . '/inc/amp-temp-setting.php' );
require_once( AMP_DIR . '/inc/amp-function.php' );
$ampfunc = New Ampfunc;
$ampfunc->register();
$amptemp = New Amptemp;
$amptemp->register();
} else {
//メインテーマの方にAMPページへ向けてのcanonicalを出力する関数を呼ぶ
add_action( 'wp_head', 'amp_frontend_add_canonical');
}
}
//メインテーマの方にAMPページへ向けてのcanonicalを出力する関数
function amp_frontend_add_canonical()
{
if(is_singular()){ //投稿と固定と添付ページだけを対象とする場合
$amp_url = get_the_permalink() . '/amp';
printf( '<link rel="amphtml" href="%s" />', esc_url( $amp_url ) );
}
}
//amp用のサイドバーを追加
add_action( 'widgets_init', 'amp_widgets_init', 100 );
function amp_widgets_init() {
register_sidebar( array(
'name' => esc_html__( 'AMP-Sidebar', '_s' ),
'id' => 'amp-sidebar',
'description' => '',
'before_widget' => '<aside id="%1$s" class="widget %2$s">',
'after_widget' => '</aside>',
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
) );
}
上記のコードで主に参考したのは、前出のものを除き以下のサイトやページです。
- WordPressの投稿や固定ページなどのURLの後ろに「/json」「/amp」などでアクセスできるようにエンドポイントを追加する方法
- Rewrite APIその1 「Rewriteとパーマリンク」(WordPressプラグイン開発のバイブルのボツ原稿から)
- Rewrite APIその2 WordPressでアプリを作る基本(WordPressプラグイン開発のバイブルのボツ原稿から)
以前はパラメーターでしたが、リスト1番目の今村さんのブログでエンドポイントの件を知り、それからこの方法に作り替えたいなと思っていましたので、実行した次第です。
AMPの仕組みとしてはパラメーターもエンドポイントも替わりはないはずですので、そういう意味ではこだわる必要はないと思いますが。
エンドポイントの有効化と無効化
エンドポイントの設定を有効や無効にするための方法の一つは、管理画面のパーマリンクの画面で「変更を保存する」ボタンを押すことです。
これは前項の今村さんのブログで記載されています。
別の方法として、flush_rewrite_rulesを使う方法があり、今回はこれを以下のようにして使いました。
- register_activation_hookで、プラグインを有効化した時に反映させる
- register_deactivation_hookで、プラグインを無効化した時に反映させる
正直なところ管理画面で一手間という点に違いはないのですが、なんとなくそれっぽいかなと。
なお、flush_rewrite_rulesはコーデックスにて以下のように書かれています。
- リライトルールの再生成はとても高コストな操作です。 ‘init’ フックでこれを実行することを勧めるチュートリアルや例が存在しますが、 バッドプラクティスです。
- リライトルールの再生成は有効化、無効化、または明示的にリライトルールを変更する必要があるときのみにすべきです。 常にトリガされるすべてのフックで実行しないでください。もっと詳細な情報は WP Engineer’s post: Custom Post Type and Permalink のコメント欄にあります。
- 特にプラグインの有効化時には、リライトルールのフラッシュをする前に、カスタム投稿やタクソノミーがちゃんと登録されていることを確かめて下さい。: Activation Checklist for WordPress Plugin Developers
また、以下のサイトでも警告されています。
当然かなり重いと言うのもあるんですが、これをよくわからないタイミングで実行されるとリライトルールがバグったりします。
サンプルコードをコピー&ペーストしかしていない場合、致命的な問題を引き起こしている可能性がありますね。
AMP用のサイドバーを設定
AMP用のサイドバーを設定し、プラグインが有効化された際に追加させています。
サイドバーの中身を投稿内容と同様にthe_contentにフックしても当然効果が及びませんが、AMP用にサイドバーを別けて手動で直せば比較的簡易に対応が可能です。
手作業で行うため手間はかかるのですが、その分シンプルで制御がしやすい状態かなと思います。
amp-function.php
慣れないクラス化を試みていますが、この程度の規模ならやらなくても良いのかなと思いますし、作り方がこれで良いのかもまだよく分かっていません。
調べて試して作っていますが、正解や完成形がわかっている作業ではないので。
正規表現はとりあえず動けば的なもので、以前のAMP化に用いたものをほぼ流用しています。
記事中にコードがあるサイト
当ブログのように、記事中にhtmlやphpなどのコードがあるサイトは、コード部分をサニタイズして出力していないと大きな問題となりますが、指定方法によってはそれでも問題が発生します。
前回のAMP化で参考にしたコードの場合だと、例えば本文中に書いた「style」という文字列まで消してしまう状態でした。
今回のAMP対応でこの問題にようやく気がつき、負荷増大は覚悟で正規表現を変更し、以下のようにhtmlタグ内の要素を消すように修正しました。
$content = preg_replace('/<(.*?)style=".*?"(.*?)>/', '<$1$2>', $content);
ただし、これでも完璧とは思えず不安は残ります。
メインテーマでバッファしたものはプラグイン側で置換できない
今回のプラグインだけでは対応できず、使用しているテーマに手をいれたのが以下の部分です。
- the_contentにフックし、バッファを用いて読み込ませていたphpファイルを、バッファを使わない形に変更する
具体的に言えば、以下のようなコードです。
add_filter('the_content','add_content_bottom',15,1);
function add_content_bottom( $the_content ) {
ob_start();
get_template_part('footer-parts');
$output_string = ob_get_contents();
ob_end_clean();
return $the_content . $output_string;
}
メインテーマ内で、the_contentで渡されるコンテンツのデータに対しob_startとob_end_cleanをつかって何らかの内容を追加している場合、この部分はプラグイン側で同じようにthe_contentにフックしても効果が及びません。
基本的な対策としては、使うだけで値が出力される関数ではなく、値の取得だけが可能な関数を使うなどでしょうか。
バッファではなく単純に結合されただけのデータであれば、プラグイン側で置換も可能です。
WordPress WP Social Bookmarking Light
メインテーマで WordPress WP Social Bookmarking Light のプラグインを使っている場合、前項の解決策を用いるために一手間かかります。
作り方にもよりますが、 WordPress WP Social Bookmarking Light を表示させたい箇所に以下のコードを記載して出力させているとします。
<?php wp_social_bookmarking_light_output_e(); ?>
AMP対応をプラグインだけでなんとかしたいという場合(つまりプラグインを取除いても問題が起きない状態が必須)、AMP用ではないソーシャルボタンを出力する WordPress WP Social Bookmarking Light は以下のような動作をさせなければなりません。
- PCでは、WordPress WP Social Bookmarking Light でボタンを出力し、AMPでは出力させないようにする
上記を実現するには、前項を踏まえて以下のような状態にする必要があります。
- the_contentにフックさせて追加する
- バッファが使えないので、出力ではなく値だけ取れる形にする
この部分に関しては、以下のサイトの情報が非常に助かりました。
プラグインではなくメインテーマ内を改造する必要があるので、メインテーマのfunctions.phpに上記サイトの以下を記載します。
function get_my_social_bookmark() {
if( function_exists( 'wp_social_bookmarking_light_output' ) ) {
$options = wp_social_bookmarking_light_options();
$services = $options['services'];
return wp_social_bookmarking_light_output( $services, null, null );
} else {
return '';
}
}
プラグイン内のコードを見てwp_social_bookmarking_light_optionsがwp_social_bookmarking_light_output_eの中で呼ばれていることはわかったのですが、それがうまく利用できず諦めかけていました…。
記事中で注記があるように、プラグインのバージョンアップで使えなくなる可能性がありますので、それまでの対策と割り切れば十分に有用な方法でした。
amp用の要素があるかどうかチェックして読み込ませるjsを制御する
2017年1月9日に知りましたが、AMP用のiframeやYoutube動画などを動作させるためのjsは、必要な時にのみ読み込ませないとエラーになる模様です。
なお、1月9日の時点では、Seasrch Console のAMPエラーのページで以下のような警告文が出てました。
「amp-twitter extension .js script」で必要なタグ「amp-twitter」がないか、正しくありません。これはまもなくエラーとなります。
「amp-iframe extension .js script」で必要なタグ「amp-iframe」がないか、正しくありません。これはまもなくエラーとなります。
「amp-youtube extension .js script」で必要なタグ「amp-youtube」がないか、正しくありません。これはまもなくエラーとなります。
この件については以下のページで解説が記載されています。
当記事にある自作プラグインでも対応を行うため、以下のコードをamp-function.phpに追加しています。
上記の追加部分を出力するために、amp-header.phpのjs設置位置に対して、以下のように独自フックを登録しています。
<script async src="https://cdn.ampproject.org/v0.js"></script>
//独自のフックを登録し、ここに出力させる
<?php apply_filters('amp_js', 'amp_js'); ?>
<script async custom-element="amp-social-share" src="https://cdn.ampproject.org/v0/amp-social-share-0.1.js"></script>
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
まったくスマートではありませんが現状では他に良い案が思いつかずでした。
問題に感じたのは、以下の2つが区別しがたい点です。
- youtubeのiframe
- youtubeではないiframe
この問題の解決策として、一番良いのはAMP用に置換済みのコンテンツを利用することだと考えました。
なぜ置換後のコンテンツを使うことが解決策になるのかと言えば、Youtubeはamp-youtubeに、Youtubeではないiframeはamp-iframeに置換ずみだからです。
通常のiframeではなくamp-youtubeとamp-iframeを検出して分岐に用いれば、問題は簡単に解決できます。
しかし、head内に設置したフックの位置的にthe_contentにフックして置換したものを利用する方法の実現が難しく、私のスキルでは形に出来ませんでした。
そこで、わざわざget_post_field('post_content')でAMP用の置換前のコンテンツを取得し、2度手間承知で置換を行い、半ば無理矢理iframeの区別を付けて分岐に用いています。
ちなみに、この記事には置換用コードの説明為にamp-youtubeという文字列が記載されていますが、Youtube自体は存在しません。
この記述に反応してYoutube用のjsが表示されてしまっているのですが、この状態でもAMPのバリデーターではエラーは出ませんでした。
厳密なようで適当な判定に思えますが、単に今のこのタイミングだからOKなのかとは考えています。
もっと良い仕組みを思いつくか、どなたかの記事を参考にさせていただける時が来れば、この部分は作り直したいと思います。
get_post_field()
なお、get_post_field('post_content')に関しては、以下のページで知った情報を利用さていたきました。
WordPressでは、投稿に関するデータを取得する方法が複数存在し、どれが使えるのかではなく、どれを使うべきかという判断に迷うことが良くあります。
今回に関しては単に値を取得できればよかったので、取得のみが可能な関数を知れたのは大きかったです。
strpos()
分岐の条件となるiframeやYouTubeなどの文字列を検出するためにstrpos()を使っていますが、検出したかどうかの判断方法として以下のように記述しています。
if( strpos($content ,'<amp-youtube') !== false ){ ... }
この形にしている理由は、以下のページがわかりやすいかと思います。
説明は上記ページにお任せし、該当箇所だけ以下の引用します。
strpos() は、見つからなかった場合、false を返しますので、「!== false」で判定。
amp-temp-setting.php
if ( !defined('ABSPATH') )
defined( 'ABSPATH' ) or die( 'Nope, not accessing this' );
//amp_plugin.phpでAmptempのインスタンスを作成している
class Amptemp {
public function register() {
add_filter( 'single_template', array($this, 'amp_template_single'));
add_filter( 'page_template', array($this, 'amp_template_page'));
}
//投稿ページ用にテンプレートを変更する
public function amp_template_single( $template )
{
if ( is_amp_endpoint() ) {
$object = get_queried_object();
if ( 'post' == $object->post_type ){
$template = AMP_DIR . '/temp/amp-single.php';
}
}
return $template;
}
//固定ページ用にテンプレートを変更する
public function amp_template_page( $template ) {
if ( is_amp_endpoint() ) {
$object = get_queried_object();
if( 'page' == $object->post_type ) {
$template = AMP_DIR . '/temp/amp-page.php';
}
}
return $template;
}
こちらも練習を兼ねてクラスで作成しています。
エンドポイントと同様に個人的には今回のメイン的な部分である、AMP用のテンプレートファイルを一つにまとめて用いる仕組みの部分です。
この仕組みは以下のサイトを参考にしました。
以前に自力でああだこうだと試して諦めましたが、上記記事を見たからこそ今回のプラグイン化を実行できたとさえ言えます。
single_template(とpage_templateもですが)は初めて知りました。
そもそもだけど、これ使い道ないし、たぶん the_content フックを使う事の方が多いと思う。というくらいに役に立たない記事でした。
上記のように書かれていますが、私には非常に有益な情報でとても感謝しています。
amp-single.php
今回はAMP用テンプレートとしてamp-single.phpとamp-page.phpの2つを用意していますが、構造的に両者に違いはありませんので、amp-single.phpのみ記載します。
また、amp-header.phpやamp-footer.phpに関しては、以前のものとほぼ同じなので省略します。
<?php
//AMP_DIRはamp_plugin.phpで定数として設定済み
include_once( AMP_DIR .'/temp/amp-header.php'); ?>
<div id="primary" class="content-area">
<main id="main" class="site-main" role="main">
<?php while ( have_posts() ) : the_post(); ?>
<?php get_template_part( 'template-parts/content', 'single' ); ?>
<?php endwhile; ?>
</main><!-- #main -->
</div><!-- #primary -->
<?php include_once( AMP_DIR .'/temp/amp-sidebar.php'); ?>
<?php include_once( AMP_DIR .'/temp/amp-footer.php'); ?>
今回は、wp_headを使わずにphpファイルとしてincludeしています。
そのため、wp_headにフックして動作するプラグインなどは一切動かず、不具合がでる可能性があります。
反面、以下のような問題を回避可能です。
- AMPでは警告が出るコードを出力するプラグイン
- 無効化しないと排除できないコードを出力するプラグイン
wp_headを使用しない結果として、AMP用のhead内には以下を手動で記述する必要があります。
- titleタグ
- オリジナルページへのcanonicalタグ
上記はwp_headがあれば自動で出力(設定にもよります)されますが、wp_headがないと対象となるフックがみつからず出力が実行されないためです。
wp_headの不使用に関して
プラグイン側で登録を解除できるjsなどで構成されたプラグインならよいのですが、そうでないものは結局メインテーマに手を入れなければならず、汎用性を考えるとこの形しか思いつきませんでした。
なお、一応ですが以下の処置を行えばwp_head()ありと同じようにプラグインを動作させることは可能だと思います(一部は動作を確認しました)。
- amp-header.phpにdo_action(wp_head)を記述
- amp-footer.phpにdo_action(wp_footer)を記述
もっと詳しくとなると、以下のサイトが参考になるはずです。
上記サイトにも以下のように書かれています。
WordPressらしさ。フックしてなんぼのWordPressのなかでも重要なwp_head()を外す行為は他人にしたらいい迷惑。なので仕事では使えません。
便利なプラグインが使えない場合がある。
やらなくてもわかりますが、これはそれほどいいものではありませんw
今回はAMP専用ということで許容範囲だと判断していますが、私もメインテーマの通常利用に関してこの仕組みを使おうとは思えませんでした。
結び
概ね以上です。
ここまでの内容では分かりにくいかと思いますが、実際には各サイトごとに合せて細かく調整を施す必要があります。
特に、AMPに不要なものを削る場合、メインテーマに対して使われているプラグインが多ければ多い程困難はますでしょう。
場合によってはプラグインの使用自体をやめないと対応できないかもしれません。
ここまでやっても、ニュース枠にでも出ない限りはアクセスアップにはほぼ繋がらないでしょうし、オーガニックで表示されても検索結果から1ページ(ランディングするページ)にしか効果がなく、便利で効果的なウィジェットなども消さざるを得ないことを考えると、やはりAMPはあまりお勧めできません。
ちなみに、一部ページ(「cssのcontentに使う日本語をunicodeに変換する用フォーム」など)は対応できていないのですが、そもそもAMPエラーだとキャッシュされないはずですので、今は放置しています。
サイト構造からみたAMP
モバイル対応とAMP対応は状況が似ているように見えますが、構造から考えると大きな違いがあります。
- モバイル対応は、レスポンシブを採用すれば根幹となる構造を変えずに対応可能
- AMPは、根幹となる構造を必ず変更する必要があり、これから先もずっとコストがかかる
言葉にするのが難しいのですが、AMP専用のタグなどを使っている時点で、レスポンシブのような普遍的なコードにはなり得ず、いつまでたっても手間がかかるという印象です。
例えば、過渡期にはWPtouchのように状況に対応できる変換プラグインが出回るのはAMPも同じなのですが、モバイル対応のためにプラグインなどの変換手段ははもはや必須ではありません。
しかし、AMPは変換手段が手放せません。AMPフォーマットだけでサイトを作るなら変換は不要ではありますが。
AMPプロジェクト側が、サイトのソースから自動でAMPフォーマットにして出力するような状態でもない限り、この状況はかわらないはずです。
しかし当然ながらそんな仕組みは非常に難しいでしょうし、結局現状がベターな選択なのだろうだとも考えています。
0人がこの記事を評価
役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。
連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。