できる!Snow Monkey カスタマイズ(基礎・初歩編)

「Snow Monkey」は、プラグインでカスタマイズを自由に出来るテーマですが、カスタマイズ方法は他のテーマと違ってプラグインで行うという事から、何が出来るのか、そしてどうやるのかの確立は、まだまだ共有されていない事が多々あるように思えます。

そこで、私が行ったカスタマイズの紹介を導入として、どのようにしてカスタマイズをしたのかを初歩編として、この記事で色々とまとめてみました。

基礎知識として…

カスタマイズ、何が出来るのかをご紹介

単純に、テーマをカスタマイズすると言っても何の事か解らないんで…
私がカスタマイズしてきた事を下記に一覧でまとめてみました。

私がカスタマイズしてきた機能

当サイトに適用中のものが下記。

  • ドロワーメニューを右にする(Hot Springs Customize Extendでのサンプル)
  • トップページだけメニュースタイルをオーバーレイ
  • カスタマイザーに「追加Javascript」を追加
  • 記事一覧の一覧スタイルに「オリジナル」スタイルを追加
  • 関連記事のスタイルを記事一覧のスタイルと別のスタイルを適用出来るようにカスタマイザー を拡張
  • お知らせバーのスタイルと機能を拡張(日付、表示期間…その他)
  • 記事を読む時間の追加

プラグインで機能を公開しているものが下記。

  • 「検索条件」の追加
  • パスワードフォームの「パスワードの可視化」
  • 移動した記事の候補表示
  • Cookie法の下部表示
  • (タイトルなし)などの際のパンくず表示の変更
  • Snow Monkey Member Postの適用投稿タイプの変更
  • Snow Monkey Member Postのメッセージ変更
Hot Springs Customizeにて上記の機能は使用できます。

別途、作業で作ったものが下記。(未配布、未公開)

  • 記事の一部分を、パスワードでロック
  • 記事の一部分を、ログインしないと見れなくする
  • Snow Monkey Member Postでのログイン・登録・サブスクリプションフォームのカスタマイズ
  • Snow Monkey Member Postでログイン後に期限が切れている場合にサブスクリプション誘導
  • Snow Monkey Member Postでログイン後にメンバー有効期限が近づいている場合、「残り●日でメンバーが一般に戻ります」警告、サブスクリプション誘導

※ 赤文字で記載している機能は後述のHTML構造を置換しているカスタマイズです。

初歩カスタマイズ

カスタマイズの流れの基本

テーマのテンプレートに対してフックを掛けて、HTML構造を置換する事でカスタマイズしている流れが基本です。
その基本は、上記で書いたカスタマイズの赤文字で記載している機能でも使われている手法です。また一覧に記載していないカスタマイズにおいても、その流れは大きく変わりません。
その方法を覚えておくだけでも、上記の一覧で、赤文字で記載した機能に似たカスタマイズをする事は可能です。また、応用次第ではもっと凄いカスタマイズを実現可能でしょう。

カスタマイズのための準備

実際にカスタマイズを行う際に、2行程ほど準備をしておくと、よりカスタマイズがしやすくなります。ここではカスタマイズをする為に、カスタマイズするための準備を行いましょう。

まず、テンプレートにフックを掛ける際に便利になる設定を行います。

WordPressのデバッグ設定を有効にする

wp-config.php
define('WP_DEBUG', true);
を追加します。
追加する位置は、$table_prefix = 'wp_';と書いてある行の下あたりでOKと思います。

このデバッグ設定を有効にする事で、Google chromeやSafariで、デベロッパーツールやWebインスペクタでHTML構造を見た際に、何のテンプレートを読み込んでいるのかがコメントアウトで表示されるようになります。

デバッグ設定を有効にして、Snow Monkeyテーマを適用しているサイトを、SafariでのWebインスペクタで要素を見た場合…

