テーマから管理画面テンプレートを上書きできない問題
ステータス: 未解決
customAdminTheme を使ったコア修正のアプローチは、これまでに2パターン試したが
両方とも実機で管理画面が壊れることを確認した(詳細は「原因2」参照)。
現時点で安全に上書きする方法は見つかっていない。本番相当の環境にこのアプローチを
適用しないこと。
現象
plugins/{テーマ名}(config.php で type => 'Theme')配下に
templates/Admin/element/... のようなファイルを配置し、baserCMS公式ドキュメント
(https://baserproject.github.io/5/theme/dashboard)の案内通りに管理画面テンプレートを
上書きしようとしても、常にコア本体(例: vendor/baserproject/bc-admin-third/templates/Admin/...)
側のファイルが使われてしまい、テーマ側のファイルが一切反映されない。
原因1: customAdminTheme がテーマに対して機能しない(コアのバグ)
- 管理画面の描画に使われる
BaserCore\View\BcAdminAppView は、コア管理テーマ
bc-admin-third をCakePHPネイティブの「テーマ」($this->theme)として保持している。
- CakePHPの
View::_paths() は、テーマのパス($themePaths)を常にプラグイン自身の
パスより優先して検索する。そのため element('{テーマ名}.Xxx/yyy') のように
プラグイン指定で要素を呼んでも、bc-admin-third 自身が持つ同名ファイルが先に見つかり、
{テーマ名} 側のファイルには到達しない。
- baserCMSは「他プラグインが管理画面テンプレートを上書きする」ケースのために
BcApp.customAdminTheme という設定を用意しており、これが正しく機能していれば
bc-admin-third のテーマパスより前にプラグインのパスを割り込ませることができる
(array_unshift で先頭に追加されるため)。
- しかし、その判定処理(
vendor/baserproject/baser-core/src/View/BcAdminAppView.php の
_paths())は次のようになっている。
$customAdminTheme = Configure::read('BcApp.customAdminTheme');
$plugins = Hash::extract(BcUtil::getEnablePlugins(), '{n}.name');
if (!$customAdminTheme || !in_array($customAdminTheme, $plugins)) {
return $paths; // ここで早期リターンし、上書きパスが追加されない
}
BcUtil::getEnablePlugins() は、DBの plugins テーブルに status=true で登録された
行のみを返す。
- 一方、テーマ(
config.php の type => 'Theme')は BcPlugin::applyAsTheme()
(sites.theme カラムを更新するのみ)で適用され、plugins テーブルには一切登録されない
(BaserCorePlugin::addTheme() が getEnablePlugins() を経由しない別経路でロードするため)。
- 結果として、
{テーマ名} のようなテーマでは手順4の判定が常に失敗し、
customAdminTheme を設定していても上書き用のパスが優先順位に追加されない。
これは実装上の考慮漏れによるbaserCMSコアのバグである。
原因2: フロント/管理画面兼用テーマでは、修正しただけでは危険(実機検証で判明)
原因1を修正して customAdminTheme を有効にしただけでは、そのテーマがフロント側の
テーマも兼ねている場合、管理画面全体のレイアウトが崩れる。 実際に検証して確認した。
仕組み
CakePHPは管理画面向けにテンプレート/レイアウト/要素を探す際、「Admin/付きパス」が
見つからなければ「Admin/無しパス」に同じルートディレクトリ内でフォールバックする
(プレフィックスのカスケード機能。Cake\View\View::_getSubPaths() の挙動)。
customAdminTheme の実装(後述の対処方法1)は、テーマのルート
(plugins/{テーマ名}/templates/)をそのまま検索パスの先頭に追加する。すると、
このカスケードが同じテーマのルート内で働いてしまい、Admin/layout/default.php が
無ければ、フロント用の layout/default.php に、Admin/Error/error500.php が
無ければフロント用の Error/error500.php に、というように誤ってフォールバックする。
フロントテーマは通常 layout/default.php・Error/*.php・element/*.php を大量に
持っているため、{テーマ名} がフロントテーマを兼ねている場合はほぼ確実にこの衝突が起き、
管理画面のレイアウトが(フロント用テンプレートに差し替わることで)崩れる。
対処方法(修正版・実機検証の結果、これも不十分と判明)
追加するパスを、テーマのルート(templates/)ではなく、あらかじめAdmin/まで
含めたパス(templates/Admin/)にすれば、カスケードのフォールバック先が常に
Admin/ 配下に留まり、フロント用テンプレートへの誤フォールバックは防げるはずだった。
// 対処方法1の array_unshift 部分(修正版)
array_unshift($paths, ROOT . DS . 'plugins' . DS . $theme . DS . 'templates' . DS . 'Admin' . DS);
しかし、実機で検証したところ、この修正版でも管理画面全体が崩れる現象が再現した。
テーマの templates/Admin/ 配下には上書き対象の1ファイル(例:
element/Dashboard/baser_news.php)しか存在しないにもかかわらず、管理画面のうち
テーマ側にテンプレートが無い画面まで含めて全体的に表示が崩れてしまう。
「Admin/配下にスコープすればフロントへの誤フォールバックだけを防げる」という理論上の
分析は、実際の挙動と一致しなかった。_paths()単体を読んだだけでは説明できない、
他の仕組み(例: テンプレートパスのキャッシュ、coreAdminThemeとの相互作用、
アセット/CSS・JS解決への影響など)が絡んでいる可能性があるが、原因はまだ特定できて
いない。オリジナル案・Admin/スコープ版のいずれも、実機検証で管理画面が壊れることを
確認しており、customAdminTheme を用いたコア修正のアプローチ自体を一旦保留し、
原因不明のまま本番相当の環境に適用しないこと。
対処方法(未解決・下記は実機検証で管理画面が壊れることを確認済み)
以下の3点は理論上すべて揃って初めて機能するはずだったが、実機検証の結果、
これを適用すると管理画面が壊れることを確認している(「原因2」参照)。現状は
参考情報として記録するのみで、このまま適用しないこと。
1. vendor/baserproject/baser-core/src/View/BcAdminAppView.php を修正する
_paths() の判定処理を、baserCMS独自の getEnablePlugins()(plugins テーブル基準、
テーマは対象外)から、CakePHP標準の Cake\Core\Plugin::isLoaded()
(実際にロードされているプラグイン/テーマかどうかの判定。テーマも対象に含まれる)に
変更する。あわせて、追加するパスを Admin/ まで含めたものにする(原因2の対処)。
// Before
$paths = parent::_paths($plugin, $cached);
$customAdminTheme = Configure::read('BcApp.customAdminTheme');
$plugins = Hash::extract(BcUtil::getEnablePlugins(), '{n}.name');
if (!$customAdminTheme || !in_array($customAdminTheme, $plugins)) {
return $paths;
}
$themes = [$customAdminTheme, Inflector::dasherize($customAdminTheme)];
foreach($themes as $theme) {
array_unshift($paths,
ROOT . DS
. 'plugins' . DS
. $theme . DS
. 'templates' . DS
);
}
return $paths;
// After
$paths = parent::_paths($plugin, $cached);
$customAdminTheme = Configure::read('BcApp.customAdminTheme');
if (!$customAdminTheme || !\Cake\Core\Plugin::isLoaded($customAdminTheme)) {
return $paths;
}
$themes = [$customAdminTheme, Inflector::dasherize($customAdminTheme)];
foreach($themes as $theme) {
array_unshift($paths,
ROOT . DS
. 'plugins' . DS
. $theme . DS
. 'templates' . DS
. 'Admin' . DS
);
}
return $paths;
_paths() 本来の意図(「customAdminTheme に指定された値が実在する読み込み済みの
プラグイン/テーマ名であること」の確認)に対して Plugin::isLoaded() の方が忠実であり、
通常プラグイン(type => 'Plugin')での既存の利用ケースには影響しない。
注意: これは vendor ファイルの直接編集(コアハック)である。composer update で
baserproject/baser-core を更新すると、この修正は上書きされて消えるため、更新の都度
再適用が必要になる。
2. 対象テーマの config/setting.php に設定を追加する
plugins/{テーマ名}/config/setting.php の BcApp 配列に1行追加する。
'BcApp' => [
// ...既存の設定...
'customAdminTheme' => '{テーマ名}',
],
3. 上書き先ファイルの配置場所
plugins/{テーマ名}/templates/Admin/... 配下に、上書きしたいコア側のテンプレートと
同じ相対パスでファイルを配置する(例: templates/Admin/element/Dashboard/baser_news.php)。
ディレクトリ名の大文字小文字(Admin は先頭大文字)を、コア側と正確に一致させること
(Linux等の大文字小文字を区別するファイルシステムでは、これがずれていると上書きされない)。
結論
現時点では、こちらの機能はbaserCMSのコアでは難しく、パネルを除外するようなループを記述しないと実現が難しいです。
テーマ内のsrc/Plugin.phpなどに直接ループを記述することでも対応は可能ですが、プラグインなどで外部に出してしまった方が、再利用が楽だと思われます。
テーマから管理画面テンプレートを上書きできない問題
ステータス: 未解決
customAdminThemeを使ったコア修正のアプローチは、これまでに2パターン試したが両方とも実機で管理画面が壊れることを確認した(詳細は「原因2」参照)。
現時点で安全に上書きする方法は見つかっていない。本番相当の環境にこのアプローチを
適用しないこと。
現象
plugins/{テーマ名}(config.phpでtype => 'Theme')配下にtemplates/Admin/element/...のようなファイルを配置し、baserCMS公式ドキュメント(https://baserproject.github.io/5/theme/dashboard)の案内通りに管理画面テンプレートを
上書きしようとしても、常にコア本体(例:
vendor/baserproject/bc-admin-third/templates/Admin/...)側のファイルが使われてしまい、テーマ側のファイルが一切反映されない。
原因1:
customAdminThemeがテーマに対して機能しない(コアのバグ)BaserCore\View\BcAdminAppViewは、コア管理テーマbc-admin-thirdをCakePHPネイティブの「テーマ」($this->theme)として保持している。View::_paths()は、テーマのパス($themePaths)を常にプラグイン自身のパスより優先して検索する。そのため
element('{テーマ名}.Xxx/yyy')のようにプラグイン指定で要素を呼んでも、
bc-admin-third自身が持つ同名ファイルが先に見つかり、{テーマ名}側のファイルには到達しない。BcApp.customAdminThemeという設定を用意しており、これが正しく機能していればbc-admin-thirdのテーマパスより前にプラグインのパスを割り込ませることができる(
array_unshiftで先頭に追加されるため)。vendor/baserproject/baser-core/src/View/BcAdminAppView.phpの_paths())は次のようになっている。BcUtil::getEnablePlugins()は、DBのpluginsテーブルにstatus=trueで登録された行のみを返す。
config.phpのtype => 'Theme')はBcPlugin::applyAsTheme()(
sites.themeカラムを更新するのみ)で適用され、pluginsテーブルには一切登録されない(
BaserCorePlugin::addTheme()がgetEnablePlugins()を経由しない別経路でロードするため)。{テーマ名}のようなテーマでは手順4の判定が常に失敗し、customAdminThemeを設定していても上書き用のパスが優先順位に追加されない。これは実装上の考慮漏れによるbaserCMSコアのバグである。
原因2: フロント/管理画面兼用テーマでは、修正しただけでは危険(実機検証で判明)
原因1を修正して
customAdminThemeを有効にしただけでは、そのテーマがフロント側のテーマも兼ねている場合、管理画面全体のレイアウトが崩れる。 実際に検証して確認した。
仕組み
CakePHPは管理画面向けにテンプレート/レイアウト/要素を探す際、「
Admin/付きパス」が見つからなければ「
Admin/無しパス」に同じルートディレクトリ内でフォールバックする(プレフィックスのカスケード機能。
Cake\View\View::_getSubPaths()の挙動)。customAdminThemeの実装(後述の対処方法1)は、テーマのルート(
plugins/{テーマ名}/templates/)をそのまま検索パスの先頭に追加する。すると、このカスケードが同じテーマのルート内で働いてしまい、
Admin/layout/default.phpが無ければ、フロント用の
layout/default.phpに、Admin/Error/error500.phpが無ければフロント用の
Error/error500.phpに、というように誤ってフォールバックする。フロントテーマは通常
layout/default.php・Error/*.php・element/*.phpを大量に持っているため、
{テーマ名}がフロントテーマを兼ねている場合はほぼ確実にこの衝突が起き、管理画面のレイアウトが(フロント用テンプレートに差し替わることで)崩れる。
対処方法(修正版・実機検証の結果、これも不十分と判明)
追加するパスを、テーマのルート(
templates/)ではなく、あらかじめAdmin/まで含めたパス(
templates/Admin/)にすれば、カスケードのフォールバック先が常にAdmin/配下に留まり、フロント用テンプレートへの誤フォールバックは防げるはずだった。しかし、実機で検証したところ、この修正版でも管理画面全体が崩れる現象が再現した。
テーマの
templates/Admin/配下には上書き対象の1ファイル(例:element/Dashboard/baser_news.php)しか存在しないにもかかわらず、管理画面のうちテーマ側にテンプレートが無い画面まで含めて全体的に表示が崩れてしまう。
「
Admin/配下にスコープすればフロントへの誤フォールバックだけを防げる」という理論上の分析は、実際の挙動と一致しなかった。
_paths()単体を読んだだけでは説明できない、他の仕組み(例: テンプレートパスのキャッシュ、
coreAdminThemeとの相互作用、アセット/CSS・JS解決への影響など)が絡んでいる可能性があるが、原因はまだ特定できて
いない。オリジナル案・
Admin/スコープ版のいずれも、実機検証で管理画面が壊れることを確認しており、
customAdminThemeを用いたコア修正のアプローチ自体を一旦保留し、原因不明のまま本番相当の環境に適用しないこと。
対処方法(未解決・下記は実機検証で管理画面が壊れることを確認済み)
以下の3点は理論上すべて揃って初めて機能するはずだったが、実機検証の結果、
これを適用すると管理画面が壊れることを確認している(「原因2」参照)。現状は
参考情報として記録するのみで、このまま適用しないこと。
1.
vendor/baserproject/baser-core/src/View/BcAdminAppView.phpを修正する_paths()の判定処理を、baserCMS独自のgetEnablePlugins()(pluginsテーブル基準、テーマは対象外)から、CakePHP標準の
Cake\Core\Plugin::isLoaded()(実際にロードされているプラグイン/テーマかどうかの判定。テーマも対象に含まれる)に
変更する。あわせて、追加するパスを
Admin/まで含めたものにする(原因2の対処)。_paths()本来の意図(「customAdminThemeに指定された値が実在する読み込み済みのプラグイン/テーマ名であること」の確認)に対して
Plugin::isLoaded()の方が忠実であり、通常プラグイン(
type => 'Plugin')での既存の利用ケースには影響しない。注意: これは vendor ファイルの直接編集(コアハック)である。
composer updateでbaserproject/baser-coreを更新すると、この修正は上書きされて消えるため、更新の都度再適用が必要になる。
2. 対象テーマの
config/setting.phpに設定を追加するplugins/{テーマ名}/config/setting.phpのBcApp配列に1行追加する。3. 上書き先ファイルの配置場所
plugins/{テーマ名}/templates/Admin/...配下に、上書きしたいコア側のテンプレートと同じ相対パスでファイルを配置する(例:
templates/Admin/element/Dashboard/baser_news.php)。ディレクトリ名の大文字小文字(
Adminは先頭大文字)を、コア側と正確に一致させること(Linux等の大文字小文字を区別するファイルシステムでは、これがずれていると上書きされない)。
結論
現時点では、こちらの機能はbaserCMSのコアでは難しく、パネルを除外するようなループを記述しないと実現が難しいです。
テーマ内のsrc/Plugin.phpなどに直接ループを記述することでも対応は可能ですが、プラグインなどで外部に出してしまった方が、再利用が楽だと思われます。