LC_ALL (moodle_setlocale())は,もう ja_JP.UTF-8 で良いのではないか?

LC_ALL (moodle_setlocale())は,もう ja_JP.UTF-8 で良いのではないか?

- Tatsuya Shirai の投稿
返信数: 2

 いまMoodle1.9からMoodle2.0へのアップグレードの実験を繰り返しています.特にサーバがWindows(Server含む)の場合に生じる問題を潰しているところです.その過程で気付いた点を報告します.

 最初の取っ掛かりは,WikiのデータのMigrationの処理中に発生したエラーです.古いWiki本文のデータを変換して新しいデータベースに複写(insert)する処理(電子メールアドレスなどを勝手にmailto:に変えている予感…)において,変換後のデータにUTF-8ではない文字コードが混ざっているためinsertできない,というエラーがいくつかのページで発生します.実は同じ問題は日本語をURLの一部に含むハイパーリンク(Aタグ)においても発生するのですが,それはこちらの現象に絡んでいます.他にもある気もしますがそれは置いておきましょう.
 よくよく調べてみると,発生源は PHP標準(PCRE)の正規表現に基く文字列置換関数 preg_replace_callback() でした.原因を探るべく突き詰めた結果,添付ファイルのようなサンプルプログラムで現象の再現に成功しました.根本的な原因はサーバ側で設定する LC_ALL というロケーションに関する設定です.分かり難いとは思いますが,状況をこちらにまとめました.Moodleはページをコールされると,まずmoodle/index.phpが呼ばれ,config.phpを読み込みます.そこからmoodle/lib/setup.phpが毎回呼び出され,その中で function moodle_setlocale()という関数を呼びます.config.phpさえコールすれば,皆さんも最低限のMoodleの関数を利用したサンプルコードを書けます(蛇足ですね^^;).ちなみに function moodle_setlocale() は lib/moodlelib.php 内にあります.Moodle1.9 と Moodle2.0 とでは一箇所のみの違いですので今回の問題はどちらのバージョンにも影響があります(この違いもかなり意味深長なのですが).

 moodle_setlocale()では,サーバOSの種類と(多分)デフォルトのサイトの言語設定に基いて LC_ALL というPCRE(正規表現ライブラリ)に関係の深いパラメータの設定を行います.LC_ALLに set_locale()関数を用いて設定する文字列は言語パックから取り寄せます.非Windows (= Linux, MacOS) では locale ,Windowsでは localewin を見出し語として呼び出します.Moodle1.9ではどちらも lang/??_utf8/moodle.phpから,Moodle2.0ではlang/??_utf8/langconfig.php から探します.

Linux(英語圏):  'en_AU.UTF-8'
Windows(英語圏): 'English_Australia.1252'
Linux(日本語圏): 'ja_JP.UTF-8'
Windows(日本語圏): 'Japanese_Japan.932'



 今回,私の環境(日本語版WindowsXP)ではアップグレード時に日本語言語パックが正しく読み込まれなかったため,'Japanese_Japan.932' ではなく,仕方なく get_string()関数は 'English_Australia.1252' を返し,それが LC_ALL に設定されました.その状態で日本語文章に対して preg_replace_callback() 関数を実施した結果,データが破壊されました.文字データの形式が CP932 (シフトJIS風)ではなく Latin-1(1252) という前提で文字列を操作したのでしょう.いえ,実際の文字データはUTF-8ですのでCP932という前提で処理を行われるのも問題だと思うのですが,Latin-1よりは安全だったようです.

 添付したサンプルコードを実行してみて頂けないでしょうか? Linux上で同じ挙動をするかどうかは分かりませんが.頭の所にある以下の3行のうちの一つのみを有効にしてみて下さい.

//  $localestr = 'ja_JP.UTF-8';             // Linux (not Windows)
//  $localestr = 'Japanese_Japan.932';      // Windows(Japanese)
    $localestr = 'English_Australia.1252';  // Windows(English)

デフォルトの状態は私が今回陥った,Laint-1前提で日本語文字を preg_replace_callback() する例です.実行結果(Windows上,Webブラウザ経由)を以下に示します.

<'English_Australia.1252'>

オリジナルの文章:string(99) "これは白井達也(shirai@mech.suzuka-ct.ac.jp)が作成したPHPのライブラリです."
リンク化対象(array):array(6) { [0]=> string(28) "shirai@mech.suzuka-ct.ac.jp" [1]=> string(28) "shirai@mech.suzuka-ct.ac.jp" [2]=> string(0) "" [3]=> string(0) "" [4]=> string(0) "" [5]=> string(3) "ac." }
変換後の文章:string(71) "これは白井達也(��が作成したPHPのライブラリです."

