htmxで browser history API を活用する方法と a-blog cms 実装のポイント

htmx は標準で browser history API をサポートしており、属性に hx-push-url="true" を追加することで、これを簡単に利用できます。a-blog cms をバックエンドで使用する際の注意点をいくつか紹介します。

※(例)と書かれている部分は実際には動作しません。

hx-push-url 属性を設定して正しく動作する GET

記事詳細(例)

<a>タグでの GETリクエストに対しては、htmx のドキュメントに従い、hx-get属性と hx-push-url属性を追加することを推奨します。

<a href="entry-1.html" hx-get="entry-1.html" hx-push-url="true">記事詳細</a>

hx-push-url 属性を設定しても正しく動作しない POST

(例)
この部分に結果が表示されます

POSTリクエストのケースでは、例えば a-blog cms でカスタムフィールド station を選択するための <select> を含むフォームを使用し、結果を表示するテンプレートとして include/htmx/result.html を指定する方法があります。

<form hx-post="" hx-push-url="true" hx-trigger="submit" hx-target="#search-result" hx-ext="ajax-header">
 <select name="station"> 
  <option value="Tokyo">Tokyo</option>
  <option value="Osaka">Osaka</option>
  <option value="Nagoya">Nagoya</option>
 </select>
 <input type="hidden" name="field[]" value="station">
  <input type="hidden" name="tpl" value="include/htmx/result.html">
<input type="submit" name="ACMS_POST_2GET" value="検索" >
</form>
<div id="search-result">この部分に結果が表示されます</div>

a-blog cms の仕様として、<form> の POST 時 name="ACMS_POST_2GET" が送られてくると、検索条件( station = Nagoya )から URL を組み立て、その後リダイレクトされ GET に変換され、以下のような URL でアクセスした結果を取得できます。

https://example.com/htmx/result.html/field/station/Nagoya/

ここで問題が発生します。 上記の URL がブラウザに表示され履歴にも登録されますが、この URL にアクセスすると Ajax で追加される部分的な HTML になってしまいます。この検索結果で履歴に登録されて欲しい URLは、テンプレートファイル include/htmx/result.html のパスが無い以下の URL であって欲しいのです。

https://example.com/field/station/Nagoya/

hx-push-url 属性を設定しても正しく動作する POST 解決編

ここまでで a-blog cms POSTリクエスト後に URL を組み立てる ACMS_POST_2GET の処理が、htmxhx-push-url と相性が悪い状況を解決する方法を考えてみました。

以下に示す JavaScriptコードを追加することで、hx-push-url 属性の動作を維持しつつ、希望する URL形式を browser history API に正しく渡すことができるようになります。

<script>
  addEventListener('htmx:beforeHistoryUpdate', function (event) {
    const proposedUrl = event.detail.history.path;
    const customUrl = proposedUrl.replace(/\/include\/htmx\/.*\.html/, '');
    event.detail.history.path = customUrl;
  });
</script>

処理としては htmx で History を更新する前の path を取得し、/include/htmx/ 〜 .html 部分を削除するという処理を追加しています。

一般的に a-blog cms の部分的なファイルは include ディレクトリに整理して保存するというルールが定着していますので、htmx で読み込むテンプレートファイルは、その include ディレクトリ内に htmx を用意し、その htmx ディレクトリ内で管理というルールでテーマを構築というルールにする事で上記の JavaScript が有効的に動作させることができます。

モジュールIDのキャッシュ

a-blog cms Ver.2.11.0よりモジュールIDのキャッシュ機能が使えるようになりました。a-blog cms のキャッシュ機能は基本的に、ページ単位でキャッシュを行う仕組みになっております。 そのため、URLが少しでも違うとキャッシュが利用されず、そのページにある全てのモジュールが動作してしまいます。ページを構成する要素はたくさんありますが、例えば「グローバルナビゲーション」や「フッター」の情報などは頻繁に変わるものではありません。 情報は変わっていないのに、そのページにある他の要素が更新されたことで、このような更新が少ないモジュールも毎回PHPが動作して生成しなおすのは、パフォーマンス的にあまりよろしくありません。

