OSDN Git Service

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