ぷぎがぽぎ このページをアンテナに追加 RSSフィード

2009-04-28

[][]symfonyセキュリティfixとアドミンジェネレータ


symfony1.2以降のアドミンジェネレータにおいてセキュリティの問題があるため、修正されたものがリリースされています。

詳しくは以下を参照

symfony1.2以降を利用しているサイトであればアップデートしておいた方がよいです。また、symfony1.2以降が対象となっているのは、今回の脆弱性はsymfony1.2以降で標準で用意されているフォームフレームワーク(sfForm)が原因となっているからのようです。

symfonyのアドミンジェネレータって?

ついでなので、symfonyのアドミンジェネレータについて説明を。

symfonyには様々な機能がありますが、その1つの目玉となる機能がアドミンジェネレータです。

ちなみにsymfonyにはジェネレータとアドミンジェネレータの2種類があります。

  • ジェネレータ
    • propel:generate-crud, doctrine:generate-crudのこと
    • CakePHPでいうbakeに相当。*1
    • 自動生成されたファイルを直接編集することでロジックに変更を加える。
    • タスクを2度実行すると上書きされて、初期の状態に戻ってしまう。
  • アドミンジェネレータ
    • propel:generate-admin, doctrine:generate-adminのこと
    • MySQLでいうphpMyAdminが高機能になったようなものが作成可能。
    • 基本は設定ファイル(generator.yml)で表示項目、表示名、検索フォームの設定などが行える
    • 必要があれば、アクションメソッドを上書きするファイルを用意することでロジックの変更も可能。
    • タスクを2度実行しても修正を加えた処理、設定はそのままでモデルの変更を反映させることが可能。*2

高機能が故に、理解し、使いこなすのは大変ですが、symfonyを使えばphpMyAdminのようなページがすぐに用意でき、場合によっては使い捨てではなく、カスタマイズすることで案件でも利用できるっていうのは便利だと思います。

symfonyのアドミンジェネレータに関する参考ページ

*1:「CakePHPを焼く」と言ったりするのに対して表現すると、「symfonyを奏でる」(propel:play-crud)って感じでしょうか。。違和感たっぷり。

*2:自動生成されたロジックはキャッシュとして作成される

2009-04-27

[][] CakePHPコンソールのシェルを作るときのメモ

CakePHP1.2での話。ドキュメントにも詳しく書かれていないのでソースを見ながら使い方を探る。

シェルやタスクって何よ?&基本的な使い方

とりあえずはドキュメントを読みましょう。そうすれば、ファイルの作成方法、配置、呼び出し方については分かります。

no title


実行結果をファイルに保存するとファイルの先頭にエスケープ文字が入る

linux環境で実行するときは

$cake shellfile > log.txt

のようにリダイレクトするとファイルの先頭にエスケープ文字列が含まれる場合があります。

これは/cake/console/cakeファイルのclearコマンドが原因なので、エスケープしておきます。

* /cake/console/cake

# escape clear command
#clear

LIB=${0/%cake/}
APP=`pwd`

exec php -q ${LIB}cake.php -working "${APP}" "$@"
          
exit;    

メッセージを出力する

通常は標準出力で、エラーがあるときは標準エラー出力にしたい場合などは既に用意された以下のメソッドを利用するとよい。

<?php
// 出力する
$this->out('標準出力したいメッセージ');
$this->err('標準エラー出力したいメッセージ');

// 出力後に自動改行したくない場合は第2引数でfalseを渡す
$this->out('改行せずに標準出力したいメッセージ', false);

// 「---」の文字列で区切り線を出力したい場合
$this->hr();

シェルを終了する

<?php
// シェルを異常終了させたい場合
//  (内部的に次の_stopメソッドを呼び出してシェルを終了している)
$this->error('タイトル', 'メッセージ');

// シェルをステータスを指定して終了させたい場合
//  (アンスコで始まるメソッドなのでPHP5でいうprotectedなメソッドを意味すると思われる)
$this->_stop('ステータスコード');

symfonyほど作り込まれた機能ではないけど、最低限必要な機能は用意されている印象。

2009-04-26

[][]コンソールで実行時にエラーのときに赤色で表示する


phpターミナルで実行するちょっとしたスクリプトを書くときにエラーがあるときは赤色で出力したいなぁと。

方法については以下のページで全てがわかります。

Colours with Linux terminals 日本語版