Startから始まるコメントアウト表記で、そのHTMLを表示する為に読み込まれているテンプレート名が記載されているので、非常に解りやすいです。個人的な要望としては、Endも欲しいですが… 追記:Snow Monkeyテーマ v5.7.0からはEndも対応されました。神!!)

My Snow Monkeyプラグインを入れておく

2つ目の準備として、開発用のプラグインを入れる必要があります。

プラグインにfunctionや処理を加えていくので、自分でカスタマイズ用のプラグインを作っていない場合は、Snow Monkey公式が用意しているカスタマイズ用の雛形プラグイン「My Snow Monkey」を開発環境に入れて、有効にしておきましょう。

My Snow Monkeyプラグインの入手方法は、Snow Monkey公式サイトのマイアカウントからダウンロード出来ます。
※ Snow Monkeyのサブスクリプションが必要です

Snow Monkey 公式サイトのマイアカウント

置換するカスタマイズを試す(実践)

それでは、試しに「この記事を書いた人」と言う、記事の下に出る部分を記事の上に表示するようにするカスタマイズをやってみましょう。

※ 説明用の簡易的なカスタマイズ機能です。実用性は無いかもしれません。
実際に、プロフィールボックスを表示しないだけなら、カスタマイザーの「記事にプロフィールボックスを表示する」設定をオフにすれば良いです。今回は、説明用にその機能をあえて使っていません。

どうすれば出来るのかを考える

どんなやりたい事でもそうだと思いますが、やりたい事を実現する為にはどうすれば出来るのかを考えなければなりません。

この場合のやりたい事と言うのは、記事の下に出る「この記事を書いた人」部分を、記事の上に表示するようにするです。
その為にはどうすればよいでしょうか?
考えられる1つの案として、

1. 記事の下に出ている「この記事を書いた人」部分を削除する
2. 記事の上に同じように表示するようにする

と言う事を行えば良いと考えられます。この方法でやってみましょう。

削除する為に表示されているテンプレートを確認

まずは、「この記事を書いた人」が表示されているエリアが、どんなテンプレートファイルで表示されているのかを調べます。
SafariのWebインスペクタで「この記事を書いた人」が表示されているエリアで読まれているテンプレートファイルを確認しましょう。

<!-- Start : template-parts/common/profile-box -->
と書かれているのが該当部分が見つかるでしょう。
なので、この記述から、Snow Monkeyテーマのディレクトリ内のtemplate-parts/common/profile-box.phpと言うテンプレートファイルが「この記事を書いた人」のエリアを表示する為に読まれているテンプレートファイルと言う事が解ります。
であれば、(このテンプレートファイルの読まれている記述を変えてしまえば良い?)と思うかもしれません。それも正解の一つです。
しかし、今回の場合はこのエリアを記事の上に表示する必要もあるので、この「この記事を書いた人」のエリアを表示しているテンプレートファイルを変更するのは、やや適切と言えないと思います。
その為、このエリアの表示を呼び出しているテンプレートを変えた方が良いと思います。
であれば、該当のテンプレートファイルを読み込んでいるであろう部分を探します。
テンプレートを読み込んでいる場所を探す為に、template-parts/common/profile-boxと言う単語で、Snow Monkeyのファイルを検索するのも良いのですが、Webインスペクタの一段上を見ると大体そのテンプレートファイルが読み込まれているテンプレートファイルが書かれています。今回の場合、一段上には<!-- Start : templates/view/content-post -->と書かれた記述が見つかりますので、それが、そのテンプレートファイルになります。
実際に開いてみると、Helper::get_template_part( 'template-parts/common/profile-box' );と書かれているので、このファイルで間違いない事も解るでしょう。

置換する為のフックの事