そこでVer.2.11.0からはページ単位以外に更新頻度が違うモジュールごとにキャッシュができる仕組みを導入しました。

設定方法

管理画面 > モジュール > 該当するモジュールIDに移動します。「条件設定」を開き「キャッシュ」に分単位で数値を設定します。 ここで設定した時間がキャッシュの有効期限となります。


モジュールキャッシュの削除タイミング

モジュールのキャッシュは以下のような条件で削除されるようになっています。

  • 該当のモジュールIDが更新されたタイミング
  • 該当のモジュールIDのキャッシュ時間の設定を経過したタイミング

なお、該当のモジュールIDのテンプレートが更新されたタイミングで別のキャッシュができますがキャッシュ自体が削除されるわけではないのでご注意ください。

フロントエンドのビルド環境がそろった developテーマについて

Ver. 2.11.0よりsiteテーマ、simpleテーマ、blogテーマ、bootstrapテーマに加えて新たにdevelopテーマを用意しました。developテーマではa-blog cms を開発する際によく使うテンプレートをシンプルにしたものが同梱されており、フロントエンドに関する開発環境も整えられています。

機能



機能 概要
SCSSのコンパイル SCSSをCSSにコンパイルします。
JSファイルの結合 複数のJSファイルを1つのファイルに結合します。
コードのLintチェック ESLintやstylelintを使用して、JSやSCSSおよびCSSのコードをチェックします。
Live Reload ファイルの変更があった場合に、ブラウザを自動的にリロードします。
ソースコードの整形 EditorConfigを使い、ソースコードを整形します。
SVGアイコンの使用 Font Awesome 5 のSVG with JavaScriptを利用して、SVGアイコンを表示します。

使い方

ローカル環境に Ver.2.11以上のa-blog cms をインストールしてください。Ver.2.11以上ではthemesディレクトリーにdevelopテーマが設置されているのでターミナルで、developテーマを設置しているディレクトリまで移動します。

$ cd  /themes/develop/

移動できたら、依存ファイルを以下のようにインストールします。

$ npm install

npmコマンド自体が存在しない場合、Node.jsをインストールすることでnpmコマンドが有効になります。Node.jsはこちらのサイトからインストールできます。

テーマ名(デフォルト: develop)を変更した場合は、package.json の config.theme を変更してください。もし、LiveReload を使用する場合は、実行しているローカル環境のアドレスに合わせて config.local を変更してください。

"config": {
  "local": "localhost",
  "theme": "テーマ名"
},

用意しているビルドコマンドについて

コマンドは、npm run ** になります。**の部分に下記のコマンドが入ります。



コマンド 概要
start CSSとJSファイルの変更を監視してビルドを行う。LiveReloadあり。
dev CSSとJSファイルの変更を監視してビルドを行う。
build CSSとJSファイルのビルドを行う。納品時には使用する。

npm run startnpm run dev では、JavaScriptに余分なコードやデバッグのためのコードが入るため、最適化されません。納品時には必ず npm run build を行なってください。 一度、npm run startと実行すると、以下のページが立ち上がるのを確認してみてください。


デフォルトではlocalhost:3000 でページが立ち上がります


その他できること

  • ファーストビューに含まれるCSSをインライン化する
  • Font Awesome 5 のSVG with JavaScriptを利用する

ファーストビューに含まれるCSSをインライン化する

スタイルの読み込みについて

カスタムスタイルは、include/head/preload.html で読んでいます。 以下記述のように preload 属性を使って読み込むようにしており、非同期でスタイルを読み込んでいます。

<link rel="preload" href="/dest/bundle.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/dest/bundle.css"></noscript>

Above the fold(ファーストビュー)に含まれる CSS のインライン化

