OSDN Git Service

- followed ini file name changes.
[ethna/ethna.git] / class / Ethna_ActionForm.php
1 <?php
2 // vim: foldmethod=marker
3 /**
4  *  Ethna_ActionForm.php
5  *
6  *  @author     Masaki Fujimoto <fujimoto@php.net>
7  *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
8  *  @package    Ethna
9  *  @version    $Id$
10  */
11
12 /** 定型フィルタ: 半角入力 */
13 define('FILTER_HW', 'numeric_zentohan,alphabet_zentohan,ltrim,rtrim,ntrim');
14
15 /** 定型フィルタ: 全角入力 */
16 define('FILTER_FW', 'kana_hantozen,ntrim');
17
18
19 // {{{ Ethna_ActionForm
20 /**
21  *  アクションフォームクラス
22  *
23  *  @author     Masaki Fujimoto <fujimoto@php.net>
24  *  @access     public
25  *  @package    Ethna
26  */
27 class Ethna_ActionForm
28 {
29     /**#@+
30      *  @access private
31      */
32
33     /** @var    array   フォーム値定義(デフォルト) */
34     var $form_template = array();
35
36     /** @var    array   フォーム値定義 */
37     var $form = array();
38
39     /** @var    array   フォーム値 */
40     var $form_vars = array();
41
42     /** @var    array   アプリケーション設定値 */
43     var $app_vars = array();
44
45     /** @var    array   アプリケーション設定値(自動エスケープなし) */
46     var $app_ne_vars = array();
47
48     /** @var    object  Ethna_Backend       バックエンドオブジェクト */
49     var $backend;
50
51     /** @var    object  Ethna_ActionError   アクションエラーオブジェクト */
52     var $action_error;
53
54     /** @var    object  Ethna_ActionError   アクションエラーオブジェクト(省略形) */
55     var $ae;
56
57     /** @var    object  Ethna_I18N  i18nオブジェクト */
58     var $i18n;
59
60     /** @var    object  Ethna_Logger    ログオブジェクト */
61     var $logger;
62
63     /** @var    object  Ethna_Plugin    プラグインオブジェクト */
64     var $plugin;
65
66     /** @var    array   フォーム定義要素 */
67     var $def = array('name', 'required', 'max', 'min', 'regexp', 'mbregexp',
68                      'custom', 'filter', 'form_type', 'type');
69
70     /** @var    array   フォーム定義のうち非プラグイン要素とみなすprefix */
71     var $def_noplugin = array('type', 'form', 'name', 'plugin', 'filter',
72                               'option', 'default');
73
74     /** @var    bool    追加検証強制フラグ */
75     var $force_validate_plus = false;
76
77     /** @var    array   アプリケーションオブジェクト(helper) */
78     var $helper_app_object = array();
79
80     /** @var    array   アプリケーションオブジェクト(helper)で利用しないフォーム名 */
81     var $helper_skip_form = array();
82
83     /** @var    int   フォーム配列で使用可能な深さの上限 */
84     var $max_form_deps = 10;
85
86     /**#@-*/
87
88     /**
89      *  Ethna_ActionFormクラスのコンストラクタ
90      *
91      *  @access public
92      *  @param  object  Ethna_Controller    &$controller    controllerオブジェクト
93      */
94     function Ethna_ActionForm(&$controller)
95     {
96         $this->backend =& $controller->getBackend();
97         $this->action_error =& $controller->getActionError();
98         $this->ae =& $this->action_error;
99         $this->i18n =& $controller->getI18N();
100         $this->logger =& $controller->getLogger();
101         $this->plugin =& $controller->getPlugin();
102
103         if (isset($_SERVER['REQUEST_METHOD']) == false) {
104             return;
105         }
106
107         // フォーム値テンプレートの更新
108         $this->form_template = $this->_setFormTemplate($this->form_template);
109
110         // アプリケーションオブジェクト(helper)の生成
111         foreach ($this->helper_app_object as $key => $value) {
112             if (is_object($value)) {
113                 continue;
114             }
115             $this->helper_app_object[$key] =& $this->_getHelperAppObject($key);
116         }
117
118         // フォーム値定義の設定
119         $this->_setFormDef_HelperObj();
120         $this->_setFormDef();
121
122         // 省略値補正
123         foreach ($this->form as $name => $value) {
124             foreach ($this->def as $k) {
125                 if (isset($value[$k]) == false) {
126                     $this->form[$name][$k] = null;
127                 }
128             }
129         }
130     }
131
132     /**
133      *  フォーム値のアクセサ(R)
134      *
135      *  @access public
136      *  @param  string  $name   フォーム値の名称
137      *  @return mixed   フォーム値
138      */
139     function get($name)
140     {
141         return $this->_getVarsByFormName($this->form_vars, $name);
142     }
143
144     /**
145      *  フォーム値定義を取得する
146      *
147      *  @access public
148      *  @param  string  $name   取得するフォーム名(nullなら全ての定義を取得)
149      *  @return array   フォーム値定義
150      */
151     function getDef($name = null)
152     {
153         if (is_null($name)) {
154             return $this->form;
155         }
156
157         if (array_key_exists($name, $this->form) == false) {
158             return null;
159         } else {
160             return $this->form[$name];
161         }
162     }
163
164     /**
165      *  フォーム項目表示名を取得する
166      *
167      *  @access public
168      *  @param  string  $name   フォーム値の名称
169      *  @return mixed   フォーム値の表示名
170      */
171     function getName($name)
172     {
173         if (isset($this->form[$name]) == false) {
174             return null;
175         }
176         if (isset($this->form[$name]['name'])
177             && $this->form[$name]['name'] != null) {
178             return $this->form[$name]['name'];
179         }
180
181         // try message catalog
182         return $this->i18n->get($name);
183     }
184     
185     /**
186      *  フォーム名に対応するキーの配列を返す
187      *
188      *  @access private
189      *  @param  string  $name   フォーム名
190      *  @return array   キーの配列
191      */
192     function _getFormNameArray($name)
193     {
194         // 多次元配列を指定した場合
195         if (preg_match('/^.*\[[^\]]+\]$/', $name)) { 
196             $buff = preg_replace('/\]\[/', '[', $name); // hoge[foo][bar] => hoge[foo[bar]
197             $buff = preg_replace('/\]/', "", $buff);    // hoge][foo[bar] => hoge[foo[bar
198             $ret = explode('[', $buff);                 // hoge[foo[bar   => array('hoge', 'foo', 'var')
199         } else {
200             // 多次元配列を指定していない場合
201             $ret = array($name);
202         }
203         return $ret;
204     }
205
206     /**
207      *  配列の中からキーで指定された要素を取り出す
208      *
209      *  @access private
210      *  @param  array   &$target    対象とする配列
211      *  @param  string  $nane       キー
212      *  @return string  指定された要素
213      */
214     function _getVarsByFormName(&$target, $name)
215     {
216         $keys = $this->_getFormNameArray($name);
217         return $this->_getVarsByKeys($target, $keys);
218     }
219
220     /**
221      *  配列の中にキーで指定された要素を登録する
222      *  フォーム定義にキーがあるかどうかは無関係に登録します
223      *
224      *  @access private
225      *  @param  array   &$target    対象とする配列
226      *  @param  string  $nane       キー
227      *  @param  mixde   $value      登録する値
228      */
229     function _setVarsByFormName(&$target, $name, $vars)
230     {
231         $keys = $this->_getFormNameArray($name);
232         $this->_setVarsByKeys($target, $keys, $vars);
233     }
234     
235     /**
236      *  配列の中からキーで指定された要素を取り出す
237      *
238      *  @access private
239      *  @param  array   &$target    対象とする配列
240      *  @param  array   $keys       キーの配列
241      *  @return string  指定された要素
242      */
243     function _getVarsByKeys(&$target, $keys)
244     {
245         $count = count($keys);
246         if ($count == 0) { // 探索完了
247             return $target;
248         } elseif ($this->max_form_deps + 1 <= $count) { // 深すぎる配列を制限する
249             return null;
250         }
251
252         // まだ探索するキーが残っている
253         $curval = array_shift($keys);
254         if (is_array($target) && array_key_exists($curval, $target)) {
255             return $this->_getVarsByKeys($target[$curval], $keys);
256         }
257         return null;
258     }
259
260     function _setVarsByKeys(&$target, $keys, &$var)
261     {
262         $count = count($keys);
263         if ($count == 0) { // 探索完了
264             $target = $var;
265             return;
266         } elseif ($this->max_form_deps + 1 <= $count) { // 深すぎる配列を制限する
267             return;
268         }
269
270         // まだ探索するキーが残っている
271         $curval = array_shift($keys);
272         if (is_array($target)) {
273             if (! array_key_exists($curval, $target)) {
274                 $target[$curval] = null;
275             }
276         } else {
277             $target = array($curval => null);
278         }
279
280         $this->_setVarsByKeys($target[$curval], $keys, $var);
281     }
282
283     /**
284      *  $_FILESの中からキーで指定された要素を取り出す
285      *
286      *  @access private
287      *  @param  array   &$target    対象とする配列
288      *  @param  string  $nane       キー
289      *  @param  string  $key        $_FILESに含まれる項目(tmp_name等)
290      *  @return string  指定された要素
291      */
292     function _getFilesInfoByFormName(&$target, $name, $key)
293     {
294         $form_keys = $this->_getFormNameArray($name);
295         array_splice($form_keys, 1, 0, $key);
296         return $this->_getVarsByKeys($target, $form_keys);
297     }
298
299     /**
300      *  ユーザから送信されたフォーム値をフォーム値定義に従ってインポートする
301      *
302      *  @access public
303      */
304     function setFormVars()
305     {
306         if (isset($_SERVER['REQUEST_METHOD']) == false) {
307             return;
308         } else if (strcasecmp($_SERVER['REQUEST_METHOD'], 'post') == 0) {
309             $http_vars =& $_POST;
310         } else {
311             $http_vars =& $_GET;
312         }
313
314         //
315         //  ethna_fid というフォーム値は、フォーム定義に関わらず受け入れる
316         //  これは、submitされたフォームを識別するために使われる
317         //  null の場合は、以下の場合である
318         //
319         //  1. フォームヘルパが使われていない
320         //  2. 画面の初期表示などで、submitされなかった
321         //  3. {form name=...} が未設定である
322         //
323         $this->form_vars['ethna_fid'] = (isset($http_vars['ethna_fid']) == false
324                                       || is_null($http_vars['ethna_fid']))
325                                       ? null
326                                       : $http_vars['ethna_fid'];
327
328         foreach ($this->form as $name => $def) {
329             $type = is_array($def['type']) ? $def['type'][0] : $def['type'];
330             if ($type == VAR_TYPE_FILE) {
331                 // ファイルの場合
332
333                 // 値の有無の検査
334                 if (is_null($this->_getFilesInfoByFormName($_FILES, $name, 'tmp_name'))) {
335                     $this->set($name, null);
336                     continue;
337                 }
338
339                 // 配列構造の検査
340                 if (is_array($def['type'])) {
341                     if (is_array($this->_getFilesInfoByFormName($_FILES, $name, 'tmp_name')) == false) {
342                         $this->handleError($name, E_FORM_WRONGTYPE_ARRAY);
343                         $this->set($name, null);
344                         continue;
345                     }
346                 } else {
347                     if (is_array($this->_getFilesInfoByFormName($_FILES, $name, 'tmp_name'))) {
348                         $this->handleError($name, E_FORM_WRONGTYPE_SCALAR);
349                         $this->set($name, null);
350                         continue;
351                     }
352                 }
353
354                 $files = null;
355                 if (is_array($def['type'])) {
356                     $files = array();
357                     // ファイルデータを再構成
358                     foreach (array_keys($this->_getFilesInfoByFormName($_FILES, $name, 'name')) as $key) {
359                         $files[$key] = array();
360                         $files[$key]['name'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'name');
361                         $files[$key]['type'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'type');
362                         $files[$key]['size'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'size');
363                         $files[$key]['tmp_name'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'tmp_name');
364                         if ($this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'error') == null) {
365                             // PHP 4.2.0 以前
366                             $files[$key]['error'] = 0;
367                         } else {
368                             $files[$key]['error'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'error');
369                         }
370                     }
371                 } else {
372                     $files['name'] = $this->_getFilesInfoByFormName($_FILES, $name, 'name');
373                     $files['type'] = $this->_getFilesInfoByFormName($_FILES, $name, 'type');
374                     $files['size'] = $this->_getFilesInfoByFormName($_FILES, $name, 'size');
375                     $files['tmp_name'] = $this->_getFilesInfoByFormName($_FILES, $name, 'tmp_name');
376                     if ($this->_getFilesInfoByFormName($_FILES, $name, 'error') == null) {
377                         // PHP 4.2.0 以前
378                         $files['error'] = 0;
379                     } else {
380                         $files['error'] = $this->_getFilesInfoByFormName($_FILES, $name, 'error');
381                     }
382                 }
383
384                 // 値のインポート
385                 $this->set($name, $files);
386
387             } else {
388                 // ファイル以外の場合
389
390                 $target_var = $this->_getVarsByFormName($http_vars, $name);
391
392                 // 値の有無の検査
393                 if (isset($target_var) == false
394                     || is_null($target_var)) {
395                     $this->set($name, null);
396                     if (isset($http_vars["{$name}_x"])
397                      && isset($http_vars["{$name}_y"])) {
398                         // 以前の仕様に合わせる
399                         $this->set($name, $http_vars["{$name}_x"]);
400                     }
401                     continue;
402                 }
403
404                 // 配列構造の検査
405                 if (is_array($def['type'])) {
406                     if (is_array($target_var) == false) {
407                         // 厳密には、この配列の各要素はスカラーであるべき
408                         $this->handleError($name, E_FORM_WRONGTYPE_ARRAY);
409                         $this->set($name, null);
410                         continue;
411                     }
412                 } else {
413                     if (is_array($target_var)) {
414                         $this->handleError($name, E_FORM_WRONGTYPE_SCALAR);
415                         $this->set($name, null);
416                         continue;
417                     }
418                 }
419
420                 // 値のインポート
421                 $this->set($name, $target_var);
422             }
423         }
424     }
425
426     /**
427      *  ユーザから送信されたフォーム値をクリアする
428      *
429      *  @access public
430      */
431     function clearFormVars()
432     {
433         $this->form_vars = array();
434     }
435
436     /**
437      *  フォーム値へのアクセサ(W)
438      *
439      *  @access public
440      *  @param  string  $name   フォーム値の名称
441      *  @param  string  $value  設定する値
442      */
443     function set($name, $value)
444     {
445         $this->_setVarsByFormName($this->form_vars, $name, $value);
446     }
447
448     /**
449      *  フォーム値定義を設定する
450      *
451      *  @access public
452      *  @param  string  $name   設定するフォーム名(nullなら全ての定義を設定)
453      *  @param  array   $value  設定するフォーム値定義
454      *  @return array   フォーム値定義
455      */
456     function setDef($name, $value)
457     {
458         if (is_null($name)) {
459             $this->form = $value;
460         }
461         else {
462             $this->form[$name] = $value;
463         }
464     }
465
466     /**
467      *  フォーム値を配列にして返す
468      *
469      *  @access public
470      *  @param  bool    $escape HTMLエスケープフラグ(true:エスケープする)
471      *  @return array   フォーム値を格納した配列
472      */
473     function &getArray($escape = true)
474     {
475         $retval = array();
476
477         $this->_getArray($this->form_vars, $retval, $escape);
478
479         return $retval;
480     }
481
482     /**
483      *  アプリケーション設定値のアクセサ(R)
484      *
485      *  @access public
486      *  @param  string  $name   キー
487      *  @return mixed   アプリケーション設定値
488      */
489     function getApp($name)
490     {
491         if (isset($this->app_vars[$name]) == false) {
492             return null;
493         }
494         return $this->app_vars[$name];
495     }
496
497     /**
498      *  アプリケーション設定値のアクセサ(W)
499      *
500      *  @access public
501      *  @param  string  $name   キー
502      *  @param  mixed   $value  値
503      */
504     function setApp($name, $value)
505     {
506         $this->app_vars[$name] = $value;
507     }
508
509     /**
510      *  アプリケーション設定値を配列にして返す
511      *
512      *  @access public
513      *  @param  boolean $escape HTMLエスケープフラグ(true:エスケープする)
514      *  @return array   フォーム値を格納した配列
515      */
516     function &getAppArray($escape = true)
517     {
518         $retval = array();
519
520         $this->_getArray($this->app_vars, $retval, $escape);
521
522         return $retval;
523     }
524
525     /**
526      *  アプリケーション設定値(自動エスケープなし)のアクセサ(R)
527      *
528      *  @access public
529      *  @param  string  $name   キー
530      *  @return mixed   アプリケーション設定値
531      */
532     function getAppNE($name)
533     {
534         if (isset($this->app_ne_vars[$name]) == false) {
535             return null;
536         }
537         return $this->app_ne_vars[$name];
538     }
539
540     /**
541      *  アプリケーション設定値(自動エスケープなし)のアクセサ(W)
542      *
543      *  @access public
544      *  @param  string  $name   キー
545      *  @param  mixed   $value  値
546      */
547     function setAppNE($name, $value)
548     {
549         $this->app_ne_vars[$name] = $value;
550     }
551
552     /**
553      *  アプリケーション設定値(自動エスケープなし)を配列にして返す
554      *
555      *  @access public
556      *  @param  boolean $escape HTMLエスケープフラグ(true:エスケープする)
557      *  @return array   フォーム値を格納した配列
558      */
559     function &getAppNEArray($escape = false)
560     {
561         $retval = array();
562
563         $this->_getArray($this->app_ne_vars, $retval, $escape);
564
565         return $retval;
566     }
567
568     /**
569      *  フォームを配列にして返す(内部処理)
570      *
571      *  @access private
572      *  @param  array   &$vars      フォーム(等)の配列
573      *  @param  array   &$retval    配列への変換結果
574      *  @param  bool    $escape     HTMLエスケープフラグ(true:エスケープする)
575      */
576     function _getArray(&$vars, &$retval, $escape)
577     {
578         foreach (array_keys($vars) as $name) {
579             if (is_array($vars[$name])) {
580                 $retval[$name] = array();
581                 $this->_getArray($vars[$name], $retval[$name], $escape);
582             } else {
583                 $retval[$name] = $escape
584                     ? htmlspecialchars($vars[$name], ENT_QUOTES) : $vars[$name];
585             }
586         }
587     }
588
589     /**
590      *  追加検証強制フラグを取得する
591      *  (通常検証でエラーが発生した場合でも_validatePlus()が呼び出される)
592      *  @access public
593      *  @return bool    true:追加検証強制 false:追加検証非強制
594      */
595     function isForceValidatePlus()
596     {
597         return $this->force_validate_plus;
598     }
599
600     /**
601      *  追加検証強制フラグを設定する
602      *
603      *  @access public
604      *  @param  $force_validate_plus    追加検証強制フラグ
605      */
606     function setForceValidatePlus($force_validate_plus)
607     {
608         $this->force_validate_plus = $force_validate_plus;
609     }
610
611     /**
612      *  フォーム値検証メソッド
613      *
614      *  @access public
615      *  @return int     発生したエラーの数
616      */
617     function validate()
618     {
619         foreach ($this->form as $name => $def) {
620             $this->_validateWithPlugin($name);
621         }
622
623         if ($this->ae->count() == 0 || $this->isForceValidatePlus()) {
624             // 追加検証メソッド
625             $this->_validatePlus();
626         }
627
628         return $this->ae->count();
629     }
630
631     /**
632      *  プラグインを使ったフォーム値検証メソッド
633      *
634      *  @access private
635      *  @param  string  $form_name  フォームの名前
636      *  @todo   ae 側に $key を与えられるようにする
637      */
638     function _validateWithPlugin($form_name)
639     {
640         // (pre) filter
641         if ($this->form[$form_name]['type'] != VAR_TYPE_FILE) {
642             
643             //    入力値とフィルタ定義を取り出す
644             $form_var = $this->get($form_name);
645             $filter = (isset($this->form[$form_name]['filter']))
646                     ? $this->form[$form_name]['filter']
647                     : null;
648
649             //    フィルタを適用
650             if (is_array($this->form[$form_name]['type']) == false) {
651                 $this->set($form_name, $this->_filter($form_var, $filter));
652             } else if ($form_var != null) {  //  配列の場合
653                 foreach (array_keys($form_var) as $key) {
654                     $this->set($form_name."[".$key."]", $this->_filter($form_var[$key], $filter));
655                 }
656             } else {  //  配列で値が空の場合
657                 $this->set($form_name, $this->_filter($form_var, $filter));
658             }
659         }
660
661         $form_vars = $this->get($form_name);
662         $plugin = $this->_getPluginDef($form_name);
663
664         // type のチェックを処理の最初に追加
665         $plugin = array_merge(array('type' => array()), $plugin);
666         if (is_array($this->form[$form_name]['type'])) {
667             $plugin['type']['type'] = $this->form[$form_name]['type'][0];
668         } else {
669             $plugin['type']['type'] = $this->form[$form_name]['type'];
670         }
671         if (isset($this->form[$form_name]['type_error'])) {
672             $plugin['type']['error'] = $this->form[$form_name]['type_error'];
673         }
674
675         // スカラーの場合
676         if (is_array($this->form[$form_name]['type']) == false) {
677             foreach (array_keys($plugin) as $name) {
678                 // break: 明示されていなければ,エラーが起きたらvalidateを継続しない
679                 $break = isset($plugin[$name]['break']) == false
680                                || $plugin[$name]['break'];
681
682                 // プラグイン取得
683                 unset($v);
684                 $v =& $this->plugin->getPlugin('Validator',
685                                                ucfirst(strtolower($name)));
686                 if (Ethna::isError($v)) {
687                     continue;
688                 }
689
690                 // バリデーション実行
691                 unset($r);
692                 $r =& $v->validate($form_name, $form_vars, $plugin[$name]);
693
694                 // エラー処理
695                 if ($r !== true) {
696                     if (Ethna::isError($r)) {
697                         $this->ae->addObject($form_name, $r);
698                     }
699                     if ($break) {
700                         break;
701                     }
702                 }
703             }
704             return;
705         }
706
707         // 配列の場合
708
709         // break 指示用の key list
710         $valid_keys = is_array($form_vars) ? array_keys($form_vars) : array();
711
712         foreach (array_keys($plugin) as $name) {
713             // break: 明示されていなければ,エラーが起きたらvalidateを継続しない
714             $break = isset($plugin[$name]['break']) == false
715                            || $plugin[$name]['break'];
716
717             // プラグイン取得
718             unset($v);
719             $v =& $this->plugin->getPlugin('Validator', ucfirst(strtolower($name)));
720             if (Ethna::isError($v)) {
721                 continue;
722             }
723
724             // 配列全体を受け取るプラグインの場合
725             if (isset($v->accept_array) && $v->accept_array == true) {
726                 // バリデーション実行
727                 unset($r);
728                 $r =& $v->validate($form_name, $form_vars, $plugin[$name]);
729
730                 // エラー処理
731                 if (Ethna::isError($r)) {
732                     $this->ae->addObject($form_name, $r);
733                     if ($break) {
734                         break;
735                     }
736                 }
737                 continue;
738             }
739
740             // 配列の各要素に対して実行
741             foreach ($valid_keys as $key) {
742                 // バリデーション実行
743                 unset($r);
744                 $r =& $v->validate($form_name, $form_vars[$key], $plugin[$name]);
745
746                 // エラー処理
747                 if (Ethna::isError($r)) {
748                     $this->ae->addObject($form_name, $r);
749                     if ($break) {
750                         unset($valid_keys[$key]);
751                     }
752                 }
753             }
754         }
755     }
756
757     /**
758      *  チェックメソッド(基底クラス)
759      *
760      *  @access public
761      *  @param  string  $name   フォーム項目名
762      *  @return array   チェック対象のフォーム値(エラーが無い場合はnull)
763      */
764     function check($name)
765     {
766         if (is_null($this->get($name)) || $this->get($name) === "") {
767             return null;
768         }
769
770         // Ethna_Backendの設定
771         $c =& Ethna_Controller::getInstance();
772         $this->backend =& $c->getBackend();
773
774         return to_array($this->get($name));
775     }
776
777     /**
778      *  チェックメソッド: 機種依存文字
779      *
780      *  @access public
781      *  @param  string  $name   フォーム項目名
782      *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
783      */
784     function &checkVendorChar($name)
785     {
786         $null = null;
787         $string = $this->get($name);
788
789         for ($i = 0; $i < strlen($string); $i++) {
790             /* JIS13区のみチェック */
791             $c = ord($string{$i});
792             if ($c < 0x80) {
793                 /* ASCII */
794             } else if ($c == 0x8e) {
795                 /* 半角カナ */
796                 $i++;
797             } else if ($c == 0x8f) {
798                 /* JIS X 0212 */
799                 $i += 2;
800             } else if ($c == 0xad || ($c >= 0xf9 && $c <= 0xfc)) {
801                 /* IBM拡張文字 / NEC選定IBM拡張文字 */
802                 return $this->ae->add($name,
803                     _et('{form} contains machine dependent code.'), E_FORM_INVALIDCHAR);
804             } else {
805                 $i++;
806             }
807         }
808
809         return $null;
810     }
811
812     /**
813      *  チェックメソッド: bool値
814      *
815      *  @access public
816      *  @param  string  $name   フォーム項目名
817      *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
818      */
819     function &checkBoolean($name)
820     {
821         $null = null;
822         $form_vars = $this->check($name);
823
824         if ($form_vars == null) {
825             return $null;
826         }
827
828         foreach ($form_vars as $v) {
829             if ($v === "") {
830                 continue;
831             }
832             if ($v != "0" && $v != "1") {
833                 return $this->ae->add($name,
834                     _et('Please input {form} properly.'), E_FORM_INVALIDCHAR);
835             }
836         }
837
838         return $null;
839     }
840
841     /**
842      *  チェックメソッド: メールアドレス
843      *
844      *  @access public
845      *  @param  string  $name   フォーム項目名
846      *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
847      */
848     function &checkMailaddress($name)
849     {
850         $null = null;
851         $form_vars = $this->check($name);
852
853         if ($form_vars == null) {
854             return $null;
855         }
856
857         foreach ($form_vars as $v) {
858             if ($v === "") {
859                 continue;
860             }
861             if (Ethna_Util::checkMailaddress($v) == false) {
862                 return $this->ae->add($name,
863                     _et('Please input {form} properly.'), E_FORM_INVALIDCHAR);
864             }
865         }
866
867         return $null;
868     }
869
870     /**
871      *  チェックメソッド: URL
872      *
873      *  @access public
874      *  @param  string  $name   フォーム項目名
875      *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
876      */
877     function &checkURL($name)
878     {
879         $null = null;
880         $form_vars = $this->check($name);
881
882         if ($form_vars == null) {
883             return $null;
884         }
885
886         foreach ($form_vars as $v) {
887             if ($v === "") {
888                 continue;
889             }
890             if (preg_match('/^(http:\/\/|https:\/\/|ftp:\/\/)/', $v) == 0) {
891                 return $this->ae->add($name,
892                     _et('Please input {form} properly.'), E_FORM_INVALIDCHAR);
893             }
894         }
895
896         return $null;
897     }
898
899     /**
900      *  フォーム値をhiddenタグとして返す
901      *
902      *  @access public
903      *  @param  array   $include_list   配列が指定された場合、その配列に含まれるフォーム項目のみが対象となる
904      *  @param  array   $exclude_list   配列が指定された場合、その配列に含まれないフォーム項目のみが対象となる
905      *  @return string  hiddenタグとして記述されたHTML
906      */
907     function getHiddenVars($include_list = null, $exclude_list = null)
908     {
909         $hidden_vars = "";
910         foreach ($this->form as $key => $value) {
911             if (is_array($include_list) == true
912                 && in_array($key, $include_list) == false) {
913                 continue;
914             }
915             if (is_array($exclude_list) == true
916                 && in_array($key, $exclude_list) == true) {
917                 continue;
918             }
919             
920             $type = is_array($value['type']) ? $value['type'][0] : $value['type'];
921             if ($type == VAR_TYPE_FILE) {
922                 continue;
923             }
924
925             $form_value = $this->get($key);
926             if (is_array($value['type'])) {
927                 $form_array = true;
928                 //  フォーム定義が配列なのにスカラーが
929                 //  渡ってきた場合は値を配列扱いとし、
930                 //  フォーム定義を尊重する
931                 if (is_array($form_value) == false) {
932                     $form_value = array($form_value);
933                 }
934             } else {
935                 //  フォーム定義がスカラーなのに配列が渡ってきた
936                 //  場合は救いようがないのでNOTICE扱いとし、タグも出力しない
937                 if (is_array($form_value)) {
938                     $this->handleError($key, E_FORM_WRONGTYPE_ARRAY);
939                     continue;
940                 }
941                 $form_value = array($form_value);
942                 $form_array = false;
943             }
944
945             if (is_null($this->get($key))) {
946                 // フォーム値が送られていない場合はそもそもhiddenタグを出力しない
947                 continue;
948             }
949
950             foreach ($form_value as $k => $v) {
951                 if ($form_array) {
952                     $form_name = "$key" . "[$k]";
953                 } else {
954                     $form_name = $key;
955                 }
956                 $hidden_vars .=
957                     sprintf("<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
958                             htmlspecialchars($form_name, ENT_QUOTES),
959                             htmlspecialchars($v, ENT_QUOTES)
960                     );
961             }
962         }
963         return $hidden_vars;
964     }
965
966     /**
967      *  フォーム値検証のエラー処理を行う
968      *
969      *  @access public
970      *  @param  string      $name   フォーム項目名
971      *  @param  int         $code   エラーコード
972      */
973     function handleError($name, $code)
974     {
975         $def = $this->getDef($name);
976
977         // ユーザ定義エラーメッセージ
978         $code_map = array(
979             E_FORM_REQUIRED     => 'required_error',
980             E_FORM_WRONGTYPE_SCALAR => 'type_error',
981             E_FORM_WRONGTYPE_ARRAY  => 'type_error',
982             E_FORM_WRONGTYPE_INT    => 'type_error',
983             E_FORM_WRONGTYPE_FLOAT  => 'type_error',
984             E_FORM_WRONGTYPE_DATETIME   => 'type_error',
985             E_FORM_WRONGTYPE_BOOLEAN    => 'type_error',
986             E_FORM_MIN_INT      => 'min_error',
987             E_FORM_MIN_FLOAT    => 'min_error',
988             E_FORM_MIN_DATETIME => 'min_error',
989             E_FORM_MIN_FILE     => 'min_error',
990             E_FORM_MIN_STRING   => 'min_error',
991             E_FORM_MAX_INT      => 'max_error',
992             E_FORM_MAX_FLOAT    => 'max_error',
993             E_FORM_MAX_DATETIME => 'max_error',
994             E_FORM_MAX_FILE     => 'max_error',
995             E_FORM_MAX_STRING   => 'max_error',
996             E_FORM_REGEXP       => 'regexp_error',
997         );
998         //   フォーム定義にエラーメッセージが定義されていれば
999         //   それを使う
1000         if (array_key_exists($code_map[$code], $def)) {
1001             $this->ae->add($name, $def[$code_map[$code]], $code);
1002             return;
1003         }
1004
1005         //   定義されていない場合は、内部のメッセージを使う
1006         if ($code == E_FORM_REQUIRED) {
1007             switch ($def['form_type']) {
1008             case FORM_TYPE_TEXT:
1009             case FORM_TYPE_PASSWORD:
1010             case FORM_TYPE_TEXTAREA:
1011             case FORM_TYPE_SUBMIT:
1012                 $message = _et('Please input {form}.');
1013                 break;
1014             case FORM_TYPE_SELECT:
1015             case FORM_TYPE_RADIO:
1016             case FORM_TYPE_CHECKBOX:
1017             case FORM_TYPE_FILE:
1018                 $message = _et('Please select {form}.');
1019                 break;
1020             default:
1021                 $message = _et('Please input {form}.');
1022                 break;
1023             }
1024         } else if ($code == E_FORM_WRONGTYPE_SCALAR) {
1025             $message = _et('Please input scalar value to {form}.');
1026         } else if ($code == E_FORM_WRONGTYPE_ARRAY) {
1027             $message = _et('Please input array value to {form}.');
1028         } else if ($code == E_FORM_WRONGTYPE_INT) {
1029             $message = _et('Please input integer value to {form}.');
1030         } else if ($code == E_FORM_WRONGTYPE_FLOAT) {
1031             $message = _et('Please input float value to {form}.');
1032         } else if ($code == E_FORM_WRONGTYPE_DATETIME) {
1033             $message = _et('Please input valid datetime to {form}.');
1034         } else if ($code == E_FORM_WRONGTYPE_BOOLEAN) {
1035             $message = _et('You can input 0 or 1 to {form}.');
1036         } else if ($code == E_FORM_MIN_INT) {
1037             $this->ae->add($name,
1038                 _et('Please input more than %d(int) to {form}.'),
1039                 $code, $def['min']);
1040             return;
1041         } else if ($code == E_FORM_MIN_FLOAT) {
1042             $this->ae->add($name,
1043                 _et('Please input more than %f(float) to {form}.'),
1044                 $code, $def['min']);
1045             return;
1046         } else if ($code == E_FORM_MIN_DATETIME) {
1047             $this->ae->add($name,
1048                 _et('Please input datetime value %s or later to {form}.'),
1049                 $code, $def['min']);
1050             return;
1051         } else if ($code == E_FORM_MIN_FILE) {
1052             $this->ae->add($name,
1053                 _et('Please specify file whose size is more than %d KB.'), 
1054                 $code, $def['min']);
1055             return;
1056         } else if ($code == E_FORM_MIN_STRING) {
1057             $this->ae->add($name,
1058                 _et('Please input more than %d full-size (%d half-size) characters to {form}.'),
1059                 $code, intval($def['min']/2), $def['min']);
1060             return;
1061         } else if ($code == E_FORM_MAX_INT) {
1062             $this->ae->add($name,
1063                 _et('Please input less than %d(int) to {form}.'),
1064                 $code, $def['max']);
1065             return;
1066         } else if ($code == E_FORM_MAX_FLOAT) {
1067             $this->ae->add($name,
1068                 _et('Please input less than %f(float) to {form}.'),
1069                 $code, $def['max']);
1070             return;
1071         } else if ($code == E_FORM_MAX_DATETIME) {
1072             $this->ae->add($name,
1073                 _et('Please input datetime value before %s to {form}.'),
1074                 $code, $def['max']);
1075             return;
1076         } else if ($code == E_FORM_MAX_FILE) {
1077             $this->ae->add($name,
1078                 _et('Please specify file whose size is less than %d KB to {form}.'), 
1079                 $code, $def['max']);
1080             return;
1081         } else if ($code == E_FORM_MAX_STRING) {
1082             $this->ae->add($name,
1083                 _et('Please input less than %d full-size (%d half-size) characters to {form}.'),
1084                 $code, intval($def['max']/2), $def['max']);
1085             return;
1086         } else if ($code == E_FORM_REGEXP) {
1087             $message = _et('Please input {form} properly.');
1088         }
1089
1090         $this->ae->add($name, $message, $code);
1091     }
1092
1093     /**
1094      *  ユーザ定義検証メソッド(フォーム値間の連携チェック等)
1095      *
1096      *  @access protected
1097      */
1098     function _validatePlus()
1099     {
1100     }
1101
1102     /**
1103      *  カスタムチェックメソッドを実行する
1104      *
1105      *  @access protected
1106      *  @param  string  $method_list    カスタムメソッド名(カンマ区切り)
1107      *  @param  string  $name           フォーム項目名
1108      */
1109     function _validateCustom($method_list, $name)
1110     {
1111         $method_list = preg_split('/\s*,\s*/', $method_list,
1112                                   -1, PREG_SPLIT_NO_EMPTY);
1113         if (is_array($method_list) == false) {
1114             return;
1115         }
1116         foreach ($method_list as $method) {
1117             $this->$method($name);
1118         }
1119     }
1120
1121     /**
1122      *  フォーム値に変換フィルタを適用する
1123      *
1124      *  @access private
1125      *  @param  mixed   $value  フォーム値
1126      *  @param  int     $filter フィルタ定義
1127      *  @return mixed   変換結果
1128      */
1129     function _filter($value, $filter)
1130     {
1131         if (is_null($filter)) {
1132             return $value;
1133         }
1134
1135         foreach (preg_split('/\s*,\s*/', $filter) as $f) {
1136             $method = sprintf('_filter_%s', $f);
1137             if (method_exists($this, $method) == false) {
1138                 $this->logger->log(LOG_WARNING,
1139                     'filter method is not defined [%s]', $method);
1140                 continue;
1141             }
1142             $value = $this->$method($value);
1143         }
1144
1145         return $value;
1146     }
1147
1148     /**
1149      *  フォーム値変換フィルタ: 全角英数字->半角英数字
1150      *
1151      *  @access protected
1152      *  @param  mixed   $value  フォーム値
1153      *  @return mixed   変換結果
1154      */
1155     function _filter_alnum_zentohan($value)
1156     {
1157         return mb_convert_kana($value, "a");
1158     }
1159
1160     /**
1161      *  フォーム値変換フィルタ: 全角数字->半角数字
1162      *
1163      *  @access protected
1164      *  @param  mixed   $value  フォーム値
1165      *  @return mixed   変換結果
1166      */
1167     function _filter_numeric_zentohan($value)
1168     {
1169         return mb_convert_kana($value, "n");
1170     }
1171
1172     /**
1173      *  フォーム値変換フィルタ: 全角英字->半角英字
1174      *
1175      *  @access protected
1176      *  @param  mixed   $value  フォーム値
1177      *  @return mixed   変換結果
1178      */
1179     function _filter_alphabet_zentohan($value)
1180     {
1181         return mb_convert_kana($value, "r");
1182     }
1183
1184     /**
1185      *  フォーム値変換フィルタ: 左空白削除
1186      *
1187      *  @access protected
1188      *  @param  mixed   $value  フォーム値
1189      *  @return mixed   変換結果
1190      */
1191     function _filter_ltrim($value)
1192     {
1193         return ltrim($value);
1194     }
1195
1196     /**
1197      *  フォーム値変換フィルタ: 右空白削除
1198      *
1199      *  @access protected
1200      *  @param  mixed   $value  フォーム値
1201      *  @return mixed   変換結果
1202      */
1203     function _filter_rtrim($value)
1204     {
1205         return rtrim($value);
1206     }
1207
1208     /**
1209      *  フォーム値変換フィルタ: NULL(0x00)削除
1210      *
1211      *  @access protected
1212      *  @param  mixed   $value  フォーム値
1213      *  @return mixed   変換結果
1214      */
1215     function _filter_ntrim($value)
1216     {
1217         return str_replace("\x00", "", $value);
1218     }
1219
1220     /**
1221      *  フォーム値変換フィルタ: 半角カナ->全角カナ
1222      *
1223      *  @access protected
1224      *  @param  mixed   $value  フォーム値
1225      *  @return mixed   変換結果
1226      */
1227     function _filter_kana_hantozen($value)
1228     {
1229         return mb_convert_kana($value, "K");
1230     }
1231
1232     /**
1233      *  フォーム値定義テンプレートを設定する
1234      *
1235      *  @access protected
1236      *  @param  array   $form_template  フォーム値テンプレート
1237      *  @return array   フォーム値テンプレート
1238      */
1239     function _setFormTemplate($form_template)
1240     {
1241         return $form_template;
1242     }
1243
1244     /**
1245      *  フォーム定義変更用、ユーザ定義ヘルパメソッド
1246      *
1247      *  Ethna_ActionForm#prepare() が実行される前に
1248      *  ユーザが動的にフォーム定義を変更したい場合に
1249      *  このメソッドをオーバーライドします。
1250      *
1251      *  $this->backend も初期化済みのため、DBやセッション
1252      *  の値に基づいてフォーム定義を変更することができます。
1253      *
1254      *  @access public 
1255      */
1256     function setFormDef_PreHelper()
1257     {
1258         //  TODO: override this method. 
1259     }
1260
1261     /**
1262      *  フォーム定義変更用、ユーザ定義ヘルパメソッド
1263      *
1264      *  フォームヘルパを使うときに、フォーム定義を動的に
1265      *  変更したい場合に、このメソッドをオーバーライドします。
1266      *
1267      *  以下の定義をテンプレートで行った場合に呼び出されます。
1268      *  
1269      *  {form ethna_action=...} (ethna_action がない場合は呼び出されません)
1270      *  {form_input action=...} (action がない場合は呼び出されません)
1271      *
1272      *  @access public 
1273      */
1274     function setFormDef_ViewHelper()
1275     {
1276         //   TODO: デフォルト実装は Ethna_ActionClass#prepare 前に
1277         //   呼び出されるものと同じ。異なる場合にオーバライドする
1278         $this->setFormDef_PreHelper(); 
1279     }
1280
1281     /**
1282      *  ヘルパオブジェクト(アプリケーションオブジェクト)
1283      *  経由でのフォーム値定義を設定する
1284      *
1285      *  @access protected
1286      */
1287     function _setFormDef_HelperObj()
1288     {
1289         foreach (array_keys($this->helper_app_object) as $key) {
1290             $object =& $this->helper_app_object[$key];
1291             $prop_def = $object->getDef();
1292
1293             foreach ($prop_def as $key => $value) {
1294                 // 1. override form_template
1295                 $form_key = isset($value['form_name']) ? $value['form_name'] : $key;
1296
1297                 if (isset($this->form_template[$form_key]) == false) {
1298                     $this->form_template[$form_key] = array();
1299                 }
1300
1301                 $this->form_template[$form_key]['type'] = $value['type'];
1302                 if (isset($value['required'])) {
1303                     $this->form_template[$form_key]['required'] = $value['required'];
1304                 }
1305
1306                 if ($value['type'] == VAR_TYPE_STRING && isset($value['length'])) {
1307                     $this->form_template[$form_key]['max'] = $value['length'];
1308                 }
1309
1310                 // 2. then activate form
1311                 if (in_array($key, $this->helper_skip_form) == false) {
1312                     if (isset($this->form[$key]) == false) {
1313                         $this->form[$key] = array();
1314                     }
1315                 }
1316             }
1317         }
1318     }
1319
1320     /**
1321      *  フォーム値定義を設定する
1322      *
1323      *  @access protected
1324      */
1325     function _setFormDef()
1326     {
1327         foreach ($this->form as $key => $value) {
1328             if (is_numeric($key)) {
1329                 $this->form[$value] = array();
1330                 unset($this->form[$key]);
1331             }
1332         }
1333
1334         foreach ($this->form as $key => $value) {
1335             if (array_key_exists($key, $this->form_template)
1336                 && is_array($this->form_template)) {
1337                 foreach ($this->form_template[$key] as $def_key => $def_value) {
1338                     if (array_key_exists($def_key, $value) == false) {
1339                         $this->form[$key][$def_key] = $def_value;
1340                     }
1341                 }
1342             }
1343         }
1344     }
1345
1346     /**
1347      *  フォーム値定義からプラグインの定義リストを分離する
1348      *
1349      *  @access protected
1350      *  @param  string  $form_name   プラグインの定義リストを取得するフォームの名前
1351      */
1352     function _getPluginDef($form_name)
1353     {
1354         //  $def = array(
1355         //               'name'         => 'number',
1356         //               'max'          => 10,
1357         //               'max_error'    => 'too large!',
1358         //               'min'          => 5,
1359         //               'min_error'    => 'too small!',
1360         //              );
1361         //
1362         // as plugin parameters:
1363         //
1364         //  $plugin_def = array(
1365         //                      'max' => array('max' => 10, 'error' => 'too large!'),
1366         //                      'min' => array('min' => 5, 'error' => 'too small!'),
1367         //                     );
1368
1369         $def = $this->getDef($form_name);
1370         $plugin = array();
1371         foreach (array_keys($def) as $key) {
1372             // 未定義要素をスキップ
1373             if ($def[$key] === null) {
1374                 continue;
1375             }
1376
1377             // プラグイン名とパラメータ名に分割
1378             $snippet = explode('_', $key, 2);
1379             $name = $snippet[0];
1380
1381             // 非プラグイン要素をスキップ
1382             if (in_array($name, $this->def_noplugin)) {
1383                 continue;
1384             }
1385
1386             if (count($snippet) == 1) {
1387                 // プラグイン名だけだった場合
1388                 if (is_array($def[$key])) {
1389                     // プラグインパラメータがあらかじめ配列で指定されている(とみなす)
1390                     $tmp = $def[$key];
1391                 } else {
1392                     $tmp = array($name => $def[$key]);
1393                 }
1394             } else {
1395                 // plugin_param の場合
1396                 $tmp = array($snippet[1] => $def[$key]);
1397             }
1398
1399             // merge
1400             if (isset($plugin[$name]) == false) {
1401                 $plugin[$name] = array();
1402             }
1403             $plugin[$name] = array_merge($plugin[$name], $tmp);
1404         }
1405
1406         return $plugin;
1407     }
1408
1409     /**
1410      *  アプリケーションオブジェクト(helper)を生成する
1411      *
1412      *  @access protected
1413      */
1414     function &_getHelperAppObject($key)
1415     {
1416         $app_object =& $this->backend->getObject($key);
1417         return $app_object;
1418     }
1419 }
1420 // }}}
1421