テーマに対して何かの動作を追加や変更するには、Snow Monkey テーマフックを使います。
Snow Monkey テーマフックとは、Snow Monkeyのテーマ用に作られているSnow Monkeyテーマのオリジナルのフックです。(説明用に、勝手に命名)
一覧には、Filter Hookと書かれていますが、Action Hookもあるので、私はテーマフックと呼ぶようにしています。(フィルタフックとアクションフックの違いは、別記事を参照してください)
Snow Monkeyテーマフックの一覧は、公式のgithubリポジトリ内のWikiにあります。

Snow Monkeyのフック一覧(作者のドヤ顔が出てますが、フック一覧の説明記述は、ほとんどケミが勝手にやった事…)

今回は「テンプレートの記述を置換する」事が目的なので、その為に使うフックとしては、該当のテンプレートパーツの出力を変更するフックであるsnow_monkey_get_template_part_$args[‘slug’]でしょう。
フック一覧でサンプルコードや説明が記載されているのですが、こう言ったリファレンスや一覧を読むのに慣れている人でなければ、フック一覧から目的の機能やフックを見つける事は難しいかもしれません。どのフックを使ったら良いのか迷ったりした場合は、それこそ公式サポートフォーラムで聞けば良いのです。

フックを使って、テンプレートパーツの出力を変更する為に

使うフックも解り、いよいよHTML記述の置換をする…となりましたが、どのように置換をすれば良いのでしょう?
テンプレートパーツの出力を変更するとは一体…どう言う事でしょうか?

フック一覧のサンプルコードの記述を一度見てみましょう。

add_action(
	'snow_monkey_get_template_part_templates/view/404',
	function( $name, $vars ) {
		\Framework\Helper::get_template_part( 'templates/view/404' );
		$html = ob_get_clean();
		ob_start();
		$html = mb_ereg_replace(
			'書き換え前の文字列',
			'書き換え後の文字列',
			$html
		);
		echo $html;
	},
	10,
	2
);

とありますね。
しかし、今回はtemplates/view/content-postのテンプレートの出力を変更したいので、フックを変更しなければなりません。
今回、変更対象となるtemplates/view/content-postの最後の-postはslugですので、フックとしてはtemplates/view/contentとなり、functionの$nameの部分にslugが入る事になります。
なので、コードとしては…

add_action(
	'snow_monkey_get_template_part_templates/view/content',
	function( $name, $vars ) {
		if ( $name === 'post' ) {
			echo 'test';
		}
	},
	10,
	2
);

となります。
実際に、このコードを実行すると、記事の部分がtestと言う記述に置換される事が確認できるでしょう。そうなっている場合は、置換は正しく出来ています。
しかし、カスタマイズ目標の「この記事を書いた人」が表示されているエリアを変更する事が出来ていませんので、それを行う為に進みましょう。

置換を行う為にテンプレートを読む(失敗するケース)

テンプレートを置換する為には、その置換を行いたい出力の文字列を取得する必要があります。
Snow Monkeyでは、\Framework\Helper::get_template_part( テンプレート名, スラグ名, 渡したい情報配列 );と言うファンクションが用意されているので、それを使いましょう。
このファンクションは、対象のテンプレートを処理した後の出力文字列を返却するファンクションです。
早速、サンプルコードを基にコードを書いてみてください。
書いてみると、おそらく下記のコードのようになると思います。

add_action(
	'snow_monkey_get_template_part_templates/view/content',
	function( $name, $vars ) {
		$html = ob_get_clean();
		ob_start();
		\Framework\Helper::get_template_part( 'templates/view/content', $name );
		if ( $name === 'post' ) {
			$html = mb_ereg_replace(
				'この記事を書いた人',
				'テスト',
				$html
			);
		}
		echo $html;
	},
	10,
	2
);

このコード、何だか上手くいきそうですが、実際に実行してみると…記事ページが表示されなくなるか記事部分が真っ白になるか、動作が出来ないはずです。

何故、置換に失敗したのかを考えてみよう

