baserCMS 多言語対応サイト(サブサイト)で個別の404エラーページを正しく出力する方法
baserCMS4系で、サブサイト機能を利用した多言語サイトを制作しているケースで、各言語ごとに個別の404エラーページを正しく出力することがなかなかむずかしい。
というのも、あるフォーラムのトピックで取り上げられていたイシューの通り、BcBaser->contentsMenu()を利用してグローバルメニューを出力している場合は、正しくサブサイトの言語のグローバルメニューが出力されるが、
BcBaser->globalMenu()とか、BcBaser->getTitle()とか、 BcBaser->crumbsList()とか(たぶん他にもあると思いますが)をdefault、あるいは各言語のエラーページ用のレイアウトファイルで利用している場合は、サブサイト閲覧時に呼び出される404エラーページになぜかメインサイトのコンテンツ(つまり日本語のコンテンツ)が出力されてしまいます。
そこで、BcBaserHelper.phpのBcBaser->contentsMenu()とBcBaser->globalMenu()のコードの違いを見比べてみたところ、BcBaser->globalMenu()のコードでは、どうもエラーページで正しくsite_idを参照できていない様で、おそらくBcBaser->getTitle()やらBcBaser->crumbsList()についても同様の事情で上述の不具合が生じているようでした。
また、過去のgitHubのclosedプルリクを漁ってみたところ、イシュー自体は異なるものの、BcBaser->contentsMenu()のみエラーページでsite_idを参照できるように改修された経緯が見当たりました。
以上のことから、BcBaserHelper.phpのこれらの関数について、エラーページでsite_idを参照できるように改修すれば、おそらく問題は解決するのですが、如何せんBcBaserHelper.phpはコアファイルだし、ましてや4系だし、ということでユーザー側でできる解決策を考えてみることにしました。
要件を整理
まず、404エラーページに表示するコンテンツの要件を以下のように整理してみます。
- 後述の
BcBaser->globalMenu()のコードの改修措置をエラーページ用のレイアウトファイルに反映させるため、エラーページ用のレイアウトファイルを用意する。 - パンくずは、
BcBaser->crumbsList()を使用しない。 <head></head>内で利用しているタイトル表示は、BcBaser->getTitle()を使用しない。
考察
上述の 1.項で触れたBcBaser->globalMenu()コードの改修措置としてパッと思いつく方法は、利用しているテーマ内(/theme/利用しているテーマ/Helper/BcBaserHelper.phpか/app/View/Helper/BcBaserHelper.phpでもOK)にBcBaserHelper.phpのコピーを置いて、それを独自に改修する方法。ただ、この方法はbaserCMSの非常にコアなBcBaserHelper.phpというヘルパーファイルが後のアップデートから取り残されてしまうリスクを伴うのですが、4系は既にメンテナンスフェーズに入っているため、今後アップデートもおそらくないと思われ、実のところこの方法でも特に問題はないかとも思います。
あるいは、独自のテーマヘルパーを作成する方法があるので、今回はせっかくなので独自テーマヘルパーを作成して対応してみようと思います。
エラーページ用のレイアウトファイルについては、メインサイトのレイアウトのdefault.phpをコピーして、サブサイト用のテーマ内のLayoutsフォルダ内にerror.phpとして配置します。
2.項のパンくずは、エラーページにおいてそれ自体の必要性をあまり感じませんが、表示させるとしたら「トップページ>404 NOT FOUND」程度のことなので、各言語のエラーページ用のレイアウトファイルにHTMLを直書きすれば良いかと思います。
3.項も2.項と同様です。エラーページに<head></head>は、通常のサイトページで考慮するようなSNS用のメタタグなどは必要ないので、これもシンプルにHTMLを直書きすれば良いかと思います。
手順
- まずは /lib/Baser/View/Helper/BcBaserHelper.phpの1847行目辺りのglobalMenu()のコードを以下のように一部改修し、/theme/利用しているテーマ/app/View/Helper/OwnThemeHelper.phpというファイル名のテーマヘルパー用のファイルとして保存します。
後は、サブサイト内のレイアウトファイルやエレメントファイルのBcBaser->globalMenu()で呼び出していた箇所をOwnTheme->globalMenu()に変更する。
<?php
class OwnThemeHelper extends BcBaserHelper {
/**
* BcBaserHelperを継承してテーマ独自のメインイメージを出力する
* テーマ内のみで使用: $this->OwnTheme->globalMenu()
*/
/**
* グローバルメニューを出力する
*
* @param array $level 取得する階層(初期値 : 1)
* @param array $options オプション(初期値 : array())
* ※ その他のパラメータについては、View::element() を参照
* @return void
*/
public function globalMenu($level = 1, $options = [])
{
echo $this->getGlobalMenu($level, $options);
}
/**
* グローバルメニューを取得する
*
* @param array $level 取得する階層(初期値 : 1)
* @param array $options オプション(初期値 : array())
* ※ その他のパラメータについては、View::element() を参照
* @return string
*/
public function getGlobalMenu($level = 1, $options = [])
{
$Content = ClassRegistry::init('Content');
if (isset($this->request->params['Content']['site_id'])) {
$siteRoot = $Content->getSiteRoot($this->request->params['Content']['site_id']);
} else {
$siteRoots = $Content->find('all', [
'conditions' => [
'Content.site_root' => true
], 'recursive' => -1]);
foreach ($siteRoots as $key => $siteRoot) {
if ($key === 0){
$siteRootSub = $siteRoot;
continue;
}
if (strpos($this->request->here, $siteRoot["Content"]["url"]) !== false) {
break;
}
$siteRoot = $siteRootSub;
}
}
$id = $siteRoot['Content']['id'];
$currentId = null;
if (isset($this->request->params['Content']['id'])) {
$currentId = $id;
}
$options = array_merge([
'tree' => $this->BcContents->getTree($id, $level),
'currentId' => $currentId,
'data' => [],
'cache' => false
], $options);
if (BcUtil::loginUser()) {
unset($options['cache']);
} else {
if ($options['cache'] === false) {
unset($options['cache']);
} else {
$options = array_merge($options, [
'cache' => [
'time' => Configure::read('BcCache.duration'),
'key' => $id]]
);
}
}
$data = array_merge([
'tree' => $options['tree'],
'currentId' => $options['currentId']
], $options['data']);
unset($options['tree'], $options['currentId'], $options['data']);
return $this->getElement('global_menu', $data, $options);
}
}
-
次に用意するファイルは、エラーページ用のレイアウトファイル。/theme/利用しているテーマ/Layouts/default.phpをサンプルにして、/theme/利用しているテーマ/Layouts/error.phpを作成。
「要件を整理」項で示した通り、パンくずは、BcBaser->crumbsList()を使用せず、例えば以下のようにHTMLを直書きします。<div class="bs-crumbs"> <ul itemscope itemtype="https://schema.org/BreadcrumbList"> <li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"> <a href="https://trial.basercms.net/" itemprop="item"> <span itemprop="name">Home</span></a><span class="separator"> > </span> <meta itemprop="position" content="1"/> </li> <li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"> <span itemprop="name">Error</span> <meta itemprop="position" content="2"/> </li> </ul> </div><head></head>内も可能な限りシンプルにして、タイトル表示は、BcBaser->getTitle()を使用せず、例えば<title>404 Not Found|Sample Theme</title>といった具合にHTMLタグを直書きします。 -
最後に各言語毎のerrorページ用のテンプレートファイル。/lib/Baser/View/Errors/error400.phpをコピーして、/theme/利用しているテーマ/Errors/error400.phpとして保存。サンプルは以下。
<?php
/**
* baserCMS : Based Website Development Project <https://basercms.net>
* Copyright (c) baserCMS Users Community <https://basercms.net/community/>
*
* @copyright Copyright (c) baserCMS Users Community
* @link https://basercms.net baserCMS Project
* @package Baser.View
* @since baserCMS v 0.1.0
* @license https://basercms.net/license/index.html
*/
?>
<?php $this->layout = "error"; ?>
<h2><?php echo $message; ?></h2>
<p class="error">
<strong><?php echo __d('baser', 'エラー'); ?>: </strong>
<?php printf(
__d('baser', 'アドレス %s に送信されたリクエストは無効です。'),
"<strong>'{$url}'</strong>"
); ?>
</p>
<?php
if (Configure::read('debug') > 0):
echo $this->element('exception_stack_trace');
endif;
<?php $this->layout = "error"; ?>とすることで、先に作成した/theme/利用しているテーマ/Layouts/error.phpを参照するようになります。
error500.phpほか、その他のerrorページ用ファイルも適宜同様に作成してください。
もし、使用しているテーマのグローバルメニューがBcBaser->globalMenu()を利用していて、多言語化されているのであれば、是非試してみてください。
追記
改修が反映されない時は、/app/tmp/cacheフォルダをフォルダごと削除して、ブラウザをリロードしてみてください。
HATTANTOCO