OSDN Git Service

5ca3a8204cbf48c048c0433207bf37ada8fdd981
[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
462         $this->form[$name] = $value;
463     }
464
465     /**
466      *  フォーム値を配列にして返す
467      *
468      *  @access public
469      *  @param  bool    $escape HTMLエスケープフラグ(true:エスケープする)
470      *  @return array   フォーム値を格納した配列
471      */
472     function &getArray($escape = true)
473     {
474         $retval = array();
475
476         $this->_getArray($this->form_vars, $retval, $escape);
477
478         return $retval;
479     }
480
481     /**
482      *  アプリケーション設定値のアクセサ(R)
483      *
484      *  @access public
485      *  @param  string  $name   キー
486      *  @return mixed   アプリケーション設定値
487      */
488     function getApp($name)
489     {
490         if (isset($this->app_vars[$name]) == false) {
491             return null;
492         }
493         return $this->app_vars[$name];
494     }
495
496     /**
497      *  アプリケーション設定値のアクセサ(W)
498      *
499      *  @access public
500      *  @param  string  $name   キー
501      *  @param  mixed   $value  値
502      */
503     function setApp($name, $value)
504     {
505         $this->app_vars[$name] = $value;
506     }
507
508     /**
509      *  アプリケーション設定値を配列にして返す
510      *
511      *  @access public
512      *  @param  boolean $escape HTMLエスケープフラグ(true:エスケープする)
513      *  @return array   フォーム値を格納した配列
514      */
515     function &getAppArray($escape = true)
516     {
517         $retval = array();
518
519         $this->_getArray($this->app_vars, $retval, $escape);
520
521         return $retval;
522     }
523
524     /**
525      *  アプリケーション設定値(自動エスケープなし)のアクセサ(R)
526      *
527      *  @access public
528      *  @param  string  $name   キー
529      *  @return mixed   アプリケーション設定値
530      */
531     function getAppNE($name)
532     {
533         if (isset($this->app_ne_vars[$name]) == false) {
534             return null;
535         }
536         return $this->app_ne_vars[$name];
537     }
538
539     /**
540      *  アプリケーション設定値(自動エスケープなし)のアクセサ(W)
541      *
542      *  @access public
543      *  @param  string  $name   キー
544      *  @param  mixed   $value  値
545      */
546     function setAppNE($name, $value)
547     {
548         $this->app_ne_vars[$name] = $value;
549     }
550
551     /**
552      *  アプリケーション設定値(自動エスケープなし)を配列にして返す
553      *
554      *  @access public
555      *  @param  boolean $escape HTMLエスケープフラグ(true:エスケープする)
556      *  @return array   フォーム値を格納した配列
557      */
558     function &getAppNEArray($escape = false)
559     {
560         $retval = array();
561
562         $this->_getArray($this->app_ne_vars, $retval, $escape);
563
564         return $retval;
565     }
566
567     /**
568      *  フォームを配列にして返す(内部処理)
569      *
570      *  @access private
571      *  @param  array   &$vars      フォーム(等)の配列
572      *  @param  array   &$retval    配列への変換結果
573      *  @param  bool    $escape     HTMLエスケープフラグ(true:エスケープする)
574      */
575     function _getArray(&$vars, &$retval, $escape)
576     {
577         foreach (array_keys($vars) as $name) {
578             if (is_array($vars[$name])) {
579                 $retval[$name] = array();
580                 $this->_getArray($vars[$name], $retval[$name], $escape);
581             } else {
582                 $retval[$name] = $escape
583                     ? htmlspecialchars($vars[$name], ENT_QUOTES) : $vars[$name];
584             }
585         }
586     }
587
588     /**
589      *  追加検証強制フラグを取得する
590      *  (通常検証でエラーが発生した場合でも_validatePlus()が呼び出される)
591      *  @access public
592      *  @return bool    true:追加検証強制 false:追加検証非強制
593      */
594     function isForceValidatePlus()
595     {
596         return $this->force_validate_plus;
597     }
598
599     /**
600      *  追加検証強制フラグを設定する
601      *
602      *  @access public
603      *  @param  $force_validate_plus    追加検証強制フラグ
604      */
605     function setForceValidatePlus($force_validate_plus)
606     {
607         $this->force_validate_plus = $force_validate_plus;
608     }
609
610     /**
611      *  フォーム値検証メソッド
612      *
613      *  @access public
614      *  @return int     発生したエラーの数
615      */
616     function validate()
617     {
618         foreach ($this->form as $name => $def) {
619             $this->_validateWithPlugin($name);
620         }
621
622         if ($this->ae->count() == 0 || $this->isForceValidatePlus()) {
623             // 追加検証メソッド
624             $this->_validatePlus();
625         }
626
627         return $this->ae->count();
628     }
629
630     /**
631      *  プラグインを使ったフォーム値検証メソッド
632      *
633      *  @access private
634      *  @param  string  $form_name  フォームの名前
635      *  @todo   ae 側に $key を与えられるようにする
636      */
637     function _validateWithPlugin($form_name)
638     {
639         // (pre) filter
640         if ($this->form[$form_name]['type'] != VAR_TYPE_FILE) {
641             
642             //    入力値とフィルタ定義を取り出す
643             $form_var = $this->get($form_name);
644             $filter = (isset($this->form[$form_name]['filter']))
645                     ? $this->form[$form_name]['filter']
646                     : null;
647
648             //    フィルタを適用
649             if (is_array($this->form[$form_name]['type']) == false) {
650                 $this->set($form_name, $this->_filter($form_var, $filter));
651             } else if ($form_var != null) {  //  配列の場合
652                 foreach (array_keys($form_var) as $key) {
653                     $this->set($form_name."[".$key."]", $this->_filter($form_var[$key], $filter));
654                 }
655             } else {  //  配列で値が空の場合
656                 $this->set($form_name, $this->_filter($form_var, $filter));
657             }
658         }
659
660         $form_vars = $this->get($form_name);
661         $plugin = $this->_getPluginDef($form_name);
662
663         // type のチェックを処理の最初に追加
664         $plugin = array_merge(array('type' => array()), $plugin);
665         if (is_array($this->form[$form_name]['type'])) {
666             $plugin['type']['type'] = $this->form[$form_name]['type'][0];
667         } else {
668             $plugin['type']['type'] = $this->form[$form_name]['type'];
669         }
670         if (isset($this->form[$form_name]['type_error'])) {
671             $plugin['type']['error'] = $this->form[$form_name]['type_error'];
672         }
673
674         // スカラーの場合
675         if (is_array($this->form[$form_name]['type']) == false) {
676             foreach (array_keys($plugin) as $name) {
677                 // break: 明示されていなければ,エラーが起きたらvalidateを継続しない
678                 $break = isset($plugin[$name]['break']) == false
679                                || $plugin[$name]['break'];
680
681                 // プラグイン取得
682                 unset($v);
683                 $v =& $this->plugin->getPlugin('Validator',
684                                                ucfirst(strtolower($name)));
685                 if (Ethna::isError($v)) {
686                     continue;
687                 }
688
689                 // バリデーション実行
690                 unset($r);
691                 $r =& $v->validate($form_name, $form_vars, $plugin[$name]);
692
693                 // エラー処理
694                 if ($r !== true) {
695                     if (Ethna::isError($r)) {
696                         $this->ae->addObject($form_name, $r);
697                     }
698                     if ($break) {
699                         break;
700                     }
701                 }
702             }
703             return;
704         }
705
706         // 配列の場合
707
708         // break 指示用の key list
709         $valid_keys = is_array($form_vars) ? array_keys($form_vars) : array();
710
711         foreach (array_keys($plugin) as $name) {
712             // break: 明示されていなければ,エラーが起きたらvalidateを継続しない
713             $break = isset($plugin[$name]['break']) == false
714                            || $plugin[$name]['break'];
715
716             // プラグイン取得
717             unset($v);
718             $v =& $this->plugin->getPlugin('Validator', ucfirst(strtolower($name)));
719             if (Ethna::isError($v)) {
720                 continue;
721             }
722
723             // 配列全体を受け取るプラグインの場合
724             if (isset($v->accept_array) && $v->accept_array == true) {
725                 // バリデーション実行
726                 unset($r);
727                 $r =& $v->validate($form_name, $form_vars, $plugin[$name]);
728
729                 // エラー処理
730                 if (Ethna::isError($r)) {
731                     $this->ae->addObject($form_name, $r);
732                     if ($break) {
733                         break;
734                     }
735                 }
736                 continue;
737             }
738
739             // 配列の各要素に対して実行
740             foreach ($valid_keys as $key) {
741                 // バリデーション実行
742                 unset($r);
743                 $r =& $v->validate($form_name, $form_vars[$key], $plugin[$name]);
744
745                 // エラー処理
746                 if (Ethna::isError($r)) {
747                     $this->ae->addObject($form_name, $r);
748                     if ($break) {
749                         unset($valid_keys[$key]);
750                     }
751                 }
752             }
753         }
754     }
755
756     /**
757      *  チェックメソッド(基底クラス)
758      *
759      *  @access public
760      *  @param  string  $name   フォーム項目名
761      *  @return array   チェック対象のフォーム値(エラーが無い場合はnull)
762      */
763     function check($name)
764     {
765         if (is_null($this->get($name)) || $this->get($name) === "") {
766             return null;
767         }
768
769         // Ethna_Backendの設定
770         $c =& Ethna_Controller::getInstance();
771         $this->backend =& $c->getBackend();
772
773         return to_array($this->get($name));
774     }
775
776     /**
777      *  チェックメソッド: 機種依存文字
778      *
779      *  @access public
780      *  @param  string  $name   フォーム項目名
781      *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
782      */
783     function &checkVendorChar($name)
784     {
785         $null = null;
786         $string = $this->get($name);
787
788         for ($i = 0; $i < strlen($string); $i++) {
789             /* JIS13区のみチェック */
790             $c = ord($string{$i});
791             if ($c < 0x80) {
792                 /* ASCII */
793             } else if ($c == 0x8e) {
794                 /* 半角カナ */
795                 $i++;
796             } else if ($c == 0x8f) {
797                 /* JIS X 0212 */
798                 $i += 2;
799             } else if ($c == 0xad || ($c >= 0xf9 && $c <= 0xfc)) {
800                 /* IBM拡張文字 / NEC選定IBM拡張文字 */
801                 return $this->ae->add($name,
802                     _et('{form} contains machine dependent code.'), E_FORM_INVALIDCHAR);
803             } else {
804                 $i++;
805             }
806         }
807
808         return $null;
809     }
810
811     /**
812      *  チェックメソッド: bool値
813      *
814      *  @access public
815      *  @param  string  $name   フォーム項目名
816      *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
817      */
818     function &checkBoolean($name)
819     {
820         $null = null;
821         $form_vars = $this->check($name);
822
823         if ($form_vars == null) {
824             return $null;
825         }
826
827         foreach ($form_vars as $v) {
828             if ($v === "") {
829                 continue;
830             }
831             if ($v != "0" && $v != "1") {
832                 return $this->ae->add($name,
833                     _et('Please input {form} properly.'), E_FORM_INVALIDCHAR);
834             }
835         }
836
837         return $null;
838     }
839
840     /**
841      *  チェックメソッド: メールアドレス
842      *
843      *  @access public
844      *  @param  string  $name   フォーム項目名
845      *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
846      */
847     function &checkMailaddress($name)
848     {
849         $null = null;
850         $form_vars = $this->check($name);
851
852         if ($form_vars == null) {
853             return $null;
854         }
855
856         foreach ($form_vars as $v) {
857             if ($v === "") {
858                 continue;
859             }
860             if (Ethna_Util::checkMailaddress($v) == false) {
861                 return $this->ae->add($name,
862                     _et('Please input {form} properly.'), E_FORM_INVALIDCHAR);
863             }
864         }
865
866         return $null;
867     }
868
869     /**
870      *  チェックメソッド: URL
871      *
872      *  @access public
873      *  @param  string  $name   フォーム項目名
874      *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
875      */
876     function &checkURL($name)
877     {
878         $null = null;
879         $form_vars = $this->check($name);
880
881         if ($form_vars == null) {
882             return $null;
883         }
884
885         foreach ($form_vars as $v) {
886             if ($v === "") {
887                 continue;
888             }
889             if (preg_match('/^(http:\/\/|https:\/\/|ftp:\/\/)/', $v) == 0) {
890                 return $this->ae->add($name,
891                     _et('Please input {form} properly.'), E_FORM_INVALIDCHAR);
892             }
893         }
894
895         return $null;
896     }
897
898     /**
899      *  フォーム値をhiddenタグとして返す
900      *
901      *  @access public
902      *  @param  array   $include_list   配列が指定された場合、その配列に含まれるフォーム項目のみが対象となる
903      *  @param  array   $exclude_list   配列が指定された場合、その配列に含まれないフォーム項目のみが対象となる
904      *  @return string  hiddenタグとして記述されたHTML
905      */
906     function getHiddenVars($include_list = null, $exclude_list = null)
907     {
908         $hidden_vars = "";
909         foreach ($this->form as $key => $value) {
910             if (is_array($include_list) == true
911                 && in_array($key, $include_list) == false) {
912                 continue;
913             }
914             if (is_array($exclude_list) == true
915                 && in_array($key, $exclude_list) == true) {
916                 continue;
917             }
918             
919             $type = is_array($value['type']) ? $value['type'][0] : $value['type'];
920             if ($type == VAR_TYPE_FILE) {
921                 continue;
922             }
923
924             $form_value = $this->get($key);
925             if (is_array($value['type'])) {
926                 $form_array = true;
927                 //  フォーム定義が配列なのにスカラーが
928                 //  渡ってきた場合は値を配列扱いとし、
929                 //  フォーム定義を尊重する
930                 if (is_array($form_value) == false) {
931                     $form_value = array($form_value);
932                 }
933             } else {
934                 //  フォーム定義がスカラーなのに配列が渡ってきた
935                 //  場合は救いようがないのでNOTICE扱いとし、タグも出力しない
936                 if (is_array($form_value)) {
937                     $this->handleError($key, E_FORM_WRONGTYPE_ARRAY);
938                     continue;
939                 }
940                 $form_value = array($form_value);
941                 $form_array = false;
942             }
943
944             if (is_null($this->get($key))) {
945                 // フォーム値が送られていない場合はそもそもhiddenタグを出力しない
946                 continue;
947             }
948
949             foreach ($form_value as $k => $v) {
950                 if ($form_array) {
951                     $form_name = "$key" . "[$k]";
952                 } else {
953                     $form_name = $key;
954                 }
955                 $hidden_vars .=
956                     sprintf("<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
957                     $form_name, htmlspecialchars($v, ENT_QUOTES));
958             }
959         }
960         return $hidden_vars;
961     }
962
963     /**
964      *  フォーム値検証のエラー処理を行う
965      *
966      *  @access public
967      *  @param  string      $name   フォーム項目名
968      *  @param  int         $code   エラーコード
969      */
970     function handleError($name, $code)
971     {
972         $def = $this->getDef($name);
973
974         // ユーザ定義エラーメッセージ
975         $code_map = array(
976             E_FORM_REQUIRED     => 'required_error',
977             E_FORM_WRONGTYPE_SCALAR => 'type_error',
978             E_FORM_WRONGTYPE_ARRAY  => 'type_error',
979             E_FORM_WRONGTYPE_INT    => 'type_error',
980             E_FORM_WRONGTYPE_FLOAT  => 'type_error',
981             E_FORM_WRONGTYPE_DATETIME   => 'type_error',
982             E_FORM_WRONGTYPE_BOOLEAN    => 'type_error',
983             E_FORM_MIN_INT      => 'min_error',
984             E_FORM_MIN_FLOAT    => 'min_error',
985             E_FORM_MIN_DATETIME => 'min_error',
986             E_FORM_MIN_FILE     => 'min_error',
987             E_FORM_MIN_STRING   => 'min_error',
988             E_FORM_MAX_INT      => 'max_error',
989             E_FORM_MAX_FLOAT    => 'max_error',
990             E_FORM_MAX_DATETIME => 'max_error',
991             E_FORM_MAX_FILE     => 'max_error',
992             E_FORM_MAX_STRING   => 'max_error',
993             E_FORM_REGEXP       => 'regexp_error',
994         );
995         //   フォーム定義にエラーメッセージが定義されていれば
996         //   それを使う
997         if (array_key_exists($code_map[$code], $def)) {
998             $this->ae->add($name, $def[$code_map[$code]], $code);
999             return;
1000         }
1001
1002         //   定義されていない場合は、内部のメッセージを使う
1003         if ($code == E_FORM_REQUIRED) {
1004             switch ($def['form_type']) {
1005             case FORM_TYPE_TEXT:
1006             case FORM_TYPE_PASSWORD:
1007             case FORM_TYPE_TEXTAREA:
1008             case FORM_TYPE_SUBMIT:
1009                 $message = _et('Please input {form}.');
1010                 break;
1011             case FORM_TYPE_SELECT:
1012             case FORM_TYPE_RADIO:
1013             case FORM_TYPE_CHECKBOX:
1014             case FORM_TYPE_FILE:
1015                 $message = _et('Please select {form}.');
1016                 break;
1017             default:
1018                 $message = _et('Please input {form}.');
1019                 break;
1020             }
1021         } else if ($code == E_FORM_WRONGTYPE_SCALAR) {
1022             $message = _et('Please input scalar value to {form}.');
1023         } else if ($code == E_FORM_WRONGTYPE_ARRAY) {
1024             $message = _et('Please input array value to {form}.');
1025         } else if ($code == E_FORM_WRONGTYPE_INT) {
1026             $message = _et('Please input integer value to {form}.');
1027         } else if ($code == E_FORM_WRONGTYPE_FLOAT) {
1028             $message = _et('Please input float value to {form}.');
1029         } else if ($code == E_FORM_WRONGTYPE_DATETIME) {
1030             $message = _et('Please input valid datetime to {form}.');
1031         } else if ($code == E_FORM_WRONGTYPE_BOOLEAN) {
1032             $message = _et('You can input 0 or 1 to {form}.');
1033         } else if ($code == E_FORM_MIN_INT) {
1034             $this->ae->add($name,
1035                 _et('Please input more than %d(int) to {form}.'),
1036                 $code, $def['min']);
1037             return;
1038         } else if ($code == E_FORM_MIN_FLOAT) {
1039             $this->ae->add($name,
1040                 _et('Please input more than %f(float) to {form}.'),
1041                 $code, $def['min']);
1042             return;
1043         } else if ($code == E_FORM_MIN_DATETIME) {
1044             $this->ae->add($name,
1045                 _et('Please input datetime value %s or later to {form}.'),
1046                 $code, $def['min']);
1047             return;
1048         } else if ($code == E_FORM_MIN_FILE) {
1049             $this->ae->add($name,
1050                 _et('Please specify file whose size is more than %d KB.'), 
1051                 $code, $def['min']);
1052             return;
1053         } else if ($code == E_FORM_MIN_STRING) {
1054             $this->ae->add($name,
1055                 _et('Please input more than %d full-size (%d half-size) characters to {form}.'),
1056                 $code, intval($def['min']/2), $def['min']);
1057             return;
1058         } else if ($code == E_FORM_MAX_INT) {
1059             $this->ae->add($name,
1060                 _et('Please input less than %d(int) to {form}.'),
1061                 $code, $def['max']);
1062             return;
1063         } else if ($code == E_FORM_MAX_FLOAT) {
1064             $this->ae->add($name,
1065                 _et('Please input less than %f(float) to {form}.'),
1066                 $code, $def['max']);
1067             return;
1068         } else if ($code == E_FORM_MAX_DATETIME) {
1069             $this->ae->add($name,
1070                 _et('Please input datetime value before %s to {form}.'),
1071                 $code, $def['max']);
1072             return;
1073         } else if ($code == E_FORM_MAX_FILE) {
1074             $this->ae->add($name,
1075                 _et('Please specify file whose size is less than %d KB to {form}.'), 
1076                 $code, $def['max']);
1077             return;
1078         } else if ($code == E_FORM_MAX_STRING) {
1079             $this->ae->add($name,
1080                 _et('Please input less than %d full-size (%d half-size) characters to {form}.'),
1081                 $code, intval($def['max']/2), $def['max']);
1082             return;
1083         } else if ($code == E_FORM_REGEXP) {
1084             $message = _et('Please input {form} properly.');
1085         }
1086
1087         $this->ae->add($name, $message, $code);
1088     }
1089
1090     /**
1091      *  ユーザ定義検証メソッド(フォーム値間の連携チェック等)
1092      *
1093      *  @access protected
1094      */
1095     function _validatePlus()
1096     {
1097     }
1098
1099     /**
1100      *  カスタムチェックメソッドを実行する
1101      *
1102      *  @access protected
1103      *  @param  string  $method_list    カスタムメソッド名(カンマ区切り)
1104      *  @param  string  $name           フォーム項目名
1105      */
1106     function _validateCustom($method_list, $name)
1107     {
1108         $method_list = preg_split('/\s*,\s*/', $method_list,
1109                                   -1, PREG_SPLIT_NO_EMPTY);
1110         if (is_array($method_list) == false) {
1111             return;
1112         }
1113         foreach ($method_list as $method) {
1114             $this->$method($name);
1115         }
1116     }
1117
1118     /**
1119      *  フォーム値に変換フィルタを適用する
1120      *
1121      *  @access private
1122      *  @param  mixed   $value  フォーム値
1123      *  @param  int     $filter フィルタ定義
1124      *  @return mixed   変換結果
1125      */
1126     function _filter($value, $filter)
1127     {
1128         if (is_null($filter)) {
1129             return $value;
1130         }
1131
1132         foreach (preg_split('/\s*,\s*/', $filter) as $f) {
1133             $method = sprintf('_filter_%s', $f);
1134             if (method_exists($this, $method) == false) {
1135                 $this->logger->log(LOG_WARNING,
1136                     'filter method is not defined [%s]', $method);
1137                 continue;
1138             }
1139             $value = $this->$method($value);
1140         }
1141
1142         return $value;
1143     }
1144
1145     /**
1146      *  フォーム値変換フィルタ: 全角英数字->半角英数字
1147      *
1148      *  @access protected
1149      *  @param  mixed   $value  フォーム値
1150      *  @return mixed   変換結果
1151      */
1152     function _filter_alnum_zentohan($value)
1153     {
1154         return mb_convert_kana($value, "a");
1155     }
1156
1157     /**
1158      *  フォーム値変換フィルタ: 全角数字->半角数字
1159      *
1160      *  @access protected
1161      *  @param  mixed   $value  フォーム値
1162      *  @return mixed   変換結果
1163      */
1164     function _filter_numeric_zentohan($value)
1165     {
1166         return mb_convert_kana($value, "n");
1167     }
1168
1169     /**
1170      *  フォーム値変換フィルタ: 全角英字->半角英字
1171      *
1172      *  @access protected
1173      *  @param  mixed   $value  フォーム値
1174      *  @return mixed   変換結果
1175      */
1176     function _filter_alphabet_zentohan($value)
1177     {
1178         return mb_convert_kana($value, "r");
1179     }
1180
1181     /**
1182      *  フォーム値変換フィルタ: 左空白削除
1183      *
1184      *  @access protected
1185      *  @param  mixed   $value  フォーム値
1186      *  @return mixed   変換結果
1187      */
1188     function _filter_ltrim($value)
1189     {
1190         return ltrim($value);
1191     }
1192
1193     /**
1194      *  フォーム値変換フィルタ: 右空白削除
1195      *
1196      *  @access protected
1197      *  @param  mixed   $value  フォーム値
1198      *  @return mixed   変換結果
1199      */
1200     function _filter_rtrim($value)
1201     {
1202         return rtrim($value);
1203     }
1204
1205     /**
1206      *  フォーム値変換フィルタ: NULL(0x00)削除
1207      *
1208      *  @access protected
1209      *  @param  mixed   $value  フォーム値
1210      *  @return mixed   変換結果
1211      */
1212     function _filter_ntrim($value)
1213     {
1214         return str_replace("\x00", "", $value);
1215     }
1216
1217     /**
1218      *  フォーム値変換フィルタ: 半角カナ->全角カナ
1219      *
1220      *  @access protected
1221      *  @param  mixed   $value  フォーム値
1222      *  @return mixed   変換結果
1223      */
1224     function _filter_kana_hantozen($value)
1225     {
1226         return mb_convert_kana($value, "K");
1227     }
1228
1229     /**
1230      *  フォーム値定義テンプレートを設定する
1231      *
1232      *  @access protected
1233      *  @param  array   $form_template  フォーム値テンプレート
1234      *  @return array   フォーム値テンプレート
1235      */
1236     function _setFormTemplate($form_template)
1237     {
1238         return $form_template;
1239     }
1240
1241     /**
1242      *  ユーザが動的にフォーム定義を変更したい場合に
1243      *  このメソッドをオーバーライドします。
1244      *  $this->backend も初期化済みのため、DBやセッション
1245      *  の値に基づいてフォーム定義を変更することができます。
1246      *
1247      *  @access public 
1248      */
1249     function setFormDef_PreHelper()
1250     {
1251         //  TODO: override this method. 
1252     }
1253
1254     /**
1255      *  ヘルパオブジェクト(アプリケーションオブジェクト)
1256      *  経由でのフォーム値定義を設定する
1257      *
1258      *  @access protected
1259      */
1260     function _setFormDef_HelperObj()
1261     {
1262         foreach (array_keys($this->helper_app_object) as $key) {
1263             $object =& $this->helper_app_object[$key];
1264             $prop_def = $object->getDef();
1265
1266             foreach ($prop_def as $key => $value) {
1267                 // 1. override form_template
1268                 $form_key = isset($value['form_name']) ? $value['form_name'] : $key;
1269
1270                 if (isset($this->form_template[$form_key]) == false) {
1271                     $this->form_template[$form_key] = array();
1272                 }
1273
1274                 $this->form_template[$form_key]['type'] = $value['type'];
1275                 if (isset($value['required'])) {
1276                     $this->form_template[$form_key]['required'] = $value['required'];
1277                 }
1278
1279                 if ($value['type'] == VAR_TYPE_STRING && isset($value['length'])) {
1280                     $this->form_template[$form_key]['max'] = $value['length'];
1281                 }
1282
1283                 // 2. then activate form
1284                 if (in_array($key, $this->helper_skip_form) == false) {
1285                     if (isset($this->form[$key]) == false) {
1286                         $this->form[$key] = array();
1287                     }
1288                 }
1289             }
1290         }
1291     }
1292
1293     /**
1294      *  フォーム値定義を設定する
1295      *
1296      *  @access protected
1297      */
1298     function _setFormDef()
1299     {
1300         foreach ($this->form as $key => $value) {
1301             if (is_numeric($key)) {
1302                 $this->form[$value] = array();
1303                 unset($this->form[$key]);
1304             }
1305         }
1306
1307         foreach ($this->form as $key => $value) {
1308             if (array_key_exists($key, $this->form_template)
1309                 && is_array($this->form_template)) {
1310                 foreach ($this->form_template[$key] as $def_key => $def_value) {
1311                     if (array_key_exists($def_key, $value) == false) {
1312                         $this->form[$key][$def_key] = $def_value;
1313                     }
1314                 }
1315             }
1316         }
1317     }
1318
1319     /**
1320      *  フォーム値定義からプラグインの定義リストを分離する
1321      *
1322      *  @access protected
1323      *  @param  string  $form_name   プラグインの定義リストを取得するフォームの名前
1324      */
1325     function _getPluginDef($form_name)
1326     {
1327         //  $def = array(
1328         //               'name'         => 'number',
1329         //               'max'          => 10,
1330         //               'max_error'    => 'too large!',
1331         //               'min'          => 5,
1332         //               'min_error'    => 'too small!',
1333         //              );
1334         //
1335         // as plugin parameters:
1336         //
1337         //  $plugin_def = array(
1338         //                      'max' => array('max' => 10, 'error' => 'too large!'),
1339         //                      'min' => array('min' => 5, 'error' => 'too small!'),
1340         //                     );
1341
1342         $def = $this->getDef($form_name);
1343         $plugin = array();
1344         foreach (array_keys($def) as $key) {
1345             // 未定義要素をスキップ
1346             if ($def[$key] === null) {
1347                 continue;
1348             }
1349
1350             // プラグイン名とパラメータ名に分割
1351             $snippet = explode('_', $key, 2);
1352             $name = $snippet[0];
1353
1354             // 非プラグイン要素をスキップ
1355             if (in_array($name, $this->def_noplugin)) {
1356                 continue;
1357             }
1358
1359             if (count($snippet) == 1) {
1360                 // プラグイン名だけだった場合
1361                 if (is_array($def[$key])) {
1362                     // プラグインパラメータがあらかじめ配列で指定されている(とみなす)
1363                     $tmp = $def[$key];
1364                 } else {
1365                     $tmp = array($name => $def[$key]);
1366                 }
1367             } else {
1368                 // plugin_param の場合
1369                 $tmp = array($snippet[1] => $def[$key]);
1370             }
1371
1372             // merge
1373             if (isset($plugin[$name]) == false) {
1374                 $plugin[$name] = array();
1375             }
1376             $plugin[$name] = array_merge($plugin[$name], $tmp);
1377         }
1378
1379         return $plugin;
1380     }
1381
1382     /**
1383      *  アプリケーションオブジェクト(helper)を生成する
1384      *
1385      *  @access protected
1386      */
1387     function &_getHelperAppObject($key)
1388     {
1389         $app_object =& $this->backend->getObject($key);
1390         return $app_object;
1391     }
1392 }
1393 // }}}
1394
1395 ?>