何故、上記のケースでは失敗するのでしょうか?
それは、\Framework\Helper::get_template_part( 'templates/view/content', $name );に、原因があります。
\Framework\Helper::get_template_partは、テンプレートの結果を取得するファンクションです。
一方、snow_monkey_get_template_part_$args[‘slug’]のSnow Monkey テーマフックは…テンプレートの結果を変更するフックです。
テンプレートの結果を取得する際に、その結果を変更する為にフックがあれば、フック結果をApply(適用)します。そうする事でテンプレートの変更結果が反映される仕様となっています。

その為、失敗のケースが失敗する理由は、\Framework\Helper::get_template_partでテンプレートを取得するのですが、その際にテーマフックを実行、テーマフック内で同じテンプレートを取得する為、テーマフックが実行され…と、無限ループに陥ってしまっている事で起こってしまうのです。

\Framework\Helper::get_template_partで、テンプレート結果を取得しようとする
snow_monkey_get_template_part_$args[‘slug’]のSnow Monkey テーマフックがあるか調べ、ある場合は適用(Apply)される

失敗するケースの場合、フック内に
\Framework\Helper::get_template_partが記述されているので1に戻り…繰り返してしまう。

置換を行う為にテンプレートを読む(成功するケース)

では、どのようにして成功のケースになるようにすれば良いでしょうか?
考えられる事としては「この無限に適用されるフックをしないようにすれば良い」となるのではないでしょうか?

フックを適用しないようにするにはフックを削除すれば良いです。
WordPressにはフックを削除する関数として、remove_filterと言うものが用意されています。remove_filterの詳しい詳細は、WordPress公式サイトの関数リファレンスを参照してください。(ここでは詳しく解説しません)

しかし、remove_filterは無名関数には使用できません。
なので、まずはSnow Monkeyテーマフックで行う処理を別のファンクションで書くように変更する事にします。
そして、remove_filterを処理するようにしましょう。
その結果、下記のようになりました。(※ まだ完成形ではありません)

add_filter(
		'snow_monkey_get_template_part_templates/view/content',
	'_templates_view_content',
	10,
	2
);

function _templates_view_content( $name, $vars ) {
	$_is_removed = remove_filter(
		'snow_monkey_get_template_part_templates/view/content',
		'_templates_view_content',
		10
	);
	if ( $_is_removed ) {
		\Framework\Helper::get_template_part( 'templates/view/content', $name );
		$html = ob_get_clean();
		ob_start();
		if ( $name==='post' ) {
			$html = mb_ereg_replace(
				'この記事を書いた人',
				'テスト',
				$html
			);
		}
		echo $html;
	}
}

実行してみると、「この記事を書いた人」のテキストが「テスト」に変わっている事が確認できると思います。(解りやすくする為に仮の置換処理を入れています)

コードの説明に入りましょう。
まず、_templates_view_contentファンクション(関数)を作成し、フックの処理をその関数で行うように変更しました。
そして、関数内では、remove_filterで該当関数のフックを削除しています。
これを行う事で、1度実行された後には、再度このフックの処理は実行されないようになります。
しかし、正しくフックが削除されていない場合は無限ループになる為、$_is_removedで削除された結果を取得し、成功している場合のみ表示するようにしています。(失敗している場合は表示されなくなりますが、そもそも正しく表示する処理が出来ていないと言う事ですので、その場合の事はあまり気にしなくていいと思います)

そして、テンプレートの取得処理を行います。フックを事前に削除しているので無限ループに陥る事なく取得がされます。
$htmlとして取得する際には、Snow Monkeyの公式サイトでも推奨されている書き方としても、ob_get_clean等を使用してhtml記述を取得しましょう。(※ 何故この書き方が必要なのかは、後述で)

最後に、分かりやすく記述していますが、取得した$htmlを、mb_ereg_replace等の関数で正規表現などを使用して文字列置換して結果を変更する事でカスタマイズが完成するようになります。

正しい置換にして、下部のエリアを削除する

