OSDN Git Service

ff6ef5cc76a39cf7ebde50302e62ef0caf5feb4e
[ethna/ethna.git] / class / Ethna_Controller.php
1 <?php
2 // vim: foldmethod=marker
3 /**
4  *  Ethna_Controller.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 // {{{ Ethna_Controller
13 /**
14  *  コントローラクラス
15  *
16  *  @todo       gatewayでswitchしてるところがダサダサ
17  *
18  *  @author     Masaki Fujimoto <fujimoto@php.net>
19  *  @access     public
20  *  @package    Ethna
21  */
22 class Ethna_Controller
23 {
24     /**#@+
25      *  @access private
26      */
27
28     /** @var    string      アプリケーションID */
29     var $appid = 'ETHNA';
30
31     /** @var    string      アプリケーションベースディレクトリ */
32     var $base = '';
33
34     /** @var    string      アプリケーションベースURL */
35     var $url = '';
36
37     /** @var    string      アプリケーションDSN(Data Source Name) */
38     var $dsn;
39
40     /** @var    array       アプリケーションディレクトリ */
41     var $directory = array();
42
43     /** @var    array       アプリケーションディレクトリ(デフォルト) */
44     var $directory_default = array(
45         'action'        => 'app/action',
46         'action_cli'    => 'app/action_cli',
47         'action_xmlrpc' => 'app/action_xmlrpc',
48         'app'           => 'app',
49         'plugin'        => 'app/plugin',
50         'bin'           => 'bin',
51         'etc'           => 'etc',
52         'filter'        => 'app/filter',
53         'locale'        => 'locale',
54         'log'           => 'log',
55         'plugins'       => array(),
56         'template'      => 'template',
57         'template_c'    => 'tmp',
58         'tmp'           => 'tmp',
59         'view'          => 'app/view',
60         'www'           => 'www',
61         'test'          => 'app/test',
62     );
63
64     /** @var    array       DBアクセス定義 */
65     var $db = array(
66         ''              => DB_TYPE_RW,
67     );
68
69     /** @var    array       拡張子設定 */
70     var $ext = array(
71         'php'           => 'php',
72         'tpl'           => 'tpl',
73     );
74
75     /** @var    array       クラス設定 */
76     var $class = array();
77
78     /** @var    array       クラス設定(デフォルト) */
79     var $class_default = array(
80         'class'         => 'Ethna_ClassFactory',
81         'backend'       => 'Ethna_Backend',
82         'config'        => 'Ethna_Config',
83         'db'            => 'Ethna_DB',
84         'error'         => 'Ethna_ActionError',
85         'form'          => 'Ethna_ActionForm',
86         'i18n'          => 'Ethna_I18N',
87         'logger'        => 'Ethna_Logger',
88         'plugin'        => 'Ethna_Plugin',
89         'renderer'      => 'Ethna_Renderer_Smarty',
90         'session'       => 'Ethna_Session',
91         'sql'           => 'Ethna_AppSQL',
92         'view'          => 'Ethna_ViewClass',
93         'url_handler'   => 'Ethna_UrlHandler',
94     );
95
96     /** @var    array       検索対象となるプラグインのアプリケーションIDのリスト */
97     var $plugin_search_appids;
98
99     /** @var    array       フィルタ設定 */
100     var $filter = array(
101     );
102
103     /** @var    string      使用言語設定 */
104     var $language;
105
106     /** @var    string      システム側エンコーディング */
107     var $system_encoding;
108
109     /** @var    string      クライアント側エンコーディング */
110     var $client_encoding;
111
112     /** @var    string  現在実行中のアクション名 */
113     var $action_name;
114
115     /** @var    string  現在実行中のXMLRPCメソッド名 */
116     var $xmlrpc_method_name;
117
118     /** @var    array   forward定義 */
119     var $forward = array();
120
121     /** @var    array   action定義 */
122     var $action = array();
123
124     /** @var    array   action(CLI)定義 */
125     var $action_cli = array();
126
127     /** @var    array   action(XMLRPC)定義 */
128     var $action_xmlrpc = array();
129
130     /** @var    array   アプリケーションマネージャ定義 */
131     var $manager = array();
132
133     /** @var    object  レンダラー */
134     var $renderer = null;
135
136     /** @var    array   smarty modifier定義 */
137     var $smarty_modifier_plugin = array();
138
139     /** @var    array   smarty function定義 */
140     var $smarty_function_plugin = array();
141
142     /** @var    array   smarty block定義 */
143     var $smarty_block_plugin = array();
144
145     /** @var    array   smarty prefilter定義 */
146     var $smarty_prefilter_plugin = array();
147
148     /** @var    array   smarty postfilter定義 */
149     var $smarty_postfilter_plugin = array();
150
151     /** @var    array   smarty outputfilter定義 */
152     var $smarty_outputfilter_plugin = array();
153
154
155     /** @var    array   フィルターチェイン(Ethna_Filterオブジェクトの配列) */
156     var $filter_chain = array();
157
158     /** @var    object  Ethna_ClassFactory  クラスファクトリオブジェクト */
159     var $class_factory = null;
160
161     /** @var    object  Ethna_ActionForm    フォームオブジェクト */
162     var $action_form = null;
163
164     /** @var    object  Ethna_View          ビューオブジェクト */
165     var $view = null;
166
167     /** @var    object  Ethna_Config        設定オブジェクト */
168     var $config = null;
169
170     /** @var    object  Ethna_Logger        ログオブジェクト */
171     var $logger = null;
172
173     /** @var    object  Ethna_Plugin        プラグインオブジェクト */
174     var $plugin = null;
175
176     /** @var    string  リクエストのゲートウェイ(www/cli/rest/xmlrpc/soap...) */
177     var $gateway = GATEWAY_WWW;
178
179     /**#@-*/
180
181
182     /**
183      *  Ethna_Controllerクラスのコンストラクタ
184      *
185      *  @access     public
186      */
187     function Ethna_Controller($gateway = GATEWAY_WWW)
188     {
189         $GLOBALS['_Ethna_controller'] =& $this;
190         if ($this->base === "") {
191             // EthnaコマンドなどでBASEが定義されていない場合がある
192             if (defined('BASE')) {
193                 $this->base = BASE;
194             }
195         }
196
197         $this->gateway = $gateway;
198
199         // クラス設定の未定義値を補完
200         foreach ($this->class_default as $key => $val) {
201             if (isset($this->class[$key]) == false) {
202                 $this->class[$key] = $val;
203             }
204         }
205
206         // ディレクトリ設定の未定義値を補完
207         foreach ($this->directory_default as $key => $val) {
208             if (isset($this->directory[$key]) == false) {
209                 $this->directory[$key] = $val;
210             }
211         }
212
213         // クラスファクトリオブジェクトの生成
214         $class_factory = $this->class['class'];
215         $this->class_factory =& new $class_factory($this, $this->class);
216
217         // エラーハンドラの設定
218         Ethna::setErrorCallback(array(&$this, 'handleError'));
219
220         // ディレクトリ名の設定(相対パス->絶対パス)
221         foreach ($this->directory as $key => $value) {
222             if ($key == 'plugins') {
223                 // Smartyプラグインディレクトリは配列で指定する
224                 $tmp = array();
225                 foreach (to_array($value) as $elt) {
226                     if (Ethna_Util::isAbsolute($elt) == false) {
227                         $tmp[] = $this->base . (empty($this->base) ? '' : '/') . $elt;
228                     }
229                 }
230                 $this->directory[$key] = $tmp;
231             } else {
232                 if (Ethna_Util::isAbsolute($value) == false) {
233                     $this->directory[$key] = $this->base . (empty($this->base) ? '' : '/') . $value;
234                 }
235             }
236         }
237
238         // 初期設定
239         list($this->language, $this->system_encoding, $this->client_encoding) = $this->_getDefaultLanguage();
240
241         $this->config =& $this->getConfig();
242         $this->dsn = $this->_prepareDSN();
243         $this->url = $this->config->get('url');
244
245         // プラグインオブジェクトの用意
246         $this->plugin =& $this->getPlugin();
247
248         //// assert (experimental)
249         //if ($this->config->get('debug') === false) {
250         //    ini_set('assert.active', 0);
251         //}
252
253         // ログ出力開始
254         $this->logger =& $this->getLogger();
255         $this->plugin->setLogger($this->logger);
256         $this->logger->begin();
257
258         // Ethnaマネージャ設定
259         $this->_activateEthnaManager();
260     }
261
262     /**
263      *  (現在アクティブな)コントローラのインスタンスを返す
264      *
265      *  @access public
266      *  @return object  Ethna_Controller    コントローラのインスタンス
267      *  @static
268      */
269     function &getInstance()
270     {
271         if (isset($GLOBALS['_Ethna_controller'])) {
272             return $GLOBALS['_Ethna_controller'];
273         } else {
274             $_ret_object = null;
275             return $_ret_object;
276         }
277     }
278
279     /**
280      *  アプリケーションIDを返す
281      *
282      *  @access public
283      *  @return string  アプリケーションID
284      */
285     function getAppId()
286     {
287         return ucfirst(strtolower($this->appid));
288     }
289
290     /**
291      *  アプリケーションIDをチェックする
292      *
293      *  @access public
294      *  @param  string  $id     アプリケーションID
295      *  @return mixed   true:OK Ethna_Error:NG
296      *  @static
297      */
298     function &checkAppId($id)
299     {
300         $true = true;
301         if (strcasecmp($id, 'ethna') === 0
302             || strcasecmp($id, 'app') === 0) {
303             return Ethna::raiseError("Application Id [$id] is reserved\n");
304         }
305         if (preg_match('/^[0-9a-zA-Z]+$/', $id) === 0) {
306             return Ethna::raiseError(
307                 "Only Numeric(0-9) and Alphabetical(A-Z) is allowed for Application Id\n"
308             );
309         }
310         return $true;
311     }
312
313     /**
314      *  アクション名をチェックする
315      *
316      *  @access public
317      *  @param  string  $action_name    アクション名
318      *  @return mixed   true:OK Ethna_Error:NG
319      *  @static
320      */
321     function &checkActionName($action_name)
322     {
323         $true = true;
324         if (preg_match('/^[a-zA-Z\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/',
325                        $action_name) === 0) {
326             return Ethna::raiseError("invalid action name [$action_name]");
327         }
328         return $true;
329     }
330
331     /**
332      *  ビュー名をチェックする
333      *
334      *  @access public
335      *  @param  string  $view_name    ビュー名
336      *  @return mixed   true:OK Ethna_Error:NG
337      *  @static
338      */
339     function &checkViewName($view_name)
340     {
341         $true = true;
342         if (preg_match('/^[a-zA-Z\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/',
343                        $view_name) === 0) {
344             return Ethna::raiseError("invalid view name [$view_name]");
345         }
346         return $true;
347     }
348
349     /**
350      *  DSNを返す
351      *
352      *  @access public
353      *  @param  string  $db_key DBキー
354      *  @return string  DSN
355      */
356     function getDSN($db_key = "")
357     {
358         if (isset($this->dsn[$db_key]) == false) {
359             return null;
360         }
361         return $this->dsn[$db_key];
362     }
363
364     /**
365      *  DSNの持続接続設定を返す
366      *
367      *  @access public
368      *  @param  string  $db_key DBキー
369      *  @return bool    true:persistent false:non-persistent(あるいは設定無し)
370      */
371     function getDSN_persistent($db_key = "")
372     {
373         $key = sprintf("dsn%s_persistent", $db_key == "" ? "" : "_$db_key");
374
375         $dsn_persistent = $this->config->get($key);
376         if (is_null($dsn_persistent)) {
377             return false;
378         }
379         return $dsn_persistent;
380     }
381
382     /**
383      *  DB設定を返す
384      *
385      *  @access public
386      *  @param  string  $db_key DBキー("", "r", "rw", "default", "blog_r"...)
387      *  @return string  $db_keyに対応するDB種別定義(設定が無い場合はnull)
388      */
389     function getDBType($db_key = null)
390     {
391         if (is_null($db_key)) {
392             // 一覧を返す
393             return $this->db;
394         }
395
396         if (isset($this->db[$db_key]) == false) {
397             return null;
398         }
399         return $this->db[$db_key];
400     }
401
402     /**
403      *  アプリケーションベースURLを返す
404      *
405      *  @access public
406      *  @return string  アプリケーションベースURL
407      */
408     function getURL()
409     {
410         return $this->url;
411     }
412
413     /**
414      *  アプリケーションベースディレクトリを返す
415      *
416      *  @access public
417      *  @return string  アプリケーションベースディレクトリ
418      */
419     function getBasedir()
420     {
421         return $this->base;
422     }
423
424     /**
425      *  クライアントタイプ/言語からテンプレートディレクトリ名を決定する
426      *
427      *  @access public
428      *  @return string  テンプレートディレクトリ
429      */
430     function getTemplatedir()
431     {
432         $template = $this->getDirectory('template');
433
434         // 言語別ディレクトリ
435         if (file_exists($template . '/' . $this->language)) {
436             $template .= '/' . $this->language;
437         }
438
439         return $template;
440     }
441
442     /**
443      *  アクションディレクトリ名を決定する
444      *
445      *  @access public
446      *  @return string  アクションディレクトリ
447      */
448     function getActiondir($gateway = null)
449     {
450         $key = 'action';
451         $gateway = is_null($gateway) ? $this->getGateway() : $gateway;
452         switch ($gateway) {
453         case GATEWAY_WWW:
454             $key = 'action';
455             break;
456         case GATEWAY_CLI:
457             $key = 'action_cli';
458             break;
459         case GATEWAY_XMLRPC:
460             $key = 'action_xmlrpc';
461             break;
462         }
463
464         return (empty($this->directory[$key]) ? ($this->base . (empty($this->base) ? '' : '/')) : ($this->directory[$key] . "/"));
465     }
466
467     /**
468      *  ビューディレクトリ名を決定する
469      *
470      *  @access public
471      *  @return string  アクションディレクトリ
472      */
473     function getViewdir()
474     {
475         return (empty($this->directory['view']) ? ($this->base . (empty($this->base) ? '' : '/')) : ($this->directory['view'] . "/"));
476     }
477
478     /**
479      *  アプリケーションディレクトリ設定を返す
480      *
481      *  @access public
482      *  @param  string  $key    ディレクトリタイプ("tmp", "template"...)
483      *  @return string  $keyに対応したアプリケーションディレクトリ(設定が無い場合はnull)
484      */
485     function getDirectory($key)
486     {
487         // for B.C.
488         if ($key == 'app' && isset($this->directory[$key]) == false) {
489             return BASE . '/app';
490         }
491
492         if (isset($this->directory[$key]) == false) {
493             return null;
494         }
495         return $this->directory[$key];
496     }
497
498     /**
499      *  アプリケーション拡張子設定を返す
500      *
501      *  @access public
502      *  @param  string  $key    拡張子タイプ("php", "tpl"...)
503      *  @return string  $keyに対応した拡張子(設定が無い場合はnull)
504      */
505     function getExt($key)
506     {
507         if (isset($this->ext[$key]) == false) {
508             return null;
509         }
510         return $this->ext[$key];
511     }
512
513     /**
514      *  クラスファクトリオブジェクトのアクセサ(R)
515      *
516      *  @access public
517      *  @return object  Ethna_ClassFactory  クラスファクトリオブジェクト
518      */
519     function &getClassFactory()
520     {
521         return $this->class_factory;
522     }
523
524     /**
525      *  アクションエラーオブジェクトのアクセサ
526      *
527      *  @access public
528      *  @return object  Ethna_ActionError   アクションエラーオブジェクト
529      */
530     function &getActionError()
531     {
532         return $this->class_factory->getObject('error');
533     }
534
535     /**
536      *  アクションフォームオブジェクトのアクセサ
537      *
538      *  @access public
539      *  @return object  Ethna_ActionForm    アクションフォームオブジェクト
540      */
541     function &getActionForm()
542     {
543         // 明示的にクラスファクトリを利用していない
544         return $this->action_form;
545     }
546
547     /**
548      *  ビューオブジェクトのアクセサ
549      *
550      *  @access public
551      *  @return object  Ethna_View          ビューオブジェクト
552      */
553     function &getView()
554     {
555         // 明示的にクラスファクトリを利用していない
556         return $this->view;
557     }
558
559     /**
560      *  backendオブジェクトのアクセサ
561      *
562      *  @access public
563      *  @return object  Ethna_Backend   backendオブジェクト
564      */
565     function &getBackend()
566     {
567         return $this->class_factory->getObject('backend');
568     }
569
570     /**
571      *  設定オブジェクトのアクセサ
572      *
573      *  @access public
574      *  @return object  Ethna_Config    設定オブジェクト
575      */
576     function &getConfig()
577     {
578         return $this->class_factory->getObject('config');
579     }
580
581     /**
582      *  i18nオブジェクトのアクセサ(R)
583      *
584      *  @access public
585      *  @return object  Ethna_I18N  i18nオブジェクト
586      */
587     function &getI18N()
588     {
589         return $this->class_factory->getObject('i18n');
590     }
591
592     /**
593      *  ログオブジェクトのアクセサ
594      *
595      *  @access public
596      *  @return object  Ethna_Logger        ログオブジェクト
597      */
598     function &getLogger()
599     {
600         return $this->class_factory->getObject('logger');
601     }
602
603     /**
604      *  セッションオブジェクトのアクセサ
605      *
606      *  @access public
607      *  @return object  Ethna_Session       セッションオブジェクト
608      */
609     function &getSession()
610     {
611         return $this->class_factory->getObject('session');
612     }
613
614     /**
615      *  SQLオブジェクトのアクセサ
616      *
617      *  @access public
618      *  @return object  Ethna_AppSQL    SQLオブジェクト
619      */
620     function &getSQL()
621     {
622         return $this->class_factory->getObject('sql');
623     }
624
625     /**
626      *  プラグインオブジェクトのアクセサ
627      *
628      *  @access public
629      *  @return object  Ethna_Plugin    プラグインオブジェクト
630      */
631     function &getPlugin()
632     {
633         return $this->class_factory->getObject('plugin');
634     }
635
636     /**
637      *  URLハンドラオブジェクトのアクセサ
638      *
639      *  @access public
640      *  @return object  Ethna_UrlHandler    URLハンドラオブジェクト
641      */
642     function &getUrlHandler()
643     {
644         return $this->class_factory->getObject('url_handler');
645     }
646
647     /**
648      *  マネージャ一覧を返す
649      *
650      *  @access public
651      *  @return array   マネージャ一覧
652      *  @obsolete
653      */
654     function getManagerList()
655     {
656         return $this->manager;
657     }
658
659     /**
660      *  実行中のアクション名を返す
661      *
662      *  @access public
663      *  @return string  実行中のアクション名
664      */
665     function getCurrentActionName()
666     {
667         return $this->action_name;
668     }
669
670     /**
671      *  実行中のXMLRPCメソッド名を返す
672      *
673      *  @access public
674      *  @return string  実行中のXMLRPCメソッド名
675      */
676     function getXmlrpcMethodName()
677     {
678         return $this->xmlrpc_method_name;
679     }
680
681     /**
682      *  使用言語を取得する
683      *
684      *  @access public
685      *  @return array   使用言語,システムエンコーディング名,クライアントエンコーディング名
686      */
687     function getLanguage()
688     {
689         return array($this->language, $this->system_encoding, $this->client_encoding);
690     }
691
692     /**
693      *  ゲートウェイを取得する
694      *
695      *  @access public
696      */
697     function getGateway()
698     {
699         return $this->gateway;
700     }
701
702     /**
703      *  ゲートウェイモードを設定する
704      *
705      *  @access public
706      */
707     function setGateway($gateway)
708     {
709         $this->gateway = $gateway;
710     }
711
712     /**
713      *  アプリケーションのエントリポイント
714      *
715      *  @access public
716      *  @param  string  $class_name     アプリケーションコントローラのクラス名
717      *  @param  mixed   $action_name    指定のアクション名(省略可)
718      *  @param  mixed   $fallback_action_name   アクションが決定できなかった場合に実行されるアクション名(省略可)
719      *  @static
720      */
721     function main($class_name, $action_name = "", $fallback_action_name = "")
722     {
723         $c =& new $class_name;
724         $c->trigger($action_name, $fallback_action_name);
725     }
726
727     /**
728      *  CLIアプリケーションのエントリポイント
729      *
730      *  @access public
731      *  @param  string  $class_name     アプリケーションコントローラのクラス名
732      *  @param  string  $action_name    実行するアクション名
733      *  @param  bool    $enable_filter  フィルタチェインを有効にするかどうか
734      *  @static
735      */
736     function main_CLI($class_name, $action_name, $enable_filter = true)
737     {
738         $c =& new $class_name(GATEWAY_CLI);
739         $c->action_cli[$action_name] = array();
740         $c->trigger($action_name, "", $enable_filter);
741     }
742
743     /**
744      *  XMLRPCアプリケーションのエントリポイント
745      *
746      *  @access public
747      *  @static
748      */
749     function main_XMLRPC($class_name)
750     {
751         if (extension_loaded('xmlrpc') == false) {
752             die("xmlrpc extension is required to enable this gateway");
753         }
754
755         $c =& new $class_name(GATEWAY_XMLRPC);
756         $c->trigger("", "", false);
757     }
758
759     /**
760      *  SOAPアプリケーションのエントリポイント
761      *
762      *  @access public
763      *  @param  string  $class_name     アプリケーションコントローラのクラス名
764      *  @param  mixed   $action_name    指定のアクション名(省略可)
765      *  @param  mixed   $fallback_action_name   アクションが決定できなかった場合に実行されるアクション名(省略可)
766      *  @static
767      */
768     function main_SOAP($class_name, $action_name = "", $fallback_action_name = "")
769     {
770         $c =& new $class_name(GATEWAY_SOAP);
771         $c->trigger($action_name, $fallback_action_name);
772     }
773
774     /**
775      *  フレームワークの処理を開始する
776      *
777      *  @access public
778      *  @param  mixed   $default_action_name    指定のアクション名
779      *  @param  mixed   $fallback_action_name   アクション名が決定できなかった場合に実行されるアクション名
780      *  @param  bool    $enable_filter  フィルタチェインを有効にするかどうか
781      *  @return mixed   0:正常終了 Ethna_Error:エラー
782      */
783     function trigger($default_action_name = "", $fallback_action_name = "", $enable_filter = true)
784     {
785         // フィルターの生成
786         if ($enable_filter) {
787             $this->_createFilterChain();
788         }
789
790         // 実行前フィルタ
791         for ($i = 0; $i < count($this->filter_chain); $i++) {
792             $r = $this->filter_chain[$i]->preFilter();
793             if (Ethna::isError($r)) {
794                 return $r;
795             }
796         }
797
798         // trigger
799         switch ($this->getGateway()) {
800         case GATEWAY_WWW:
801             $this->_trigger_WWW($default_action_name, $fallback_action_name);
802             break;
803         case GATEWAY_CLI:
804             $this->_trigger_CLI($default_action_name);
805             break;
806         case GATEWAY_XMLRPC:
807             $this->_trigger_XMLRPC();
808             break;
809         case GATEWAY_SOAP:
810             $this->_trigger_SOAP();
811             break;
812         }
813
814         // 実行後フィルタ
815         for ($i = count($this->filter_chain) - 1; $i >= 0; $i--) {
816             $r = $this->filter_chain[$i]->postFilter();
817             if (Ethna::isError($r)) {
818                 return $r;
819             }
820         }
821     }
822
823     /**
824      *  フレームワークの処理を実行する(WWW)
825      *
826      *  引数$default_action_nameに配列が指定された場合、その配列で指定された
827      *  アクション以外は受け付けない(指定されていないアクションが指定された
828      *  場合、配列の先頭で指定されたアクションが実行される)
829      *
830      *  @access private
831      *  @param  mixed   $default_action_name    指定のアクション名
832      *  @param  mixed   $fallback_action_name   アクション名が決定できなかった場合に実行されるアクション名
833      *  @return mixed   0:正常終了 Ethna_Error:エラー
834      */
835     function _trigger_WWW($default_action_name = "", $fallback_action_name = "")
836     {
837         // アクション名の取得
838         $action_name = $this->_getActionName($default_action_name, $fallback_action_name);
839
840         // マネージャ実行チェック
841         $this->_ethnaManagerEnabledCheck($action_name);
842
843         // アクション定義の取得
844         $action_obj =& $this->_getAction($action_name);
845         if (is_null($action_obj)) {
846             if ($fallback_action_name != "") {
847                 $this->logger->log(LOG_DEBUG, 'undefined action [%s] -> try fallback action [%s]', $action_name, $fallback_action_name);
848                 $action_obj =& $this->_getAction($fallback_action_name);
849             }
850             if (is_null($action_obj)) {
851                 return Ethna::raiseError("undefined action [%s]", E_APP_UNDEFINED_ACTION, $action_name);
852             } else {
853                 $action_name = $fallback_action_name;
854             }
855         }
856
857         // アクション実行前フィルタ
858         for ($i = 0; $i < count($this->filter_chain); $i++) {
859             $r = $this->filter_chain[$i]->preActionFilter($action_name);
860             if ($r != null) {
861                 $this->logger->log(LOG_DEBUG, 'action [%s] -> [%s] by %s', $action_name, $r, get_class($this->filter_chain[$i]));
862                 $action_name = $r;
863             }
864         }
865         $this->action_name = $action_name;
866
867         // 言語設定
868         $this->_setLanguage($this->language, $this->system_encoding, $this->client_encoding);
869
870         // オブジェクト生成
871         $backend =& $this->getBackend();
872
873         $form_name = $this->getActionFormName($action_name);
874         $this->action_form =& new $form_name($this);
875         $this->action_form->setFormVars();
876
877         // バックエンド処理実行
878         $backend->setActionForm($this->action_form);
879
880         $session =& $this->getSession();
881         $session->restore();
882         $forward_name = $backend->perform($action_name);
883
884         // アクション実行後フィルタ
885         for ($i = count($this->filter_chain) - 1; $i >= 0; $i--) {
886             $r = $this->filter_chain[$i]->postActionFilter($action_name, $forward_name);
887             if ($r != null) {
888                 $this->logger->log(LOG_DEBUG, 'forward [%s] -> [%s] by %s', $forward_name, $r, get_class($this->filter_chain[$i]));
889                 $forward_name = $r;
890             }
891         }
892
893         // コントローラで遷移先を決定する(オプション)
894         $forward_name = $this->_sortForward($action_name, $forward_name);
895
896         if ($forward_name != null) {
897             $view_class_name = $this->getViewClassName($forward_name);
898             $this->view =& new $view_class_name($backend, $forward_name, $this->_getForwardPath($forward_name));
899             $this->view->preforward();
900             $this->view->forward();
901         }
902
903         return 0;
904     }
905
906     /**
907      *  フレームワークの処理を実行する(CLI)
908      *
909      *  @access private
910      *  @param  mixed   $default_action_name    指定のアクション名
911      *  @return mixed   0:正常終了 Ethna_Error:エラー
912      */
913     function _trigger_CLI($default_action_name = "")
914     {
915         return $this->_trigger_WWW($default_action_name);
916     }
917
918     /**
919      *  フレームワークの処理を実行する(XMLRPC)
920      *
921      *  @access private
922      *  @param  mixed   $action_name    指定のアクション名
923      *  @return mixed   0:正常終了 Ethna_Error:エラー
924      */
925     function _trigger_XMLRPC($action_name = "")
926     {
927         // prepare xmlrpc server
928         $xmlrpc_gateway_method_name = "_Ethna_XmlrpcGateway";
929         $xmlrpc_server = xmlrpc_server_create();
930
931         $method = null;
932         $param = xmlrpc_decode_request(file_get_contents('php://input'), $method);
933         $this->xmlrpc_method_name = $method;
934
935         $request = xmlrpc_encode_request(
936             $xmlrpc_gateway_method_name,
937             $param,
938             array(
939                 'output_type'   => 'xml',
940                 'verbosity'     => 'pretty',
941                 'escaping'      => array('markup'),
942                 'version'       => 'xmlrpc',
943                 'encoding'      => 'utf-8'
944             )
945         ); 
946
947         xmlrpc_server_register_method(
948             $xmlrpc_server,
949             $xmlrpc_gateway_method_name,
950             $xmlrpc_gateway_method_name
951         );
952
953         // send request
954         $r = xmlrpc_server_call_method(
955             $xmlrpc_server,
956             $request,
957             null,
958             array(
959                 'output_type'   => 'xml',
960                 'verbosity'     => 'pretty',
961                 'escaping'      => array('markup'),
962                 'version'       => 'xmlrpc',
963                 'encoding'      => 'utf-8'
964             )
965         );
966
967         header('Content-Length: ' . strlen($r));
968         header('Content-Type: text/xml; charset=UTF-8');
969         print $r;
970     }
971
972     /**
973      *  _trigger_XMLRPCのコールバックメソッド
974      *
975      *  @access public
976      */
977     function trigger_XMLRPC($method, $param)
978     {
979         // アクション定義の取得
980         $action_obj =& $this->_getAction($method);
981         if (is_null($action_obj)) {
982             return Ethna::raiseError("undefined xmlrpc method [%s]", E_APP_UNDEFINED_ACTION, $method);
983         }
984
985         // オブジェクト生成
986         $backend =& $this->getBackend();
987
988         $form_name = $this->getActionFormName($method);
989         $this->action_form =& new $form_name($this);
990         $def = $this->action_form->getDef();
991         $n = 0;
992         foreach ($def as $key => $value) {
993             if (isset($param[$n]) == false) {
994                 $this->action_form->set($key, null);
995             } else {
996                 $this->action_form->set($key, $param[$n]);
997             }
998             $n++;
999         }
1000
1001         // バックエンド処理実行
1002         $backend->setActionForm($this->action_form);
1003
1004         $session =& $this->getSession();
1005         $session->restore();
1006         $r = $backend->perform($method);
1007
1008         return $r;
1009     }
1010
1011     /**
1012      *  SOAPフレームワークの処理を実行する
1013      *
1014      *  @access private
1015      */
1016     function _trigger_SOAP()
1017     {
1018         // SOAPエントリクラス
1019         $gg =& new Ethna_SOAP_GatewayGenerator();
1020         $script = $gg->generate();
1021         eval($script);
1022
1023         // SOAPリクエスト処理
1024         $server =& new SoapServer(null, array('uri' => $this->config->get('url')));
1025         $server->setClass($gg->getClassName());
1026         $server->handle();
1027     }
1028
1029     /**
1030      *  エラーハンドラ
1031      *
1032      *  エラー発生時の追加処理を行いたい場合はこのメソッドをオーバーライドする
1033      *  (アラートメール送信等−デフォルトではログ出力時にアラートメール
1034      *  が送信されるが、エラー発生時に別にアラートメールをここで送信
1035      *  させることも可能)
1036      *
1037      *  @access public
1038      *  @param  object  Ethna_Error     エラーオブジェクト
1039      */
1040     function handleError(&$error)
1041     {
1042         // ログ出力
1043         list ($log_level, $dummy) = $this->logger->errorLevelToLogLevel($error->getLevel());
1044         $message = $error->getMessage();
1045         $this->logger->log($log_level, sprintf("%s [ERROR CODE(%d)]", $message, $error->getCode()));
1046     }
1047
1048     /**
1049      *  エラーメッセージを取得する
1050      *
1051      *  @access public
1052      *  @param  int     $code       エラーコード
1053      *  @return string  エラーメッセージ
1054      */
1055     function getErrorMessage($code)
1056     {
1057         $message_list =& $GLOBALS['_Ethna_error_message_list'];
1058         for ($i = count($message_list)-1; $i >= 0; $i--) {
1059             if (array_key_exists($code, $message_list[$i])) {
1060                 return $message_list[$i][$code];
1061             }
1062         }
1063         return null;
1064     }
1065
1066     /**
1067      *  実行するアクション名を返す
1068      *
1069      *  @access private
1070      *  @param  mixed   $default_action_name    指定のアクション名
1071      *  @return string  実行するアクション名
1072      */
1073     function _getActionName($default_action_name, $fallback_action_name)
1074     {
1075         // フォームから要求されたアクション名を取得する
1076         $form_action_name = $this->_getActionName_Form();
1077         $form_action_name = preg_replace('/[^a-z0-9\-_]+/i', '', $form_action_name);
1078         $this->logger->log(LOG_DEBUG, 'form_action_name[%s]', $form_action_name);
1079
1080         // Ethnaマネージャへのフォームからのリクエストは拒否
1081         if ($form_action_name == "__ethna_info__" ||
1082             $form_action_name == "__ethna_unittest__") {
1083             $form_action_name = "";
1084         }
1085
1086         // フォームからの指定が無い場合はエントリポイントに指定されたデフォルト値を利用する
1087         if ($form_action_name == "" && count($default_action_name) > 0) {
1088             $tmp = is_array($default_action_name) ? $default_action_name[0] : $default_action_name;
1089             if ($tmp{strlen($tmp)-1} == '*') {
1090                 $tmp = substr($tmp, 0, -1);
1091             }
1092             $this->logger->log(LOG_DEBUG, '-> default_action_name[%s]', $tmp);
1093             $action_name = $tmp;
1094         } else {
1095             $action_name = $form_action_name;
1096         }
1097
1098         // エントリポイントに配列が指定されている場合は指定以外のアクション名は拒否する
1099         if (is_array($default_action_name)) {
1100             if ($this->_isAcceptableActionName($action_name, $default_action_name) == false) {
1101                 // 指定以外のアクション名で合った場合は$fallback_action_name(or デフォルト)
1102                 $tmp = $fallback_action_name != "" ? $fallback_action_name : $default_action_name[0];
1103                 if ($tmp{strlen($tmp)-1} == '*') {
1104                     $tmp = substr($tmp, 0, -1);
1105                 }
1106                 $this->logger->log(LOG_DEBUG, '-> fallback_action_name[%s]', $tmp);
1107                 $action_name = $tmp;
1108             }
1109         }
1110
1111         $this->logger->log(LOG_DEBUG, '<<< action_name[%s] >>>', $action_name);
1112
1113         return $action_name;
1114     }
1115
1116     /**
1117      *  フォームにより要求されたアクション名を返す
1118      *
1119      *  アプリケーションの性質に応じてこのメソッドをオーバーライドして下さい。
1120      *  デフォルトでは"action_"で始まるフォーム値の"action_"の部分を除いたもの
1121      *  ("action_sample"なら"sample")がアクション名として扱われます
1122      *
1123      *  @access protected
1124      *  @return string  フォームにより要求されたアクション名
1125      */
1126     function _getActionName_Form()
1127     {
1128         if (isset($_SERVER['REQUEST_METHOD']) == false) {
1129             return null;
1130         }
1131
1132         $url_handler =& $this->getUrlHandler();
1133         if ($_SERVER['REQUEST_METHOD'] == "GET") {
1134             $tmp_vars = $_GET;
1135         } else if ($_SERVER['REQUEST_METHOD'] == "POST") {
1136             $tmp_vars = $_POST;
1137         }
1138
1139         if (empty($_SERVER['URL_HANDLER']) == false) {
1140             $tmp_vars['__url_handler__'] = $_SERVER['URL_HANDLER'];
1141             $tmp_vars['__url_info__'] = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : null;
1142             $tmp_vars = $url_handler->requestToAction($tmp_vars);
1143
1144             if ($_SERVER['REQUEST_METHOD'] == "GET") {
1145                 $_GET = array_merge($_GET, $tmp_vars);
1146             } else if ($_SERVER['REQUEST_METHOD'] == "POST") {
1147                 $_POST = array_merge($_POST, $tmp_vars);
1148             }
1149             $_REQUEST = array_merge($_REQUEST, $tmp_vars);
1150         }
1151
1152         if (strcasecmp($_SERVER['REQUEST_METHOD'], 'post') == 0) {
1153             $http_vars =& $_POST;
1154         } else {
1155             $http_vars =& $_GET;
1156         }
1157
1158         // フォーム値からリクエストされたアクション名を取得する
1159         $action_name = $sub_action_name = null;
1160         foreach ($http_vars as $name => $value) {
1161             if ($value == "" || strncmp($name, 'action_', 7) != 0) {
1162                 continue;
1163             }
1164
1165             $tmp = substr($name, 7);
1166
1167             // type="image"対応
1168             if (preg_match('/_x$/', $name) || preg_match('/_y$/', $name)) {
1169                 $tmp = substr($tmp, 0, strlen($tmp)-2);
1170             }
1171
1172             // value="dummy"となっているものは優先度を下げる
1173             if ($value == "dummy") {
1174                 $sub_action_name = $tmp;
1175             } else {
1176                 $action_name = $tmp;
1177             }
1178         }
1179         if ($action_name == null) {
1180             $action_name = $sub_action_name;
1181         }
1182
1183         return $action_name;
1184     }
1185
1186     /**
1187      *  アクション名を指定するクエリ/HTMLを生成する
1188      *
1189      *  @access public
1190      *  @param  string  $action action to request
1191      *  @param  string  $type   hidden, url...
1192      *  @todo   consider gateway
1193      */
1194     function getActionRequest($action, $type = "hidden")
1195     {
1196         $s = null; 
1197         if ($type == "hidden") {
1198             $s = sprintf('<input type="hidden" name="action_%s" value="true">', htmlspecialchars($action, ENT_QUOTES));
1199         } else if ($type == "url") {
1200             $s = sprintf('action_%s=true', urlencode($action));
1201         }
1202         return $s;
1203     }
1204
1205     /**
1206      *  フォームにより要求されたアクション名に対応する定義を返す
1207      *
1208      *  @access private
1209      *  @param  string  $action_name    アクション名
1210      *  @return array   アクション定義
1211      */
1212     function &_getAction($action_name, $gateway = null)
1213     {
1214         $action = array();
1215         $gateway = is_null($gateway) ? $this->getGateway() : $gateway;
1216         switch ($gateway) {
1217         case GATEWAY_WWW:
1218             $action =& $this->action;
1219             break;
1220         case GATEWAY_CLI:
1221             $action =& $this->action_cli;
1222             break;
1223         case GATEWAY_XMLRPC:
1224             $action =& $this->action_xmlrpc;
1225             break;
1226         }
1227
1228         $action_obj = array();
1229         if (isset($action[$action_name])) {
1230             $action_obj = $action[$action_name];
1231             if (isset($action_obj['inspect']) && $action_obj['inspect']) {
1232                 return $action_obj;
1233             }
1234         } else {
1235             $this->logger->log(LOG_DEBUG, "action [%s] is not defined -> try default", $action_name);
1236         }
1237
1238         // アクションスクリプトのインクルード
1239         $this->_includeActionScript($action_obj, $action_name);
1240
1241         // 省略値の補正
1242         if (isset($action_obj['class_name']) == false) {
1243             $action_obj['class_name'] = $this->getDefaultActionClass($action_name);
1244         }
1245
1246         if (isset($action_obj['form_name']) == false) {
1247             $action_obj['form_name'] = $this->getDefaultFormClass($action_name);
1248         } else if (class_exists($action_obj['form_name']) == false) {
1249             // 明示指定されたフォームクラスが定義されていない場合は警告
1250             $this->logger->log(LOG_WARNING, 'stated form class is not defined [%s]', $action_obj['form_name']);
1251         }
1252
1253         // 必要条件の確認
1254         if (class_exists($action_obj['class_name']) == false) {
1255             $this->logger->log(LOG_NOTICE, 'action class is not defined [%s]', $action_obj['class_name']);
1256             $_ret_object = null;
1257             return $_ret_object;
1258         }
1259         if (class_exists($action_obj['form_name']) == false) {
1260             // フォームクラスは未定義でも良い
1261             $class_name = $this->class_factory->getObjectName('form');
1262             $this->logger->log(LOG_DEBUG, 'form class is not defined [%s] -> falling back to default [%s]', $action_obj['form_name'], $class_name);
1263             $action_obj['form_name'] = $class_name;
1264         }
1265
1266         $action_obj['inspect'] = true;
1267         $action[$action_name] = $action_obj;
1268         return $action[$action_name];
1269     }
1270
1271     /**
1272      *  アクション名とアクションクラスからの戻り値に基づいて遷移先を決定する
1273      *
1274      *  @access protected
1275      *  @param  string  $action_name    アクション名
1276      *  @param  string  $retval         アクションクラスからの戻り値
1277      *  @return string  遷移先
1278      */
1279     function _sortForward($action_name, $retval)
1280     {
1281         return $retval;
1282     }
1283
1284     /**
1285      *  フィルタチェインを生成する
1286      *
1287      *  @access private
1288      */
1289     function _createFilterChain()
1290     {
1291         $this->filter_chain = array();
1292         foreach ($this->filter as $filter) {
1293             //バージョン0.2.0以前のフィルタ群から探す
1294             $file = sprintf("%s/%s.%s", $this->getDirectory('filter'), $filter,$this->getExt('php'));
1295             if (file_exists($file)) {
1296                 include_once $file;
1297                 if (class_exists($filter)) {
1298                     $this->filter_chain[] =& new $filter($this);
1299                 }
1300             } else {  //プラグインから探す.
1301                 $filter_plugin =& $this->plugin->getPlugin('Filter', $filter);
1302                 if (Ethna::isError($filter_plugin)) {
1303                     continue;
1304                 }
1305
1306                 $this->filter_chain[] =& $filter_plugin;
1307             }
1308         }
1309     }
1310
1311     /**
1312      *  アクション名が実行許可されているものかどうかを返す
1313      *
1314      *  @access private
1315      *  @param  string  $action_name            リクエストされたアクション名
1316      *  @param  array   $default_action_name    許可されているアクション名
1317      *  @return bool    true:許可 false:不許可
1318      */
1319     function _isAcceptableActionName($action_name, $default_action_name)
1320     {
1321         foreach (to_array($default_action_name) as $name) {
1322             if ($action_name == $name) {
1323                 return true;
1324             } else if ($name{strlen($name)-1} == '*') {
1325                 if (strncmp($action_name, substr($name, 0, -1), strlen($name)-1) == 0) {
1326                     return true;
1327                 }
1328             }
1329         }
1330         return false;
1331     }
1332
1333     /**
1334      *  指定されたアクションのフォームクラス名を返す(オブジェクトの生成は行わない)
1335      *
1336      *  @access public
1337      *  @param  string  $action_name    アクション名
1338      *  @return string  アクションのフォームクラス名
1339      */
1340     function getActionFormName($action_name)
1341     {
1342         $action_obj =& $this->_getAction($action_name);
1343         if (is_null($action_obj)) {
1344             return null;
1345         }
1346
1347         return $action_obj['form_name'];
1348     }
1349
1350     /**
1351      *  アクションに対応するフォームクラス名が省略された場合のデフォルトクラス名を返す
1352      *
1353      *  デフォルトでは[プロジェクトID]_Form_[アクション名]となるので好み応じてオーバライドする
1354      *
1355      *  @access public
1356      *  @param  string  $action_name    アクション名
1357      *  @return string  アクションフォーム名
1358      */
1359     function getDefaultFormClass($action_name, $gateway = null)
1360     {
1361         $gateway_prefix = $this->_getGatewayPrefix($gateway);
1362
1363         $postfix = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($action_name));
1364         $r = sprintf("%s_%sForm_%s", $this->getAppId(), $gateway_prefix ? $gateway_prefix . "_" : "", $postfix);
1365         $this->logger->log(LOG_DEBUG, "default action class [%s]", $r);
1366
1367         return $r;
1368     }
1369
1370     /**
1371      *  getDefaultFormClass()で取得したクラス名からアクション名を取得する
1372      *
1373      *  getDefaultFormClass()をオーバーライドした場合、こちらも合わせてオーバーライド
1374      *  することを推奨(必須ではない)
1375      *
1376      *  @access public
1377      *  @param  string  $class_name     フォームクラス名
1378      *  @return string  アクション名
1379      */
1380     function actionFormToName($class_name)
1381     {
1382         $prefix = sprintf("%s_Form_", $this->getAppId());
1383         if (preg_match("/$prefix(.*)/", $class_name, $match) == 0) {
1384             // 不明なクラス名
1385             return null;
1386         }
1387         $target = $match[1];
1388
1389         $action_name = substr(preg_replace('/([A-Z])/e', "'_' . strtolower('\$1')", $target), 1);
1390
1391         return $action_name;
1392     }
1393
1394     /**
1395      *  アクションに対応するフォームパス名が省略された場合のデフォルトパス名を返す
1396      *
1397      *  デフォルトでは_getDefaultActionPath()と同じ結果を返す(1ファイルに
1398      *  アクションクラスとフォームクラスが記述される)ので、好みに応じて
1399      *  オーバーライドする
1400      *
1401      *  @access public
1402      *  @param  string  $action_name    アクション名
1403      *  @return string  form classが定義されるスクリプトのパス名
1404      */
1405     function getDefaultFormPath($action_name)
1406     {
1407         return $this->getDefaultActionPath($action_name);
1408     }
1409
1410     /**
1411      *  指定されたアクションのクラス名を返す(オブジェクトの生成は行わない)
1412      *
1413      *  @access public
1414      *  @param  string  $action_name    アクションの名称
1415      *  @return string  アクションのクラス名
1416      */
1417     function getActionClassName($action_name)
1418     {
1419         $action_obj =& $this->_getAction($action_name);
1420         if ($action_obj == null) {
1421             return null;
1422         }
1423
1424         return $action_obj['class_name'];
1425     }
1426
1427     /**
1428      *  アクションに対応するアクションクラス名が省略された場合のデフォルトクラス名を返す
1429      *
1430      *  デフォルトでは[プロジェクトID]_Action_[アクション名]となるので好み応じてオーバライドする
1431      *
1432      *  @access public
1433      *  @param  string  $action_name    アクション名
1434      *  @return string  アクションクラス名
1435      */
1436     function getDefaultActionClass($action_name, $gateway = null)
1437     {
1438         $gateway_prefix = $this->_getGatewayPrefix($gateway);
1439
1440         $postfix = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($action_name));
1441         $r = sprintf("%s_%sAction_%s", $this->getAppId(), $gateway_prefix ? $gateway_prefix . "_" : "", $postfix);
1442         $this->logger->log(LOG_DEBUG, "default action class [%s]", $r);
1443
1444         return $r;
1445     }
1446
1447     /**
1448      *  getDefaultActionClass()で取得したクラス名からアクション名を取得する
1449      *
1450      *  getDefaultActionClass()をオーバーライドした場合、こちらも合わせてオーバーライド
1451      *  することを推奨(必須ではない)
1452      *
1453      *  @access public
1454      *  @param  string  $class_name     アクションクラス名
1455      *  @return string  アクション名
1456      */
1457     function actionClassToName($class_name)
1458     {
1459         $prefix = sprintf("%s_Action_", $this->getAppId());
1460         if (preg_match("/$prefix(.*)/", $class_name, $match) == 0) {
1461             // 不明なクラス名
1462             return null;
1463         }
1464         $target = $match[1];
1465
1466         $action_name = substr(preg_replace('/([A-Z])/e', "'_' . strtolower('\$1')", $target), 1);
1467
1468         return $action_name;
1469     }
1470
1471     /**
1472      *  アクションに対応するアクションパス名が省略された場合のデフォルトパス名を返す
1473      *
1474      *  デフォルトでは"foo_bar" -> "/Foo/Bar.php"となるので好み応じてオーバーライドする
1475      *
1476      *  @access public
1477      *  @param  string  $action_name    アクション名
1478      *  @return string  アクションクラスが定義されるスクリプトのパス名
1479      */
1480     function getDefaultActionPath($action_name)
1481     {
1482         $r = preg_replace('/_(.)/e', "'/' . strtoupper('\$1')", ucfirst($action_name)) . '.' . $this->getExt('php');
1483         $this->logger->log(LOG_DEBUG, "default action path [%s]", $r);
1484
1485         return $r;
1486     }
1487
1488     /**
1489      *  指定された遷移名に対応するビュークラス名を返す(オブジェクトの生成は行わない)
1490      *
1491      *  @access public
1492      *  @param  string  $forward_name   遷移先の名称
1493      *  @return string  view classのクラス名
1494      */
1495     function getViewClassName($forward_name)
1496     {
1497         if ($forward_name == null) {
1498             return null;
1499         }
1500
1501         if (isset($this->forward[$forward_name])) {
1502             $forward_obj = $this->forward[$forward_name];
1503         } else {
1504             $forward_obj = array();
1505         }
1506
1507         if (isset($forward_obj['view_name'])) {
1508             $class_name = $forward_obj['view_name'];
1509             if (class_exists($class_name)) {
1510                 return $class_name;
1511             }
1512         } else {
1513             $class_name = null;
1514         }
1515
1516         // viewのインクルード
1517         $this->_includeViewScript($forward_obj, $forward_name);
1518
1519         if (is_null($class_name) == false && class_exists($class_name)) {
1520             return $class_name;
1521         } else if (is_null($class_name) == false) {
1522             $this->logger->log(LOG_WARNING, 'stated view class is not defined [%s] -> try default', $class_name);
1523         }
1524
1525         $class_name = $this->getDefaultViewClass($forward_name);
1526         if (class_exists($class_name)) {
1527             return $class_name;
1528         } else {
1529             $class_name = $this->class_factory->getObjectName('view');
1530             $this->logger->log(LOG_DEBUG, 'view class is not defined for [%s] -> use default [%s]', $forward_name, $class_name);
1531             return $class_name;
1532         }
1533     }
1534
1535     /**
1536      *  遷移名に対応するビュークラス名が省略された場合のデフォルトクラス名を返す
1537      *
1538      *  デフォルトでは[プロジェクトID]_View_[遷移名]となるので好み応じてオーバライドする
1539      *
1540      *  @access public
1541      *  @param  string  $forward_name   forward名
1542      *  @return string  view classクラス名
1543      */
1544     function getDefaultViewClass($forward_name, $gateway = null)
1545     {
1546         $gateway_prefix = $this->_getGatewayPrefix($gateway);
1547
1548         $postfix = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($forward_name));
1549         $r = sprintf("%s_%sView_%s", $this->getAppId(), $gateway_prefix ? $gateway_prefix . "_" : "", $postfix);
1550         $this->logger->log(LOG_DEBUG, "default view class [%s]", $r);
1551
1552         return $r;
1553     }
1554
1555     /**
1556      *  遷移名に対応するビューパス名が省略された場合のデフォルトパス名を返す
1557      *
1558      *  デフォルトでは"foo_bar" -> "/Foo/Bar.php"となるので好み応じてオーバーライドする
1559      *
1560      *  @access public
1561      *  @param  string  $forward_name   forward名
1562      *  @return string  view classが定義されるスクリプトのパス名
1563      */
1564     function getDefaultViewPath($forward_name)
1565     {
1566         $r = preg_replace('/_(.)/e', "'/' . strtoupper('\$1')", ucfirst($forward_name)) . '.' . $this->getExt('php');
1567         $this->logger->log(LOG_DEBUG, "default view path [%s]", $r);
1568
1569         return $r;
1570     }
1571
1572     /**
1573      *  遷移名に対応するテンプレートパス名が省略された場合のデフォルトパス名を返す
1574      *
1575      *  デフォルトでは"foo_bar"というforward名が"foo/bar" + テンプレート拡張子となる
1576      *  ので好み応じてオーバライドする
1577      *
1578      *  @access public
1579      *  @param  string  $forward_name   forward名
1580      *  @return string  forwardパス名
1581      */
1582     function getDefaultForwardPath($forward_name)
1583     {
1584         return str_replace('_', '/', $forward_name) . '.' . $this->ext['tpl'];
1585     }
1586     
1587     /**
1588      *  テンプレートパス名から遷移名を取得する
1589      *
1590      *  getDefaultForwardPath()をオーバーライドした場合、こちらも合わせてオーバーライド
1591      *  することを推奨(必須ではない)
1592      *
1593      *  @access public
1594      *  @param  string  $forward_path   テンプレートパス名
1595      *  @return string  遷移名
1596      */
1597     function forwardPathToName($forward_path)
1598     {
1599         $forward_path = preg_replace('/^\/+/', '', $forward_path);
1600         $forward_path = preg_replace(sprintf('/\.%s$/', $this->getExt('tpl')), '', $forward_path);
1601
1602         return str_replace('/', '_', $forward_path);
1603     }
1604
1605     /**
1606      *  遷移名からテンプレートファイルのパス名を取得する
1607      *
1608      *  @access private
1609      *  @param  string  $forward_name   forward名
1610      *  @return string  テンプレートファイルのパス名
1611      */
1612     function _getForwardPath($forward_name)
1613     {
1614         $forward_obj = null;
1615
1616         if (isset($this->forward[$forward_name]) == false) {
1617             // try default
1618             $this->forward[$forward_name] = array();
1619         }
1620         $forward_obj =& $this->forward[$forward_name];
1621         if (isset($forward_obj['forward_path']) == false) {
1622             // 省略値補正
1623             $forward_obj['forward_path'] = $this->getDefaultForwardPath($forward_name);
1624         }
1625
1626         return $forward_obj['forward_path'];
1627     }
1628
1629     /**
1630      *  レンダラを取得する(getTemplateEngine()はそのうち廃止されgetRenderer()に統合される予定)
1631      *
1632      *  @access public
1633      *  @return object  Ethna_Renderer  レンダラオブジェクト
1634      */
1635     function &getRenderer()
1636     {
1637         $_ret_object =& $this->getTemplateEngine();
1638         return $_ret_object;
1639     }
1640
1641     /**
1642      *  テンプレートエンジン取得する
1643      *
1644      *  @access public
1645      *  @return object  Ethna_Renderer  レンダラオブジェクト
1646      *  @obsolete
1647      */
1648     function &getTemplateEngine()
1649     {
1650         if (is_object($this->renderer)) {
1651             return $this->renderer;
1652         }
1653         
1654         $this->renderer =& $this->class_factory->getObject('renderer');
1655        
1656         // {{{ for B.C.
1657         if (strtolower(get_class($this->renderer)) == "ethna_renderer_smarty") {
1658             // user defined modifiers
1659             foreach ($this->smarty_modifier_plugin as $modifier) {
1660                 $name = str_replace('smarty_modifier_', '', $modifier);
1661                 $this->renderer->setPlugin($name,'modifier', $modifier);
1662             }
1663
1664             // user defined functions
1665             foreach ($this->smarty_function_plugin as $function) {
1666                 if (!is_array($function)) {
1667                     $name = str_replace('smarty_function_', '', $function);
1668                     $this->renderer->setPlugin($name, 'function', $function);
1669                 } else {
1670                     $this->renderer->setPlugin($function[1], 'function', $function);
1671                 }
1672             }
1673
1674             // user defined blocks
1675             foreach ($this->smarty_block_plugin as $block) {
1676                 if (!is_array($block)) {
1677                     $name = str_replace('smarty_block_', '', $block);
1678                     $this->renderer->setPlugin($name,'block', $block);
1679                 } else {
1680                     $this->renderer->setPlugin($block[1],'block', $block);
1681                 }
1682             }
1683
1684             // user defined prefilters
1685             foreach ($this->smarty_prefilter_plugin as $prefilter) {
1686                 if (!is_array($prefilter)) {
1687                     $name = str_replace('smarty_prefilter_', '', $prefilter);
1688                     $this->renderer->setPlugin($name,'prefilter', $prefilter);
1689                 } else {
1690                     $this->renderer->setPlugin($prefilter[1],'prefilter', $prefilter);
1691                 }
1692             }
1693
1694             // user defined postfilters
1695             foreach ($this->smarty_postfilter_plugin as $postfilter) {
1696                 if (!is_array($postfilter)) {
1697                     $name = str_replace('smarty_postfilter_', '', $postfilter);
1698                     $this->renderer->setPlugin($name,'postfilter', $postfilter);
1699                 } else {
1700                     $this->renderer->setPlugin($postfilter[1],'postfilter', $postfilter);
1701                 }
1702             }
1703
1704             // user defined outputfilters
1705             foreach ($this->smarty_outputfilter_plugin as $outputfilter) {
1706                 if (!is_array($outputfilter)) {
1707                     $name = str_replace('smarty_outputfilter_', '', $outputfilter);
1708                     $this->renderer->setPlugin($name,'outputfilter', $outputfilter);
1709                 } else {
1710                     $this->renderer->setPlugin($outputfilter[1],'outputfilter', $outputfilter);
1711                 }
1712             }
1713         }
1714
1715         //テンプレートエンジンのデフォルトの設定
1716         $this->_setDefaultTemplateEngine($this->renderer);
1717         // }}}
1718
1719         return $this->renderer;
1720     }
1721
1722     /**
1723      *  テンプレートエンジンのデフォルト状態を設定する
1724      *
1725      *  @access protected
1726      *  @param  object  Ethna_Renderer  レンダラオブジェクト
1727      *  @obsolete
1728      */
1729     function _setDefaultTemplateEngine(&$renderer)
1730     {
1731     }
1732
1733     /**
1734      *  使用言語を設定する
1735      *
1736      *  将来への拡張のためのみに存在しています。現在は特にオーバーライドの必要はありません。
1737      *
1738      *  @access protected
1739      *  @param  string  $language           言語定義(LANG_JA, LANG_EN...)
1740      *  @param  string  $system_encoding    システムエンコーディング名
1741      *  @param  string  $client_encoding    クライアントエンコーディング
1742      */
1743     function _setLanguage($language, $system_encoding = null, $client_encoding = null)
1744     {
1745         $this->language = $language;
1746         $this->system_encoding = $system_encoding;
1747         $this->client_encoding = $client_encoding;
1748
1749         $i18n =& $this->getI18N();
1750         $i18n->setLanguage($language, $system_encoding, $client_encoding);
1751     }
1752
1753     /**
1754      *  デフォルト状態での使用言語を取得する
1755      *
1756      *  @access protected
1757      *  @return array   使用言語,システムエンコーディング名,クライアントエンコーディング名
1758      */
1759     function _getDefaultLanguage()
1760     {
1761         return array(LANG_JA, null, null);
1762     }
1763
1764     /**
1765      *  デフォルト状態でのゲートウェイを取得する
1766      *
1767      *  @access protected
1768      *  @return int     ゲートウェイ定義(GATEWAY_WWW, GATEWAY_CLI...)
1769      */
1770     function _getDefaultGateway($gateway)
1771     {
1772         if (is_null($GLOBALS['_Ethna_gateway']) == false) {
1773             return $GLOBALS['_Ethna_gateway'];
1774         }
1775         return GATEWAY_WWW;
1776     }
1777
1778     /**
1779      *  ゲートウェイに対応したクラス名のプレフィクスを取得する
1780      *
1781      *  @access public
1782      *  @param  string  $gateway    ゲートウェイ
1783      *  @return string  ゲートウェイクラスプレフィクス
1784      */
1785     function _getGatewayPrefix($gateway = null)
1786     {
1787         $gateway = is_null($gateway) ? $this->getGateway() : $gateway;
1788         switch ($gateway) {
1789         case GATEWAY_WWW:
1790             $prefix = '';
1791             break;
1792         case GATEWAY_CLI:
1793             $prefix = 'Cli';
1794             break;
1795         case GATEWAY_XMLRPC:
1796             $prefix = 'Xmlrpc';
1797             break;
1798         default:
1799             $prefix = '';
1800             break;
1801         }
1802
1803         return $prefix;
1804     }
1805
1806     /**
1807      *  マネージャクラス名を取得する
1808      *
1809      *  @access public
1810      *  @param  string  $name   マネージャキー
1811      *  @return string  マネージャクラス名
1812      */
1813     function getManagerClassName($name)
1814     {
1815         return sprintf('%s_%sManager', $this->getAppId(), ucfirst($name));
1816     }
1817
1818     /**
1819      *  アプリケーションオブジェクトクラス名を取得する
1820      *
1821      *  @access public
1822      *  @param  string  $name   アプリケーションオブジェクトキー
1823      *  @return string  マネージャクラス名
1824      */
1825     function getObjectClassName($name)
1826     {
1827         $name = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($name));
1828         return sprintf('%s_%s', $this->getAppId(), $name);
1829     }
1830
1831     /**
1832      *  アクションスクリプトをインクルードする
1833      *
1834      *  ただし、インクルードしたファイルにクラスが正しく定義されているかどうかは保証しない
1835      *
1836      *  @access private
1837      *  @param  array   $action_obj     アクション定義
1838      *  @param  string  $action_name    アクション名
1839      */
1840     function _includeActionScript($action_obj, $action_name)
1841     {
1842         $class_path = $form_path = null;
1843
1844         $action_dir = $this->getActiondir();
1845
1846         // class_path属性チェック
1847         if (isset($action_obj['class_path'])) {
1848             // フルパス指定サポート
1849             $tmp_path = $action_obj['class_path'];
1850             if (Ethna_Util::isAbsolute($tmp_path) == false) {
1851                 $tmp_path = $action_dir . $tmp_path;
1852             }
1853
1854             if (file_exists($tmp_path) == false) {
1855                 $this->logger->log(LOG_WARNING, 'class_path file not found [%s] -> try default', $tmp_path);
1856             } else {
1857                 include_once $tmp_path;
1858                 $class_path = $tmp_path;
1859             }
1860         }
1861
1862         // デフォルトチェック
1863         if (is_null($class_path)) {
1864             $class_path = $this->getDefaultActionPath($action_name);
1865             if (file_exists($action_dir . $class_path)) {
1866                 include_once $action_dir . $class_path;
1867             } else {
1868                 $this->logger->log(LOG_DEBUG, 'default action file not found [%s] -> try all files', $class_path);
1869                 $class_path = null;
1870             }
1871         }
1872         
1873         // 全ファイルインクルード
1874         if (is_null($class_path)) {
1875             $this->_includeDirectory($this->getActiondir());
1876             return;
1877         }
1878
1879         // form_path属性チェック
1880         if (isset($action_obj['form_path'])) {
1881             // フルパス指定サポート
1882             $tmp_path = $action_obj['form_path'];
1883             if (Ethna_Util::isAbsolute($tmp_path) == false) {
1884                 $tmp_path = $action_dir . $tmp_path;
1885             }
1886
1887             if ($tmp_path == $class_path) {
1888                 return;
1889             }
1890             if (file_exists($tmp_path) == false) {
1891                 $this->logger->log(LOG_WARNING, 'form_path file not found [%s] -> try default', $tmp_path);
1892             } else {
1893                 include_once $tmp_path;
1894                 $form_path = $tmp_path;
1895             }
1896         }
1897
1898         // デフォルトチェック
1899         if (is_null($form_path)) {
1900             $form_path = $this->getDefaultFormPath($action_name);
1901             if ($form_path == $class_path) {
1902                 return;
1903             }
1904             if (file_exists($action_dir . $form_path)) {
1905                 include_once $action_dir . $form_path;
1906             } else {
1907                 $this->logger->log(LOG_DEBUG, 'default form file not found [%s] -> maybe falling back to default form class', $form_path);
1908             }
1909         }
1910     }
1911
1912     /**
1913      *  ビュースクリプトをインクルードする
1914      *
1915      *  ただし、インクルードしたファイルにクラスが正しく定義されているかどうかは保証しない
1916      *
1917      *  @access private
1918      *  @param  array   $forward_obj    遷移定義
1919      *  @param  string  $forward_name   遷移名
1920      */
1921     function _includeViewScript($forward_obj, $forward_name)
1922     {
1923         $view_dir = $this->getViewdir();
1924
1925         // view_path属性チェック
1926         if (isset($forward_obj['view_path'])) {
1927             // フルパス指定サポート
1928             $tmp_path = $forward_obj['view_path'];
1929             if (Ethna_Util::isAbsolute($tmp_path) == false) {
1930                 $tmp_path = $view_dir . $tmp_path;
1931             }
1932
1933             if (file_exists($tmp_path) == false) {
1934                 $this->logger->log(LOG_WARNING, 'view_path file not found [%s] -> try default', $tmp_path);
1935             } else {
1936                 include_once $tmp_path;
1937                 return;
1938             }
1939         }
1940
1941         // デフォルトチェック
1942         $view_path = $this->getDefaultViewPath($forward_name);
1943         if (file_exists($view_dir . $view_path)) {
1944             include_once $view_dir . $view_path;
1945             return;
1946         } else {
1947             $this->logger->log(LOG_DEBUG, 'default view file not found [%s]', $view_path);
1948             $view_path = null;
1949         }
1950     }
1951
1952     /**
1953      *  ディレクトリ以下の全てのスクリプトをインクルードする
1954      *
1955      *  @access private
1956      */
1957     function _includeDirectory($dir)
1958     {
1959         $ext = "." . $this->ext['php'];
1960         $ext_len = strlen($ext);
1961
1962         if (is_dir($dir) == false) {
1963             return;
1964         }
1965
1966         $dh = opendir($dir);
1967         if ($dh) {
1968             while (($file = readdir($dh)) !== false) {
1969                 if ($file != '.' && $file != '..' && is_dir("$dir/$file")) {
1970                     $this->_includeDirectory("$dir/$file");
1971                 }
1972                 if (substr($file, -$ext_len, $ext_len) != $ext) {
1973                     continue;
1974                 }
1975                 include_once $dir . '/' . $file;
1976             }
1977         }
1978         closedir($dh);
1979     }
1980
1981     /**
1982      *  設定ファイルのDSN定義から使用するデータを再構築する(スレーブアクセス分岐等)
1983      *
1984      *  DSNの定義方法(デフォルト:設定ファイル)を変えたい場合はここをオーバーライドする
1985      *
1986      *  @access protected
1987      *  @return array   DSN定義(array('DBキー1' => 'dsn1', 'DBキー2' => 'dsn2', ...))
1988      */
1989     function _prepareDSN()
1990     {
1991         $r = array();
1992
1993         foreach ($this->db as $key => $value) {
1994             $config_key = "dsn";
1995             if ($key != "") {
1996                 $config_key .= "_$key";
1997             }
1998             $dsn = $this->config->get($config_key);
1999             if (is_array($dsn)) {
2000                 // 種別1つにつき複数DSNが定義されている場合はアクセス分岐
2001                 $dsn = $this->_selectDSN($key, $dsn);
2002             }
2003             $r[$key] = $dsn;
2004         }
2005         return $r;
2006     }
2007
2008     /**
2009      *  DSNのアクセス分岐を行う
2010      *  
2011      *  スレーブサーバへの振分け処理(デフォルト:ランダム)を変更したい場合はこのメソッドをオーバーライドする
2012      *
2013      *  @access protected
2014      *  @param  string  $type       DB種別
2015      *  @param  array   $dsn_list   DSN一覧
2016      *  @return string  選択されたDSN
2017      */
2018     function _selectDSN($type, $dsn_list)
2019     {
2020         if (is_array($dsn_list) == false) {
2021             return $dsn_list;
2022         }
2023
2024         // デフォルト:ランダム
2025         list($usec, $sec) = explode(' ', microtime());
2026         mt_srand($sec + ((float) $usec * 100000));
2027         $n = mt_rand(0, count($dsn_list)-1);
2028         
2029         return $dsn_list[$n];
2030     }
2031
2032     /**
2033      *  Ethnaマネージャを設定する
2034      *
2035      *  不要な場合は空のメソッドとしてオーバーライドしてもよい
2036      *
2037      *  @access protected
2038      */
2039     function _activateEthnaManager()
2040     {
2041         if ($this->config->get('debug') == false) {
2042             return;
2043         }
2044
2045         require_once ETHNA_BASE . '/class/Ethna_InfoManager.php';
2046         
2047         // see if we have simpletest
2048         if (file_exists_ex('simpletest/unit_tester.php', true)) {
2049             require_once ETHNA_BASE . '/class/Ethna_UnitTestManager.php';
2050         }
2051
2052         // action設定
2053         $this->action['__ethna_info__'] = array(
2054             'form_name' =>  'Ethna_Form_Info',
2055             'form_path' =>  sprintf('%s/class/Action/Ethna_Action_Info.php', ETHNA_BASE),
2056             'class_name' => 'Ethna_Action_Info',
2057             'class_path' => sprintf('%s/class/Action/Ethna_Action_Info.php', ETHNA_BASE),
2058         );
2059
2060         // forward設定
2061         $this->forward['__ethna_info__'] = array(
2062             'forward_path'  => sprintf('%s/tpl/info.tpl', ETHNA_BASE),
2063             'view_name'     => 'Ethna_View_Info',
2064             'view_path'     => sprintf('%s/class/View/Ethna_View_Info.php', ETHNA_BASE),
2065         );
2066         
2067         
2068         // action設定
2069         $this->action['__ethna_unittest__'] = array(
2070             'form_name' =>  'Ethna_Form_UnitTest',
2071             'form_path' =>  sprintf('%s/class/Action/Ethna_Action_UnitTest.php', ETHNA_BASE),
2072             'class_name' => 'Ethna_Action_UnitTest',
2073             'class_path' => sprintf('%s/class/Action/Ethna_Action_UnitTest.php', ETHNA_BASE),
2074         );
2075
2076         // forward設定
2077         $this->forward['__ethna_unittest__'] = array(
2078             'forward_path'  => sprintf('%s/tpl/unittest.tpl', ETHNA_BASE),
2079             'view_name'     => 'Ethna_View_UnitTest',
2080             'view_path'     => sprintf('%s/class/View/Ethna_View_UnitTest.php', ETHNA_BASE),
2081         );
2082
2083     }
2084
2085     /**
2086      *  Ethnaマネージャが実行可能かをチェックする
2087      *
2088      *  Ethnaマネージャを実行するよう指示されているにも関わらず、
2089      *  debug が trueでない場合は実行を停止する。
2090      *
2091      *  @access private
2092      */
2093     function _ethnaManagerEnabledCheck($action_name)
2094     {
2095         if ($this->config->get('debug') == false
2096          && ($action_name == '__ethna_info__' || $action_name == '__ethna_unittest__')) {
2097             $this->ethnaManagerCheckErrorMsg($action_name);
2098             exit(0);
2099         }
2100     }
2101
2102     /**
2103      *  Ethnaマネージャが実行不能な場合のエラーメッセージを
2104      *  表示する。運用上の都合でこのメッセージを出力したくない
2105      *  場合は、このメソッドをオーバーライドせよ
2106      *
2107      *  @access protected
2108      */
2109      function ethnaManagerCheckErrorMsg($action_name)
2110      {
2111          $appid = $this->getAppId();
2112          $run_action = ($action_name == '__ethna_info__')
2113                      ? ' show Application Info List '
2114                      : ' run Unit Test ';
2115          echo "Ethna cannot {$run_action} under your application setting.<br>";
2116          echo "HINT: You must set {$appid}/etc/{$appid}.ini debug setting 'true'.<br>";
2117          echo "<br>";
2118          echo "In {$appid}.ini, please set as follows :<br><br>";
2119          echo "\$config = array ( 'debug' => true, );";
2120      } 
2121
2122     /**
2123      *  CLI実行中フラグを取得する
2124      *
2125      *  @access public
2126      *  @return bool    CLI実行中フラグ
2127      *  @obsolete
2128      */
2129     function getCLI()
2130     {
2131         return $this->gateway == GATEWAY_CLI ? true : false;
2132     }
2133
2134     /**
2135      *  CLI実行中フラグを設定する
2136      *
2137      *  @access public
2138      *  @param  bool    CLI実行中フラグ
2139      *  @obsolete
2140      */
2141     function setCLI($cli)
2142     {
2143         $this->gateway = $cli ? GATEWAY_CLI : $this->_getDefaultGateway();
2144     }
2145 }
2146 // }}}
2147
2148 /**
2149  *  XMLRPCゲートウェイのスタブクラス
2150  *
2151  *  @access     public
2152  */
2153 function _Ethna_XmlrpcGateway($method_stub, $param)
2154 {
2155     $ctl =& Ethna_Controller::getInstance();
2156     $method = $ctl->getXmlrpcMethodName();
2157     $r = $ctl->trigger_XMLRPC($method, $param);
2158     if (Ethna::isError($r)) {
2159         return array(
2160             'faultCode' => $r->getCode(),
2161             'faultString' => $r->getMessage(),
2162         );
2163     }
2164     return $r;
2165 }
2166 ?>