preload属性を使って、非同期にスタイルを読むようにしたことで、スタイル読み込みでブロックされずレンダリングが速くなります。 ただその影響で、スタイルが当たっていないHTMLが一瞬表示される可能性があります。これを解決するために、Above the fold(ファーストビュー)のCSSをインライン化して読み込みます。

ファーストビューのスタイルが完成したら、themes/develop/src/index.html に 現在のソース貼り付けます。(ブラウザで表示したときのソースを貼り付ける) その後、npm run build コマンドを実行します。 ビルドコマンドを実行すると、themes/develop/dest/index.html にファイルができます。 dest/index.html にインラインCSSが書き出されている(17行目あたり)ので、このCSSを、include/head/preload.html に設置します。

<style>
<!-- ファーストビュー(above the fold )のインラインCSS を挿入。インラインCSSは webpackでbuildすると、dest/index.html に生成される。 -->
</style>

Font Awesome 5 のSVG with JavaScriptを使用する

アイコンの利用を考え、developテーマでは、Font Awesome 5 の SVG with JavaScript が利用できるようになっています。JavaScriptで必要なフォントのみを読み込むことでスピードを向上させることができます。

アイコンのインポート

SVG with JavaScript では必要なアイコンのみインポートして利用します。 アイコンの種類により、インポートするパッケージが違うので気をつけてください。例えばブランドアイコン系は、@fortawesome/free-brands-svg-icons から読み込みます。

themes/develop/src/fonts.js
import { faUser, faSignOutAlt } from '@fortawesome/free-solid-svg-icons';
import { faFacebook, faTwitter, faInstagram } from '@fortawesome/free-brands-svg-icons';

命名規則はクラス名(ケバブケース)をキャメルケースにしたものになります。

.fa-sign-out-alt のアイコンを使いたい場合は、 faSignOutAlt を読み込みます。

themes/develop/src/fonts.js
import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons';

アイコンの登録

インポートしたアイコンを以下のように使用できるように登録します。使用するアイコン全てを指定します。

themes/develop/src/fonts.js
library.add(faUser, faSignOutAlt, faFacebook, faTwitter, faInstagram);

アイコンの表示

あとは、以下のようにアイコンを表示させることができます。

<i class="fab fa-twitter"></i>
<i class="fab fa-facebook"></i>
<i class="fas fa-sign-out-alt"></i>

CSSのクラス名はFont Awsome の公式サイトから調べることができます。

developテーマで開発するTips

パフォーマンス向上のために、include/head/js.html で読み込んでいる組み込み JSをTouch_SessionWithContribution を使って、投稿者以上の場合だけ読み込む方法もあります。以下は組み込みJSを使わずに自力でサイトをカスタマイズするための方法です。

/include/head/js.html

developテーマの/include/head/js.htmlには以下のように記述されています。タッチモジュールの冒頭に「!(エクスクラメーション)」が記述されているので、今回は削除してください。

<!-- 組み込みJSを使わない場合、エクスクラメーションを外して、投稿者以上の場合だけ読み込むようにする -->
<!-- !BEGIN_MODULE Touch_SessionWithContribution -->
<script src="%{JS_LIB_JQUERY_DIR}jquery-%{JS_LIB_JQUERY_DIR_VERSION}.min.js" charset="UTF-8"></script><!-- BEGIN_MODULE Js -->
<script src="/acms.js{arguments}" charset="UTF-8" id="acms-js"></script><!-- END_MODULE Js -->
<!-- !END_MODULE Touch_SessionWithContribution -->

以下のように修正します

<!-- 組み込みJSを使わない場合、エクスクラメーションを外して、投稿者以上の場合だけ読み込むようにする -->
<!-- BEGIN_MODULE Touch_SessionWithContribution -->
<script src="%{JS_LIB_JQUERY_DIR}jquery-%{JS_LIB_JQUERY_DIR_VERSION}.min.js" charset="UTF-8"></script><!-- BEGIN_MODULE Js -->
<script src="/acms.js{arguments}" charset="UTF-8" id="acms-js"></script><!-- END_MODULE Js -->
<!-- END_MODULE Touch_SessionWithContribution -->