この削除したい「この記事を書いた人」のエリアですが、<div class=”wp-profile-box”>から</div>で囲まれています。
HTML記述を置換する際に正規表現で置換をするのですが、その囲まれている間にも</div>がいくつもあるので、正規表現で削除するのは、実は一苦労だったりします。
その為、PHP Simple HTML DOM Parserなどのライブラリを使用して、取得した$htmlをDOM Parserで判定する手法を取る方が楽と思います。(※ 使用するならGoutteの方が良いでしょう)

ただ、テーマのカスタマイズにそのようなDOM Parserなどのライブラリを使うのは、パーサーが遅い分、ページ全体の表示速度を落としてしまいます。なので、出来るだけ使用せずにやってみる方法を取るのが良いです。
と言うことで…PHPの標準関数であるDOMを使います。

と言いたいのですが…DOM関数の話をし出すと、カスタマイズの本題から外れてしまうので、ここでは簡易的にコードを紹介し、説明は簡単なものに留めます。
ファンクションを下記のコードに変えてみましょう。

function _templates_view_content( $name, $vars ) {
	$_is_removed = remove_filter(
		'snow_monkey_get_template_part_templates/view/content',
		'_templates_view_content',
		10
	);
	if ( $_is_removed ) {
		\Framework\Helper::get_template_part( 'templates/view/content', $name );
		$html = ob_get_clean();
		ob_start();
		if ( $name==='post' ) {
			$domDocument = new DOMDocument();
			@$domDocument->loadHTML( $html );
			$xPath = new DOMXPath( $domDocument );
			foreach ( $xPath->query('//div[@class="wp-profile-box"]') as $node ) {
				$node->nodeValue = null;
			}
			$html = $domDocument->saveHTML();
		}
		echo $html;
	}
}

実行してみると、「この記事を書いた人」エリアが表示されなくなる(削除される)でしょう。
PHPネイティブのDOMDocumentとDOMXPathを使用し、class=”wp-profile-box”にあるdivのDOMNodeを削除したからです。(詳しくは、DOMDocument等のPHPリファレンスを調べてみてください)

いよいよ完成へ!上部に出す位置を考える

さて、下部から削除が出来ました。
なので、次は上部に「この記事を書いた人」エリアを出すようにしたいと思います。
表示したい場所を探しましょう。

大体は、<header class="c-entry__header">の閉じた後くらいに追加のHTMLを入れるとすれば、HTML構造的には合うと思います。しかし、そこに入れると少し表示が崩れてしまうと思いますので、追加のHTMLを入れるとすれば、<div class="c-entry__body">のすぐ後でしょうか?

上部に出す置換をやってみる(失敗するケース)

やってみましょう。今回はDOMパースをする必要はないので、素直に正規表現を使ってみましょう。
さて、置換する前に、置換する為の「この記事を書いた人」エリアを表示するHTMLをどのように書けば良いのか…を知らなくてはなりません。
そのエリアを表示しているのが、templates/view/content-post.phpですのでそれを開きます。
該当部分を探してみましょう。

<?php
		if ( get_option( 'mwt-display-profile-box' ) ) {
			Helper::get_template_part( 'template-parts/common/profile-box' );
		}
?>

見つかったのは上記の記述でした。と言うことは置換する箇所にはHelper::get_template_part( 'template-parts/common/profile-box' );と書けば、上手くいきそうな気がします。また、if文でget_option判定もしておくと良さそうです。ついでに判定も追加しちゃいましょう。
やってみましょう。