というわけで、PHPでお手軽にとりあえずエラーがあったときに赤く表示させる簡単関数(output_error)のサンプル

ただし、端末が対応していなくちゃだめです。

<?php
function output_error($text)
{
    printf("\033[0m\033[41;37m%s\033[0m\n", $text);
}

output_error("Error is occured!");
?>

これをmacのターミナルで実行した結果は以下のとおり。

f:id:brtRiver:20090427020049p:image

気をつけなくちゃいけないのが、リダイレクトしてファイルに書き込んだりすると制御文字が含まれてしまう事ですね。

2009-04-20

[][]sfFormの表示フォーマットを変更する

symfonyのformフレームワークテンプレート中に

<table>
<?php echo $form ?>
</table>

とするだけで、定義したフォームのHTMLを吐き出して以下のような画面ができます*1

f:id:brtRiver:20090420191931p:image

そして、入力にエラーがある場合は同じコードで以下のような画面ができあがります。

f:id:brtRiver:20090420192005p:image

では、この表示フォーマットを変更したい場合はどうしたらよいのかというのが本日のミニtips

ドキュメントには書かれていないのですが、ちゃんとメソッドが用意されています。

フォームクラスのsetupメソッド内で以下のようにsetRowFormatに新しい表示フォーマットを渡してあげればOKです。

<?php
...
  public function setup()
  {
    $format = "<tr>\n  <th>%label%</th>\n  <td>%field%%help%%error%%hidden_fields%</td>\n</tr>\n";
    $this->widgetSchema->getFormFormatter()->setRowFormat($format);
  }
...

$formatで指定している文字列が新しい表示フォーマットになります。見ればなんとなくわかると思いますが、%で囲まれた文字列(%label%, %help%, %error%, %hidden_fields%)が置換表示される各項目になります。

今回はエラーがヘルプの後ろに表示されるように変更しました。

そして変更後の表示は以下のようになりました。

f:id:brtRiver:20090420192750p:image


標準でどのように指定されているかは$this->widgetSchema->getFormFormatter()->getRowFormat()の戻り値を見ればわかります。

この例では並びだけを変えましたが、helpの表示部分にスタイルのクラスを指定したりする場合などの利用も考えられます。

ソースを追わないと知らないままですが、ちょっと追えばちゃんと機能が用意されているところがsymfonyっぽい。

ref:symfony 1.x legacy website

ref:symfony 1.x legacy website

*1:デザインはスタイルシートで指定済みの結果です

2009-04-12

[][]symfonyで画像認証を使う

最近では一般的になりつつあるCAPTCHA(画像認証)をsymfonyで使ってみた手順メモ。設定すると以下のようなキャプチャがフォームで利用できます。

f:id:brtRiver:20090413034052p:image


sfCryptoCaptchaPlugin

プラグインであるだろうと思い、探してみつけたのが、sfCryptoCaptchaPlugin

ただし、Readme通りにやってもうまくいかない箇所がいくつかあるので実際に動作した手順のメモ

プラグインのインストール

plugin:installコマンドが通らなかったので、svnでチェックアウトしてくる。

$ mkdir plugins/sfCryptoCaptchaPlugin
$ cd plugins/sfCryptoCaptchaPlugin
$ svn co http://svn.symfony-project.com/plugins/sfCryptoCaptchaPlugin/trunk .
プラグインを有効にする

プロジェクトでプラグインを有効にする。以下の例はenablePluginsで設定している場合。enableAllPluginsExceptで設定している場合は何も書かなくても読み込まれるはず。

<?php
..
      $this->enablePlugins(array('sfCryptoCaptchaPlugin'));

次に、キャプチャ画像の再読み込みで利用するモジュールを利用できるように有効にしておく

  • apps/アプリーション名/config/setting.yml
all
  .settings:
    ....
    enabled_modules:        [default, sfCryptoCaptcha]

最後に、プラグインの画像関連のファイルをweb以下に配置するために、コマンドを叩く

$ symfony plugin:publish-assets
$ symfony cc

ウィジットの設定

入力欄はテキストボックスで、バリデーターはインストールしたプラグインで用意されているsfValidatorSfCryptoCaptchaを使う。

書き方は色々ありますが、とりあえず、captchaの部分だけを既存フォームに追加できるような書き方で説明。

  • フォームクラス(HogeForm.class.php)にウィジットを追加