ただし、組み込みJSを使わないことになるので、組み込みJSに標準で搭載されているスライダーや画像ビューワーなどのすべての組み込みJSが動作しなくなるためご注意ください。

必要な機能やライブラリは、自分でインストール、実装する必要があります。

バンドル環境が整っていますので、 npm経由でライブラリをインストールし、 importする方式をお勧めします。以下 Lazy Load を実装する例になります。

$ npm i vanilla-lazyload

index.js 6行目

import LazyLoad from 'vanilla-lazyload';

index.js 34行目 domContentLoadedイベント内に追記

domContentLoaded(() => {
  new LazyLoad({elements_selector: '.js-lazy-load'});
});

HTMLに以下のソースコードを記述して、表示します。 成功した場合、一瞬プレースホルダー画像が現れた後、すぐに猫の写真が表示されます。

<img data-src="http://placekitten.com/400/300" src="/images/placeholder/image.svg" width="400" class="js-lazy-load">

そのほかにもSmartPhotoScrollHintなど、a-blog cmsの組み込みJSに標準で実装されている機能の一部はnpm経由でインストールできるようになっています。

$ npm i smartphoto
import SmartPhoto from 'smartphoto';

domContentLoaded(() => {
  new SmartPhoto('js-smartphoto');
});
$ npm i scroll-hint
import ScrollHint from 'scroll-hint';

domContentLoaded(() => {
  new ScrollHint('js-scroll-hint');
});

JVNで報告された脆弱性への対応について

JVN#48443978

a-blog cms で脆弱性が見つかりました。 該当の状況に当てはまる場合は大変お手数ですが以下のご対応をお願いいたします。

JVN識別番号

  • JVN#48443978

今回は見つかった脆弱性は「Ping送信機能のディレクトリトラバーサル」になります。

Ping機能の脆弱性

内容

編集者以上で利用できるPing送信機能を使用する事で、任意のファイル情報を閲覧することが可能。

攻撃条件

  • 攻撃者が編集者以上の権限でログイン可能なこと

影響を受けるバージョン

  • a-blog cms Ver. 3.1.9 以下のバージョン (Ver.3.1.x系)
  • a-blog cms Ver. 3.0.30 以下のバージョン (Ver.3.0.x系)
  • a-blog cms Ver. 2.11.59 以下のバージョン (Ver.2.11.x系)
  • a-blog cms Ver. 2.10.51 以下のバージョン (Ver.2.10.x系)
  • a-blog cms Ver. 2.9.x 以下のバージョン(フィックスバージョンはありません

ワークアラウンド(回避方法)

以下ファイルを削除することで、Ping送信機能を無効にでき、今回の脆弱性を回避できます。

php/ACMS/POST/PingWeblogUpdate.php

CMSバージョンアップによる対応

Ping送信機能を利用したい場合は、CMSのアップデートをご検討ください。

各マイナーバージョン毎にフィックスバージョンがリリースしております。 「影響を受けるバージョン」 よりも新しいフィックスバージョンにアップデートを行なってください。

最後に

この度はご迷惑をおかけしてしまい大変申し訳ございません。
該当する問題がありましたら、お早めにバージョンアップのご検討をお願いいたします。 また、迅速にご報告いただいたユーザーの皆さま、誠にありがとうございました。

今後もご報告いただいた内容に対して真摯に受け止め修正と改善を行ってまいります。 よろしくお願いいたします。

本件に関するお問い合わせ先

本件についてご不明点などありましたら以下のお問い合わせよりご連絡ください。

有限会社アップルップル
メールアドレス:info@a-blogcms.jp
お問い合わせフォーム:https://www.a-blogcms.jp/contact/