function _templates_view_content( $name, $vars ) {
	$_is_removed = remove_filter(
		'snow_monkey_get_template_part_templates/view/content',
		'_templates_view_content',
		10
	);
	if ( $_is_removed ) {
		\Framework\Helper::get_template_part( 'templates/view/content', $name );
		$html = ob_get_clean();
		ob_start();
		if ( $name==='post' ) {
			if ( get_option( 'mwt-display-profile-box' ) ) {
				$domDocument = new DOMDocument();
				@$domDocument->loadHTML( $html );
				$xPath = new DOMXPath( $domDocument );
				foreach ( $xPath->query('//div[@class="wp-profile-box"]') as $node ) {
					$node->nodeValue = null;
				}
				$html = $domDocument->saveHTML();

				$html = mb_ereg_replace(
					'<div class="c-entry__body">',
					'<div class="c-entry__body">' . Helper::get_template_part( 'template-parts/common/profile-box' ),
					$html
				);
			}
		}
		echo $html;
	}
}

Helperが見つからないとエラーが出るでしょう。
実は、このHelperは、Frameworkと言うクラス内に実装されています(Snow Monkey5以降)ので、使用する場合は\Framework\Helper::にしなければなりません。
Helper::\Framework\Helper::に変更して実行してみましょう。

表示されるようにはなりますが、ページの一番上にどどーんと出てしまっています。

何故、失敗した?

\Framework\Helper::get_template_partは、出力をする関数です。
そのままでは取得ではなく、出力してしまいます。その為、ob_get_cleanなどを使用して、出力をせずに変数に代入などをしなければなければなりません。
前述していたob_get_cleanなどを使用する理由は、実はこの為です。
なので、それを踏まえて再度修正しましょう。いよいよ完成です。

完成版!!

add_filter(
		'snow_monkey_get_template_part_templates/view/content',
	'_templates_view_content',
	10,
	2
);

function _templates_view_content( $name, $vars ) {
	$_is_removed = remove_filter(
		'snow_monkey_get_template_part_templates/view/content',
		'_templates_view_content',
		10
	);
	if ( $_is_removed ) {
		\Framework\Helper::get_template_part( 'templates/view/content', $name );
		$html = ob_get_clean();
		ob_start();
		if ( $name==='post' ) {
			if ( get_option( 'mwt-display-profile-box' ) ) {
				$domDocument = new DOMDocument();
				@$domDocument->loadHTML( $html );
				$xPath = new DOMXPath( $domDocument );
				foreach ( $xPath->query('//div[@class="wp-profile-box"]') as $node ) {
					$node->nodeValue = null;
				}
				$html = $domDocument->saveHTML();

				\Framework\Helper::get_template_part( 'template-parts/common/profile-box' );
				$replaceHtml = ob_get_clean();
				ob_start();

				$html = mb_ereg_replace(
					'<div class="c-entry__body">',
					'<div class="c-entry__body">' . $replaceHtml,
					$html
				);
			}
		}
		echo $html;
	}
}

これが完成版となると思います。実行してみると、記事上部に「この記事を書いた人」が表示されるように変更出来たことでしょう。

最後に

今回の機能では実用性はありませんが、カスタマイズの流れは理解されたのではないかと思います。DOMの削除、置換…これらを上手く使う事で表示したい位置に表示したいHTMLに変更する事もできると思います。是非お試しください。
わからない部分があれば、お問い合わせか、Snow Monkey オンラインコミュニティ等にご質問されるなどすれば、答えられると思います。(作者のキタジマさんに迷惑が掛からない程度でお願いしますw)

この「できる!Snow Monkeyカスタマイズ」記事ですが…
今後定期的にシリーズ化していくかもしれません。
(まだいっぱい書く事がありましたが、ブロックが壊れるくらいに長く書いてしまっているのでそれは別記事にします!)
ではまた!!

この記事を書いた人

kmix-39

kmix-39

主に日本の関西に出没する。
小学時代にWindows 95に触れ、BASICを始めて以来、HTML、Perl、Java(JSP, Servlet)、COBOL、C++、C#、PHP、Objective-C、Swift…と、数々の言語を地獄の現場を経験しながら覚えていった元IT社畜。
「好きな時に 好きなことを 好きなだけ」をしたい。