<?php
...
    // captcha
    $key = "captcha";
    $this->widgetSchema[$key] = new sfWidgetFormInput();
    $this->widgetSchema->setLabel($key, '画像認証');
    $this->validatorSchema[$key] = new sfValidatorSfCryptoCaptcha();
    $this->validatorSchema[$key]->setOption('required', true);
    $this->validatorSchema[$key]->setOption('trim', true);
    $this->validatorSchema[$key]->setMessage('required', '画像の数字を入力してください');
    $this->validatorSchema[$key]->setMessage('wrong_captcha', '認証できませんでした。再度入力してください');

テンプレートには認証画像を表示するために、以下のようにコードを追加する

  • フォームを表示するテンプレート(editSuccess.php)にヘルパーの読み込みと認証画像表示部分を追加
<?php use_helper('sfCryptoCaptcha'); ?>

<table>
<?php echo $form ?>
<tr>
  <td colspan="2" style="text-align: center"><?php echo captcha_image(); echo captcha_reload_button(); ?></td>
</tr>
</table>

これで、冒頭にあったような画像認証ができあがり。簡単ですね。


カスタマイズ方法

画像認証の基本設定はプラグインのconfig/app.ymlに書かれてあります。

これを自分のapp.ymlで書けば上書きできます。

たとえば、画像認証で利用する文字を数字のみにしたい場合は

all:
  sf_crypto_captcha:
  #Authorized characters
    chars_used:                       '0123456789'  # characters used for random captchas without "easy" option

と書けばOK。

2009-04-09

[][] sfObjectRouteで確認画面を作ってみる

[追記] 4/9 sfObjectRouteCollectionを使った場合も追加

sfObjectRouteとは?

アシアルさんのブログが一番わかりやすいので、そちらを最初に読むとよくわかります。

参照: symfony 1.2のルーティングまとめ - アシアルブログ

sfObjectRouteを使うメリットは?

アクションの記述が減ります。ルーティングのルールに従って処理されるからです。

データベースからidで値を持ってくる処理は、ルーティングの設定さえ行えば、アクションにはモデル取得のための記述が

$job = $this->getRoute()->getObject();

だけになります。

sfObjectRouteを使った場合のデメリットは?

今回のお題のような確認画面を新しく作りたいというような場合にどうやっていいか悩む。

そして、場合によっては、ルールを超えるために複雑なコーディングになるかもしれないし、場合によってはsfObjectRouteを使わない方が良い場合もある。

確認画面を作ってみる

今回はJobeetのJobeetJobというモデルに対しての編集、更新処理を行う部分で実装してみます。

通常のsfObjectRouteに確認画面のために追加する -ルーティング設定-

symfonyでは以下のような編集画面(job_edit)と更新処理(job_update)を想定しています。

# apps/frontend/config/routing.yml
job_edit:
  url:     /job/:id/edit.:sf_format
  class:   sfDoctrineRoute
  options: { model: JobeetJob, type: object }
  param:   { module: job, action: edit, sf_format: html }
  requirements: { sf_method: get }
 
job_update:
  url:     /job/:id.:sf_format
  class:   sfDoctrineRoute
  options: { model: JobeetJob, type: object }
  param:   { module: job, action: update, sf_format: html }
  requirements: { sf_method: put }

ここで、確認画面は以下のように考えてみました。

  • 確認画面はjob_updateに対して、putでなくpostする
  • 入力画面に戻る処理のためにjpb_reeditというルーティングを用意し、このルーティングにpostする

そして、以下のように修正してみました。

# apps/frontend/config/routing.yml
job_edit:
  url:     /job/:id/edit.:sf_format
  class:   sfDoctrineRoute
  options: { model: JobeetJob, type: object }
  param:   { module: job, action: edit, sf_format: html }
  requirements: { sf_method: get }

job_reedit:
  url:     /job/:id/edit.:sf_format
  class:   sfDoctrineRoute
  options: { model: JobeetJob, type: object }
  param:   { module: job, action: edit, sf_format: html }
  requirements: { sf_method: post }

job_update:
  url:     /job/:id.:sf_format
  class:   sfDoctrineRoute
  options: { model: JobeetJob, type: object }
  param:   { module: job, action: update, sf_format: html }
  requirements: { sf_method: [post, put] }

また、このルーティングの書き方が鬱陶しいなぁと言う場合は、sfObjectRouteCollectionを使うとよいです。