<'Japanese_Japan.932' と 'ja_JP.UTF-8'>

オリジナルの文章:string(99) "これは白井達也(shirai@mech.suzuka-ct.ac.jp)が作成したPHPのライブラリです."
リンク化対象(array):array(6) { [0]=> string(27) "shirai@mech.suzuka-ct.ac.jp" [1]=> string(27) "shirai@mech.suzuka-ct.ac.jp" [2]=> string(0) "" [3]=> string(0) "" [4]=> string(0) "" [5]=> string(3) "ac." }
変換後の文章:string(72) "これは白井達也()が作成したPHPのライブラリです."

 オリジナルの文章のうちのメールアドレス部を切り取ってコールバック関数に渡して置換する.リンク化対象と書いたデータがコールバック関数に切り取って送られた部分です.Latin-1扱いの前者の場合は1文字余分に切り取られていますのでコールバック関数には28文字が送られています.本来ですとこのコールバック関数でメールアドレスをmailto:に変換して戻すのですが,それはサンプルでは省略しています.CP932, UTF-8扱いの後者では切り取ってコールバック関数に送られた方も,残された方もUTF-8のマルチバイト形式を維持していますので,文字化けは発生していません.

 なるほど.正しく言語パックが読み込まれていれば私の環境ではこのような怪奇現象は発生しなかったでしょう.それでヨシヨシという問題では実は無いのです.LC_ALL はサイト単位で設定されます.いえ, ログインしているユーザの選択している言語パックに依存するのかも知れません.でも,cronによる処理や管理者が行う処理(アップグレードを含む)の場合はどうでしょう.一つのサイトに多言語を使うユーザが混在しているとします.それぞれの言語で Wiki のデータを書いている.それを一括処理する際に一つのロケール,それが例えば Latin-1 に設定されていたら? 日本語で書かれた私のWikiがそのサーバ上にあったのならば今回私が遭遇したのと同じ問題が発生するはずです.

 そもそも LC_ALL をUTF-8以外に設定する必然性があるのでしょうか.カレンダーの日付の文字化け問題とも確かに絡んでいましたが,それは低次元の箇所で文字データをUTF-8化しなかったことに起因している気がします.Moodle上のデータはUTF-8ですし,ファイル名の文字コードもそれに準じるでしょう(あるいは準じるようにするべきでしょう,fs_moodleのように).したがって,OSがWindowsの場合も,Linuxのように(enかjpかはともかく),文字コードの属性はUTF-8に統一すべきなのではないでしょうか.

Tatsuya Shirai への返信

Re: LC_ALL (moodle_setlocale())は,もう ja_JP.UTF-8 で良いのではないか?

- Haruhiko Okumura の投稿

なるほど!

Linuxには English_Australia.1252 はないのですが,en_AU に設定して再現できました。

Haruhiko Okumura への返信

Re: LC_ALL (moodle_setlocale())は,もう ja_JP.UTF-8 で良いのではないか?

- Tatsuya Shirai の投稿

 最初はパターン修飾子uなのかな,と思ったのですが,これは(Perl非互換の)正規表現に含まれるマルチバイトの取り扱いであって,置換対象文字列の文字コードのことではないですね.

 Latin-1やCP932で文字列を扱う箇所が皆無とは言いがたいのですが,OSに依存するかなり低位な箇所です.たとえば日付をdate()で取得する箇所は,日本語Windows版PHPですと”年”,”月”,”日”や曜日などをCP932(シフトJIS準拠)で返します.でもこれは早々にUTF-8に変換してその後の処理に取り掛かるべきです.Excelから取得したデータにしてもUTF-16LEからUTF-8に変換して処理していたはずです.標準のMoodleであれば英語版WindowsでLatin-1なファイル名は使用できない(アンダーバーに置換)はずです.SCORMファイル(ZIP圧縮)の中のファイル名にシフトJISを含むものがあったり,PowerPointをレッスンに取り込む際にトラブる話も本件とは別次元です.

 海外でも,意外とWindowsServer上でMoodleを運用しているサイトがあるようですね(フォーラムは活発です).そのようなサイト上で日本語のWikiコンテンツなどがMoodle2.0へのアップグレード時に破壊されるのが容易に想像できます… いまはset_locale(LC_ALL, ''); とすることでサーバOSの持つロケールを自動的に取得する(Windows版PHPのみの機能のようです)ことにしていますが,Linux同様に,get_string('locale', 'langconfig'); で取得するように変えてもイイのじゃないかな?と考えています.副作用は怖いですけれども,逆に副作用から問題点を炙り出すべきなのかも知れませんね.