上記のような設定を以下のように短く書き換える事ができます。

# apps/frontend/config/routing.yml
job:
  class: sfDoctrineRouteCollection
  options:
    model: JobeetJob
    actions: [edit, update]
    collection_actions: []
    object_actions:
      reedit: post
      update: [post, put]

actionsで使用するアクションを定義し、object_actionsで追加もしくは変更するアクション名とそのsf_methodを定義します。

コレクションを使わない先ほどのrouting.ymlとは微妙に異なります。

アクションとしてreeditアクションそのものを定義しているので、reeditアクションも作成しなくてはなりません。

それは次のアクションの改修部分で言及します。


通常のsfObjectRouteに確認画面のために追加する -アクション-

アクションでsf_methodは$request->isMethod('post')のようにして確認できます。これを利用して、処理を分岐していきます。

また、確認画面で利用するテンプレートは編集画面と共通でeditとし、確認画面用のhidden作成やfreezeの機能などは以前取り上げたネタの流用です。

参照: sfFormで確認画面を作るためのhidden作成 - ぷぎがぽぎ

<?php
class jobActions extends sfActions
{
  public function executeUpdate(sfWebRequest $request)
  {
    $this->setTemplate('edit');
    $this->form = new JobeetJobForm($this->getRoute()->getObject());
    $this->form->bind($request->getParameter($this->form->getName()));
    if ($this->form->isValid()) {
      // method == post => confirm
      if ($request->isMethod('post')) {
        $this->form->freeze(); // widgetを全てhiddenに変換する
        return sfView::SUCCESS;
      }
      // method == put => update
      if ($request->isMethod('put')) {
        $job = $this->form->save();
        $this->redirect($this->generateUrl('job_edit', $job));
      }
    }
  }
  public function executeEdit(sfWebRequest $request)
  {
    $this->form = new JobeetJobForm($this->getRoute()->getObject());
    // method == post => return from confirm
    if ($request->isMethod('post')) $this->form->bind($request->getParameter($this->form->getName()));
  }

sf_methodで確認画面なのか、編集画面に戻ってきた遷移なのかを判断しています。

sfObjectRouteを利用しているおかげもあり、かなりすっきりです。

sf_methodを用いない場合では、hiddenでmode=confirmなどというようなやり方をしていたりしますが、このsf_methodを用いた方法のほうがすっきりしますね。

また、sfObjectRouteCollectionでrouting.ymlを指定した場合はreeditアクションそのものも定義しなくてはなりません。

実際の処理は入力画面に戻るだけですので、以下のようなシンプルなメソッドになります。forwardする方法もありますが、余計なフィルタ処理などを通したくないので直接editアクションのメソッドを呼び出してみました。

<?php
...
  public function executeReedit(sfWebRequest $request)
  {
    $this->setTemplate('edit');
    $this->executeEdit($request);
  }
通常のsfObjectRouteに確認画面のために追加する -テンプレート-

テンプレートはeditSuccess.phpで共通なので確認画面かどうかの判断が必要になります。

sf_methodを用いる方法と自前拡張のisFreezeを用いる方法の2つがあります。

  • editSuccess.php
// その1 sf_methodで確認(アクションがupdateでsf_methodがpostの場合は確認画面)
<?php if ($sf_request->isMethod('post') && $sf_request->getParameter('action') === "update"): ?>
確認画面ですよ
<?php else: ?>
編集画面ですよ
<?php endif ?>

// その2 isFreezeで確認
<?php if ($form->isFreeze): ?>
確認画面ですよ
<?php else: ?>
編集画面ですよ
<?php endif ?>

とりあえずここでは、sf_methodを使うパターン説明を続けます。

あと、入力画面にもどったりするためにそれぞれフォームタグを用意します。ちょっと不細工なんですが、分かりやすいかと思いますので。。

  • editSuccess.php
<?php if ($sf_request->isMethod('post') && $sf_request->getParameter('action') === "update"): ?>

....[確認画面表示]....
  <!-- 入力画面に戻る -->
  <form method="post" action="<?php echo url_for('job_reedit', $form->getObject()) ?>">
  <?php echo $form ?>
  <input type="submit" name="submit" value="入力画面に戻る">
  </form>

  <!-- 更新処理を実行する -->
  <?php echo form_tag_for($form, '@job') ?>
  <?php echo $form ?>
  <input type="submit" name="submit" value="実行する">
  </form>

<?php else: ?>

  <!-- 編集画面表示 -->
  <?php echo form_tag_for($form, '@job', array('method' => 'post')) ?>
  <table>
  <?php echo $form ?>
  </table>
  <input type="submit" name="submit" value="確認する">
  </form>

<?php endif ?>

確認画面が無い場合でsfObjectRouteを使う場合は、form_tag_forヘルパーを使えば良いのですが、

今回のように、job_reeditというような自前のルーティングを指定したい場合には使えないみたいです。

なので、入力画面に戻るformタグはurl_forを使っています。ただし、getObjectメソッドが使えるのでid=XXXのような指定は不要です。

また、確認画面に進む場合は、sf_methodをpostに変更しなければならないので、第3引数でmethodというキーで'post'を指定しておきます。

RESTについてはきちんと理解できていないかもしれないので、突っ込みどころ満載かもしれませんが、可読性もほどほどよいですし、なによりアクションにDoctrineやPropelの存在を感じさせずにコードが書けるところが良いですね。

(おまけ)sfObjectRouteでモデルから値をとってくる処理を変更したい場合

ちなみに、モデルからデータをとってくるときに、idだけではなく、セッションに保持している値(ログインIDなど)を用いてモデルから値を取得したい場合もあります。この場合はrouting設定でoptionsでmethodを指定すればOKです。

たとえば、DoctrineでmyMethodを指定する場合は以下のようにします。

# apps/frontend/config/routing.yml
job_edit:
  url:     /job/:id/edit.:sf_format
  class:   sfDoctrineRoute
  options: { model: JobeetJob, type: object, method: myMethod }
  param:   { module: job, action: edit, sf_format: html }
  requirements: { sf_method: get }

あとは、モデル(JobeetJobTable.class.php)にmyMethodを作成します。このとき、パラメータ配列で渡されます。

<?php
....
public function myMethod($params)
{
  // パラメータは配列で取得
  $id = $params['id'];
  // 新しい処理などを追加
  $user_name = sfContext::getInstance()->getUser()->getUserName();
  ....
}

また、sfObjectRouteCollectionでrouting.ymlを書いている場合はmodel_methodsにobjectというキーでメソッドを登録します。

# apps/frontend/config/routing.yml
job:
  class: sfDoctrineRouteCollection
  options:
    model: JobeetJob
    actions: [edit, update]
    collection_actions: []
    object_actions:
      reedit: post
      update: [post, put]
    model_methods:
      object: myMethod

初めてsfObjectRouteを使ってみたのですが、実践でも使える機能だと感じました。

理解するのに時間もかかってしまいますが、やっぱりアクションに記述するコードがすっきりするのが良いですね。

にしても、symfonyって使いやすくなる一方で学習コストがあがっていくなぁー。

[][]sfFormでjQueryのカレンダーを日本語化して使ってみる

sfFormExtraPluginをインストールする

まず、sfFormの標準のwidgetにはjQueryのカレンダー(Datepicker)を利用したwidgetがありません。

sfFormExtraPluginをインストールする必要があります。

参照: symfony 1.x legacy website

jQuery関連のライブラリを用意する

jQuery本体のライブラリをjQueryのサイトからダウンロードします。

参照: jQuery

カレンダーはjQuery UIというライブラリに含まれるので、これもダウンロードしておきます。

参照:Download Builder | jQuery UI

あと、日本語パッケージがUIのdevelopment-bundleディレクトリに含まれていますので、合計で以下の3ファイルが必要になります。

  • jquery-1.3.2.min.js
  • jquery-ui-1.7.1.custom.min.js
  • /development-bundle/ui/i18n/ui.datepicker-ja.js

また、スタイルシートもUIに含まれています。今回はsmoothnessというテーマでダウンロードしたので、以下のファイルを利用します。

  • smoothness/jquery-ui-1.7.1.custom.css

jQuery関連ファイルのアップロード

jsファイル

webディレクトリにあるjsディレクトリにjQueryディレクトリを用意し以下のようなパスで配置しました。

  • アプリケーションルート/web/js/jquery/jquery-1.3.2.min.js
  • アプリケーションルート/web/js/jquery/jquery-ui-1.7.1.custom.min.js
  • アプリケーションルート/web/js/jquery/ui/i18n/ui.datepicker-ja.js
cssファイル

こちらも同じくwebディレクトリにあるcssディレクトリにsmoothnessディレクトリを用意し、以下のようなパスで配置しました。

  • アプリケーションルート/web/css/smoothness/jquery-ui-1.7.1.custom.css

日本語化したwidgetを用意する

sfFormExtraPluginにあるsfWidgetFormJQueryDateウィジットを使うのですが、どうせ日本語化に特化させるためにいろいろとオプションを指定することになるので、jpWidgetFormJQueryDateという新しいウィジットを作ってしまう事にします。

実際はプラグインで作ったのですが、ここではとりあえずライブラリとして作った場合で説明します。

jpWidgetFormJQueryDate.class.php

以下のような内容でjpWidgetFormJQueryDate.class.phpを用意します。

  • アプリケーションルート/lib/widget/jpWidgetFormJQueryDate.class.php
<?php
class jpWidgetFormJQueryDate extends sfWidgetFormJQueryDate
{
  protected function configure($options = array(), $attributes = array())
  {
    parent::configure($options, $attributes);
    $this->setOption('culture', 'ja');
    # $this->setOption('format', '%year%年%month%月%day%日'); // 古いsfWidgetFormJqueryDataを利用している場合
    $this->getOption('date_widget')->setOption('format', '%year%年%month%月%day%日');
    if ($this->getOption('config') === '{}') {
      $this->setOption('config', '{buttonText: "カレンダーで選択"}');
    }
  }
  public function getStylesheets()
  {
    return array(
                 'smoothness/jquery-ui-1.7.1.custom' => 'all'
                 );
  }
  public function getJavaScripts()
  {
    return array(
                 'jquery/jquery-1.3.2.min',
                 'jquery/jquery-ui-1.7.1.custom.min',
                 'jquery/ui/i18n/ui.datepicker-ja'
                 );
  }
}

cultureでjaを指定することで日本語化されたカレンダーを利用できます。

また、このウィジットで読み込む必要があるファイルはgetStyleSheetsとgetJavaScriptsメソッドの戻り値配列で指定することで用意できます。ただし、自動的には読み込んでくれません。これは後述にテンプレートの部分に追記が必要。

Formクラスでwidgetを指定

そして、フォームクラスでwidgetSchemaとして指定します。

  • HogeForm.class.php
    $this->widgetSchema['expired_at'] = new jpWidgetFormJQueryDate();
CSSとJSファイルを読み込む

最後に、テンプレートで追加したスタイルシートとJSファイルを読み込むように以下のように加えます。

ウィジットに書いただけでは自動的に読み込んでくれないという不親切設計です。

  • editSuccess.php
ここでは$formにsfFormオブジェクトがアサインされている場合の例です。
<?php include_javascripts_for_form($form) ?>
<?php include_stylesheets_for_form($form) ?>
日本語化(ちょっと不自然)

これで準備は完了です。フォームを表示すると以下のようにカレンダーを利用できます。

f:id:brtRiver:20090410025145p:image


うまくファイルが読み込まれない、日本語化されない場合は

symfony ccと、クッキーを削除するとうまくいくかも。

でも、よくみると、カレンダーのヘッダー部分の年月が惜しいことになっています。

「年」という表示が年月の間に無いために不自然な表示になってしまっています。

年月の間に「年」という文字を追加する

そこで、jQueryを使ってこのヘッダー部分に「年」という文字を追加するようにしてみます。

widgetのrenderメソッドをオーバーライドして、親クラスのレンダー結果に「年」を追加するjavascriptを書き加えます。

  • jpWidgetFormJQueryDate.class.php
<?php
...
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    return parent::render($name, $value, $attributes, $errors).<<<EOF
<script type="text/javascript">
jQuery(document).ready(function(){
  var  eleYear = ".ui-datepicker-year";
  jQuery(".ui-datepicker-trigger, div#ui-datepicker-div").click(function() {
    jQuery("span.jpYearSuffix").remove();
    jQuery(eleYear).after("<span class='jpYearSuffix'>年</span>");
  });
});
</script>
EOF
;
  }

これで、もう一度表示させてみると

f:id:brtRiver:20090410030155p:image

月を移動しても、ちゃんと表示されています。

このように、jQueryのおかげで、コアファイルには修正を加えずにそれらしく日本語化もできましたとさ。

ただし、年をプルダウンで表示させたりすると崩れちゃいますけどね。。

2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |