OSDN Git Service

* extlibディレクトリ作成とプラグイン検索パス変更のブランチ
authorsotarok <sotarok@2ef88817-412d-0410-a32c-8029a115e976>
Sat, 11 Apr 2009 01:29:15 +0000 (01:29 +0000)
committersotarok <sotarok@2ef88817-412d-0410-a32c-8029a115e976>
Sat, 11 Apr 2009 01:29:15 +0000 (01:29 +0000)
257 files changed:
Idea_Plugin_Extlib/.cvsignore [new file with mode: 0644]
Idea_Plugin_Extlib/.ethna [new file with mode: 0644]
Idea_Plugin_Extlib/CHANGES [new file with mode: 0644]
Idea_Plugin_Extlib/Ethna.php [new file with mode: 0644]
Idea_Plugin_Extlib/LICENSE [new file with mode: 0644]
Idea_Plugin_Extlib/README [new file with mode: 0644]
Idea_Plugin_Extlib/bin/ethna.bat [new file with mode: 0755]
Idea_Plugin_Extlib/bin/ethna.sh [new file with mode: 0755]
Idea_Plugin_Extlib/bin/ethna_handle.php [new file with mode: 0644]
Idea_Plugin_Extlib/bin/ethna_make_package.php [new file with mode: 0644]
Idea_Plugin_Extlib/bin/ethna_make_package.sh [new file with mode: 0755]
Idea_Plugin_Extlib/bin/ethna_run_test.php [new file with mode: 0644]
Idea_Plugin_Extlib/bin/ethna_run_test.sh [new file with mode: 0755]
Idea_Plugin_Extlib/class/Action/Ethna_Action_Info.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Action/Ethna_Action_UnitTest.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/CLI/Ethna_CLI_ActionClass.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/DB/Ethna_DB_ADOdb.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/DB/Ethna_DB_Creole.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/DB/Ethna_DB_PEAR.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_ActionClass.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_ActionError.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_ActionForm.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_AppManager.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_AppObject.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_AppSQL.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_AppSearchObject.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Backend.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_CacheManager.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_ClassFactory.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Config.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Controller.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_DB.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Error.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Filter.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Generator.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Getopt.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Handle.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_I18N.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_InfoManager.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Logger.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_MailSender.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_PearWrapper.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Plugin.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Renderer.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Session.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_UnitTestCase.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_UnitTestManager.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_UnitTestReporter.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_UrlHandler.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_Util.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Ethna_ViewClass.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Cachemanager/Ethna_Plugin_Cachemanager_Localfile.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Cachemanager/Ethna_Plugin_Cachemanager_Memcache.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Csrf/Ethna_Plugin_Csrf_Session.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Cachemanager.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Csrf.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Filter.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Generator.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Handle.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Logwriter.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Urlhandler.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Validator.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Action.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_ActionTest.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_AppManager.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_AppObject.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_EntryPoint.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_I18n.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Plugin.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Project.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Template.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_View.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_ViewTest.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddAction.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddActionTest.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddAppManager.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddAppObject.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddEntryPoint.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddProject.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddTemplate.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddTest.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddView.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddViewTest.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_ChannelUpdate.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_ClearCache.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_I18n.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_InfoPlugin.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_InstallPlugin.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_ListPlugin.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_MakePluginPackage.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_PearLocal.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_UninstallPlugin.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_UpgradePlugin.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Alertmail.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Default.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Echo.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_File.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Syslog.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/block.form.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.checkbox_list.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.csrfid.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.form_input.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.form_name.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.form_submit.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.is_error.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.message.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.select.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.uniqid.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/function.url.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.checkbox.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.count.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.filter.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.form_value.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.i18n.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.join.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.number_format.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.select.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.strftime.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.truncate_i18n.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.unique.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.wordwrap_i18n.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Custom.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_File.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Max.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Mbregexp.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmax.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmin.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Min.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Regexp.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Required.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmax.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmaxcompat.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmin.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmincompat.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Type.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Renderer/Ethna_Renderer_Rhaco.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/Renderer/Ethna_Renderer_Smarty.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_ActionForm.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_Gateway.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_GatewayGenerator.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_Util.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_WsdlGenerator.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/View/Ethna_View_Info.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/View/Ethna_View_List.php [new file with mode: 0644]
Idea_Plugin_Extlib/class/View/Ethna_View_UnitTest.php [new file with mode: 0644]
Idea_Plugin_Extlib/misc/_ethna [new file with mode: 0644]
Idea_Plugin_Extlib/misc/optional_package/OPTIONAL_PACKAGE_HOWTO [new file with mode: 0644]
Idea_Plugin_Extlib/misc/optional_package/Smarty/build/build [new file with mode: 0644]
Idea_Plugin_Extlib/misc/optional_package/Smarty/build/build.bat [new file with mode: 0644]
Idea_Plugin_Extlib/misc/optional_package/Smarty/build/build.conf [new file with mode: 0644]
Idea_Plugin_Extlib/misc/optional_package/Smarty/src/Smarty-2.6.22.tar.gz [new file with mode: 0644]
Idea_Plugin_Extlib/misc/optional_package/simpletest/build/build [new file with mode: 0644]
Idea_Plugin_Extlib/misc/optional_package/simpletest/build/build.bat [new file with mode: 0644]
Idea_Plugin_Extlib/misc/optional_package/simpletest/build/build.conf [new file with mode: 0644]
Idea_Plugin_Extlib/misc/optional_package/simpletest/src/simpletest_1.0.1.tar.gz [new file with mode: 0644]
Idea_Plugin_Extlib/misc/sample_package.ini [new file with mode: 0644]
Idea_Plugin_Extlib/misc/sample_plugin.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.action.default.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.actionclass.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.actionform.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.controller.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.error.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.plugin.filter.default.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.unittest.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.url_handler.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.view.default.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/app.viewclass.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/dot.ethna [new file with mode: 0644]
Idea_Plugin_Extlib/skel/etc.ini.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/locale/ethna_sysmsg.default.ini [new file with mode: 0644]
Idea_Plugin_Extlib/skel/locale/ja_JP/ethna_sysmsg.ini [new file with mode: 0644]
Idea_Plugin_Extlib/skel/locale/skel.msg.ini [new file with mode: 0644]
Idea_Plugin_Extlib/skel/locale/skel.msg.po [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.action.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.action_cli.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.action_test.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.action_xmlrpc.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.app_manager.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.app_object.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.entry_cli.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.entry_www.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.template.tpl [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.test.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.view.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/skel.view_test.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/template.index.tpl [new file with mode: 0644]
Idea_Plugin_Extlib/skel/www.css.ethna.css [new file with mode: 0644]
Idea_Plugin_Extlib/skel/www.index.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/www.info.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/www.unittest.php [new file with mode: 0644]
Idea_Plugin_Extlib/skel/www.xmlrpc.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionError_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Filter_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_FormTemplate_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_MultiArray_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Custom_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Max_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Mbregexp_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Mbstrmax_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Mbstrmin_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Min_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Regexp_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Required_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmax_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmaxcompat_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmin_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmincompat_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Type_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ClassFactory_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_Class_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_Config_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_Controller_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_Error_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_Getopt_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_I18N_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_Logger_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_MockProject.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_MocktestManager.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_UnitTestBase.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_UrlHandler_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_Util_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Ethna_ViewClass_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Cachemanager/Ethna_Plugin_Cachemanager_Localfile_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Csrf/Ethna_Plugin_Csrf_Session_Test.php [new file with mode: 0755]
Idea_Plugin_Extlib/test/Plugin/Handle/Ethna_Plugin_Handle_AddTemplate_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Handle/Ethna_Plugin_Handle_I18n_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Handle/Ethna_Plugin_Handle_PearLocal_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Logwriter/Ethna_Plugin_Logwriter_Echo_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Logwriter/Ethna_Plugin_Logwriter_File_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_function_select_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_checkbox_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_select_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_unique_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_wordwrap_i18n_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Custom_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Max_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Mbregexp_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmax_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmin_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Min_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Regexp_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Required_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmax_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmaxcompat_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmin_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmincompat_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Type_Test.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/TextDetailReporter.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/run_test.stdin.txt [new file with mode: 0644]
Idea_Plugin_Extlib/test/skel/skel.action.i18ntest.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/skel/skel.template.i18ntest.tpl [new file with mode: 0644]
Idea_Plugin_Extlib/test/skel/skel.view.i18ntest.php [new file with mode: 0644]
Idea_Plugin_Extlib/test/test_message_catalog.ini [new file with mode: 0644]
Idea_Plugin_Extlib/tpl/info.tpl [new file with mode: 0644]
Idea_Plugin_Extlib/tpl/unittest.tpl [new file with mode: 0644]

diff --git a/Idea_Plugin_Extlib/.cvsignore b/Idea_Plugin_Extlib/.cvsignore
new file mode 100644 (file)
index 0000000..f1e25bb
--- /dev/null
@@ -0,0 +1 @@
+package.xml
diff --git a/Idea_Plugin_Extlib/.ethna b/Idea_Plugin_Extlib/.ethna
new file mode 100644 (file)
index 0000000..52da908
--- /dev/null
@@ -0,0 +1,3 @@
+[repositry]
+channel_master = "pear.ethna.jp"
+channel_local = "pear.ethna.jp"
diff --git a/Idea_Plugin_Extlib/CHANGES b/Idea_Plugin_Extlib/CHANGES
new file mode 100644 (file)
index 0000000..ad29699
--- /dev/null
@@ -0,0 +1,679 @@
+* 変更点一覧
+
+** 2.5.0-preview4
+
+*** features
+
+- フォーム定義に関する変更
+-- フォーム定義を動的に変更するためのAPIをさらに追加
+-- Ethna_ActionForm#setFormDef_ViewHelper
+- APPID_Controller.php のスケルトンに継承を想定したメソッドを追加
+-- skel/app.controller.php _setDefaultTemplateEngin
+- add-project 時の www 以下に出来るエントリポイントから APPID_Controller へのパスを相対パスに変更
+
+*** bug fix
+
+- Ethna_Controller#getTemplatedir を無視してテンプレートディレクトリを決定していたバグを修正(thanks: hiko)
+-- getTemplatedirメソッドをオーバライドしても強制的にロケールが付加されていた
+-- https://sourceforge.jp/ticket/browse.php?group_id=1343&tid=15570
+- "ethna pear-local list -a" の実行結果がエラーになってしまうバグを修正
+-- https://sourceforge.jp/ticket/browse.php?group_id=1343&tid=15760
+- safe-mode が ON の際に、CacheManager_Localfile がディレクトリを生成できないので、tmp ディレクトリ直下にキャッシュファイルを作成するようにした
+-- skel/skel.app_manager.php も修正
+- APPID-ini.php が存在しない場合,またはURLが設定にない場合,デフォルトURLが HTTP_HOST で設定されていたが,末尾に / がなかったので修正
+- フォームヘルパで自動的に出力されるhiddenタグの閉じ忘れを修正(thanks: id:syachi5150)
+
+** 2.5.0-preview3
+
+*** features
+
+- アクションフォームに関する変更
+-- フォーム定義を多次元配列に対応させました (thanks: id:syachi5150)
+--- http://d.hatena.ne.jp/syachi5150/20081022/1224676038
+-- フォーム定義を「'def' => array(),」 と定義しなくても、「'def',」 と定義するだけで親のフォームテンプレートの定義を補うようにした (thanks: sotarok)
+-- フォーム定義を動的に変更するためのAPIを追加
+--- Ethna_ActionForm#setFormDef_PreHelper
+    Ethna_Backend や Ethna_Session が初期化後に呼ばれる
+- フォームヘルパに関する変更
+-- 1つのテンプレートに 複数 {form} が指定されたときに、submitされたformに対してのみ補正処理が働くように改善 この場合、{form name=...} 属性の指定が必須
+-- 1つのテンプレートに 複数 {form} が置かれた場合に、それぞれのフォームの配列を区別するようにした
+- Smarty プラグインに関する変更
+-- Ethna 組み込みの Smarty プラグインを分割
+--- Ethna 組み込みの Smarty プラグインとして class/Plugin/Smarty/ に Smarty のプラグイン形式で個別に作成
+--- それに伴い Ethna_Smarty_Plugin クラスは削除
+--- 読み込み順は次のように指定 1. Controller の plugin ディレクトリ 2. Ethna 組み込みの Plugin/Smarty/ ディレクトリ 3. samrty デフォルトのプラグイン
+-- デフォルトの smarty プラグイン よりも Controller の plugins ディレクトリに定義されたプラグインを優先させるように変更
+-- アプリケーション独自のSmarty Pluginの定義場所を app/plugin/Smarty にできるようデフォルトでディレクトリの作成、コントローラに値のセットするよう変更
+- その他雑多な変更
+-- Smarty を 2.6.22 に追随
+-- アプリケーションの最終処理を行うメソッドとして、Ethna_Controller#end を追加
+-- フィルタを一貫してプラグインから取得するように変更
+
+*** bug fix
+
+- safe-mode が ON の際に、Ethna_View_Test がエラーを吐く現象を回避 (thanks:longkey1 [ethna-users:1059])
+- "ethna add-view" コマンドにて、locale 及び client encoding のデフォルト設定が誤っていたバグを修正
+- Ethna_Renderer_Rhaco.php を 1.x 系の最新バージョン 1.6.1 に追随 (thanks: id:akiraneko [ethna-users:1081])
+- 複数ファイルをアップロード(つまり配列を使用)する際、必須チェックが機能しなかったバグを修正(thanks: id:syachi5150)
+- ethna add-app-manager コマンドで生成されるアプリケーションマネージャのクラス名が、[Appid]_Controller#getManagerClassName の設定を反映するように修正。
+- smarty_modifier_unique プラグインが、仕様通り動作していなかったバグを修正
+- Ethna_PearWrapper のエラー処理が誤っていたのを修正 (thanks: id:nazo)
+-- http://wassr.jp/user/nazo/statuses/SkfJTckkN2
+- Ethna_ActionForm#getHiddenVars メソッドで、フォーム定義が配列で設定された値がスカラーの場合に警告が出ていたのを修正(thanks: maru_cc)
+-- 逆に、フォーム定義がスカラーで値が配列の場合は救いようがないので警告扱い
+- www/info.php を実行したり、www/unittest.php を実行すると、サーバが応答しなくなることがあるバグを修正
+-- アクションクラスの書き方によっては、Ethna_InfoManager が 無限ループに陥っていたため
+-- http://sourceforge.jp/tracker/index.php?func=detail&aid=10006&group_id=1343&atid=5092
+
+** 2.5.0-preview2
+
+*** features
+
+- PEAR依存を排除するための変更。依存を排除する理由は以下の通り。
+  1. PEAR が PEAR2 に移行するに伴い、APIが不安定になること
+  2. Ethna が依存している PEAR_Error は既に非推奨であること
+  3. 外部ライブラリにできうる限り依存しない方がユーザの便宜となる
+  4. PEAR に依存していると、PHPライセンスと抵触しているライセンスで配布できない
+-- Console_Getopt の代替として、Ethna_Getopt.php を追加 (Public Domain)
+-- 性質上依存せざるを得ない以下のファイルを除き、Console_Getopt への依存を排除
+--- ETHNA_BASE/bin/ethna_make_package.php
+--- ETHNA_BASE/class/Ethna_PearWrapper.php
+-- [Breaking B.C] Ethna から PEAR_Error まわりの依存を排除。これに伴い、Ethnaクラス が持っていた PEARコアコンポーネンツ の機能は使えなくなっている。
+--- Ethnaクラス に PEAR ライクなエラーチェックメソッドを追加し、それに伴う変更
+--- Ethna_Error で PEAR を呼び出していた部分を修正し、PEARに任せていたメンバ設定等を最実装
+--- PEAR.php で定義されていた OS_WINDOWS 定数の代替として、 ETHNA_OS_WINDOWS 定数を定義した
+    これは PEAR が、OS_WINDOWS 定数が再定義されているかをチェックしていないため
+- 国際化メッセージの生成支援機構として、i18n コマンドを実装
+-- gettext, Ethna組み込みのメッセージカタログに対応
+-- ethna i18n [-b|--basedir=dir] [-l|--locale] [-g|--gettext] [extdir1] [extdir2] ...
+-- メッセージファイルが存在する場合は、Ethna 組み込みのメッセージカタログの場合は、既存の翻訳
+   を自動的にマージする。gettext の場合は、新たにファイルを生成し、msgmerge プログラムを使って
+   翻訳を既存のものとマージするように促す
+- 配布する Smarty を 2.6.20 に追随
+- [Breaking B.C] 互換性を保つために残されていた内部メソッドを削除
+-- Ethna_ViewClass#_getTemplateEngine
+- Ethna_ActionClass のメンバに $logger(Ethna_Logger) を追加
+- Ethna_ViewClass のメンバに $ctl(Ethna_Controller) を追加
+-- i18n 周りの情報を容易に変更させるようにするため
+- Ethna_Controller#_setLanguage メソッドを、backend, Session, actionform の初期化が終わってから呼ぶようにした。
+- 2.5.0 preview1 で追加した Ethna_ViewClass#_setLanguage メソッドを削除
+-- アクション実行後のロケール変更はあまり意味がないため :(
+
+*** bug fix
+
+- テストディレクトリの変更のタイミングによっては、Ethna_UnitTestMangerがWARNINGを出す問題を回避 (thanks: maru_cc)
+- selected="selected" の修正漏れを修正 (thanks:maru_cc) 
+- [Breaking B.C] Ethna_Plugin_CacheManager_Memcache の接続デフォルトが persistent になっていたのを通常接続に変更
+-- [appid]/etc/[appid]-ini.php の memcache_use_connect 設定を memcache_use_pconnect に変更
+- プラグインのクラス名にアンダーバーを許していなかったが、PHPのクラス名的に正当な文字であればOKにするように変更(thanks:maru_cc)
+- Ethna_I18N.php で、メッセージをパースする際に空行を見逃していたバグを修正
+- Ethna_MailSender にてメールを送信する際、テンプレートが存在しなかった場合にも空メールを送ってしまうバグを修正 (thanks: ryosuke@sekido.info -> [ethna-users:1053])
+- smarty_modifier_checkbox が仕様に反する動作をしていたバグを修正し、仕様を厳密化した(thanks: maru_cc)
+-- checked が付くのはスカラーで、0 と空文字列、null, false 以外の場合とする 
+- Ethna_ActionError#_getActionForm で、E_NOTICE が出る問題を回避
+
+** 2.5.0-preview1
+
+*** features
+
+- ソースコード全体をUTF-8化
+-- 但し、日本語のソースコードコメントはそのまま
+-- [Breaking B.C] フレームワークで扱う内部エンコーディング(mb_internal_encoding)もデフォルトはUTF-8に変更。但し、これは Ethna_Controller#_getDefaultLanguage
+   をオーバーライドし、クライアントエンコーディングの値を変えることで変更可能です。
+-- 内部エンコーディングの変更に伴い、動作しなくなった箇所を修正
+--- Ethna_Plugin_Validator_Min.php
+--- Ethna_Plugin_Validator_Max.php
+--- VAR_TYPE_STRING の場合の、最大値最小値のプラグインを再編し、
+    マルチバイトのものとそうでないものを分離。互換性確保用途のプラグインも追加
+---- Ethna_Plugin_Validator_MbStrMax.php     (マルチバイト文字列最大値)
+---- Ethna_Plugin_Validator_MbStrMin.php     (マルチバイト文字列最小値)
+---- Ethna_Plugin_Validator_StrMax.php       (シングルバイト文字列最大値)
+---- Ethna_Plugin_Validator_StrMin.php       (シングルバイト文字列最小値)
+---- Ethna_Plugin_Validator_StrMaxCompat.php (2.3.x までの互換性確保用)
+---- Ethna_Plugin_Validator_StrMinCompat.php (2.3.x までの互換性確保用)
+-- 内部エンコーディングの変更に伴う動作の変更
+--- Ethna_Plugin_Validator_Mbregexp のデフォルトのエンコーディングは、クライアントエンコーディングが仮定されます。デフォルトはUTF-8です。
+- 国際化 (i18n) のための機能追加および変更
+-- [Breaking B.C] 言語名として解釈していた部分をロケール名に変更
+--- これにより、[appid]template/ja, [appid]/locale/ja の「ja」の部分が ja_JP に置き換わります。よって、古いバージョンから移行する場合はディレクトリ名の変更が必要です。
+--- Ethna_ViewClass に、言語切り替え用の _setLanguage メソッドを追加 (protected)
+--- Ethna.php で定義されていた、LANG_JA, LANG_EN はこの変更により使用されないので削除
+-- [Breaking B.C] gettext を使用する際には [appid]/etc/[appid]-ini.php で 'use_gettext' => true と設定しないと gettext を使わないようにした
+--- 2.3.5 までのコードは、gettext.so がロードされていれば *無条件に* gettext が実行されるようになっているので、Ethna 独自のメッセージカタログとの選択がわかりづらいため。
+--- 2.3.5までのコードで gettext を利用している場合は、設定が明示的に必要です。
+-- "ethna add-project" コマンドに [-l|--locale] [-e|--encoding] オプションを追加
+-- "ethna add-[view|templete]" コマンドに [-l|--locale] [-e|--encoding] オプションを追加
+-- スケルトンの日本語コメントをすべてASCIIに変更(好みのエンコーディングで編集できるようにするため)
+-- gettextを使わない場合向けに、Ethna独自のメッセージカタログを実装
+--- ini ファイルライクなフォーマットで msgid と翻訳を格納する方式
+--- Ethna_I18N#setLanguage で出力ロケールの切り替えも可能
+- [Breaking B.C] レンタルサーバを考慮して、[appid]_Controllerの include_path を、[appid]/lib を優先するように変更
+-- include_path の順番に依存するコードは少ないとは思いますが、移行の際は注意すべきです。
+- "ethna add-project" コマンドに [-s|skeldir] オプションを追加
+-- 指定されたスケルトンディレクトリに、ETHNA_HOME/skel と同じファイル名のものが存在する場合はそちら
+   を優先した上で、ETHNA_HOME/skel にないファイルは [appid]/skel にコピーする
+- [Breaking B.C] Ethna_ActionForm のバリデータは、プラグインのものしか使用しなくなりました。
+-- Ethna_ActionForm, [Appid]ActionForm の use_validator_plugin 変数を削除
+
+*** bug fixes
+
+- tpl/info.tpl のタグミスを修正
+- smarty_modifier_plugin が配列の場合に、プラグインとして登録されないバグを修正
+- フォームヘルパでセレクトボックスの配列フォームを作ると値が保持されない点を修正 (ethna-users:0868)
+- smarty_modifier_select の戻り値が、諸々のHTML標準と異なっていたバグを修正(thanks: maru_cc)
+-- selected="true" -> selected="selected"
+- アプリケーションIDの始めの文字に数値を許していたバグを修正
+-- クラス名のprefixになるため、数値を許すと自動生成物がコンパイルエラーを起こす
+- Ethna_Util#getRandom で open_basedir が有効な場合に、 /proc を開けず警告が出る点を回避(thanks. sotarok)
+-- http://d.hatena.ne.jp/sotarok/20070813/1187055110
+- Ethna_ClassFactory#getManager の第1引数を、大文字小文字を区別しないように修正。(thanks:maru_cc)
+-- 第1引数はクラス名の一部として扱われており、PHPがクラス名の大文字小文字を区別しないことから、
+   大文字小文字を区別せず同じインスタンスを返すのが妥当と考えられる。
+- Ethna_Plugin_LogWriter クラスにて、バックトレース走査時の軽微なバグを修正(ethna-users:1024, thanks:sfio)
+- Ethna_Config.php にて、設定ファイルのロックが機能していなかったバグを修正
+
+** 2.3.5
+
+*** features
+
+- PEAR チャンネルサーバに ethna/simpletest, ethna/Smarty を追加
+-- インストール後のsimpletest, Smartyのパスで悩む罠を軽減することが目的
+-- pear コマンドで Ethna をインストールするときにこれらを Optional に依存するように設定。既存のインストールを考慮して、required にはしていない。
+- Ethnaコマンドに一般的なテストケースコマンドとして add-test コマンドを追加(thanks: BoBpp)
+-- ethna add-test -s [skelname] [name] で実行できます
+-- http://blog.as-roma.com/BoBlog/index.php?itemid=1338
+-- これは自動登録されるため、[appid]_UnitTestManager に定義を追加する必要はありません(thanks: id:okonomi)
+--- http://d.hatena.ne.jp/okonomi/20080408
+- Ethna_Renderer_Rhacoを追加(experimental)
+- Ethna_DB_ADOdbのdebug時のログ出力をEthnaのLoggerに変更(@see http://d.hatena.ne.jp/sotarok/20071224)
+- Ethna add-[|action|view]-test コマンドで生成されるテストケースがデフォルトでfailするように改善
+- Ethna のユニットテスト実行時に [appid]/etc/[appid]-ini.php のデバッグ設定がfalseの場合のエラー処理を改善
+-- エラー処理をphpに任せて画面を真っ白にするのではなく、親切なエラーメッセージを表示する
+- [action|view] のユニットテスト生成時、対応するアクション(ビュー)スクリプトがない場合は警告を生成するようにした。
+- Ethna の add-[action|view] コマンドで、同時にユニットテストを作成できるようにするオプションを追加。
+-- ただし、add-view コマンドで -t を指定した場合は、これらのオプションは無視される。
+-- ethna add-[action|view] add-view [-w|--with-unittest] [-u|--unittestskel=file] [action|view]
+
+*** bug fixes
+
+- ethna pear-local コマンドで Ethna を [appid]/lib/ にインストールすると、[appid]_Controller.php のinclude_path
+  の設定によっては ethnaコマンドが動かなくなるのを回避 (thanks: sotarok)
+-- ethna pear-local コマンドで Ethna を [appid]/lib にインストールしても、[appid]/bin/ethna が使えるようにした。
+- 配列のフォームをvalidateする際、値がnullだとフィルタが適用されないバグを修正
+- Ethna_Plugin_Cachemanager_Memcache に引数がなかったためにプラグイン呼び出しに失敗していたバグを修正(thanks sfio, ethna-users:0818)
+- Ethna_PearWrapper、Ethna_Plugin_Csrf_Session, Ethna_InfoManager 等を微調整(thanks sfio, ethna-users:0825)
+- form_input の default 属性が、入力値で上書きできなかったバグを修正(thanks sotarok, ethna-users:0836)
+- call_user_func の戻り値がオブジェクトだった場合に、E_NOTICEが出る問題を回避(PHP 4.4限定) [ethna-users:0910]
+- ActionForm の validate test の結果が、次のテストに引き継がれてしまうバグを修正(thanks: maru_cc)
+
+** 2.3.2
+
+*** features
+
+- %%[breaking B.C.]%% Ethna_UrlHandler (URLハンドラ) をプラグイン化
+-- Ethna_Plugin_Urlhandler_Default を追加
+-- %% $action_map を App_Urlhandler から App_Plugin_Urlhandler_Default
+に移動する必要があります %%
+-- やっぱり戻しました。プラグインを呼び出したいときにApp_UrlHandlerクラスで指定するように変更。
+- プラグインのクラスが既に存在する場合は特別にファイルの検索をスキップするようにした。
+- Ethna_ViewClass::_getFormInput_* で $separator のデフォルトを '' から "\n" に変更
+- Ethna_Controller::_trigger_XMLRPC で $GLOBALS['HTTP_RAW_POST_DATA'] を使わずに 'php://input' を使うように変更
+-- php.ini の設定が不要になりました。
+- Ethna_MailSender
+-- $type 引数を $template と rename して、より積極的にテンプレート名と解釈するようにした。
+--- $def を特に指定しなければ ViewClass の forward_name と同様に template/ja/mail/ 以下からテンプレートを探します。
+-- multipart: 2 つ以上の添付、ファイル名を指定した添付に対応しました。
+--- ただしデフォルトの content-type は application/octet-stream でごまかしているのと、日本語ファイル名がてきとうです。
+- Ethna_Renderer, Ethna_Renderer_Smarty
+-- perform() の第2引数に $capture フラグを追加
+-- true のときは Smarty 的に display でなく fetch になります。
+- Ethna_Util::isRootDir() 追加
+- ethna_make_packageで.svnに対応
+- Ethna_Plugin_Validator_Mbregexp 追加 (thx: mumumu)
+-- mb_eregを使ったマルチバイト対応正規表現プラグイン
+- Ethna_Plugin_Handle_PearLocal 追加 
+-- PEARパッケージを各プロジェクト毎に管理できるプラグイン
+- View のユニットテストができなくなっていたバグを修正(thx: sfio, ethna-users:0651)
+
+*** bug fixes
+
+- raiseError()類の引数が間違っていたのを修正 (thx: sfio)
+- プラグインパッケージインストール時に '{$application_id}' が置換されないバグを修正
+- add-template が正しく動作していなかったのを修正
+- Ethna_ViewClass::_getFormInput_Select で multiple を考慮していなかったのを修正
+- Ethna_AppObject::_getSQL_SearchId で救済になってないエラーのスキップを削除
+-- 有効な key がないときに、どちらにしろ SQL エラーになってた
+- OS_WINDOWSでgetAppController()が無限ループになっていたのを修正
+-- ルートディレクトリ判定に失敗していた
+- Console_Getoptなどのアップグレードに対応
+-- php4対応のreference返しがなくなっていたのに伴って発生していたnoticeを回避
+- xmlrpcのパラメータがActionFormに渡っていなかったのを修正(#9845)
+- file_type の検査 が機能しない問題を修正
+- MailSenderでテンプレートファイルを指定しない場合の挙動を修正
+- MailSenderのBare LFをCRLFに置換(#9898, ethna-users:0588)
+- Smarty の $script 変数の値が、PATH_INFOの値が含まれると潜在的に誤動作するバグを修正(thx: cockok, ethna-users:0687)
+
+** 2.3.1
+
+*** features
+
+- ethnaコマンドで@PHP-BIN@が置換されずに残っている場合(CVS版を使っているときなど)に対応
+- デフォルトテンプレートにバージョン番号をこっそり追加
+
+*** bug fixes
+
+- Mac/Windowsでpear経由でのインストールに失敗していた問題を解消
+-- すべてのroleをphpにして、ethna.{sh,bat}のみscriptを指定
+- Ethna_ViewClass::setPlugin() で $plugin の検証に is_callable を使用 (ethna-users:0507)
+- install-plugin が正しく動いていなかったのを修正 (#9582)
+- ethna.shでPHPのパスが指定されていなかったのを修正(ethna-users:0508)
+- Ethna_AppObjectで'key'の条件にunique_key, multiple_keyが漏れていたのを修正
+- Ethna_ViewClassで<label id="foo">となっていたのを<label for="foo">に修正
+
+** 2.3.0
+
+*** features
+
+- ethnaコマンドのハンドラ再編
+-- 全般的にgetopt化
+--- "--basedir" で対象アプリの場所を指定
+--- "--skelfile" で生成元のスケルトンファイルを指定
+-- 全てのgeneratorで "アプリ -> Ethna本体" の順にスケルトンファイルを探すように変更
+-- add-action-cli, add-action-xmlrpcを廃止、add-actionに "--gateway=www|cli|xmlrpc" を追加
+-- add-entry-point追加
+--- ethna add-entry-point --gateway=cli foo で bin/foo.php, app/action/Foo.php を生成
+-- pearコマンドを使うハンドラに "--pearopt" を追加(experimental)
+--- ethna install-plugin -p--alldeps -p--force foo bar のように指定する
+-- Ethna_Handle::_getopt()の出力を変更
+
+- misc追加
+-- plugin packagerのサンプル
+-- おまけ: _ethna (zshの補完関数)
+
+- Smarty, PEAR_DBのincludeのタイミングを変更
+-- 必要時に Ethna_ClassFactory::_include() を使うようにした。
+
+- Ethna_AppObjectをpostgres, sqliteに簡易対応
+-- 1テーブルの1レコードが1オブジェクトに対応するような単純なモデルのみ対応
+-- まだdb typeごとに調整が必要になることがあります。
+-- pgsqlでsequenceに対応
+-- テーブル名、カラム名の自動quoteに対応
+
+- add-* ハンドル機能追加
+-- add-template: --skelfile オプションで生成元のスケルトンファイルを指定できるようにした
+
+- {form_input}ヘルパー
+-- select, radio, checkboxに対応
+-- 選択肢をフォーム定義で指定できるようにした(afのmethod, property, managerなど)
+-- 外側の{form}ブロックからaction名, default値を取得できるようにした
+-- フォーム定義からもdefault値を指定できるようにした
+
+- Ethna_Plugin_Handle_{Install,Upgrade}Plugin に --state オプションを追加
+- local のプラグインの prefix を App に変更(app_idの予約語扱い)
+
+- Ethna_Plugin_Handle_ClearCache 追加
+-- 現状 smarty, pear, cachemanager_localfile, tmp以下問答無用で削除、のみの対応
+- ethna_error_handler() の print 条件を変更
+-- Logwriter プラグイン化に伴う $has_echo 条件のバグを修正
+-- $has_echo に加えて $config->get('debug') を見るようにした
+- Ethna_Handle で Ethna_Controller と App_Controller が共存する場合の扱いが混乱していたのを整理
+- Ethna_Hanlde に mkdir(), chmod(), purgeDir() を追加
+- Cachemanager プラグイン中の PEAR::raiseError() を Ethna::raiseError() に変更
+- Ethna_Logger で Ethna_Config オブジェクトの取得に失敗したときの処理を修正
+- ethna {install,uninstall,upgrade}-plugin で skel から generate されるファイルの上書き確認を廃止
+
+- Ethna_Plugin_Handle_ListPlugin
+-- パッケージ管理に係わらずプラグインの一覧を表示
+-- パッケージ管理下にあるときはパッケージ名とバージョンを表示
+- Ethna_Plugin_Handle_UpgradePlugin, Ethna_Plugin_Handle_ChannelUpdate
+-- プラグインパッケージのupgrade, pear channelのupdateに対応
+-- http://pear.server/get/Package-1.2.3.tgz のようなinstall, upgradeに対応
+- PearWrapper, Ethna_Handleでのデフォルトターゲット(localかmasterか)をlocalに変更、統一
+- Ethna_Plugin_Handle_{Install,Uninstall,Info,List}Plugin
+-- master, localのハンドラを分けていたのを統合
+-- ダウンロード済みの tgz に対応
+-- Console_GetOpt で --channel, --basedir, --local, --master のオプションを追加
+-- new PEAR_Error() 時の error handler を callback($ui, 'displayFatalError') に変更
+
+- Ethna_UrlHandlerクラスを追加(ステキurl対応)
+- Smartyプラグイン関数smarty_function_url追加
+- Ethna_AppObjectからのフォーム定義生成サポート追加
+-- [2006/08/23] 激しくα
+- Ethna_ClassFactory::getObject()でクラス定義に無いキーが渡された場合はEthna_AppObject()のキーであると仮定してオブジェクト生成
+- アプリケーションスケルトン生成時にアプリケーション固有のActionClass, ActionForm, ViewClassも生成するように変更
+- Ethna_SkeltonGeneratorクラスをEthna_Generatorクラスに名称変更
+- Ethna_SkeltonGeneratorクラスの各メソッドをプラグイン化
+- Ethna_Config::get()で引数を指定しないと全設定を格納した配列を返すように変更
+- Ethna_ViewClass::_getTemplateEngine()で設定値を格納した$configテンプレート変数を設定するように変更
+- Ethnaのパッケージシステムを追加
+-- ethna用のpear channelからプラグインのパッケージをインストールできるようになります
+-- Ethna_PearWrapper, Ethna_Plugin_Handle_{Install,Info,List,Uninstall}_Plugin_{Master,Local}を追加
+-- local: アプリケーション(プロジェクト)のディレクトリ、master: Ethna本体のあるディレクトリのイメージです
+-- PearWrapperはethnaコマンド(Handle)から呼び出されることが前提
+-- Ethna_SkeltonGeneratorにあったメソッドをEthna_Handleに移動、少し追加
+
+- エラーハンドリング方針を多少変更
+-- @演算子を使ったエラー抑制を廃止
+
+- [breaking B.C.] Ethna_ClassFactoryのリファクタリング
+-- Ethna_Backend::getObject()メソッドを追加しました
+-- これにより、Ethna_Controllerの$classメンバに
+ $class = array(
+   // ...
+   'user' => 'Some_Foo_Bar',
+ ),
+と記述することで
+ $user =& $this->backend->getObject('user');
+としてSome_Foo_Barクラスのオブジェクトを取得することが出来ます
+-- クラス定義が見つからない場合は下記の順でファイルを探しに行きます(include_path)
++++ Some_Foo_Bar.php (そのまま)
++++ Foo/Some_Foo_Bar.php (Ethna style)
++++ Foo/Bar.php (Ethna & PEAR style)
++++ Some/Foo/Bar.php (PEAR style)
+-- アプリケーションマネージャの生成もEthna_ClassFactoryで行われます(Ethna_ClassFactory::getManager()が追加されています)
+-- これに伴い、〜2.1.xではコントローラクラスに
+ $manager = array(
+   'um' => 'User',
+ );
+のように記述されていると、Ethna_ActionClass、Ethna_ViewClass、Ethna_AppObject、Ethna_*Managerで
+ $this->um
+としてマネージャオブジェクトにアクセスできていたのですが、この機能が廃止されています(不評なら戻します@preview2)
+- Ethna_Plugin_Logwriter_File::begin()でログファイルのパーミッションを設定するように変更
+- ハードタブ -> ソフトタブ
+- test runnerの追加
+- [breaking B.C.] Ethna_Loggerリファクタリング
+-- Ethna_LogWriterのプラグイン化
+-- カンマ区切りでの複数ファシリティサポート
+-- _getLogWriter()クラスをオーバーライドしている方に影響があります(2.3.0以降はPlugin/Logwriter以下にLogwriterクラスを置いて、ファシリティでその名前を指定すれば任意のLogwriterを追加可能です)
+- [breaking B.C.] Ethna_Renderer追加
+-- 〜2.1.xでは直接扱っいてたテンプレートエンジンオブジェクトをEthna_Rendererクラスでwrapしました
+-- Ethna_Controller::getTemplateEngine()はobsoleteとなりますので今後はEthna_Controller::getRenderer()をご利用ください
+-- Ethna_Controller::_setDefaultTemplateEngine(), Ethna_View::_setDefault(), Ethna_Controller::getTemplateEngine()の引数、戻り値は2.1.xまでのSmartyオブジェクトではなくEthna_Rendererオブジェクトとなります
+-- これに伴い、Ethna_Controller::_setDefaultTemplateEngine(), Ethna_Controller::getTemplateEngine()を利用しているアプリケーションではアップデート時にEthna_Renderer::getEngine()を利用して後方互換性を維持するように変更が必要となります
+ e.g.
+ $smarty =& $this->controller->getTemplateEngine();
+ →
+ $renderer =& $this->controller->getTemplateEngine();
+ $smarty =& $renderer->getEngine();
+- プラグインシステム追加(w/ Ethna_Pluginクラス)
+-- Ethna_Handle, Ethna_CacheManager, Ethna_LogWriterをプラグインシステムに移行
+-- Ethna_ActionFormのバリデータをプラグインシステムに移行(Ethna_ActionForm::use_validator_pluginがtrueのときのみ)
+-- see also
+--- http://ethna.jp/ethna-document-dev_guide-plugin.html
+--- http://ethna.jp/ethna-document-dev_guide-form-validate_with_plugin.html
+- ethnaコマンドにアクション名、ビュー名のチェック処理を追加(Ethna_Controller::checkActionName(), Ethna_Controller::checkViewName()を追加)
+- Ethna_CacheManager_Memcache(キャッシュマネージャのmemcacheサポート)追加
+- Ethna_Sessionにregenerate_idメソッドの追加
+- Ethna_Plugin_Csrf(CSRF対策コード)追加
+
+
+
+*** bug fixes
+
+- [[#9009>http://sourceforge.jp/tracker/index.php?func=detail&aid=9009&group_id=1343&atid=5092]](%s等があるSQLをEchoLoggerでDebugするとWarning)
+- アクション定義のform_pathが正しく動作していなかった問題を修正
+- コントローラが複数あるときにset_error_handler()が何度も実行されるのを回避
+- CacheManager_Localfileの@statでのWARNINGを回避
+- Ethna_Plugin_Validator_Customでエラーが2重登録されていたのを修正
+- プラグインの親クラスがないときにエラーになっていたのを修正
+- Ethna_DB_PEAR, Ethna_AppObjectのWARNINGを回避([ethna-users:0383])
+- Windowsでホームディレクトリの.ethnaファイルが参照されない問題を修正
+- session_startしていないとrestoreメソッドがうまく動かない問題を修正
+- ethnaコマンドにサポートされていないオプションのみを指定して起動した場合(ethna -hなど)にFatal Errorとなる問題を修正
+- Ethna_Backend::getDBのNoticeエラーを修正
+- キャッシュマネージャのエラーコードが256以上(アプリケーション用)になっていた問題を修正
+- ethna add-action-testしたときにファイルがapp/action_cliに生成されてしまう問題を修正
+- Ethna_SkeltonGeneratorクラスのtypoを修正(proejct -> project)
+- Ethna_ActionFormでプラグインを使わないときにフィルタが機能しないバグを修正
+
+
+** [2006/06/07] 2.1.2
+
+*** bug fixes
+
+- Ethna_Controller::getActionRequest()メソッドのデフォルト状態の振舞いを修正
+
+
+** [2006/06/07] 2.1.1
+
+*** bug fixes
+
+- ethna.batのパスを修正
+
+** [2006/06/06] 2.1.0
+
+*** features
+
+- ethnaコマンドのETHNA_HOMEをインストール時に決定するように改善
+- Ethna_ActionForm::validate() で多次元配列が渡されたときのnoticeを回避
+- Ethna_Backend::setActionForm(), Ethna_Backend::setActionClass()メソッドを追加
+- Ethna_FilterのスケルトンにpreActionFilter()/postActionFilter()を追加
+- Ethna_AppObject::_getPropDef()にキャッシュ処理を追加
+- Ethna_CacheManagerクラスを追加(w/ localfile) - from GREE:)
+- Ethna_DB::getDSN()メソッドを追加
+- iniファイルのスケルトンにdsnサンプル追加
+- add-templateコマンド追加(by nnno)
+- add-project時のデフォルトテンプレートデザインを変更
+- ethnaコマンドに-v(--version)オプションを追加
+- smarty_modifier_select(), smarty_function_select()の"selected"属性のxhtml対応(selected="true")
+- {form_name}, {form_input}プラグイン追加(激しくexperimentalというかongoing)
+- Ethna_ViewClassでhelperアクションフォーム対応
+-- Ethna_ViewClass->helper_action_form = array('some_action_name' => null, ...)とすると{form_name}とかで使えます
+- [breaking B.C.] Ethna_ActionClassのpreforward()サポート(むかーしのコードにありましたのです)削除
+- (ぷち)省エネブロックプラグイン{form}...{/form}追加
+-- ethna_action引数も追加(勝手にhiddenタグ生成)
+- Ethna_Controllerに$smarty_block_pluginプロパティを追加
+- ethnaコマンドにadd-action-cliを追加
+- [breaking B.C.] main_CLIのアクション定義ディレクトリをaction_cliに変更
+-- controllerのdirectoryプロパティに'bin'要素を追加
+- ethnaコマンドにadd-app-managerを追加(thanks butatic)
+- Ethna_ActionForm リファクタリング (by いちい)
+-- $this->form の省略値補正を setFormVars() からコンストラクタに移動
+-- フォーム値のスカラー/配列チェックを setFormVars() でするように変更
+--- vaildate() する前に setFormVars() でエラー (handleError()) が発生することがあります
+-- フォーム値のスカラー/配列チェックでフォーム値定義と異なる場合は null にする
+-- ファイルデータの再構成を常に行うように変更
+-- フォーム値定義が配列で required, max/min の設定がある場合のバグを修正
+-- _filter_alnum_zentohan() を追加 (mb_convert_kana($value, "a"))
+- XMLRPCゲートウェイにfaultCodeサポートを追加
+-- actionでEthna_Error(あるいはPEAR_Error)オブジェクトを返すとエラーを返せます
+- XMLRPCゲートウェイサポート追加(experimental)
+-- ethna add-action-xmlrpc [action]でXMLRPCメソッドを追加可能
+-- 引数1つとフォーム定義1つが定義順に対応します
+-- ToDo
+--- 出力バッファチェック
+--- method not foundなどエラー処理対応
+- Ethna_ActionFormクラスのコンストラクタでsetFormVars()を実行しないように変更
+- スケルトンに含まれる'your name'をマクロ({$author})に変更(~/.ethna対応)
+- なげやり便利関数file_exists_ex(), is_absolute_path()を追加
+- SimpleTestとの連携機能を追加(ethnaコマンドにadd-action-test,add-view-testの追加など)
+-- SimpleTestのインストールチェックを追加
+- package.xml生成スクリプト改善(ethnaコマンドインストール対応など)
+- Haste_ADOdb, Haste_Creoleマージ(from Haste Project by haltさん)
+- Ethna_AppObjectクラスのテーブル/プロパティ定義自動生成サポート追加(from generate_app_object originally by 井上さん+haltさん)
+- Ethna_Controller::getAppdir()メソッドを追加
+- Ethna_Controller::getDBType()の引数がnullだった場合に定義一覧を返すように変更
+- ethnaコマンドラインハンドラを追加(+ハンドラをpluggableに+add-viewでテンプレート生成サポート)−please cp bin/ethna to /usr/local/bin or somewhere
+ generate_project_skelton.php -> ethna add-project
+ generate_action_script.php   -> ethna add-action
+ generate_view_script.php     -> ethna add-view
+ generate_app_object.php      -> ethna add-app-object
+- [breaking B.C.] client_typeを廃止 -> gateway追加
+-- CLIENT_TYPE定数廃止
+-- Ethna_Controller::getClientType(), Ethna_Controller::setClientType()廃止
+-- Ethna_Controller::setCLI()/Ethna_Controller::getCLI() -> obsolete
+-- GATEWAY定数追加(GATEWAY_WWW, GATEWAY_CLI, GATEWAY_XMLRPC, GATEWAY_SOAP)
+-- Ethna_Controller::setGateway()/Ethna_Controller::getGateway()追加
+-- 作りかけのAMFゲートウェイサポートを(一旦)廃止
+- Ethna_SkeltonGenerator::_checkAppId()をEthna_Controller::checkAppId()に移動
+- generate_app_objectを追加
+- クラスのメソッドもSmartyFunctionとして登録できるように修正
+
+*** bug fixes
+
+- [[#8435>http://sourceforge.jp/tracker/index.php?func=detail&aid=8435&group_id=1343&atid=5092]](Ethna_AppObject prop_def[]['seq']が未設定)
+- [[#8079>http://sourceforge.jp/tracker/index.php?func=detail&aid=8079&group_id=1343&atid=5092]](FilterでBackendを呼ぶとActionFormの値が空になる)
+- [[#8200>http://sourceforge.jp/tracker/index.php?func=detail&aid=8200&group_id=1343&atid=5092]](PHP5.1.0以降でafのvalidate()で日付チェックが効かない)
+- [[#8179>http://sourceforge.jp/tracker/index.php?func=detail&aid=8179&group_id=1343&atid=5092]](getManagerの戻り値が参照渡しになっていない)
+- [[#8400>http://sourceforge.jp/tracker/index.php?func=detail&aid=8400&group_id=1343&atid=5092]](AppObject prop_def[]['form_name']がNULL)
+- [[#7751>http://sourceforge.jp/tracker/index.php?func=detail&aid=7751&group_id=1343&atid=5092]](SAFE_MODEでmail関数の第5引数があるとWaning)を修正
+- [[#8496>http://sourceforge.jp/tracker/index.php?func=detail&aid=8496&group_id=1343&atid=5092]](Ethna_AppObject.php内のtypo)を修正
+- [[#8387>http://sourceforge.jp/tracker/index.php?func=detail&aid=8387&group_id=1343&atid=5092]](checkMailaddressやcheckURLでNotice)を修正
+- [[#8130>http://sourceforge.jp/tracker/index.php?func=detail&aid=8130&group_id=1343&atid=5092]](Noticeつぶし)を修正
+- typo fixed (aleady -> already)
+- [[#7717>http://sourceforge.jp/tracker/index.php?func=detail&aid=7717&group_id=1343&atid=5092]](Ethna_AppObject::add()でNotice)を修正
+- [[#7664>http://sourceforge.jp/tracker/index.php?func=detail&aid=7664&group_id=1343&atid=5092]](Ethna_AppObjectのバグ)を修正
+- [[#7729>http://sourceforge.jp/tracker/index.php?func=detail&aid=7729&group_id=1343&atid=5092]](ethna_infoがFirefoxだとずれる)を修正
+
+- (within beta) ethna_handle.phpが無用にob_end_clean()する問題を修正
+- (within beta) ethna add-viewでプロジェクトディレクトリを指定した場合に正しくファイルが生成されない問題を修正
+- (within beta) Windows版のethnaコマンドがパッケージからインストールした場合実行できない問題を修正
+- (within beta) ActionFormの配列のフォーム値が破壊される問題を修正(by sfioさん)
+
+
+** [2006/01/29] 0.2.0
+
+*** features
+
+- 文字列のmin/maxエラーのデフォルトエラーメッセージを修正
+- フォーム値定義にカスタムエラーメッセージを定義できるように変更
+- Ethna_Controller::main_CLI()メソッドにフィルタを無効化させるオプションを追加
+- Ethna_ActionFormクラスのフォーム値定義をダイナミックに変更出来るように修正
+- Ethna_ActionFormクラスのフォーム値定義にテンプレート機能を追加
+- Ethna_Backend::getActionClasss()メソッドの追加(実行中のアクションクラスを取得)
+- ~/.ethnaファイルによるユーザ定義スケルトンマクロの追加
+- smarty_function_selectに$empty引数を追加
+- mb_*の変換元エンコーディングを、EUC-JP固定から内部エンコーディングに変更
+- Ethna_Backend::begin()、Ethna_Backend::commit()、Ethna_Backend::rollback()を廃止
+- Ethna_Controller::getDB()をEthna_Controller::getDBType()に変更
+- Ethna_DBクラスを抽象クラス(扱い)として新たにEthna_DBクラスを実装したEthna_DB_PEARクラスを追加
+- Ethna_LogWriterクラスを抽象クラス(扱い)として新たにEthna_LogWriterクラスを実装したEthna_LogWriter_Echo、Ethna_LogWriter_File、Ethna_LogWriter_Syslogクラスを追加
+- log_facilityがnullの場合のログ出力クラスをEthna_LogWriter_EchoからEthna_LogWriterに変更(ログ出力なし)
+- log_facilityにクラス名を書いた場合はそのクラスをログ出力クラスとして利用するように変更
+- Ethna_Filter::preFilter()、Ethna_Filter::postFilter()がEthna_Errorオブジェクトを返した場合は実行を中止するように変更
+- Ethna_InfoManagerの設定表示項目を追加
+- Ethna_ActionForm::isForceValidatePlus()、Ethna_ActionForm::setForceValidatePlus()メソッドと、$force_validate_plusメンバを追加($force_validate_plusをtrueに設定すると、通常検証でエラーが発生した場合でも_validatePlus()メソッドが実行される−デフォルト:false)
+- フォーム値定義のcustom属性にカンマ区切りでの複数メソッドサポートを追加
+
+*** bug fixes
+
+- htmlspecialcharsにENT_QUOTESオプションを追加
+- Ethna_AppSQLクラスのコンストラクタメソッド名を修正
+- [[#7659>http://sourceforge.jp/tracker/index.php?func=detail&aid=7659&group_id=1343&atid=5092]](Ethna_Config.phpでNoticeエラー)を修正
+- Ethna_SOAP_ActionForm.phpのtypoを修正
+- [[#6616>http://sourceforge.jp/tracker/index.php?func=detail&aid=6616&group_id=1343&atid=5092]](セッションにObjectを格納できない)を修正
+- [[#7640>https://sourceforge.jp/tracker/index.php?func=detail&aid=7640&group_id=1343&atid=5092]](機種依存文字のチェックでエラーメッセージが表示されない。)を修正
+- [[#6566>https://sourceforge.jp/tracker/index.php?func=detail&aid=6566&group_id=1343&atid=5092]](skel.action.phpのサンプルでtypo)を修正
+- [[#7451>https://sourceforge.jp/tracker/index.php?func=detail&aid=7451&group_id=1343&atid=5092]](PHP 5.0.5対応)を修正
+- .museum対応
+- Ethna_Backendクラスのクラスメンバ多重定義を修正
+- BASE定数の影響でコントローラの継承が困難な問題を修正
+- Windows環境で定義されていないLOG_LOCAL定数を評価してしまう問題を修正
+- [[#6423>http://sourceforge.jp/tracker/index.php?func=detail&aid=6423&group_id=1343&atid=5092]](php-4.4.0で大量のエラーの後、Segv(11))を修正(patch by ramsyさん)
+- [[#6074>http://sourceforge.jp/tracker/index.php?func=detail&aid=6074&group_id=1343&atid=5092]](generate_project_skelton.phpの動作異常)を修正
+- safe_mode=onの場合にuid/gid warningが発生する(可能性のある)問題を修正
+- 不要な参照渡しを削除
+- その他細かな修正(elseif -> else if等)
+- PATH_SEPARATOR/DIRECTORY_SEPARATORが未定義の場合(PHP 4.1.x等)の問題を修正
+- smarty_modifier_wordwrap_i18n()の改行対応
+- ユーザ定義フォーム検証メソッドが呼び出されない(ことがある)問題を修正
+- マルチカラムプライマリキー利用時にオブジェクトの正当性が正しく判別できない問題を修正
+- Ethna_AppObjectのJOIN検索がSQLエラーになる(ことがある)問題を修正
+- セッションを復帰させるタイミングを遅延(無限ループする問題を修正)
+- Ethna_MalSenderからmail()関数にオプションを渡せるように修正
+- Ethna_View_List::_fixNameObjectに対象オブジェクトも渡すように修正
+
+
+** [2005/03/02] 0.1.5
+
+*** features
+
+- Ethna_Controller::getCLI()(CLIで実行中かどうかを返すメソッド)を追加
+- ethna_error_handlerがphp.iniの設定に応じてPHPログも出力するように変更
+- Smartyプラグイン(truncate_i18n)を追加
+- Ethna_AppObject/Ethna_AppManagerにキャッシュ機構を追加(experimental)
+- メールテンプレートエンジンのフックメソッドを追加
+- MIMEエンコード用ユーティリティメソッドを追加
+- include_pathのセパレータのwin32対応
+
+*** bug fixes
+
+- ethna_error_handlerのtypoを修正
+- Ethna_Sessionクラスでログが正しく出力されない問題を修正
+
+
+** [2005/01/14] 0.1.4
+
+*** features
+
+- Ethna_AppObjectでJOINした場合に、(可能なら)プライマリキーでGROUP BYするように変更
+
+*** bug fixes
+
+- __ethna_info__が全く動作しない問題を修正:(
+
+
+** [2005/01/13] 0.1.3
+
+*** features
+
+- Ethna_AppSearchObjectの複合条件対応
+- Ethna_ClassFactoryクラスを追加
+- Ethna_Controllerのbackend, i18n, session, action_errorメンバを廃止
+- Ethna_Controller::getClass()メソッドを廃止
+- Ethna_ActionClassにauthenticateメソッドを追加
+- preActionFilter/postActionFilterを追加(experimental)
+- Ethna_View_List(リスト表示用ビュー基底クラス)のソート対応
+- 組み込みSmarty関数is_error()を追加
+- Ethna_ActionForm::handleErrorの第2引数を廃止
+- Ethna_ActionForm::_handleErrorをpublicメソッドに変更(Ethna_ActionForm::handleErrorに名称変更)
+- Ethna_ActionForm::getDefメソッドに引数を追加(省略可)
+
+*** bug fixes
+
+- フォーム定義に配列を指定していた場合のカスタムチェックメソッドの呼び出しが正しく行われない問題を修正
+- フォーム定義に配列を指定していた場合の必須チェックが正しく行われない問題を修正
+- __ethna_info__がサブディレクトリに定義されたアクションを正しく取得できない問題を修正
+- VAR_TYPE_FILEの場合はregexp属性が無効になるように修正
+
+
+** [2004/12/23] 0.1.2
+
+*** features
+
+- __ethna_info__アクションを追加
+- class_path, form_path, view_path属性のフルパス指定サポートを追加
+- スクリプトを1ファイルにまとめるツール(bin/unify_script.php)を追加
+
+*** bug fixes
+
+- プロジェクトスケルトン生成時にアプリケーションIDの文字種/予約語をチェックするように修正
+- 'form_name'を指定すると無用に警告が発生する問題を修正
+- 絶対パス判定のプラットフォーム依存を修正(Windows対応改善)
+- VAR_TYPE_INTとVAR_TYPE_FLOATの定義値が重複していた問題を修正
+- SOAP/Mobile(AU)でアクションスクリプトのパスが正しく取得できない問題を修正
+- Ethna_Util::getRandom()でmt_srand()しつつrand()を呼んでいた箇所をmt_rand()を呼び出すように修正
+- CHANGESのエンコーディング修正(ISO-2022-JP -> EUC-JP)
+- フレームワークが発行するSQL文に一部残っていたセミコロンを削除
+- エントリポイント(index.php)に記述されたデフォルトアクション名の1要素目にアスタリスクが使用されていると、正しく動作しない(かもしれない)問題を修正~
+例(こんな場合):
+ <?php
+ include_once('../../app/Sample_Controller.php');
+ Sample_Controller::Main('Sample_Controller', array(
+  'login*',
+ ));
+ ?>
+
+
+** [2004/12/10] 0.1.1
+
+*** bug fixes
+
+- ビューオブジェクトのpreforward()が呼ばれないことがある問題を修正
+- アクション/ビューのスケルトン生成時にファイルを上書きしないように修正
+- ビューのスケルトンでクラス名が正しく置換されない問題を修正
+
+** [2004/12/09] 0.1.0
+
+- 初期リリース
+
diff --git a/Idea_Plugin_Extlib/Ethna.php b/Idea_Plugin_Extlib/Ethna.php
new file mode 100644 (file)
index 0000000..a078ad8
--- /dev/null
@@ -0,0 +1,469 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+//
+//  PEAR OS_WINDOWS constant replacement.
+//
+//  PEAR の OS_WINDOWS 定数は、defined関数で
+//  既に定義されているかをチェックしていない。
+//  よって require_once 'PEAR.php' とすると
+//  E_NOTICEが出ることから、Windows環境判定用
+//  として独自の定数を定義する
+//
+if (substr(PHP_OS, 0, 3) == 'WIN'
+ && !defined('ETHNA_OS_WINDOWS')) {
+    define('ETHNA_OS_WINDOWS', true);
+} elseif (!defined('ETHNA_OS_WINDOWS')) {
+    define('ETHNA_OS_WINDOWS', false);
+}
+
+if (!defined('PATH_SEPARATOR')) {
+    if (ETHNA_OS_WINDOWS) {
+        /** include_path separator(Windows) */
+        define('PATH_SEPARATOR', ';');
+    } else {
+        /** include_path separator(Unix) */
+        define('PATH_SEPARATOR', ':');
+    }
+}
+if (!defined('DIRECTORY_SEPARATOR')) {
+    if (ETHNA_OS_WINDOWS) {
+        /** directory separator(Windows) */
+        define('DIRECTORY_SEPARATOR', '\\');
+    } else {
+        /** separator(Unix) */
+        define('DIRECTORY_SEPARATOR', '/');
+    }
+}
+
+/** バージョン定義 */
+define('ETHNA_VERSION', '2.5.0-preview3');
+
+/**
+ * ダミーのエラーモード
+ * PEAR非依存、かつ互換性を維持するためのもの
+ */
+define('ETHNA_ERROR_DUMMY', 'dummy');
+
+/** Ethnaベースディレクトリ定義 */
+define('ETHNA_BASE', dirname(__FILE__));
+
+require_once ETHNA_BASE . '/class/Ethna_ActionClass.php';
+require_once ETHNA_BASE . '/class/Ethna_ActionError.php';
+require_once ETHNA_BASE . '/class/Ethna_ActionForm.php';
+require_once ETHNA_BASE . '/class/Ethna_AppManager.php';
+require_once ETHNA_BASE . '/class/Ethna_AppObject.php';
+require_once ETHNA_BASE . '/class/Ethna_AppSQL.php';
+require_once ETHNA_BASE . '/class/Ethna_AppSearchObject.php';
+require_once ETHNA_BASE . '/class/Ethna_Backend.php';
+require_once ETHNA_BASE . '/class/Ethna_CacheManager.php';
+require_once ETHNA_BASE . '/class/Ethna_Config.php';
+require_once ETHNA_BASE . '/class/Ethna_Controller.php';
+require_once ETHNA_BASE . '/class/Ethna_ClassFactory.php';
+require_once ETHNA_BASE . '/class/Ethna_DB.php';
+require_once ETHNA_BASE . '/class/Ethna_Error.php';
+require_once ETHNA_BASE . '/class/Ethna_Filter.php';
+require_once ETHNA_BASE . '/class/Ethna_Handle.php';
+require_once ETHNA_BASE . '/class/Ethna_I18N.php';
+require_once ETHNA_BASE . '/class/Ethna_Logger.php';
+require_once ETHNA_BASE . '/class/Ethna_MailSender.php';
+require_once ETHNA_BASE . '/class/Ethna_Session.php';
+require_once ETHNA_BASE . '/class/Ethna_Generator.php';
+require_once ETHNA_BASE . '/class/Ethna_UrlHandler.php';
+require_once ETHNA_BASE . '/class/Ethna_Util.php';
+require_once ETHNA_BASE . '/class/Ethna_ViewClass.php';
+require_once ETHNA_BASE . '/class/View/Ethna_View_List.php';
+require_once ETHNA_BASE . '/class/Ethna_Plugin.php';
+require_once ETHNA_BASE . '/class/Ethna_Renderer.php';
+require_once ETHNA_BASE . '/class/CLI/Ethna_CLI_ActionClass.php';
+
+if (extension_loaded('soap')) {
+    require_once ETHNA_BASE . '/class/SOAP/Ethna_SOAP_ActionForm.php';
+    require_once ETHNA_BASE . '/class/SOAP/Ethna_SOAP_Gateway.php';
+    require_once ETHNA_BASE . '/class/SOAP/Ethna_SOAP_GatewayGenerator.php';
+    require_once ETHNA_BASE . '/class/SOAP/Ethna_SOAP_Util.php';
+    require_once ETHNA_BASE . '/class/SOAP/Ethna_SOAP_WsdlGenerator.php';
+}
+
+/** ゲートウェイ: WWW */
+define('GATEWAY_WWW', 1);
+
+/** ゲートウェイ: CLI */
+define('GATEWAY_CLI', 2);
+
+/** ゲートウェイ: XMLRPC */
+define('GATEWAY_XMLRPC', 3);
+
+/** ゲートウェイ: SOAP */
+define('GATEWAY_SOAP', 4);
+
+
+/** DB種別定義: R/W */
+define('DB_TYPE_RW', 1);
+
+/** DB種別定義: R/O */
+define('DB_TYPE_RO', 2);
+
+/** DB種別定義: Misc  */
+define('DB_TYPE_MISC', 3);
+
+
+/** 要素型: 整数 */
+define('VAR_TYPE_INT', 1);
+
+/** 要素型: 浮動小数点数 */
+define('VAR_TYPE_FLOAT', 2);
+
+/** 要素型: 文字列 */
+define('VAR_TYPE_STRING', 3);
+
+/** 要素型: 日付 */
+define('VAR_TYPE_DATETIME', 4);
+
+/** 要素型: 真偽値 */
+define('VAR_TYPE_BOOLEAN', 5);
+
+/** 要素型: ファイル */
+define('VAR_TYPE_FILE', 6);
+
+
+/** フォーム型: text */
+define('FORM_TYPE_TEXT', 1);
+
+/** フォーム型: password */
+define('FORM_TYPE_PASSWORD', 2);
+
+/** フォーム型: textarea */
+define('FORM_TYPE_TEXTAREA', 3);
+
+/** フォーム型: select */
+define('FORM_TYPE_SELECT', 4);
+
+/** フォーム型: radio */
+define('FORM_TYPE_RADIO', 5);
+
+/** フォーム型: checkbox */
+define('FORM_TYPE_CHECKBOX', 6);
+
+/** フォーム型: button */
+define('FORM_TYPE_SUBMIT', 7);
+
+/** フォーム型: file */
+define('FORM_TYPE_FILE', 8);
+
+/** フォーム型: button */
+define('FORM_TYPE_BUTTON', 9);
+
+/** フォーム型: hidden */
+define('FORM_TYPE_HIDDEN', 10);
+
+
+/** エラーコード: 一般エラー */
+define('E_GENERAL', 1);
+
+/** エラーコード: DB接続エラー */
+define('E_DB_CONNECT', 2);
+
+/** エラーコード: DB設定なし */
+define('E_DB_NODSN', 3);
+
+/** エラーコード: DBクエリエラー */
+define('E_DB_QUERY', 4);
+
+/** エラーコード: DBユニークキーエラー */
+define('E_DB_DUPENT', 5);
+
+/** エラーコード: DB種別エラー */
+define('E_DB_INVALIDTYPE', 6);
+
+/** エラーコード: セッションエラー(有効期限切れ) */
+define('E_SESSION_EXPIRE', 16);
+
+/** エラーコード: セッションエラー(IPアドレスチェックエラー) */
+define('E_SESSION_IPCHECK', 17);
+
+/** エラーコード: アクション未定義エラー */
+define('E_APP_UNDEFINED_ACTION', 32);
+
+/** エラーコード: アクションクラス未定義エラー */
+define('E_APP_UNDEFINED_ACTIONCLASS', 33);
+
+/** エラーコード: アプリケーションオブジェクトID重複エラー */
+define('E_APP_DUPENT', 34);
+
+/** エラーコード: アプリケーションメソッドが存在しない */
+define('E_APP_NOMETHOD', 35);
+
+/** エラーコード: ロックエラー */
+define('E_APP_LOCK', 36);
+
+/** エラーコード: 読み込みエラー */
+define('E_APP_READ', 37);
+
+/** エラーコード: 書き込みエラー */
+define('E_APP_WRITE', 38);
+
+/** エラーコード: CSV分割エラー(行継続) */
+define('E_UTIL_CSV_CONTINUE', 64);
+
+/** エラーコード: フォーム値型エラー(スカラー引数に配列指定) */
+define('E_FORM_WRONGTYPE_SCALAR', 128);
+
+/** エラーコード: フォーム値型エラー(配列引数にスカラー指定) */
+define('E_FORM_WRONGTYPE_ARRAY', 129);
+
+/** エラーコード: フォーム値型エラー(整数型) */
+define('E_FORM_WRONGTYPE_INT', 130);
+
+/** エラーコード: フォーム値型エラー(浮動小数点数型) */
+define('E_FORM_WRONGTYPE_FLOAT', 131);
+
+/** エラーコード: フォーム値型エラー(日付型) */
+define('E_FORM_WRONGTYPE_DATETIME', 132);
+
+/** エラーコード: フォーム値型エラー(BOOL型) */
+define('E_FORM_WRONGTYPE_BOOLEAN', 133);
+
+/** エラーコード: フォーム値型エラー(FILE型) */
+define('E_FORM_WRONGTYPE_FILE', 134);
+
+/** エラーコード: フォーム値必須エラー */
+define('E_FORM_REQUIRED', 135);
+
+/** エラーコード: フォーム値最小値エラー(整数型) */
+define('E_FORM_MIN_INT', 136);
+
+/** エラーコード: フォーム値最小値エラー(浮動小数点数型) */
+define('E_FORM_MIN_FLOAT', 137);
+
+/** エラーコード: フォーム値最小値エラー(文字列型) */
+define('E_FORM_MIN_STRING', 138);
+
+/** エラーコード: フォーム値最小値エラー(日付型) */
+define('E_FORM_MIN_DATETIME', 139);
+
+/** エラーコード: フォーム値最小値エラー(ファイル型) */
+define('E_FORM_MIN_FILE', 140);
+
+/** エラーコード: フォーム値最大値エラー(整数型) */
+define('E_FORM_MAX_INT', 141);
+
+/** エラーコード: フォーム値最大値エラー(浮動小数点数型) */
+define('E_FORM_MAX_FLOAT', 142);
+
+/** エラーコード: フォーム値最大値エラー(文字列型) */
+define('E_FORM_MAX_STRING', 143);
+
+/** エラーコード: フォーム値最大値エラー(日付型) */
+define('E_FORM_MAX_DATETIME', 144);
+
+/** エラーコード: フォーム値最大値エラー(ファイル型) */
+define('E_FORM_MAX_FILE', 145);
+
+/** エラーコード: フォーム値文字種(正規表現)エラー */
+define('E_FORM_REGEXP', 146);
+
+/** エラーコード: フォーム値数値(カスタムチェック)エラー */
+define('E_FORM_INVALIDVALUE', 147);
+
+/** エラーコード: フォーム値文字種(カスタムチェック)エラー */
+define('E_FORM_INVALIDCHAR', 148);
+
+/** エラーコード: 確認用エントリ入力エラー */
+define('E_FORM_CONFIRM', 149);
+
+/** エラーコード: キャッシュタイプ不正 */
+define('E_CACHE_INVALID_TYPE', 192);
+
+/** エラーコード: キャッシュ値なし */
+define('E_CACHE_NO_VALUE', 193);
+
+/** エラーコード: キャッシュ有効期限 */
+define('E_CACHE_EXPIRED', 194);
+
+/** エラーコード: キャッシュエラー(その他) */
+define('E_CACHE_GENERAL', 195);
+
+/** エラーコード: プラグインが見つからない */
+define('E_PLUGIN_NOTFOUND', 196);
+
+/** エラーコード: プラグインエラー(その他) */
+define('E_PLUGIN_GENERAL', 197);
+
+if (defined('E_STRICT') == false) {
+    /** PHP 5との互換保持定義 */
+    define('E_STRICT', 2048);
+}
+
+/** Ethnaグローバル変数: エラーコールバック関数 */
+$GLOBALS['_Ethna_error_callback_list'] = array();
+
+/** Ethnaグローバル変数: エラーメッセージ */
+$GLOBALS['_Ethna_error_message_list'] = array();
+
+
+// {{{ Ethna
+/**
+ *  Ethnaフレームワーククラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna
+{
+    /**
+     *  渡されたオブジェクトが Ethna_Error オブジェクト
+     *  またはそのサブクラスのオブジェクトかどうかチェックします。
+     *
+     *  @param mixed  $data    チェックする変数
+     *  @param mixed  $msgcode チェックするエラーメッセージまたはエラーコード  
+     *  @return mixed 変数が、Ethna_Error の場合に TRUEを返します。
+     *                第2引数が設定された場合は、さらに 所与された $msgcode
+     *                を含む場合のみ TRUEを返します。
+     *  @static
+     */
+    function isError($data, $msgcode = NULL)
+    {
+        if (!is_object($data)) {
+            return false;
+        }
+
+        $class_name = get_class($data);
+        if (strcasecmp($class_name, 'Ethna_Error') === 0
+         || is_subclass_of($data, 'Ethna_Error')) {
+            if ($msgcode == NULL) {
+                return true;
+            } elseif ($data->getCode() == $msgcode) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     *  Ethna_Errorオブジェクトを生成する(エラーレベル:E_USER_ERROR)
+     *
+     *  @access public
+     *  @param  string  $message            エラーメッセージ
+     *  @param  int     $code               エラーコード
+     *  @static
+     */
+    function &raiseError($message, $code = E_GENERAL)
+    {
+        $userinfo = null;
+        if (func_num_args() > 2) {
+            $userinfo = array_slice(func_get_args(), 2);
+            if (count($userinfo) == 1 && is_array($userinfo[0])) {
+                $userinfo = $userinfo[0];
+            }
+        }
+        $error = new Ethna_Error($message, $code, ETHNA_ERROR_DUMMY, E_USER_ERROR, $userinfo, 'Ethna_Error');
+        return $error;
+    }
+
+    /**
+     *  Ethna_Errorオブジェクトを生成する(エラーレベル:E_USER_WARNING)
+     *
+     *  @access public
+     *  @param  string  $message            エラーメッセージ
+     *  @param  int     $code               エラーコード
+     *  @static
+     */
+    function &raiseWarning($message, $code = E_GENERAL)
+    {
+        $userinfo = null;
+        if (func_num_args() > 2) {
+            $userinfo = array_slice(func_get_args(), 2);
+            if (count($userinfo) == 1 && is_array($userinfo[0])) {
+                $userinfo = $userinfo[0];
+            }
+        }
+
+        $error = new Ethna_Error($message, $code, ETHNA_ERROR_DUMMY, E_USER_WARNING, $userinfo, 'Ethna_Error');
+        return $error;
+    }
+
+    /**
+     *  Ethna_Errorオブジェクトを生成する(エラーレベル:E_USER_NOTICE)
+     *
+     *  @access public
+     *  @param  string  $message            エラーメッセージ
+     *  @param  int     $code               エラーコード
+     *  @static
+     */
+    function &raiseNotice($message, $code = E_GENERAL)
+    {
+        $userinfo = null;
+        if (func_num_args() > 2) {
+            $userinfo = array_slice(func_get_args(), 2);
+            if (count($userinfo) == 1 && is_array($userinfo[0])) {
+                $userinfo = $userinfo[0];
+            }
+        }
+
+        $error = new Ethna_Error($message, $code, ETHNA_ERROR_DUMMY, E_USER_NOTICE, $userinfo, 'Ethna_Error');
+        return $error;
+    }
+
+    /**
+     *  エラー発生時の(フレームワークとしての)コールバック関数を設定する
+     *
+     *  @access public
+     *  @param  mixed   string:コールバック関数名 array:コールバッククラス(名|オブジェクト)+メソッド名
+     *  @static
+     */
+    function setErrorCallback($callback)
+    {
+        $GLOBALS['_Ethna_error_callback_list'][] = $callback;
+    }
+
+    /**
+     *  エラー発生時の(フレームワークとしての)コールバック関数をクリアする
+     *
+     *  @access public
+     *  @static
+     */
+    function clearErrorCallback()
+    {
+        $GLOBALS['_Ethna_error_callback_list'] = array();
+    }
+
+    /**
+     *  エラー発生時の処理を行う(コールバック関数/メソッドを呼び出す)
+     *  
+     *  @access public
+     *  @param  object  Ethna_Error     Ethna_Errorオブジェクト
+     *  @static
+     */
+    function handleError(&$error)
+    {
+        for ($i = 0; $i < count($GLOBALS['_Ethna_error_callback_list']); $i++) {
+            $callback =& $GLOBALS['_Ethna_error_callback_list'][$i];
+            if (is_array($callback) == false) {
+                call_user_func($callback, $error);
+            } else if (is_object($callback[0])) {
+                $object =& $callback[0];
+                $method = $callback[1];
+
+                // perform some more checks?
+                $object->$method($error);
+            } else {  
+                //  call statically
+                call_user_func($callback, $error);
+            }
+        }
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/LICENSE b/Idea_Plugin_Extlib/LICENSE
new file mode 100644 (file)
index 0000000..ffd56fc
--- /dev/null
@@ -0,0 +1,31 @@
+The BSD License
+
+Copyright (c) 2004-2006, Masaki Fujimoto
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+  - Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer. 
+  - Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following
+       disclaimer in the documentation and/or other materials provided
+       with the distribution. 
+  - Neither the name of the author nor the names of its contributors
+    may be used to endorse or promote products derived from this
+       software without specific prior written permission. 
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/Idea_Plugin_Extlib/README b/Idea_Plugin_Extlib/README
new file mode 100644 (file)
index 0000000..200ab37
--- /dev/null
@@ -0,0 +1,46 @@
+- Ethna README
+
+Ethna(えすな)は、PHPを利用したウェブアプリケーションフレームワークで、
+似たようなコードを書かなくてよいことを目標としています。
+
+Webサイトは http://ethna.jp です。
+
+ここでは、Ethna に関する情報源、及びバグ(要望)報告についての情報を提供します。
+
+----
+
+1. Ethna の情報源
+
+- ドキュメント
+-- http://ethna.jp/ethna-document.html
+
+- API ドキュメント
+-- http://ethna.jp/doc/
+
+- メーリングリスト 
+
+-- ユーザ向けメーリングリスト(ethna-users)
+--- http://ml.ethna.jp/mailman/listinfo/users
+
+-- Subversion リポジトリ 更新状況(ethna-cvs)
+--- http://ml.ethna.jp/mailman/listinfo/cvs
+
+- IRC
+
+WIDE系ネットワーク Ethna:*.jp でEthnaの使い方や開発について
+議論しています。コミッタもいますので、バグなどについて文句
+を言うのはここが一番伝わりやすいかもしれません。
+
+IRCって何? という方は、IRC普及委員会 を参照して下さい。
+
+http://irc.nahi.to/
+
+2. バグ、要望等を報告する方法
+
+Ethna を使っていて、バグや変な挙動を見つけた場合は、開発者に報告をお願い
+します。報告する手段は以下の通りです。
+
+- IRCチャンネル(WIDE系ネットワーク Ethna:*.jp) -> ここが一番伝わりやすいです
+- ユーザ向けメーリングリスト(ethna-users)にバグについて投稿する
+- sourceforge.jp のバグトラッカ
+-- http://sourceforge.jp/tracker/index.php?group_id=1343&atid=5092&func=add
diff --git a/Idea_Plugin_Extlib/bin/ethna.bat b/Idea_Plugin_Extlib/bin/ethna.bat
new file mode 100755 (executable)
index 0000000..521dd55
--- /dev/null
@@ -0,0 +1,51 @@
+@echo off
+
+rem
+rem   ethna.bat
+rem
+rem   simple command line gateway
+rem
+rem   $Id: ethna.bat 457 2007-02-07 11:14:39Z ichii386 $
+rem
+
+if "%OS%"=="Windows_NT" @setlocal
+
+if NOT "%PHP_PEAR_INSTALL_DIR%" == "" (
+set DEFAULT_ETHNA_HOME=%PHP_PEAR_INSTALL_DIR%\Ethna
+) ELSE (
+set DEFAULT_ETHNA_HOME=%~dp0
+)
+
+goto init
+goto cleanup
+
+:init
+
+if "%ETHNA_HOME%" == "" set ETHNA_HOME=%DEFAULT_ETHNA_HOME%
+set DEFAULT_ETHNA_HOME=
+
+if "%PHP_COMMAND%" == "" goto no_phpcommand
+if "%PHP_CLASSPATH%" == "" goto set_classpath
+
+goto run
+goto cleanup
+
+:run
+IF EXIST "@PEAR-DIR@\Ethna" (
+  %PHP_COMMAND% -d html_errors=off -qC "@PEAR-DIR@\Ethna\bin\ethna_handle.php" %1 %2 %3 %4 %5 %6 %7 %8 %9
+) ELSE (
+  %PHP_COMMAND% -d html_errors=off -qC "%ETHNA_HOME%\bin\ethna_handle.php" %1 %2 %3 %4 %5 %6 %7 %8 %9
+)
+goto cleanup
+
+:no_phpcommand
+set PHP_COMMAND=php.exe
+goto init
+
+:set_classpath
+set PHP_CLASSPATH=%ETHNA_HOME%\class
+goto init
+
+:cleanup
+if "%OS%"=="Windows_NT" @endlocal
+REM pause
diff --git a/Idea_Plugin_Extlib/bin/ethna.sh b/Idea_Plugin_Extlib/bin/ethna.sh
new file mode 100755 (executable)
index 0000000..116a1fa
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+#   ethna.sh
+#
+#   simple command line gateway
+#
+#   $Id: ethna.sh 439 2007-01-04 06:23:15Z ichii386 $
+#
+
+CUR_DIR="$PWD"
+
+if test -z "$ETHNA_HOME"
+then
+    while [ 1 ];
+    do
+        if test -f ".ethna"
+        then
+            if test -d "$PWD""/lib/Ethna"
+            then
+                ETHNA_HOME="$PWD""/lib/Ethna"
+                break
+            fi
+        fi
+        if [ "$PWD" = "/" ];
+        then
+            if test "@PEAR-DIR@/pear" = '@'PEAR-DIR'@'
+            then
+                ETHNA_HOME="/usr/share/php/Ethna"
+            else
+                ETHNA_HOME="@PEAR-DIR@/Ethna"
+            fi
+            break
+        fi
+        cd ..
+    done
+fi
+
+cd $CUR_DIR
+
+if test -z "$PHP_COMMAND"
+then
+    if test "@PHP-BIN@" = '@'PHP-BIN'@'
+    then
+        PHP_COMMAND="php"
+    else
+        PHP_COMMAND="@PHP-BINARY@"
+    fi
+    export PHP_COMMAND
+fi
+
+if test -z "$PHP_CLASSPATH"
+then
+    PHP_CLASSPATH="$ETHNA_HOME/class"
+    export PHP_CLASSPATH
+fi
+
+$PHP_COMMAND -d html_errors=off -qC $ETHNA_HOME/bin/ethna_handle.php $*
diff --git a/Idea_Plugin_Extlib/bin/ethna_handle.php b/Idea_Plugin_Extlib/bin/ethna_handle.php
new file mode 100644 (file)
index 0000000..3483de0
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+/**
+ *  ethna_handle.php
+ *
+ *  Ethna Handle Gateway
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+while (ob_get_level()) {
+    ob_end_clean();
+}
+
+$base = dirname(dirname(dirname(__FILE__)));
+ini_set('include_path', $base.PATH_SEPARATOR.ini_get('include_path'));
+
+require_once 'Ethna/Ethna.php';
+require_once ETHNA_BASE . '/class/Ethna_Getopt.php';
+
+// fetch arguments
+$opt = new Ethna_Getopt();
+$arg_list = $opt->readPHPArgv();
+if (Ethna::isError($arg_list)) {
+    echo $arg_list->getMessage()."\n";
+    exit(2);
+}
+array_shift($arg_list);  // remove "ethna_handle.php"
+
+$eh =& new Ethna_Handle();
+
+//  はじめの引数に - が含まれていればそれを分離する
+//  含まれていた場合、それは -v|--version でなければならない
+list($my_arg_list, $arg_list) = _Ethna_HandleGateway_SeparateArgList($arg_list);
+$r = $opt->getopt($my_arg_list, "v", array("version"));
+if (Ethna::isError($r)) {
+    usage($eh);
+    exit(1);
+}
+
+// ad-hoc:(
+foreach ($r[0] as $opt) {
+    if ($opt[0] == "v" || $opt[0] == "--version") {
+        _Ethna_HandleGateway_ShowVersion();
+        exit(2);
+    }
+}
+
+if (count($arg_list) == 0) {
+    usage($eh);
+    exit(1);
+}
+
+$id = array_shift($arg_list);
+
+$handler =& $eh->getHandler($id);
+if (Ethna::isError($handler)) {
+    printf("no such command: %s\n\n", $id);
+    usage($eh);
+    exit(1);
+}
+
+// don't know what will happen:)
+$handler->setArgList($arg_list);
+$r = $handler->perform();
+if (Ethna::isError($r)) {
+    printf("error occured w/ command [%s]\n  -> %s\n\n", $id, $r->getMessage());
+    if ($r->getCode() == 'usage') {
+        $handler->usage();
+    }
+    exit(1);
+}
+
+/**
+ *  usage
+ */
+function usage(&$eh)
+{
+    $handler_list = $eh->getHandlerList();
+    printf("usage: ethna [option] [command] [args...]\n\n");
+    printf("available options are as follows:\n\n");
+    printf("  -v, --version    show version and exit\n");
+    printf("\navailable commands are as follows:\n\n");
+    foreach ($handler_list as $handler) {
+        printf("  %s -> %s\n", $handler->getId(), $handler->getDescription());
+    }
+}
+
+/**
+ *  fetch options for myself
+ */
+function _Ethna_HandleGateway_SeparateArgList($arg_list)
+{
+    $my_arg_list = array();
+
+    //  はじめの引数に - が含まれていたら、
+    //  それを $my_arg_list に入れる
+    //  これは --version 判定のため 
+    for ($i = 0; $i < count($arg_list); $i++) {
+        if ($arg_list[$i]{0} == '-') {
+            // assume this should be an option for myself
+            $my_arg_list[] = $arg_list[$i];
+        } else {
+            break;
+        }
+    }
+    $arg_list = array_slice($arg_list, $i);
+
+    return array($my_arg_list, $arg_list);
+}
+
+/**
+ *  show version
+ */
+function _Ethna_HandleGateway_ShowVersion()
+{
+    $version = <<<EOD
+Ethna %s (using PHP %s)
+
+Copyright (c) 2004-%s,
+  Masaki Fujimoto <fujimoto@php.net>
+  halt feits <halt.feits@gmail.com>
+  Takuya Ookubo <sfio@sakura.ai.to>
+  nozzzzz <nozzzzz@gmail.com>
+  cocoitiban <cocoiti@comio.info>
+  Yoshinari Takaoka <takaoka@beatcraft.com>
+
+http://ethna.jp/
+
+EOD;
+    printf($version, ETHNA_VERSION, PHP_VERSION, date('Y'));
+}
+?>
diff --git a/Idea_Plugin_Extlib/bin/ethna_make_package.php b/Idea_Plugin_Extlib/bin/ethna_make_package.php
new file mode 100644 (file)
index 0000000..c72af91
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+/**
+ *  ethna_make_package.php
+ *
+ *  package.xml generator for Ethna
+ *
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+require_once 'PEAR.php';
+require_once 'Console/Getopt.php';
+require_once 'PEAR/PackageFileManager2.php';
+require_once 'PEAR/PackageFileManager/File.php';   // avoid bugs
+
+// args
+$arg_list = Console_Getopt::readPHPArgv();
+$state = "stable";
+$is_old_package = false;
+$get_version = false;
+
+$r = Console_Getopt::getopt($arg_list, "abov", array("alpha", "beta", "old-package", "version"));
+foreach ($r[0] as $opt) {
+    if ($opt[0] == "a" || $opt[0] == "--alpha") {
+        $state = "alpha";
+    }
+    if ($opt[0] == "b" || $opt[0] == "--beta") {
+        $state = "beta";
+    }
+    if ($opt[0] == "o" || $opt[0] == "--old-package") {
+        $is_old_package = true;
+    }
+    if ($opt[0] == "v" || $opt[0] == "--version") {
+        $get_version = true;
+    }
+}
+
+$description = 'Ethna Web Application Framework';
+$package = 'Ethna';
+
+// x.0.y -> beta
+// x.1.y -> stable
+$major_version = "2.5";
+$minor_version = "0";
+
+if ($state == 'alpha' || $state == 'beta') {
+    $version = $major_version . strftime('.%Y%m%d%H');
+} else {
+    $version = $major_version . "." . $minor_version;
+}
+
+if ($get_version) {
+    print $version;
+    exit();
+}
+
+$config = array(
+    'baseinstalldir' => 'Ethna',
+    'packagedirectory' => dirname(dirname(__FILE__)),
+    'filelistgenerator' => 'file',
+    'ignore' => array('CVS/', '.svn/', 'package.xml', 'ethna_make_package.php', 'ethna_make_package.sh', '*optional_package*', ),
+    'changelogoldtonew' => false,
+    'exceptions' => array('README' => 'doc', 'LICENSE' => 'doc', 'CHANGES' => 'doc',),
+    'description' => $description,
+    'exceptions' => array('bin/ethna.sh' => 'script', 'bin/ethna.bat' => 'script'),
+    'installexceptions' => array('bin/ethna.sh' => '/', 'bin/ethna.bat' => '/'),
+    'installas' => array('bin/ethna.sh' => 'ethna', 'bin/ethna.bat' => 'ethna.bat'),
+);
+$ethna_channel = 'pear.ethna.jp';
+$packagexml = new PEAR_PackageFileManager2();
+$packagexml->setOptions($config);
+$packagexml->setPackage($package);
+$packagexml->setSummary('Ethna PHP Framework Package');
+$packagexml->setDescription($description);
+$packagexml->setChannel($ethna_channel);
+$packagexml->setAPIVersion($version);
+$packagexml->setReleaseVersion($version);
+$packagexml->setReleaseStability($state);
+$packagexml->setAPIStability($state);
+$packagexml->setNotes('Ethna PHP Web Application Framework');
+$packagexml->setPackageType('php');
+
+$packagexml->addRole('*', 'php');
+
+$packagexml->setPhpDep('4.1.0');
+$packagexml->setPearinstallerDep('1.3.5');
+$packagexml->addPackageDepWithChannel('optional', 'DB', 'pear.php.net');
+$packagexml->addPackageDepWithChannel('optional', 'Smarty', $ethna_channel);
+$packagexml->addPackageDepWithChannel('optional', 'simpletest', $ethna_channel);
+
+$packagexml->addMaintainer('lead', 'fujimoto' , 'Masaki Fujimoto', 'fujimoto@php.net');
+$packagexml->addMaintainer('lead', 'halt' , 'halt feits', 'halt.feits@gmail.com');
+$packagexml->addMaintainer('lead', 'cocoitiban', 'Keita Arai', 'cocoiti@comio.info');
+$packagexml->addMaintainer('lead', 'ichii386', 'ICHII Takashi', 'ichii386@schweetheart.jp');
+
+$packagexml->setLicense('The BSD License', 'http://www.opensource.org/licenses/bsd-license.php');
+
+$packagexml->addReplacement('bin/ethna.bat', 'pear-config', '@PEAR-DIR@', 'php_dir');
+$packagexml->addReplacement('bin/ethna.bat', 'pear-config', '@PHP-BIN@', 'bin_dir');
+$packagexml->addReplacement('bin/ethna.sh', 'pear-config', '@PHP-BINARY@', 'php_bin');
+$packagexml->addReplacement('bin/ethna.sh', 'pear-config', '@PEAR-DIR@', 'php_dir');
+$packagexml->addReplacement('bin/ethna.sh', 'pear-config', '@PHP-BIN@', 'bin_dir');
+
+$packagexml->addRelease();
+$packagexml->setOSInstallCondition('windows');
+$packagexml->addInstallAs('bin/ethna.bat', 'ethna.bat');
+$packagexml->addIgnoreToRelease('bin/ethna.sh');
+$packagexml->addRelease();
+$packagexml->addInstallAs('bin/ethna.sh', 'ethna');
+$packagexml->addIgnoreToRelease('bin/ethna.bat');
+
+$packagexml->generateContents();
+
+if ($is_old_package) {
+    $pkg =& $packagexml->exportCompatiblePackageFile1();
+    $pkg->writePackageFile();
+} else {
+    $packagexml->writePackageFile();
+}
+?>
diff --git a/Idea_Plugin_Extlib/bin/ethna_make_package.sh b/Idea_Plugin_Extlib/bin/ethna_make_package.sh
new file mode 100755 (executable)
index 0000000..74a0d7d
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/sh
+#
+#   ethna_make_package.sh
+#
+#   ...:(
+#
+#   $Id: ethna_make_package.sh 461 2007-04-19 14:50:45Z ichii386 $
+#
+tmpdir="/tmp/ethna"
+
+if [ ! -d $tmpdir ]
+then
+    mkdir -p $tmpdir
+fi
+
+if [ "$1" = "-b" ]
+then
+    beta=$1
+fi
+
+if [ "$1" = "-a" ]
+then
+    alpha=$1
+    beta=$alpha
+fi
+
+# chdir to basedir
+cwd=`dirname $0`
+basedir="$cwd/../"
+cd $basedir
+basedir=`pwd`
+
+version=`php $basedir/bin/ethna_make_package.php $beta -v`
+targetdir="$tmpdir/Ethna-$version"
+
+rm -f $basedir/package.xml
+
+rm -fr $targetdir
+mkdir $targetdir
+cp -a . "$targetdir/"
+
+#  create optional package
+optpkg_dir="$targetdir/misc/optional_package"
+cd $optpkg_dir/Smarty/src
+tar xvfz Smarty*.tar.gz
+cd $optpkg_dir/Smarty/build
+./build
+cp $optpkg_dir/Smarty/release/*.tgz $tmpdir
+cd $optpkg_dir/simpletest/src
+tar xvfz simpletest*.tar.gz
+cd $optpkg_dir/simpletest/build
+./build
+cp $optpkg_dir/simpletest/release/*.tgz $tmpdir
+rm -rf $optpkg_dir
+cd $basedir
+
+find $targetdir -name "CVS" -o -name ".svn" | xargs rm -fr
+
+# create package for php 5
+php $basedir/bin/ethna_make_package.php $beta
+cp -f $basedir/package.xml $tmpdir/
+cd $tmpdir
+tar zcvf Ethna-$version.tgz package.xml Ethna-$version
+
+cd $basedir
+php $basedir/bin/ethna_make_package.php $beta -o
+cp -f $basedir/package.xml $tmpdir/
+cd $tmpdir
+tar zcvf Ethna-$version-dev.tgz package.xml Ethna-$version
+
diff --git a/Idea_Plugin_Extlib/bin/ethna_run_test.php b/Idea_Plugin_Extlib/bin/ethna_run_test.php
new file mode 100644 (file)
index 0000000..0f78875
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/**
+ *  ethna_run_test.php
+ *
+ *  Ethna Test Runner
+ *
+ *  @author     Kazuhiro Hosoi <hosoi@gree.co.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/** Ethnaインストールルートディレクトリ */
+define('ETHNA_INSTALL_BASE', dirname(dirname(__FILE__)));
+
+/** include_pathの設定(このtest runnerがあるディレクトリを追加) */
+ini_set('include_path', dirname(ETHNA_INSTALL_BASE) . PATH_SEPARATOR . ini_get('include_path'));
+
+/** Ethna関連クラスのインクルード */
+require_once 'Ethna/Ethna.php';
+
+/** SimpleTestのインクルード */
+require_once 'simpletest/unit_tester.php';
+require_once 'simpletest/reporter.php';
+require_once 'Ethna/test/TextDetailReporter.php';
+require_once 'Ethna/test/Ethna_UnitTestBase.php';
+
+/** テストケースがあるディレクトリ */
+$test_dir = ETHNA_INSTALL_BASE . '/test';
+
+$test = &new GroupTest('Ethna All tests');
+
+// テストケースのファイルリストを取得
+require_once ETHNA_INSTALL_BASE . '/class/Ethna_Getopt.php';
+$opt = new Ethna_Getopt();
+$args = $opt->readPHPArgv();
+list($args, $opts) = $opt->getopt($args, '', array());
+array_shift($opts);
+if (count($opts) > 0) {
+    $file_list = $opts;
+} else {
+    $file_list = getFileList($test_dir);
+}
+
+// テストケースを登録
+foreach ($file_list as $file) {
+    $test->addTestFile($file);
+}
+
+// 結果をコマンドラインに出力
+$test->run(new TextDetailReporter());
+
+//{{{ getFileList
+/**
+ * getFileList
+ *
+ * @param string $dir_path
+ */
+function getFileList($dir_path)
+{
+    $file_list = array();
+
+    $dir = opendir($dir_path);
+
+    if ($dir == false) {
+        return false;
+    }
+
+    while($file_path = readdir($dir)) {
+
+        $full_path = $dir_path . '/'. $file_path;
+
+        if (is_file($full_path)){
+
+            // テストケースのファイルのみ読み込む
+            if (preg_match('/^(Ethna_)(.*)(_Test.php)$/',$file_path,$matches)) {
+                $file_list[] = $full_path;
+            }
+
+        // サブディレクトリがある場合は,再帰的に読み込む.
+        // "."で始まるディレクトリは読み込まない.
+        } else if (is_dir($full_path) && !preg_match('/^\./',$file_path,$matches)) {
+
+            $file_list = array_merge($file_list,getFileList($full_path));
+        }
+    }
+
+    closedir($dir);
+    return $file_list;
+}
+//}}}
+
+?>
diff --git a/Idea_Plugin_Extlib/bin/ethna_run_test.sh b/Idea_Plugin_Extlib/bin/ethna_run_test.sh
new file mode 100755 (executable)
index 0000000..c614e9f
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+BIN_DIR=`dirname $0`
+ETHNA_DIR="$BIN_DIR/.."
+TEST_DIR="$ETHNA_DIR/test"
+TEST_RUNNER="$BIN_DIR/ethna_run_test.php"
+
+php $TEST_RUNNER $* < $TEST_DIR/run_test.stdin.txt
diff --git a/Idea_Plugin_Extlib/class/Action/Ethna_Action_Info.php b/Idea_Plugin_Extlib/class/Action/Ethna_Action_Info.php
new file mode 100644 (file)
index 0000000..d9c27bb
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Action_Info.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Form_Info
+/**
+ *  __ethna_info__フォームの実装
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Form_Info extends Ethna_ActionForm
+{
+    /**
+     *  @access private
+     *  @var    array   フォーム値定義
+     */
+    var $form = array(
+    );
+}
+// }}}
+
+// {{{ Ethna_Action_Info
+/**
+ *  __ethna_info__アクションの実装
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Action_Info extends Ethna_ActionClass
+{
+    /**
+     *  __ethna_info__アクションの前処理
+     *
+     *  @access public
+     *  @return string      Forward先(正常終了ならnull)
+     */
+    function prepare()
+    {
+        return null;
+    }
+
+    /**
+     *  __ethna_info__アクションの実装
+     *
+     *  @access public
+     *  @return string  遷移名
+     */
+    function perform()
+    {
+        return '__ethna_info__';
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Action/Ethna_Action_UnitTest.php b/Idea_Plugin_Extlib/class/Action/Ethna_Action_UnitTest.php
new file mode 100644 (file)
index 0000000..2c01bcd
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ *  Ethna_Action_UnitTest.php
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/**
+ *  __ethna_unittest__フォームの実装
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Form_UnitTest extends Ethna_ActionForm
+{
+    /**
+     *  @access private
+     *  @var    array   フォーム値定義
+     */
+    var $form = array(
+    );
+}
+
+/**
+ *  __ethna_unittest__アクションの実装
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Action_UnitTest extends Ethna_ActionClass
+{
+    /**
+     *  __ethna_unittest__アクションの前処理
+     *
+     *  @access public
+     *  @return string      Forward先(正常終了ならnull)
+     */
+    function prepare()
+    {
+        return null;
+    }
+
+    /**
+     *  __ethna_unittest__アクションの実装
+     *
+     *  @access public
+     *  @return string  遷移名
+     */
+    function perform()
+    {
+        return '__ethna_unittest__';
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/CLI/Ethna_CLI_ActionClass.php b/Idea_Plugin_Extlib/class/CLI/Ethna_CLI_ActionClass.php
new file mode 100644 (file)
index 0000000..b98fd8f
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  action_class.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_CLI_ActionClass
+/**
+ *  コマンドラインaction実行クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ *  @obsolete
+ */
+class Ethna_CLI_ActionClass extends Ethna_ActionClass
+{
+    /**
+     *  action処理
+     *
+     *  @access public
+     */
+    function Perform()
+    {
+        parent::Perform();
+        $_SERVER['REMOTE_ADDR'] = "0.0.0.0";
+        $_SERVER['HTTP_USER_AGENT'] = "";
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/DB/Ethna_DB_ADOdb.php b/Idea_Plugin_Extlib/class/DB/Ethna_DB_ADOdb.php
new file mode 100644 (file)
index 0000000..a63b890
--- /dev/null
@@ -0,0 +1,433 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_DB_ADOdb.php
+ *
+ *  @package    Ethna
+ *  @author     halt feits <halt.feits@gmail.com>
+ *  @version    $Id$
+ */
+
+/**
+ * ADOdb config setting
+ */
+define('ADODB_OUTP', 'ethna_adodb_logger'); //disable output error
+
+require_once 'adodb/adodb.inc.php';
+
+function ethna_adodb_logger ($msg, $newline) {
+    $c =& Ethna_Controller::getInstance();
+    $logger =& $c->getLogger();
+    
+    $logger->log(LOG_DEBUG, strip_tags(str_replace("\n", "", $msg)));
+}
+
+/**
+ *  Ethna_DB_ADOdb
+ *
+ *  EthnaのフレームワークでADOdbオブジェクトを扱うための抽象クラス
+ *
+ *  @package    Ethna
+ *  @author     halt feits <halt.feits@gmail.com>
+ *  @access     public
+ */
+class Ethna_DB_ADOdb extends Ethna_DB
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  DB              DBオブジェクト */
+    var $db;
+
+    /** @var    string   dsn */
+    var $dsn;
+
+    /**#@-*/
+
+
+    /**
+     *  コンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     *  @param  string  $dsn                                DSN
+     *  @param  bool    $persistent                         持続接続設定
+     */
+    function Ethna_DB_ADOdb(&$controller, $dsn, $persistent)
+    {
+        parent::Ethna_DB($controller, $dsn, $persistent);
+
+        $this->logger =& $controller->getLogger();
+    }
+
+    //{{{ connect
+    /**
+     *  DBに接続する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function connect()
+    {
+        $dsn = $this->parseDSN($this->dsn);
+        
+        if ($dsn['phptype'] == 'sqlite') {
+            $path = $dsn['database'];
+            $this->db = ADONewConnection("sqlite");
+            $this->db->Connect($path);
+        } else {
+            $this->db = ADONewConnection($this->dsn);
+        }
+
+        if ( $this->db ) {
+            $this->db->SetFetchMode(ADODB_FETCH_ASSOC);
+            return true;
+        } else {
+            return false;
+        }    
+    }
+    //}}}
+
+    //{{{ disconnect
+    /**
+     *  DB接続を切断する
+     *
+     *  @access public
+     */
+    function disconnect()
+    {
+        //$this->db->close();
+        return 0;
+    }
+    //}}}
+
+    //{{{ isValid
+    /**
+     *  DB接続状態を返す
+     *
+     *  @access public
+     *  @return bool    true:正常(接続済み) false:エラー/未接続
+     */
+    function isValid()
+    {
+        if ( is_object($this->db) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+    //}}}
+
+    //{{{ begin
+    /**
+     *  DBトランザクションを開始する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function begin()
+    {
+        return $this->db->BeginTrans();
+    }
+    //}}}
+
+    //{{{ rollback
+    /**
+     *  DBトランザクションを中断する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function rollback()
+    {
+        $this->db->RollbackTrans();
+        return 0;
+    }
+    //}}}
+
+    //{{{ commit
+    /**
+     *  DBトランザクションを終了する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function commit()
+    {
+        $this->db->CommitTrans();
+        return 0;
+    }
+    //}}}
+
+    //{{{ query
+    /**
+     *  クエリを発行する
+     *
+     *  @access public
+     *  @param  string  $query  SQL文
+     *  @return mixed   DB_Result:結果オブジェクト Ethna_Error:エラー
+     */
+    function &query($query, $inputarr = false)
+    {
+        return $this->_query($query, $inputarr);
+    }
+    //}}}
+    
+    //{{{ _query
+    /**
+     *  クエリを発行する
+     *
+     *  @access private
+     *  @param  string  $query  SQL文
+     *  @return mixed   DB_Result:結果オブジェクト Ethna_Error:エラー
+     */
+    function &_query($query, $inputarr = false)
+    {
+        $this->logger->log(LOG_DEBUG, $query);
+        $r =& $this->db->execute($query, $inputarr);
+
+        if ($r === false) {
+
+            $error = Ethna::raiseError('エラー SQL[%s] CODE[%d] MESSAGE[%s]',
+                E_DB_QUERY,
+                $query,
+                $this->db->ErrorNo(),
+                $this->db->ErrorMsg());
+
+            return $error;
+
+        }
+
+        return $r;
+    }
+    //}}}
+
+    //{{{ getAll
+    /**
+     * getAll
+     *
+     * @access public
+     */
+    function getAll($query, $inputarr = false)
+    {
+        $this->db->SetFetchMode(ADODB_FETCH_ASSOC);
+        return $this->db->getAll($query, $inputarr);
+    }
+    //}}}
+
+    //{{{ getOne
+    function getOne($query, $inputarr = false)
+    {
+        return $this->db->GetOne($query, $inputarr);
+    }
+    //}}}
+
+    //{{{ getRow
+    function getRow($query, $inputarr = false)
+    {
+        return $this->db->GetRow($query, $inputarr);
+    }
+    //}}}
+
+    //{{{ getCol
+    function getCol($query, $inputarr = false)
+    {
+        return $this->db->GetCol($query, $inputarr);
+    }
+    //}}}
+
+    //{{{ execute
+    function execute($query, $inputarr = false)
+    {
+        return $this->db->Execute($query, $inputarr);
+    }
+    //}}}
+
+    //{{{ replace
+    function replace($table, $arrFields, $keyCols, $autoQuote = false)
+    {
+        return $this->db->Replace($table, $arrFields, $keyCols, $autoQuote);
+    }
+    //}}}
+
+    //{{{ autoExecute
+    function autoExecute($table, $fields, $mode, $where = false, $forceUpdate = true, $magicq = false)
+    {
+        return $this->db->AutoExecute($table, $fields, $mode, $where, $forceUpdate, $magicq);
+    }
+    //}}}
+    
+    //{{{ pageExecute
+    /**
+     * pageExecute
+     *
+     * @param string $query
+     * @param string $nrows
+     * @param integer $page
+     * @param array $inputarr
+     */
+    function pageExecute($query, $nrows, $page, $inputarr = false)
+    {
+        return $this->db->PageExecute($query, $nrows, $page, $inputarr);
+    }
+    //}}}
+
+    // {{{ parseDSN()
+
+    /**
+     * Parse a data source name
+     *
+     * Additional keys can be added by appending a URI query string to the
+     * end of the DSN.
+     *
+     * The format of the supplied DSN is in its fullest form:
+     * <code>
+     *  phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
+     * </code>
+     *
+     * Most variations are allowed:
+     * <code>
+     *  phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644
+     *  phptype://username:password@hostspec/database_name
+     *  phptype://username:password@hostspec
+     *  phptype://username@hostspec
+     *  phptype://hostspec/database
+     *  phptype://hostspec
+     *  phptype(dbsyntax)
+     *  phptype
+     * </code>
+     *
+     * @param string $dsn Data Source Name to be parsed
+     *
+     * @return array an associative array with the following keys:
+     *  + phptype:  Database backend used in PHP (mysql, odbc etc.)
+     *  + dbsyntax: Database used with regards to SQL syntax etc.
+     *  + protocol: Communication protocol to use (tcp, unix etc.)
+     *  + hostspec: Host specification (hostname[:port])
+     *  + database: Database to use on the DBMS server
+     *  + username: User name for login
+     *  + password: Password for login
+     */
+    function parseDSN($dsn)
+    {
+        $parsed = array(
+            'phptype'  => false,
+            'dbsyntax' => false,
+            'username' => false,
+            'password' => false,
+            'protocol' => false,
+            'hostspec' => false,
+            'port'     => false,
+            'socket'   => false,
+            'database' => false,
+        );
+
+        if (is_array($dsn)) {
+            $dsn = array_merge($parsed, $dsn);
+            if (!$dsn['dbsyntax']) {
+                $dsn['dbsyntax'] = $dsn['phptype'];
+            }
+            return $dsn;
+        }
+
+        // Find phptype and dbsyntax
+        if (($pos = strpos($dsn, '://')) !== false) {
+            $str = substr($dsn, 0, $pos);
+            $dsn = substr($dsn, $pos + 3);
+        } else {
+            $str = $dsn;
+            $dsn = null;
+        }
+
+        // Get phptype and dbsyntax
+        // $str => phptype(dbsyntax)
+        if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
+            $parsed['phptype']  = $arr[1];
+            $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
+        } else {
+            $parsed['phptype']  = $str;
+            $parsed['dbsyntax'] = $str;
+        }
+
+        if (!count($dsn)) {
+            return $parsed;
+        }
+
+        // Get (if found): username and password
+        // $dsn => username:password@protocol+hostspec/database
+        if (($at = strrpos($dsn, '@')) !== false) {
+            $str = substr($dsn, 0, $at);
+            $dsn = substr($dsn, $at + 1);
+            if (($pos = strpos($str, ':')) !== false) {
+                $parsed['username'] = rawurldecode(substr($str, 0, $pos));
+                $parsed['password'] = rawurldecode(substr($str, $pos + 1));
+            } else {
+                $parsed['username'] = rawurldecode($str);
+            }
+        }
+
+        // Find protocol and hostspec
+        if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
+            // $dsn => proto(proto_opts)/database
+            $proto      = $match[1];
+            $proto_opts = $match[2] ? $match[2] : false;
+            $dsn        = $match[3];
+        } else {
+            // $dsn => protocol+hostspec/database (old format)
+            if (strpos($dsn, '+') !== false) {
+                list($proto, $dsn) = explode('+', $dsn, 2);
+            }
+            if (strpos($dsn, '/') !== false) {
+                list($proto_opts, $dsn) = explode('/', $dsn, 2);
+            } else {
+                $proto_opts = $dsn;
+                $dsn        = null;
+            }
+        }
+
+        // process the different protocol options
+        $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
+        $proto_opts         = rawurldecode($proto_opts);
+        if ($parsed['protocol'] == 'tcp') {
+            if (strpos($proto_opts, ':') !== false) {
+                list($parsed['hostspec'], $parsed['port']) = explode(':', $proto_opts);
+            } else {
+                $parsed['hostspec'] = $proto_opts;
+            }
+        } elseif ($parsed['protocol'] == 'unix') {
+            $parsed['socket'] = $proto_opts;
+        }
+
+        // Get dabase if any
+        // $dsn => database
+        if ($dsn) {
+            if (($pos = strpos($dsn, '?')) === false) {
+                // /database
+                $parsed['database'] = rawurldecode($dsn);
+            } else {
+                // /database?param1=value1&param2=value2
+                $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
+                $dsn                = substr($dsn, $pos + 1);
+                if (strpos($dsn, '&') !== false) {
+                    $opts = explode('&', $dsn);
+                } else { // database?param1=value1
+                    $opts = array($dsn);
+                }
+                foreach ($opts as $opt) {
+                    list($key, $value) = explode('=', $opt);
+                    if (!isset($parsed[$key])) {
+                        // don't allow params overwrite
+                        $parsed[$key] = rawurldecode($value);
+                    }
+                }
+            }
+        }
+
+        return $parsed;
+    }
+
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/DB/Ethna_DB_Creole.php b/Idea_Plugin_Extlib/class/DB/Ethna_DB_Creole.php
new file mode 100644 (file)
index 0000000..d69c5f3
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_DB_Creole.php
+ *
+ *  @package    Ethna
+ *  @author     halt feits <halt.feits@gmail.com>
+ *  @version    $Id$
+ */
+
+require_once 'creole/Creole.php';
+
+/**
+ *  Ethna用DB抽象クラス
+ *
+ *  EthnaのフレームワークでDBオブジェクトを扱うための抽象クラス
+ *  (のつもり...あぁすばらしきPHP 4)
+ *
+ *  @package    Ethna
+ *  @author     halt feits <halt.feits@gmail.com>
+ *  @access     public
+ */
+class Ethna_DB_Creole extends Ethna_DB
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  DB              DBオブジェクト */
+    var $db;
+
+    /** @var    string   dsn */
+    var $dsn;
+
+    /**#@-*/
+
+
+    /**
+     *  コンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     *  @param  string  $dsn                                DSN
+     *  @param  bool    $persistent                         持続接続設定
+     */
+    function Ethna_DB_Creole(&$controller, $dsn, $persistent)
+    {
+        parent::Ethna_DB($controller, $dsn, $persistent);
+    }
+
+    /**
+     *  DBに接続する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function connect()
+    {
+        $this->db = Creole::getConnection($this->dsn);
+        return 0;
+    }
+
+    /**
+     *  DB接続を切断する
+     *
+     *  @access public
+     */
+    function disconnect()
+    {
+        $this->db->close();
+        return 0;
+    }
+
+    /**
+     *  DB接続状態を返す
+     *
+     *  @access public
+     *  @return bool    true:正常(接続済み) false:エラー/未接続
+     */
+    function isValid()
+    {
+        if ( is_object($this->db) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     *  DBトランザクションを開始する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function begin()
+    {
+        return 0;
+    }
+
+    /**
+     *  DBトランザクションを中断する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function rollback()
+    {
+        $this->db->rollback();
+        return 0;
+    }
+
+    /**
+     *  DBトランザクションを終了する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function commit()
+    {
+        $this->db->commit();
+        return 0;
+    }
+
+    /**
+     *
+     * PrepareStatement
+     *
+     * @return  Object
+     * @access  public
+     */
+    function prepareStatement($sql)
+    {
+        return $this->db->prepareStatement($sql);
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/DB/Ethna_DB_PEAR.php b/Idea_Plugin_Extlib/class/DB/Ethna_DB_PEAR.php
new file mode 100644 (file)
index 0000000..30b3029
--- /dev/null
@@ -0,0 +1,540 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_DB_PEAR.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+require_once 'DB.php';
+
+// {{{ Ethna_DB_PEAR
+/**
+ *  Ethna_DBクラスの実装(PEAR版)
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_DB_PEAR extends Ethna_DB
+{
+    // {{{ properties
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  DB              PEAR DBオブジェクト */
+    var $db;
+
+    /** @var    array   トランザクション管理スタック */
+    var $transaction = array();
+
+
+    /** @var    object  Ethna_Logger    ログオブジェクト */
+    var $logger;
+
+    /** @var    object  Ethna_AppSQL    SQLオブジェクト */
+    var $sql;
+
+    /** @var    string  DBタイプ(mysql, pgsql...) */
+    var $type;
+
+    /** @var    string  DSN */
+    var $dsn;
+
+    /** @var    array   DSN (DB::parseDSN()の返り値) */
+    var $dsninfo;
+
+    /** @var    bool    持続接続フラグ */
+    var $persistent;
+
+    /**#@-*/
+    // }}}
+
+    // {{{ Ethna_DBクラスの実装
+    // {{{ Ethna_DB_PEAR
+    /**
+     *  Ethna_DB_PEARクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     *  @param  string  $dsn                                DSN
+     *  @param  bool    $persistent                         持続接続設定
+     */
+    function Ethna_DB_PEAR(&$controller, $dsn, $persistent)
+    {
+        parent::Ethna_DB($controller, $dsn, $persistent);
+
+        $this->db = null;
+        $this->logger =& $controller->getLogger();
+        $this->sql =& $controller->getSQL();
+
+        $this->dsninfo = DB::parseDSN($dsn);
+        $this->dsninfo['new_link'] = true;
+        $this->type = $this->dsninfo['phptype'];
+    }
+    // }}}
+
+    // {{{ connect
+    /**
+     *  DBに接続する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function connect()
+    {
+        $this->db =& DB::connect($this->dsninfo, $this->persistent);
+        if (DB::isError($this->db)) {
+            $error = Ethna::raiseError('DB Connection Error: %s',
+                E_DB_CONNECT,
+                $this->db->getUserInfo());
+            $error->addUserInfo($this->db);
+            $this->db = null;
+            return $error;
+        }
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ disconnect
+    /**
+     *  DB接続を切断する
+     *
+     *  @access public
+     */
+    function disconnect()
+    {
+        if ($this->isValid() == false) {
+            return;
+        }
+        $this->db->disconnect();
+    }
+    // }}}
+
+    // {{{ isValid
+    /**
+     *  DB接続状態を返す
+     *
+     *  @access public
+     *  @return bool    true:正常 false:エラー
+     */
+    function isValid()
+    {
+        if (is_null($this->db)
+            || is_resource($this->db->connection) == false) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+    // }}}
+
+    // {{{ begin
+    /**
+     *  DBトランザクションを開始する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function begin()
+    {
+        if (count($this->transaction) > 0) {
+            $this->transaction[] = true;
+            return 0;
+        }
+
+        $r = $this->query('BEGIN;');
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        $this->transaction[] = true;
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ rollback
+    /**
+     *  DBトランザクションを中断する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function rollback()
+    {
+        if (count($this->transaction) == 0) {
+            return 0;
+        }
+
+        // ロールバック時はスタック数に関わらずトランザクションをクリアする
+        $r = $this->query('ROLLBACK;');
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        $this->transaction = array();
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ commit
+    /**
+     *  DBトランザクションを終了する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function commit()
+    {
+        if (count($this->transaction) == 0) {
+            return 0;
+        } else if (count($this->transaction) > 1) {
+            array_pop($this->transaction);
+            return 0;
+        }
+
+        $r = $this->query('COMMIT;');
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        array_pop($this->transaction);
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ getMetaData
+    /**
+     *  テーブル定義情報を取得する
+     *
+     *  @access public
+     *  @param  string  $table  テーブル名
+     *  @return mixed   array: PEAR::DBに準じたメタデータ Ethna_Error::エラー
+     */
+    function &getMetaData($table)
+    {
+        $def =& $this->db->tableInfo($table);
+        if (is_array($def) === false) {
+            return $def;
+        }
+
+        foreach (array_keys($def) as $k) {
+            $def[$k] = array_map('strtolower', $def[$k]);
+
+            // type
+            $type_map = array(
+                'int'       => array(
+                    'int', 'integer', '^int\(?[0-9]\+', '^serial', '[a-z]+int$',
+                ),
+                'boolean'   => array(
+                    'bit', 'bool', 'boolean',
+                ),
+                'datetime'  => array(
+                    'timestamp', 'datetime',
+                ),
+            );
+            foreach ($type_map as $convert_to => $regex) {
+                foreach ($regex as $r) {
+                    if (preg_match('/'.$r.'/', $def[$k]['type'])) {
+                        $def[$k]['type'] = $convert_to;
+                        break 2;
+                    }
+                }
+            }
+
+            // flags
+            $def[$k]['flags'] = explode(' ', $def[$k]['flags']);
+            switch ($this->type) {
+            case 'mysql':
+                // auto_increment があれば sequence
+                if (in_array('auto_increment', $def[$k]['flags'])) {
+                    $def[$k]['flags'][] = 'sequence';
+                }
+                break;
+            case 'pgsql':
+                // nextval があれば sequence
+                foreach ($def[$k]['flags'] as $f) {
+                    if (strpos($f, 'nextval') !== false) {
+                        $def[$k]['flags'][] = 'sequence';
+                        break;
+                    }
+                }
+                break;
+            case 'sqlite':
+                // integer, primary key ならば auto_increment を追加
+                if ($def[$k]['type'] == 'int'
+                    && in_array('primary_key', $def[$k]['flags'])) {
+                    $def[$k]['flags'][] = 'sequence';
+                }
+                break;
+            }
+        }
+
+        return $def;
+    }
+    // }}}
+    // }}}
+
+    // {{{ Ethna_AppObject連携のための実装
+    // {{{ getType
+    /**
+     *  DBタイプを返す
+     *
+     *  @access public
+     *  @return string  DBタイプ
+     */
+    function getType()
+    {
+        return $this->type;
+    }
+    // }}}
+
+    // {{{ query
+    /**
+     *  クエリを発行する
+     *
+     *  @access public
+     *  @param  string  $query  SQL文
+     *  @return mixed   DB_Result:結果オブジェクト Ethna_Error:エラー
+     */
+    function &query($query)
+    {
+        return $this->_query($query);
+    }
+    // }}}
+
+    // {{{ getNextId
+    /**
+     *  直後のINSERTに使うIDを取得する
+     *  (pgsqlのみ対応)
+     *
+     *  @access public
+     *  @return mixed   int
+     */
+    function getNextId($table_name, $field_name)
+    {
+        if ($this->isValid() == false) {
+            return null;
+        } else if ($this->type == 'pgsql') {
+            $seq_name = sprintf('%s_%s', $table_name, $field_name);
+            $ret = $this->db->nextId($seq_name);
+            return $ret;
+        }
+
+        return null;
+    }
+    // }}}
+
+    // {{{ getInsertId
+    /**
+     *  直前のINSERTによるIDを取得する
+     *  (mysql, sqliteのみ対応)
+     *
+     *  @access public
+     *  @return mixed   int:直近のINSERTにより生成されたID null:未サポート
+     */
+    function getInsertId()
+    {
+        if ($this->isValid() == false) {
+            return null;
+        } else if ($this->type == 'mysql') {
+            return mysql_insert_id($this->db->connection);
+        } else if ($this->type == 'sqlite') {
+            return sqlite_last_insert_rowid($this->db->connection);
+        }
+
+        return null;
+    }
+    // }}}
+
+    // {{{ fetchRow
+    /**
+     *  DB_Result::fetchRow()の結果を整形して返す
+     *
+     *  @access public
+     *  @return int     更新行数
+     */
+    function &fetchRow(&$res, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
+    {
+        $row =& $res->fetchRow($fetchmode, $rownum);
+        if (is_array($row) === false) {
+            return $row;
+        }
+
+        if ($this->type === 'sqlite') {
+            // "table"."column" -> column
+            foreach ($row as $k => $v) {
+                unset($row[$k]);
+                if (($f = strstr($k, '.')) !== false) {
+                    $k = substr($f, 1);
+                }
+                if ($k{0} === '"' && $k{strlen($k)-1} === '"') {
+                    $k = substr($k, 1, -1);
+                }
+                $row[$k] = $v;
+            }
+        }
+
+        return $row;
+    }
+    // }}}
+
+    // {{{ affectedRows
+    /**
+     *  直近のクエリによる更新行数を取得する
+     *
+     *  @access public
+     *  @return int     更新行数
+     */
+    function affectedRows()
+    {
+        return $this->db->affectedRows();
+    }
+    // }}}
+
+    // {{{ quoteIdentifier
+    /**
+     *  dbのtypeに応じて識別子をquoteする
+     *  (配列の場合は各要素をquote)
+     *
+     *  @access protected
+     *  @param  mixed   $identifier array or string
+     */
+    function quoteIdentifier($identifier)
+    {
+        if (is_array($identifier)) {
+            foreach (array_keys($identifier) as $key) {
+                $identifier[$key] = $this->quoteIdentifier($identifier[$key]);
+            }
+            return $identifier;
+        }
+            
+        switch ($this->type) {
+        case 'mysql':
+            $ret = '`' . $identifier . '`';
+            break;
+        case 'pgsql':
+        case 'sqlite':
+        default:
+            $ret = '"' . $identifier . '"';
+            break;
+        }
+        return $ret;
+    }
+    // }}}
+    // }}}
+
+    // {{{ Ethna_DB_PEAR独自の実装
+    // {{{ sqlquery
+    /**
+     *  SQL文指定クエリを発行する
+     *
+     *  @access public
+     *  @param  string  $sqlid      SQL-ID(+引数)
+     *  @return mixed   DB_Result:結果オブジェクト Ethna_Error:エラー
+     */
+    function &sqlquery($sqlid)
+    {
+        $args = func_get_args();
+        array_shift($args);
+        $query = $this->sql->get($sqlid, $args);
+
+        return $this->_query($query);
+    }
+    // }}}
+
+    // {{{ sql
+    /**
+     *  SQL文を取得する
+     *  
+     *  @access public
+     *  @param  string  $sqlid      SQL-ID
+     *  @return string  SQL文
+     */
+    function sql($sqlid)
+    {
+        $args = func_get_args();
+        array_shift($args);
+        $query = $this->sql->get($sqlid, $args);
+
+        return $query;
+    }
+    // }}}
+
+    // {{{ lock
+    /**
+     *  テーブルをロックする
+     *
+     *  @access public
+     *  @param  mixed   ロック対象テーブル名
+     *  @return mixed   DB_Result:結果オブジェクト Ethna_Error:エラー
+     */
+    function lock($tables)
+    {
+        $this->message = null;
+
+        $sql = "";
+        foreach (to_array($tables) as $table) {
+            if ($sql != "") {
+                $sql .= ", ";
+            }
+            $sql .= "$table WRITE";
+        }
+
+        return $this->query("LOCK TABLES $sql");
+    }
+    // }}}
+
+    // {{{ unlock
+    /**
+     *  テーブルのロックを解放する
+     *
+     *  @access public
+     *  @return mixed   DB_Result:結果オブジェクト Ethna_Error:エラー
+     */
+    function unlock()
+    {
+        $this->message = null;
+        return $this->query("UNLOCK TABLES");
+    }
+    // }}}
+
+    // {{{ _query
+    /**
+     *  クエリを発行する
+     *
+     *  @access private
+     *  @param  string  $query  SQL文
+     *  @return mixed   DB_Result:結果オブジェクト Ethna_Error:エラー
+     */
+    function &_query($query)
+    {
+        $this->logger->log(LOG_DEBUG, "$query");
+        $r =& $this->db->query($query);
+        if (DB::isError($r)) {
+            if ($r->getCode() == DB_ERROR_ALREADY_EXISTS) {
+                $error = Ethna::raiseNotice('Unique Constraint Error SQL[%s]',
+                    E_DB_DUPENT,
+                    $query,
+                    $this->db->errorNative(),
+                    $r->getUserInfo());
+            } else {
+                $error = Ethna::raiseError('Query Error SQL[%s] CODE[%d] MESSAGE[%s]',
+                    E_DB_QUERY,
+                    $query,
+                    $this->db->errorNative(),
+                    $r->getUserInfo());
+            }
+            return $error;
+        }
+        return $r;
+    }
+    // }}}
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_ActionClass.php b/Idea_Plugin_Extlib/class/Ethna_ActionClass.php
new file mode 100644 (file)
index 0000000..a76411c
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionClass.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_ActionClass
+/**
+ *  action実行クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_ActionClass
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Backend       backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Config        設定オブジェクト    */
+    var $config;
+
+    /** @var    object  Ethna_I18N          i18nオブジェクト */
+    var $i18n;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト */
+    var $action_error;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト(省略形) */
+    var $ae;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト */
+    var $action_form;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト(省略形) */
+    var $af;
+
+    /** @var    object  Ethna_Session       セッションオブジェクト */
+    var $session;
+
+    /** @var    object  Ethna_Plugin        プラグインオブジェクト */
+    var $plugin;
+
+    /** @var    object  Ethna_Logger    ログオブジェクト */
+    var $logger;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_ActionClassのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Backend   $backend    backendオブジェクト
+     */
+    function Ethna_ActionClass(&$backend)
+    {
+        $c =& $backend->getController();
+        $this->backend =& $backend;
+        $this->config =& $this->backend->getConfig();
+        $this->i18n =& $this->backend->getI18N();
+
+        $this->action_error =& $this->backend->getActionError();
+        $this->ae =& $this->action_error;
+
+        $this->action_form =& $this->backend->getActionForm();
+        $this->af =& $this->action_form;
+
+        $this->session =& $this->backend->getSession();
+        $this->plugin =& $this->backend->getPlugin();
+        $this->logger =& $this->backend->getLogger();
+    }
+
+    /**
+     *  アクション実行前の認証処理を行う
+     *
+     *  @access public
+     *  @return string  遷移名(nullなら正常終了, falseなら処理終了)
+     */
+    function authenticate()
+    {
+        return null;
+    }
+
+    /**
+     *  アクション実行前の処理(フォーム値チェック等)を行う
+     *
+     *  @access public
+     *  @return string  遷移名(nullなら正常終了, falseなら処理終了)
+     */
+    function prepare()
+    {
+        return null;
+    }
+
+    /**
+     *  アクション実行
+     *
+     *  @access public
+     *  @return string  遷移名(nullなら遷移は行わない)
+     */
+    function perform()
+    {
+        return null;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_ActionError.php b/Idea_Plugin_Extlib/class/Ethna_ActionError.php
new file mode 100644 (file)
index 0000000..56560f5
--- /dev/null
@@ -0,0 +1,225 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionError.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_ActionError
+/**
+ *  アプリケーションエラー管理クラス
+ *
+ *  @access     public
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @package    Ethna
+ *  @todo   配列フォームを扱えるようにする
+ */
+class Ethna_ActionError
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    array   エラーオブジェクトの一覧 */
+    var $error_list = array();
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト */
+    var $action_form = null;
+
+    /** @var    object  Ethna_Logger        ログオブジェクト */
+    var $logger = null;
+    /**#@-*/
+
+    /**
+     *  Ethna_ActionErrorクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_ActionError()
+    {
+    }
+
+    /**
+     *  エラーオブジェクトを生成/追加する
+     *
+     *  @access public
+     *  @param  string  $name       エラーの発生したフォーム項目名(不要ならnull)
+     *  @param  string  $message    エラーメッセージ
+     *  @param  int     $code       エラーコード
+     *  @return Ethna_Error エラーオブジェクト
+     */
+    function &add($name, $message, $code = null)
+    {
+        if (func_num_args() > 3) {
+            $userinfo = array_slice(func_get_args(), 3);
+            $error =& Ethna::raiseNotice($message, $code, $userinfo);
+        } else {
+            $error =& Ethna::raiseNotice($message, $code);
+        }
+        $this->addObject($name, $error);
+        return $error;
+    }
+
+    /**
+     *  Ethna_Errorオブジェクトを追加する
+     *
+     *  @access public
+     *  @param  string              $name   エラーに対応するフォーム項目名(不要ならnull)
+     *  @param  object  Ethna_Error $error  エラーオブジェクト
+     */
+    function addObject($name, &$error)
+    {
+        $elt = array();
+        $elt['name'] = $name;
+        $elt['object'] =& $error;
+        $this->error_list[] = $elt;
+
+        // ログ出力(補足)
+        $af =& $this->_getActionForm();
+        $logger =& $this->_getLogger();
+        $logger->log(LOG_NOTICE, '{form} -> [%s]', $this->action_form->getName($name));
+    }
+
+    /**
+     *  エラーオブジェクトの数を返す
+     *
+     *  @access public
+     *  @return int     エラーオブジェクトの数
+     */
+    function count()
+    {
+        return count($this->error_list);
+    }
+
+    /**
+     *  エラーオブジェクトの数を返す(count()メソッドのエイリアス)
+     *
+     *  @access public
+     *  @return int     エラーオブジェクトの数
+     */
+    function length()
+    {
+        return count($this->error_list);
+    }
+
+    /**
+     *  登録されたエラーオブジェクトを全て削除する
+     *
+     *  @access public
+     */
+    function clear()
+    {
+        $this->error_list = array();
+    }
+
+    /**
+     *  指定されたフォーム項目にエラーが発生しているかどうかを返す
+     *
+     *  @access public
+     *  @param  string  $name   フォーム項目名
+     *  @return bool    true:エラーが発生している false:エラーが発生していない
+     */
+    function isError($name)
+    {
+        foreach ($this->error_list as $error) {
+            if (strcasecmp($error['name'], $name) == 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     *  指定されたフォーム項目に対応するエラーメッセージを返す
+     *
+     *  @access public
+     *  @param  string  $name   フォーム項目名
+     *  @return string  エラーメッセージ(エラーが無い場合はnull)
+     */
+    function getMessage($name)
+    {
+        foreach ($this->error_list as $error) {
+            if (strcasecmp($error['name'], $name) == 0) {
+                return $this->_getMessage($error);
+            }
+        }
+        return null;
+    }
+
+    /**
+     *  エラーオブジェクトを配列にして返す
+     *
+     *  @access public
+     *  @return array   エラーオブジェクトの配列
+     */
+    function getErrorList()
+    {
+        return $this->error_list;
+    }
+
+    /**
+     *  エラーメッセージを配列にして返す
+     *
+     *  @access public
+     *  @return array   エラーメッセージの配列
+     */
+    function getMessageList()
+    {
+        $message_list = array();
+
+        foreach ($this->error_list as $error) {
+            $message_list[] = $this->_getMessage($error);
+        }
+        return $message_list;
+    }
+
+    /**
+     *  アプリケーションエラーメッセージを取得する
+     *
+     *  @access private
+     *  @param  array   エラーエントリ
+     *  @return string  エラーメッセージ
+     */
+    function _getMessage(&$error)
+    {
+        $af =& $this->_getActionForm();
+        $form_name = $af->getName($error['name']);
+        return str_replace("{form}", $form_name, $error['object']->getMessage());
+    }
+
+    /**
+     *  Ethna_ActionFormオブジェクトを取得する
+     *
+     *  @access private
+     *  @return object  Ethna_ActionForm
+     */
+    function &_getActionForm()
+    {
+        if (isset($this->action_form) == false) {
+            $controller =& Ethna_Controller::getInstance();
+            $this->action_form =& $controller->getActionForm();
+        }
+        return $this->action_form;
+    }
+
+    /**
+     *  Ethna_Loggerオブジェクトを取得する
+     *
+     *  @access private
+     *  @return object  Ethna_Logger
+     */
+    function &_getLogger()
+    {
+        if (is_null($this->logger)) {
+            $controller =& Ethna_Controller::getInstance();
+            $this->logger =& $controller->getLogger();
+        }
+        return $this->logger;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_ActionForm.php b/Idea_Plugin_Extlib/class/Ethna_ActionForm.php
new file mode 100644 (file)
index 0000000..e412e4e
--- /dev/null
@@ -0,0 +1,1419 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/** 定型フィルタ: 半角入力 */
+define('FILTER_HW', 'numeric_zentohan,alphabet_zentohan,ltrim,rtrim,ntrim');
+
+/** 定型フィルタ: 全角入力 */
+define('FILTER_FW', 'kana_hantozen,ntrim');
+
+
+// {{{ Ethna_ActionForm
+/**
+ *  アクションフォームクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_ActionForm
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    array   フォーム値定義(デフォルト) */
+    var $form_template = array();
+
+    /** @var    array   フォーム値定義 */
+    var $form = array();
+
+    /** @var    array   フォーム値 */
+    var $form_vars = array();
+
+    /** @var    array   アプリケーション設定値 */
+    var $app_vars = array();
+
+    /** @var    array   アプリケーション設定値(自動エスケープなし) */
+    var $app_ne_vars = array();
+
+    /** @var    object  Ethna_Backend       バックエンドオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト */
+    var $action_error;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト(省略形) */
+    var $ae;
+
+    /** @var    object  Ethna_I18N  i18nオブジェクト */
+    var $i18n;
+
+    /** @var    object  Ethna_Logger    ログオブジェクト */
+    var $logger;
+
+    /** @var    object  Ethna_Plugin    プラグインオブジェクト */
+    var $plugin;
+
+    /** @var    array   フォーム定義要素 */
+    var $def = array('name', 'required', 'max', 'min', 'regexp', 'mbregexp',
+                     'custom', 'filter', 'form_type', 'type');
+
+    /** @var    array   フォーム定義のうち非プラグイン要素とみなすprefix */
+    var $def_noplugin = array('type', 'form', 'name', 'plugin', 'filter',
+                              'option', 'default');
+
+    /** @var    bool    追加検証強制フラグ */
+    var $force_validate_plus = false;
+
+    /** @var    array   アプリケーションオブジェクト(helper) */
+    var $helper_app_object = array();
+
+    /** @var    array   アプリケーションオブジェクト(helper)で利用しないフォーム名 */
+    var $helper_skip_form = array();
+
+    /** @var    int   フォーム配列で使用可能な深さの上限 */
+    var $max_form_deps = 10;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_ActionFormクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    controllerオブジェクト
+     */
+    function Ethna_ActionForm(&$controller)
+    {
+        $this->backend =& $controller->getBackend();
+        $this->action_error =& $controller->getActionError();
+        $this->ae =& $this->action_error;
+        $this->i18n =& $controller->getI18N();
+        $this->logger =& $controller->getLogger();
+        $this->plugin =& $controller->getPlugin();
+
+        if (isset($_SERVER['REQUEST_METHOD']) == false) {
+            return;
+        }
+
+        // フォーム値テンプレートの更新
+        $this->form_template = $this->_setFormTemplate($this->form_template);
+
+        // アプリケーションオブジェクト(helper)の生成
+        foreach ($this->helper_app_object as $key => $value) {
+            if (is_object($value)) {
+                continue;
+            }
+            $this->helper_app_object[$key] =& $this->_getHelperAppObject($key);
+        }
+
+        // フォーム値定義の設定
+        $this->_setFormDef_HelperObj();
+        $this->_setFormDef();
+
+        // 省略値補正
+        foreach ($this->form as $name => $value) {
+            foreach ($this->def as $k) {
+                if (isset($value[$k]) == false) {
+                    $this->form[$name][$k] = null;
+                }
+            }
+        }
+    }
+
+    /**
+     *  フォーム値のアクセサ(R)
+     *
+     *  @access public
+     *  @param  string  $name   フォーム値の名称
+     *  @return mixed   フォーム値
+     */
+    function get($name)
+    {
+        return $this->_getVarsByFormName($this->form_vars, $name);
+    }
+
+    /**
+     *  フォーム値定義を取得する
+     *
+     *  @access public
+     *  @param  string  $name   取得するフォーム名(nullなら全ての定義を取得)
+     *  @return array   フォーム値定義
+     */
+    function getDef($name = null)
+    {
+        if (is_null($name)) {
+            return $this->form;
+        }
+
+        if (array_key_exists($name, $this->form) == false) {
+            return null;
+        } else {
+            return $this->form[$name];
+        }
+    }
+
+    /**
+     *  フォーム項目表示名を取得する
+     *
+     *  @access public
+     *  @param  string  $name   フォーム値の名称
+     *  @return mixed   フォーム値の表示名
+     */
+    function getName($name)
+    {
+        if (isset($this->form[$name]) == false) {
+            return null;
+        }
+        if (isset($this->form[$name]['name'])
+            && $this->form[$name]['name'] != null) {
+            return $this->form[$name]['name'];
+        }
+
+        // try message catalog
+        return $this->i18n->get($name);
+    }
+    
+    /**
+     *  フォーム名に対応するキーの配列を返す
+     *
+     *  @access private
+     *  @param  string  $name   フォーム名
+     *  @return array   キーの配列
+     */
+    function _getFormNameArray($name)
+    {
+        // 多次元配列を指定した場合
+        if (preg_match('/^.*\[[^\]]+\]$/', $name)) { 
+            $buff = preg_replace('/\]\[/', '[', $name); // hoge[foo][bar] => hoge[foo[bar]
+            $buff = preg_replace('/\]/', "", $buff);    // hoge][foo[bar] => hoge[foo[bar
+            $ret = explode('[', $buff);                 // hoge[foo[bar   => array('hoge', 'foo', 'var')
+        } else {
+            // 多次元配列を指定していない場合
+            $ret = array($name);
+        }
+        return $ret;
+    }
+
+    /**
+     *  配列の中からキーで指定された要素を取り出す
+     *
+     *  @access private
+     *  @param  array   &$target    対象とする配列
+     *  @param  string  $nane       キー
+     *  @return string  指定された要素
+     */
+    function _getVarsByFormName(&$target, $name)
+    {
+        $keys = $this->_getFormNameArray($name);
+        return $this->_getVarsByKeys($target, $keys);
+    }
+
+    /**
+     *  配列の中にキーで指定された要素を登録する
+     *  フォーム定義にキーがあるかどうかは無関係に登録します
+     *
+     *  @access private
+     *  @param  array   &$target    対象とする配列
+     *  @param  string  $nane       キー
+     *  @param  mixde   $value      登録する値
+     */
+    function _setVarsByFormName(&$target, $name, $vars)
+    {
+        $keys = $this->_getFormNameArray($name);
+        $this->_setVarsByKeys($target, $keys, $vars);
+    }
+    
+    /**
+     *  配列の中からキーで指定された要素を取り出す
+     *
+     *  @access private
+     *  @param  array   &$target    対象とする配列
+     *  @param  array   $keys       キーの配列
+     *  @return string  指定された要素
+     */
+    function _getVarsByKeys(&$target, $keys)
+    {
+        $count = count($keys);
+        if ($count == 0) { // 探索完了
+            return $target;
+        } elseif ($this->max_form_deps + 1 <= $count) { // 深すぎる配列を制限する
+            return null;
+        }
+
+        // まだ探索するキーが残っている
+        $curval = array_shift($keys);
+        if (is_array($target) && array_key_exists($curval, $target)) {
+            return $this->_getVarsByKeys($target[$curval], $keys);
+        }
+        return null;
+    }
+
+    function _setVarsByKeys(&$target, $keys, &$var)
+    {
+        $count = count($keys);
+        if ($count == 0) { // 探索完了
+            $target = $var;
+            return;
+        } elseif ($this->max_form_deps + 1 <= $count) { // 深すぎる配列を制限する
+            return;
+        }
+
+        // まだ探索するキーが残っている
+        $curval = array_shift($keys);
+        if (is_array($target)) {
+            if (! array_key_exists($curval, $target)) {
+                $target[$curval] = null;
+            }
+        } else {
+            $target = array($curval => null);
+        }
+
+        $this->_setVarsByKeys($target[$curval], $keys, $var);
+    }
+
+    /**
+     *  $_FILESの中からキーで指定された要素を取り出す
+     *
+     *  @access private
+     *  @param  array   &$target    対象とする配列
+     *  @param  string  $nane       キー
+     *  @param  string  $key        $_FILESに含まれる項目(tmp_name等)
+     *  @return string  指定された要素
+     */
+    function _getFilesInfoByFormName(&$target, $name, $key)
+    {
+        $form_keys = $this->_getFormNameArray($name);
+        array_splice($form_keys, 1, 0, $key);
+        return $this->_getVarsByKeys($target, $form_keys);
+    }
+
+    /**
+     *  ユーザから送信されたフォーム値をフォーム値定義に従ってインポートする
+     *
+     *  @access public
+     */
+    function setFormVars()
+    {
+        if (isset($_SERVER['REQUEST_METHOD']) == false) {
+            return;
+        } else if (strcasecmp($_SERVER['REQUEST_METHOD'], 'post') == 0) {
+            $http_vars =& $_POST;
+        } else {
+            $http_vars =& $_GET;
+        }
+
+        //
+        //  ethna_fid というフォーム値は、フォーム定義に関わらず受け入れる
+        //  これは、submitされたフォームを識別するために使われる
+        //  null の場合は、以下の場合である
+        //
+        //  1. フォームヘルパが使われていない
+        //  2. 画面の初期表示などで、submitされなかった
+        //  3. {form name=...} が未設定である
+        //
+        $this->form_vars['ethna_fid'] = (isset($http_vars['ethna_fid']) == false
+                                      || is_null($http_vars['ethna_fid']))
+                                      ? null
+                                      : $http_vars['ethna_fid'];
+
+        foreach ($this->form as $name => $def) {
+            $type = is_array($def['type']) ? $def['type'][0] : $def['type'];
+            if ($type == VAR_TYPE_FILE) {
+                // ファイルの場合
+
+                // 値の有無の検査
+                if (is_null($this->_getFilesInfoByFormName($_FILES, $name, 'tmp_name'))) {
+                    $this->set($name, null);
+                    continue;
+                }
+
+                // 配列構造の検査
+                if (is_array($def['type'])) {
+                    if (is_array($this->_getFilesInfoByFormName($_FILES, $name, 'tmp_name')) == false) {
+                        $this->handleError($name, E_FORM_WRONGTYPE_ARRAY);
+                        $this->set($name, null);
+                        continue;
+                    }
+                } else {
+                    if (is_array($this->_getFilesInfoByFormName($_FILES, $name, 'tmp_name'))) {
+                        $this->handleError($name, E_FORM_WRONGTYPE_SCALAR);
+                        $this->set($name, null);
+                        continue;
+                    }
+                }
+
+                $files = null;
+                if (is_array($def['type'])) {
+                    $files = array();
+                    // ファイルデータを再構成
+                    foreach (array_keys($this->_getFilesInfoByFormName($_FILES, $name, 'name')) as $key) {
+                        $files[$key] = array();
+                        $files[$key]['name'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'name');
+                        $files[$key]['type'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'type');
+                        $files[$key]['size'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'size');
+                        $files[$key]['tmp_name'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'tmp_name');
+                        if ($this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'error') == null) {
+                            // PHP 4.2.0 以前
+                            $files[$key]['error'] = 0;
+                        } else {
+                            $files[$key]['error'] = $this->_getFilesInfoByFormName($_FILES, $name."[".$key."]", 'error');
+                        }
+                    }
+                } else {
+                    $files['name'] = $this->_getFilesInfoByFormName($_FILES, $name, 'name');
+                    $files['type'] = $this->_getFilesInfoByFormName($_FILES, $name, 'type');
+                    $files['size'] = $this->_getFilesInfoByFormName($_FILES, $name, 'size');
+                    $files['tmp_name'] = $this->_getFilesInfoByFormName($_FILES, $name, 'tmp_name');
+                    if ($this->_getFilesInfoByFormName($_FILES, $name, 'error') == null) {
+                        // PHP 4.2.0 以前
+                        $files['error'] = 0;
+                    } else {
+                        $files['error'] = $this->_getFilesInfoByFormName($_FILES, $name, 'error');
+                    }
+                }
+
+                // 値のインポート
+                $this->set($name, $files);
+
+            } else {
+                // ファイル以外の場合
+
+                $target_var = $this->_getVarsByFormName($http_vars, $name);
+
+                // 値の有無の検査
+                if (isset($target_var) == false
+                    || is_null($target_var)) {
+                    $this->set($name, null);
+                    if (isset($http_vars["{$name}_x"])
+                     && isset($http_vars["{$name}_y"])) {
+                        // 以前の仕様に合わせる
+                        $this->set($name, $http_vars["{$name}_x"]);
+                    }
+                    continue;
+                }
+
+                // 配列構造の検査
+                if (is_array($def['type'])) {
+                    if (is_array($target_var) == false) {
+                        // 厳密には、この配列の各要素はスカラーであるべき
+                        $this->handleError($name, E_FORM_WRONGTYPE_ARRAY);
+                        $this->set($name, null);
+                        continue;
+                    }
+                } else {
+                    if (is_array($target_var)) {
+                        $this->handleError($name, E_FORM_WRONGTYPE_SCALAR);
+                        $this->set($name, null);
+                        continue;
+                    }
+                }
+
+                // 値のインポート
+                $this->set($name, $target_var);
+            }
+        }
+    }
+
+    /**
+     *  ユーザから送信されたフォーム値をクリアする
+     *
+     *  @access public
+     */
+    function clearFormVars()
+    {
+        $this->form_vars = array();
+    }
+
+    /**
+     *  フォーム値へのアクセサ(W)
+     *
+     *  @access public
+     *  @param  string  $name   フォーム値の名称
+     *  @param  string  $value  設定する値
+     */
+    function set($name, $value)
+    {
+        $this->_setVarsByFormName($this->form_vars, $name, $value);
+    }
+
+    /**
+     *  フォーム値定義を設定する
+     *
+     *  @access public
+     *  @param  string  $name   設定するフォーム名(nullなら全ての定義を設定)
+     *  @param  array   $value  設定するフォーム値定義
+     *  @return array   フォーム値定義
+     */
+    function setDef($name, $value)
+    {
+        if (is_null($name)) {
+            $this->form = $value;
+        }
+
+        $this->form[$name] = $value;
+    }
+
+    /**
+     *  フォーム値を配列にして返す
+     *
+     *  @access public
+     *  @param  bool    $escape HTMLエスケープフラグ(true:エスケープする)
+     *  @return array   フォーム値を格納した配列
+     */
+    function &getArray($escape = true)
+    {
+        $retval = array();
+
+        $this->_getArray($this->form_vars, $retval, $escape);
+
+        return $retval;
+    }
+
+    /**
+     *  アプリケーション設定値のアクセサ(R)
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @return mixed   アプリケーション設定値
+     */
+    function getApp($name)
+    {
+        if (isset($this->app_vars[$name]) == false) {
+            return null;
+        }
+        return $this->app_vars[$name];
+    }
+
+    /**
+     *  アプリケーション設定値のアクセサ(W)
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @param  mixed   $value  値
+     */
+    function setApp($name, $value)
+    {
+        $this->app_vars[$name] = $value;
+    }
+
+    /**
+     *  アプリケーション設定値を配列にして返す
+     *
+     *  @access public
+     *  @param  boolean $escape HTMLエスケープフラグ(true:エスケープする)
+     *  @return array   フォーム値を格納した配列
+     */
+    function &getAppArray($escape = true)
+    {
+        $retval = array();
+
+        $this->_getArray($this->app_vars, $retval, $escape);
+
+        return $retval;
+    }
+
+    /**
+     *  アプリケーション設定値(自動エスケープなし)のアクセサ(R)
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @return mixed   アプリケーション設定値
+     */
+    function getAppNE($name)
+    {
+        if (isset($this->app_ne_vars[$name]) == false) {
+            return null;
+        }
+        return $this->app_ne_vars[$name];
+    }
+
+    /**
+     *  アプリケーション設定値(自動エスケープなし)のアクセサ(W)
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @param  mixed   $value  値
+     */
+    function setAppNE($name, $value)
+    {
+        $this->app_ne_vars[$name] = $value;
+    }
+
+    /**
+     *  アプリケーション設定値(自動エスケープなし)を配列にして返す
+     *
+     *  @access public
+     *  @param  boolean $escape HTMLエスケープフラグ(true:エスケープする)
+     *  @return array   フォーム値を格納した配列
+     */
+    function &getAppNEArray($escape = false)
+    {
+        $retval = array();
+
+        $this->_getArray($this->app_ne_vars, $retval, $escape);
+
+        return $retval;
+    }
+
+    /**
+     *  フォームを配列にして返す(内部処理)
+     *
+     *  @access private
+     *  @param  array   &$vars      フォーム(等)の配列
+     *  @param  array   &$retval    配列への変換結果
+     *  @param  bool    $escape     HTMLエスケープフラグ(true:エスケープする)
+     */
+    function _getArray(&$vars, &$retval, $escape)
+    {
+        foreach (array_keys($vars) as $name) {
+            if (is_array($vars[$name])) {
+                $retval[$name] = array();
+                $this->_getArray($vars[$name], $retval[$name], $escape);
+            } else {
+                $retval[$name] = $escape
+                    ? htmlspecialchars($vars[$name], ENT_QUOTES) : $vars[$name];
+            }
+        }
+    }
+
+    /**
+     *  追加検証強制フラグを取得する
+     *  (通常検証でエラーが発生した場合でも_validatePlus()が呼び出される)
+     *  @access public
+     *  @return bool    true:追加検証強制 false:追加検証非強制
+     */
+    function isForceValidatePlus()
+    {
+        return $this->force_validate_plus;
+    }
+
+    /**
+     *  追加検証強制フラグを設定する
+     *
+     *  @access public
+     *  @param  $force_validate_plus    追加検証強制フラグ
+     */
+    function setForceValidatePlus($force_validate_plus)
+    {
+        $this->force_validate_plus = $force_validate_plus;
+    }
+
+    /**
+     *  フォーム値検証メソッド
+     *
+     *  @access public
+     *  @return int     発生したエラーの数
+     */
+    function validate()
+    {
+        foreach ($this->form as $name => $def) {
+            $this->_validateWithPlugin($name);
+        }
+
+        if ($this->ae->count() == 0 || $this->isForceValidatePlus()) {
+            // 追加検証メソッド
+            $this->_validatePlus();
+        }
+
+        return $this->ae->count();
+    }
+
+    /**
+     *  プラグインを使ったフォーム値検証メソッド
+     *
+     *  @access private
+     *  @param  string  $form_name  フォームの名前
+     *  @todo   ae 側に $key を与えられるようにする
+     */
+    function _validateWithPlugin($form_name)
+    {
+        // (pre) filter
+        if ($this->form[$form_name]['type'] != VAR_TYPE_FILE) {
+            
+            //    入力値とフィルタ定義を取り出す
+            $form_var = $this->get($form_name);
+            $filter = (isset($this->form[$form_name]['filter']))
+                    ? $this->form[$form_name]['filter']
+                    : null;
+
+            //    フィルタを適用
+            if (is_array($this->form[$form_name]['type']) == false) {
+                $this->set($form_name, $this->_filter($form_var, $filter));
+            } else if ($form_var != null) {  //  配列の場合
+                foreach (array_keys($form_var) as $key) {
+                    $this->set($form_name."[".$key."]", $this->_filter($form_var[$key], $filter));
+                }
+            } else {  //  配列で値が空の場合
+                $this->set($form_name, $this->_filter($form_var, $filter));
+            }
+        }
+
+        $form_vars = $this->get($form_name);
+        $plugin = $this->_getPluginDef($form_name);
+
+        // type のチェックを処理の最初に追加
+        $plugin = array_merge(array('type' => array()), $plugin);
+        if (is_array($this->form[$form_name]['type'])) {
+            $plugin['type']['type'] = $this->form[$form_name]['type'][0];
+        } else {
+            $plugin['type']['type'] = $this->form[$form_name]['type'];
+        }
+        if (isset($this->form[$form_name]['type_error'])) {
+            $plugin['type']['error'] = $this->form[$form_name]['type_error'];
+        }
+
+        // スカラーの場合
+        if (is_array($this->form[$form_name]['type']) == false) {
+            foreach (array_keys($plugin) as $name) {
+                // break: 明示されていなければ,エラーが起きたらvalidateを継続しない
+                $break = isset($plugin[$name]['break']) == false
+                               || $plugin[$name]['break'];
+
+                // プラグイン取得
+                unset($v);
+                $v =& $this->plugin->getPlugin('Validator',
+                                               ucfirst(strtolower($name)));
+                if (Ethna::isError($v)) {
+                    continue;
+                }
+
+                // バリデーション実行
+                unset($r);
+                $r =& $v->validate($form_name, $form_vars, $plugin[$name]);
+
+                // エラー処理
+                if ($r !== true) {
+                    if (Ethna::isError($r)) {
+                        $this->ae->addObject($form_name, $r);
+                    }
+                    if ($break) {
+                        break;
+                    }
+                }
+            }
+            return;
+        }
+
+        // 配列の場合
+
+        // break 指示用の key list
+        $valid_keys = is_array($form_vars) ? array_keys($form_vars) : array();
+
+        foreach (array_keys($plugin) as $name) {
+            // break: 明示されていなければ,エラーが起きたらvalidateを継続しない
+            $break = isset($plugin[$name]['break']) == false
+                           || $plugin[$name]['break'];
+
+            // プラグイン取得
+            unset($v);
+            $v =& $this->plugin->getPlugin('Validator', ucfirst(strtolower($name)));
+            if (Ethna::isError($v)) {
+                continue;
+            }
+
+            // 配列全体を受け取るプラグインの場合
+            if (isset($v->accept_array) && $v->accept_array == true) {
+                // バリデーション実行
+                unset($r);
+                $r =& $v->validate($form_name, $form_vars, $plugin[$name]);
+
+                // エラー処理
+                if (Ethna::isError($r)) {
+                    $this->ae->addObject($form_name, $r);
+                    if ($break) {
+                        break;
+                    }
+                }
+                continue;
+            }
+
+            // 配列の各要素に対して実行
+            foreach ($valid_keys as $key) {
+                // バリデーション実行
+                unset($r);
+                $r =& $v->validate($form_name, $form_vars[$key], $plugin[$name]);
+
+                // エラー処理
+                if (Ethna::isError($r)) {
+                    $this->ae->addObject($form_name, $r);
+                    if ($break) {
+                        unset($valid_keys[$key]);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     *  チェックメソッド(基底クラス)
+     *
+     *  @access public
+     *  @param  string  $name   フォーム項目名
+     *  @return array   チェック対象のフォーム値(エラーが無い場合はnull)
+     */
+    function check($name)
+    {
+        if (is_null($this->get($name)) || $this->get($name) === "") {
+            return null;
+        }
+
+        // Ethna_Backendの設定
+        $c =& Ethna_Controller::getInstance();
+        $this->backend =& $c->getBackend();
+
+        return to_array($this->get($name));
+    }
+
+    /**
+     *  チェックメソッド: 機種依存文字
+     *
+     *  @access public
+     *  @param  string  $name   フォーム項目名
+     *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
+     */
+    function &checkVendorChar($name)
+    {
+        $null = null;
+        $string = $this->get($name);
+
+        for ($i = 0; $i < strlen($string); $i++) {
+            /* JIS13区のみチェック */
+            $c = ord($string{$i});
+            if ($c < 0x80) {
+                /* ASCII */
+            } else if ($c == 0x8e) {
+                /* 半角カナ */
+                $i++;
+            } else if ($c == 0x8f) {
+                /* JIS X 0212 */
+                $i += 2;
+            } else if ($c == 0xad || ($c >= 0xf9 && $c <= 0xfc)) {
+                /* IBM拡張文字 / NEC選定IBM拡張文字 */
+                return $this->ae->add($name,
+                    _et('{form} contains machine dependent code.'), E_FORM_INVALIDCHAR);
+            } else {
+                $i++;
+            }
+        }
+
+        return $null;
+    }
+
+    /**
+     *  チェックメソッド: bool値
+     *
+     *  @access public
+     *  @param  string  $name   フォーム項目名
+     *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
+     */
+    function &checkBoolean($name)
+    {
+        $null = null;
+        $form_vars = $this->check($name);
+
+        if ($form_vars == null) {
+            return $null;
+        }
+
+        foreach ($form_vars as $v) {
+            if ($v === "") {
+                continue;
+            }
+            if ($v != "0" && $v != "1") {
+                return $this->ae->add($name,
+                    _et('Please input {form} properly.'), E_FORM_INVALIDCHAR);
+            }
+        }
+
+        return $null;
+    }
+
+    /**
+     *  チェックメソッド: メールアドレス
+     *
+     *  @access public
+     *  @param  string  $name   フォーム項目名
+     *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
+     */
+    function &checkMailaddress($name)
+    {
+        $null = null;
+        $form_vars = $this->check($name);
+
+        if ($form_vars == null) {
+            return $null;
+        }
+
+        foreach ($form_vars as $v) {
+            if ($v === "") {
+                continue;
+            }
+            if (Ethna_Util::checkMailaddress($v) == false) {
+                return $this->ae->add($name,
+                    _et('Please input {form} properly.'), E_FORM_INVALIDCHAR);
+            }
+        }
+
+        return $null;
+    }
+
+    /**
+     *  チェックメソッド: URL
+     *
+     *  @access public
+     *  @param  string  $name   フォーム項目名
+     *  @return object  Ethna_Error エラーオブジェクト(エラーが無い場合はnull)
+     */
+    function &checkURL($name)
+    {
+        $null = null;
+        $form_vars = $this->check($name);
+
+        if ($form_vars == null) {
+            return $null;
+        }
+
+        foreach ($form_vars as $v) {
+            if ($v === "") {
+                continue;
+            }
+            if (preg_match('/^(http:\/\/|https:\/\/|ftp:\/\/)/', $v) == 0) {
+                return $this->ae->add($name,
+                    _et('Please input {form} properly.'), E_FORM_INVALIDCHAR);
+            }
+        }
+
+        return $null;
+    }
+
+    /**
+     *  フォーム値をhiddenタグとして返す
+     *
+     *  @access public
+     *  @param  array   $include_list   配列が指定された場合、その配列に含まれるフォーム項目のみが対象となる
+     *  @param  array   $exclude_list   配列が指定された場合、その配列に含まれないフォーム項目のみが対象となる
+     *  @return string  hiddenタグとして記述されたHTML
+     */
+    function getHiddenVars($include_list = null, $exclude_list = null)
+    {
+        $hidden_vars = "";
+        foreach ($this->form as $key => $value) {
+            if (is_array($include_list) == true
+                && in_array($key, $include_list) == false) {
+                continue;
+            }
+            if (is_array($exclude_list) == true
+                && in_array($key, $exclude_list) == true) {
+                continue;
+            }
+            
+            $type = is_array($value['type']) ? $value['type'][0] : $value['type'];
+            if ($type == VAR_TYPE_FILE) {
+                continue;
+            }
+
+            $form_value = $this->get($key);
+            if (is_array($value['type'])) {
+                $form_array = true;
+                //  フォーム定義が配列なのにスカラーが
+                //  渡ってきた場合は値を配列扱いとし、
+                //  フォーム定義を尊重する
+                if (is_array($form_value) == false) {
+                    $form_value = array($form_value);
+                }
+            } else {
+                //  フォーム定義がスカラーなのに配列が渡ってきた
+                //  場合は救いようがないのでNOTICE扱いとし、タグも出力しない
+                if (is_array($form_value)) {
+                    $this->handleError($key, E_FORM_WRONGTYPE_ARRAY);
+                    continue;
+                }
+                $form_value = array($form_value);
+                $form_array = false;
+            }
+
+            if (is_null($this->get($key))) {
+                // フォーム値が送られていない場合はそもそもhiddenタグを出力しない
+                continue;
+            }
+
+            foreach ($form_value as $k => $v) {
+                if ($form_array) {
+                    $form_name = "$key" . "[$k]";
+                } else {
+                    $form_name = $key;
+                }
+                $hidden_vars .=
+                    sprintf("<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
+                    $form_name, htmlspecialchars($v, ENT_QUOTES));
+            }
+        }
+        return $hidden_vars;
+    }
+
+    /**
+     *  フォーム値検証のエラー処理を行う
+     *
+     *  @access public
+     *  @param  string      $name   フォーム項目名
+     *  @param  int         $code   エラーコード
+     */
+    function handleError($name, $code)
+    {
+        $def = $this->getDef($name);
+
+        // ユーザ定義エラーメッセージ
+        $code_map = array(
+            E_FORM_REQUIRED     => 'required_error',
+            E_FORM_WRONGTYPE_SCALAR => 'type_error',
+            E_FORM_WRONGTYPE_ARRAY  => 'type_error',
+            E_FORM_WRONGTYPE_INT    => 'type_error',
+            E_FORM_WRONGTYPE_FLOAT  => 'type_error',
+            E_FORM_WRONGTYPE_DATETIME   => 'type_error',
+            E_FORM_WRONGTYPE_BOOLEAN    => 'type_error',
+            E_FORM_MIN_INT      => 'min_error',
+            E_FORM_MIN_FLOAT    => 'min_error',
+            E_FORM_MIN_DATETIME => 'min_error',
+            E_FORM_MIN_FILE     => 'min_error',
+            E_FORM_MIN_STRING   => 'min_error',
+            E_FORM_MAX_INT      => 'max_error',
+            E_FORM_MAX_FLOAT    => 'max_error',
+            E_FORM_MAX_DATETIME => 'max_error',
+            E_FORM_MAX_FILE     => 'max_error',
+            E_FORM_MAX_STRING   => 'max_error',
+            E_FORM_REGEXP       => 'regexp_error',
+        );
+        //   フォーム定義にエラーメッセージが定義されていれば
+        //   それを使う
+        if (array_key_exists($code_map[$code], $def)) {
+            $this->ae->add($name, $def[$code_map[$code]], $code);
+            return;
+        }
+
+        //   定義されていない場合は、内部のメッセージを使う
+        if ($code == E_FORM_REQUIRED) {
+            switch ($def['form_type']) {
+            case FORM_TYPE_TEXT:
+            case FORM_TYPE_PASSWORD:
+            case FORM_TYPE_TEXTAREA:
+            case FORM_TYPE_SUBMIT:
+                $message = _et('Please input {form}.');
+                break;
+            case FORM_TYPE_SELECT:
+            case FORM_TYPE_RADIO:
+            case FORM_TYPE_CHECKBOX:
+            case FORM_TYPE_FILE:
+                $message = _et('Please select {form}.');
+                break;
+            default:
+                $message = _et('Please input {form}.');
+                break;
+            }
+        } else if ($code == E_FORM_WRONGTYPE_SCALAR) {
+            $message = _et('Please input scalar value to {form}.');
+        } else if ($code == E_FORM_WRONGTYPE_ARRAY) {
+            $message = _et('Please input array value to {form}.');
+        } else if ($code == E_FORM_WRONGTYPE_INT) {
+            $message = _et('Please input integer value to {form}.');
+        } else if ($code == E_FORM_WRONGTYPE_FLOAT) {
+            $message = _et('Please input float value to {form}.');
+        } else if ($code == E_FORM_WRONGTYPE_DATETIME) {
+            $message = _et('Please input valid datetime to {form}.');
+        } else if ($code == E_FORM_WRONGTYPE_BOOLEAN) {
+            $message = _et('You can input 0 or 1 to {form}.');
+        } else if ($code == E_FORM_MIN_INT) {
+            $this->ae->add($name,
+                _et('Please input more than %d(int) to {form}.'),
+                $code, $def['min']);
+            return;
+        } else if ($code == E_FORM_MIN_FLOAT) {
+            $this->ae->add($name,
+                _et('Please input more than %f(float) to {form}.'),
+                $code, $def['min']);
+            return;
+        } else if ($code == E_FORM_MIN_DATETIME) {
+            $this->ae->add($name,
+                _et('Please input datetime value %s or later to {form}.'),
+                $code, $def['min']);
+            return;
+        } else if ($code == E_FORM_MIN_FILE) {
+            $this->ae->add($name,
+                _et('Please specify file whose size is more than %d KB.'), 
+                $code, $def['min']);
+            return;
+        } else if ($code == E_FORM_MIN_STRING) {
+            $this->ae->add($name,
+                _et('Please input more than %d full-size (%d half-size) characters to {form}.'),
+                $code, intval($def['min']/2), $def['min']);
+            return;
+        } else if ($code == E_FORM_MAX_INT) {
+            $this->ae->add($name,
+                _et('Please input less than %d(int) to {form}.'),
+                $code, $def['max']);
+            return;
+        } else if ($code == E_FORM_MAX_FLOAT) {
+            $this->ae->add($name,
+                _et('Please input less than %f(float) to {form}.'),
+                $code, $def['max']);
+            return;
+        } else if ($code == E_FORM_MAX_DATETIME) {
+            $this->ae->add($name,
+                _et('Please input datetime value before %s to {form}.'),
+                $code, $def['max']);
+            return;
+        } else if ($code == E_FORM_MAX_FILE) {
+            $this->ae->add($name,
+                _et('Please specify file whose size is less than %d KB to {form}.'), 
+                $code, $def['max']);
+            return;
+        } else if ($code == E_FORM_MAX_STRING) {
+            $this->ae->add($name,
+                _et('Please input less than %d full-size (%d half-size) characters to {form}.'),
+                $code, intval($def['max']/2), $def['max']);
+            return;
+        } else if ($code == E_FORM_REGEXP) {
+            $message = _et('Please input {form} properly.');
+        }
+
+        $this->ae->add($name, $message, $code);
+    }
+
+    /**
+     *  ユーザ定義検証メソッド(フォーム値間の連携チェック等)
+     *
+     *  @access protected
+     */
+    function _validatePlus()
+    {
+    }
+
+    /**
+     *  カスタムチェックメソッドを実行する
+     *
+     *  @access protected
+     *  @param  string  $method_list    カスタムメソッド名(カンマ区切り)
+     *  @param  string  $name           フォーム項目名
+     */
+    function _validateCustom($method_list, $name)
+    {
+        $method_list = preg_split('/\s*,\s*/', $method_list,
+                                  -1, PREG_SPLIT_NO_EMPTY);
+        if (is_array($method_list) == false) {
+            return;
+        }
+        foreach ($method_list as $method) {
+            $this->$method($name);
+        }
+    }
+
+    /**
+     *  フォーム値に変換フィルタを適用する
+     *
+     *  @access private
+     *  @param  mixed   $value  フォーム値
+     *  @param  int     $filter フィルタ定義
+     *  @return mixed   変換結果
+     */
+    function _filter($value, $filter)
+    {
+        if (is_null($filter)) {
+            return $value;
+        }
+
+        foreach (preg_split('/\s*,\s*/', $filter) as $f) {
+            $method = sprintf('_filter_%s', $f);
+            if (method_exists($this, $method) == false) {
+                $this->logger->log(LOG_WARNING,
+                    'filter method is not defined [%s]', $method);
+                continue;
+            }
+            $value = $this->$method($value);
+        }
+
+        return $value;
+    }
+
+    /**
+     *  フォーム値変換フィルタ: 全角英数字->半角英数字
+     *
+     *  @access protected
+     *  @param  mixed   $value  フォーム値
+     *  @return mixed   変換結果
+     */
+    function _filter_alnum_zentohan($value)
+    {
+        return mb_convert_kana($value, "a");
+    }
+
+    /**
+     *  フォーム値変換フィルタ: 全角数字->半角数字
+     *
+     *  @access protected
+     *  @param  mixed   $value  フォーム値
+     *  @return mixed   変換結果
+     */
+    function _filter_numeric_zentohan($value)
+    {
+        return mb_convert_kana($value, "n");
+    }
+
+    /**
+     *  フォーム値変換フィルタ: 全角英字->半角英字
+     *
+     *  @access protected
+     *  @param  mixed   $value  フォーム値
+     *  @return mixed   変換結果
+     */
+    function _filter_alphabet_zentohan($value)
+    {
+        return mb_convert_kana($value, "r");
+    }
+
+    /**
+     *  フォーム値変換フィルタ: 左空白削除
+     *
+     *  @access protected
+     *  @param  mixed   $value  フォーム値
+     *  @return mixed   変換結果
+     */
+    function _filter_ltrim($value)
+    {
+        return ltrim($value);
+    }
+
+    /**
+     *  フォーム値変換フィルタ: 右空白削除
+     *
+     *  @access protected
+     *  @param  mixed   $value  フォーム値
+     *  @return mixed   変換結果
+     */
+    function _filter_rtrim($value)
+    {
+        return rtrim($value);
+    }
+
+    /**
+     *  フォーム値変換フィルタ: NULL(0x00)削除
+     *
+     *  @access protected
+     *  @param  mixed   $value  フォーム値
+     *  @return mixed   変換結果
+     */
+    function _filter_ntrim($value)
+    {
+        return str_replace("\x00", "", $value);
+    }
+
+    /**
+     *  フォーム値変換フィルタ: 半角カナ->全角カナ
+     *
+     *  @access protected
+     *  @param  mixed   $value  フォーム値
+     *  @return mixed   変換結果
+     */
+    function _filter_kana_hantozen($value)
+    {
+        return mb_convert_kana($value, "K");
+    }
+
+    /**
+     *  フォーム値定義テンプレートを設定する
+     *
+     *  @access protected
+     *  @param  array   $form_template  フォーム値テンプレート
+     *  @return array   フォーム値テンプレート
+     */
+    function _setFormTemplate($form_template)
+    {
+        return $form_template;
+    }
+
+    /**
+     *  フォーム定義変更用、ユーザ定義ヘルパメソッド
+     *
+     *  Ethna_ActionForm#prepare() が実行される前に
+     *  ユーザが動的にフォーム定義を変更したい場合に
+     *  このメソッドをオーバーライドします。
+     *
+     *  $this->backend も初期化済みのため、DBやセッション
+     *  の値に基づいてフォーム定義を変更することができます。
+     *
+     *  @access public 
+     */
+    function setFormDef_PreHelper()
+    {
+        //  TODO: override this method. 
+    }
+
+    /**
+     *  フォーム定義変更用、ユーザ定義ヘルパメソッド
+     *
+     *  フォームヘルパを使うときに、フォーム定義を動的に
+     *  変更したい場合に、このメソッドをオーバーライドします。
+     *
+     *  以下の定義をテンプレートで行った場合に呼び出されます。
+     *  
+     *  {form ethna_action=...} (ethna_action がない場合は呼び出されません)
+     *  {form_input action=...} (action がない場合は呼び出されません)
+     *
+     *  @access public 
+     */
+    function setFormDef_ViewHelper()
+    {
+        //   TODO: デフォルト実装は Ethna_ActionClass#prepare 前に
+        //   呼び出されるものと同じ。異なる場合にオーバライドする
+        $this->setFormDef_PreHelper(); 
+    }
+
+    /**
+     *  ヘルパオブジェクト(アプリケーションオブジェクト)
+     *  経由でのフォーム値定義を設定する
+     *
+     *  @access protected
+     */
+    function _setFormDef_HelperObj()
+    {
+        foreach (array_keys($this->helper_app_object) as $key) {
+            $object =& $this->helper_app_object[$key];
+            $prop_def = $object->getDef();
+
+            foreach ($prop_def as $key => $value) {
+                // 1. override form_template
+                $form_key = isset($value['form_name']) ? $value['form_name'] : $key;
+
+                if (isset($this->form_template[$form_key]) == false) {
+                    $this->form_template[$form_key] = array();
+                }
+
+                $this->form_template[$form_key]['type'] = $value['type'];
+                if (isset($value['required'])) {
+                    $this->form_template[$form_key]['required'] = $value['required'];
+                }
+
+                if ($value['type'] == VAR_TYPE_STRING && isset($value['length'])) {
+                    $this->form_template[$form_key]['max'] = $value['length'];
+                }
+
+                // 2. then activate form
+                if (in_array($key, $this->helper_skip_form) == false) {
+                    if (isset($this->form[$key]) == false) {
+                        $this->form[$key] = array();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     *  フォーム値定義を設定する
+     *
+     *  @access protected
+     */
+    function _setFormDef()
+    {
+        foreach ($this->form as $key => $value) {
+            if (is_numeric($key)) {
+                $this->form[$value] = array();
+                unset($this->form[$key]);
+            }
+        }
+
+        foreach ($this->form as $key => $value) {
+            if (array_key_exists($key, $this->form_template)
+                && is_array($this->form_template)) {
+                foreach ($this->form_template[$key] as $def_key => $def_value) {
+                    if (array_key_exists($def_key, $value) == false) {
+                        $this->form[$key][$def_key] = $def_value;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     *  フォーム値定義からプラグインの定義リストを分離する
+     *
+     *  @access protected
+     *  @param  string  $form_name   プラグインの定義リストを取得するフォームの名前
+     */
+    function _getPluginDef($form_name)
+    {
+        //  $def = array(
+        //               'name'         => 'number',
+        //               'max'          => 10,
+        //               'max_error'    => 'too large!',
+        //               'min'          => 5,
+        //               'min_error'    => 'too small!',
+        //              );
+        //
+        // as plugin parameters:
+        //
+        //  $plugin_def = array(
+        //                      'max' => array('max' => 10, 'error' => 'too large!'),
+        //                      'min' => array('min' => 5, 'error' => 'too small!'),
+        //                     );
+
+        $def = $this->getDef($form_name);
+        $plugin = array();
+        foreach (array_keys($def) as $key) {
+            // 未定義要素をスキップ
+            if ($def[$key] === null) {
+                continue;
+            }
+
+            // プラグイン名とパラメータ名に分割
+            $snippet = explode('_', $key, 2);
+            $name = $snippet[0];
+
+            // 非プラグイン要素をスキップ
+            if (in_array($name, $this->def_noplugin)) {
+                continue;
+            }
+
+            if (count($snippet) == 1) {
+                // プラグイン名だけだった場合
+                if (is_array($def[$key])) {
+                    // プラグインパラメータがあらかじめ配列で指定されている(とみなす)
+                    $tmp = $def[$key];
+                } else {
+                    $tmp = array($name => $def[$key]);
+                }
+            } else {
+                // plugin_param の場合
+                $tmp = array($snippet[1] => $def[$key]);
+            }
+
+            // merge
+            if (isset($plugin[$name]) == false) {
+                $plugin[$name] = array();
+            }
+            $plugin[$name] = array_merge($plugin[$name], $tmp);
+        }
+
+        return $plugin;
+    }
+
+    /**
+     *  アプリケーションオブジェクト(helper)を生成する
+     *
+     *  @access protected
+     */
+    function &_getHelperAppObject($key)
+    {
+        $app_object =& $this->backend->getObject($key);
+        return $app_object;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_AppManager.php b/Idea_Plugin_Extlib/class/Ethna_AppManager.php
new file mode 100644 (file)
index 0000000..eb11706
--- /dev/null
@@ -0,0 +1,281 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_AppManager.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/** アプリケーションオブジェクト状態: 使用可能 */
+define('OBJECT_STATE_ACTIVE', 0);
+/** アプリケーションオブジェクト状態: 使用不可 */
+define('OBJECT_STATE_INACTIVE', 100);
+
+
+/** アプリケーションオブジェクトソートフラグ: 昇順 */
+define('OBJECT_SORT_ASC', 0);
+/** アプリケーションオブジェクトソートフラグ: 降順 */
+define('OBJECT_SORT_DESC', 1);
+
+
+/** アプリケーションオブジェクトインポートオプション: NULLプロパティ無変換 */
+define('OBJECT_IMPORT_IGNORE_NULL', 1);
+
+/** アプリケーションオブジェクトインポートオプション: NULLプロパティ→空文字列変換 */
+define('OBJECT_IMPORT_CONVERT_NULL', 2);
+
+
+// {{{ Ethna_AppManager
+/**
+ *  アプリケーションマネージャのベースクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_AppManager
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Backend       backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Config        設定オブジェクト */
+    var $config;
+
+    /**  @var    object  Ethna_DB      DBオブジェクト */
+    var $db;
+
+    /** @var    object  Ethna_I18N          i18nオブジェクト */
+    var $i18n;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト */
+    var $action_form;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト(省略形) */
+    var $af;
+
+    /** @var    object  Ethna_Session       セッションオブジェクト */
+    var $session;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_AppManagerのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Backend   &$backend   backendオブジェクト
+     */
+    function Ethna_AppManager(&$backend)
+    {
+        // 基本オブジェクトの設定
+        $this->backend =& $backend;
+        $this->config =& $backend->getConfig();
+        $this->i18n =& $backend->getI18N();
+        $this->action_form =& $backend->getActionForm();
+        $this->af =& $this->action_form;
+        $this->session =& $backend->getSession();
+
+        $db_list = $backend->getDBList();
+        if (Ethna::isError($db_list) == false) {
+            foreach ($db_list as $elt) {
+                $varname = $elt['varname'];
+                $this->$varname =& $elt['db'];
+            }
+        }
+    }
+
+    /**
+     *  属性の一覧を返す
+     *
+     *  @access public
+     *  @param  string  $attr_name  属性の名前(変数名)
+     *  @return array   属性値一覧
+     */
+    function getAttrList($attr_name)
+    {
+        $varname = $attr_name . "_list";
+        return $this->$varname;
+    }
+
+    /**
+     *  属性の表示名を返す
+     *
+     *  @access public
+     *  @param  string  $attr_name  属性の名前(変数名)
+     *  @param  mixed   $id         属性ID
+     *  @return string  属性の表示名
+     */
+    function getAttrName($attr_name, $id)
+    {
+        $varname = $attr_name . "_list";
+        if (is_array($this->$varname) == false) {
+            return null;
+        }
+        $list =& $this->$varname;
+        if (isset($list[$id]) == false) {
+            return null;
+        }
+        return $list[$id]['name'];
+    }
+
+    /**
+     *  属性の表示名(詳細)を返す
+     *
+     *  @access public
+     *  @param  string  $attr_name  属性の名前(変数名)
+     *  @param  mixed   $id         属性ID
+     *  @return string  属性の詳細表示名
+     */
+    function getAttrLongName($attr_name, $id)
+    {
+        $varname = $attr_name . "_list";
+        if (is_array($this->$varname) == false) {
+            return null;
+        }
+        $list =& $this->$varname;
+        if (isset($list[$id]['long_name']) == false) {
+            return null;
+        }
+
+        return $list[$id]['long_name'];
+    }
+
+    /**
+     *  オブジェクトの一覧を返す
+     *
+     *  @access public
+     *  @param  string  $class  Ethna_AppObjectの継承クラス名
+     *  @param  array   $filter     検索条件
+     *  @param  array   $order      検索結果ソート条件
+     *  @param  int     $offset     検索結果取得オフセット
+     *  @param  int     $count      検索結果取得数
+     *  @return mixed   array(0 => 検索条件にマッチした件数, 1 => $offset, $countにより指定された件数のオブジェクトID一覧) Ethna_Error:エラー
+     *  @todo   パフォーマンス対策(1オブジェクトの占有メモリが多い場合)
+     */
+    function getObjectList($class, $filter = null,
+                           $order = null, $offset = null, $count = null)
+    {
+        global $_ETHNA_APP_MANAGER_OL_CACHE;
+
+        $object_list = array();
+        $class_name = sprintf("%s_%s", $this->backend->getAppId(), $class);
+
+        // キャッシュチェック
+        $cache_class = strtolower($class_name);
+        if (is_array($_ETHNA_APP_MANAGER_OL_CACHE) == false
+            || array_key_exists($cache_class, $_ETHNA_APP_MANAGER_OL_CACHE) == false) {
+            $_ETHNA_APP_MANAGER_OL_CACHE[$cache_class] = array();
+        }
+        $cache_key = serialize(array($filter, $order, $offset, $count));
+        if (array_key_exists($cache_key, $_ETHNA_APP_MANAGER_OL_CACHE[$cache_class])) {
+            list($length, $prop_list)
+                = $_ETHNA_APP_MANAGER_OL_CACHE[$cache_class][$cache_key];
+        } else {
+            // キャッシュ更新
+            $tmp =& new $class_name($this->backend);
+            list($length, $prop_list)
+                = $tmp->searchProp(null, $filter, $order, $offset, $count);
+            $_ETHNA_APP_MANAGER_OL_CACHE[$cache_class][$cache_key]
+                = array($length, $prop_list);
+        }
+
+        foreach ($prop_list as $prop) {
+            $object =& new $class_name($this->backend, null, null, $prop);
+            $object_list[] = $object;
+        }
+
+        return array($length, $object_list);
+    }
+
+    /**
+     *  オブジェクトプロパティの一覧を返す
+     *
+     *  getObjectList()メソッドは条件にマッチするIDを元にEthna_AppObjectを生成する
+     *  ためコストがかかる。こちらはプロパティのみをSELECTするので低コストでデータ
+     *  を取得することが可能。
+     *
+     *  @access public
+     *  @param  string  $class      Ethna_AppObjectの継承クラス名
+     *  @param  array   $keys       取得するプロパティ一覧(nullなら全て)
+     *  @param  array   $filter     検索条件
+     *  @param  array   $order      検索結果ソート条件
+     *  @param  int     $offset     検索結果取得オフセット
+     *  @param  int     $count      検索結果取得数
+     *  @return mixed   array(0 => 検索条件にマッチした件数,
+     *                        1 => $offset, $countにより指定された件数のプロパティ一覧)
+     *                  Ethna_Error:エラー
+     */
+    function getObjectPropList($class, $keys = null, $filter = null,
+                               $order = null, $offset = null, $count = null)
+    {
+        global $_ETHNA_APP_MANAGER_OPL_CACHE;
+
+        $prop_list = array();
+        $class_name = sprintf("%s_%s", $this->backend->getAppId(), $class);
+
+        // キャッシュチェック
+        $cache_class = strtolower($class_name);
+        if (is_array($_ETHNA_APP_MANAGER_OPL_CACHE) == false
+            || array_key_exists($cache_class, $_ETHNA_APP_MANAGER_OPL_CACHE) == false) {
+            $_ETHNA_APP_MANAGER_OPL_CACHE[$cache_class] = array();
+        }
+        $cache_key = serialize(array($filter, $order, $offset, $count));
+        if (array_key_exists($cache_key, $_ETHNA_APP_MANAGER_OPL_CACHE[$cache_class])) {
+            // skip
+        } else {
+            // キャッシュ更新
+            $tmp =& new $class_name($this->backend);
+            $_ETHNA_APP_MANAGER_OPL_CACHE[$cache_class][$cache_key]
+                = $tmp->searchProp($keys, $filter, $order, $offset, $count);
+        }
+
+        return $_ETHNA_APP_MANAGER_OPL_CACHE[$cache_class][$cache_key];
+    }
+
+    /**
+     *  オブジェクトプロパティを返す
+     *
+     *  getObjectPropList()メソッドの簡易版で、$filterにより結果が1エントリに
+     *  制限される場合(プライマリキーでの検索等)に利用する
+     *
+     *  @access public
+     *  @param  string  $class      Ethna_AppObjectの継承クラス名
+     *  @param  array   $keys       取得するプロパティ一覧
+     *  @param  array   $filter     検索条件
+     *  @return mixed   array:プロパティ一覧 null:エントリなし Ethna_Error:エラー
+     */
+    function getObjectProp($class, $keys = null, $filter = null)
+    {
+        global $_ETHNA_APP_MANAGER_OP_CACHE;
+
+        $prop_list = array();
+        $class_name = sprintf("%s_%s", $this->backend->getAppId(), $class);
+
+        // キャッシュチェック
+        $cache_class = strtolower($class_name);
+        if (is_array($_ETHNA_APP_MANAGER_OP_CACHE) == false
+            || array_key_exists($cache_class, $_ETHNA_APP_MANAGER_OP_CACHE) == false) {
+            $_ETHNA_APP_MANAGER_OP_CACHE[$cache_class] = array();
+        }
+        $cache_key = serialize(array($filter));
+        if (array_key_exists($cache_key, $_ETHNA_APP_MANAGER_OP_CACHE[$cache_class])) {
+            // skip
+        } else {
+            // キャッシュ更新
+            $tmp =& new $class_name($this->backend);
+            list(, $prop) = $tmp->searchProp($keys, $filter);
+            $_ETHNA_APP_MANAGER_OP_CACHE[$cache_class][$cache_key]
+                = count($prop) > 0 ? $prop[0] : null;
+        }
+
+        return $_ETHNA_APP_MANAGER_OP_CACHE[$cache_class][$cache_key];
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_AppObject.php b/Idea_Plugin_Extlib/class/Ethna_AppObject.php
new file mode 100644 (file)
index 0000000..8eb1a17
--- /dev/null
@@ -0,0 +1,1658 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_AppObject.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_AppObject
+/**
+ *  アプリケーションオブジェクトのベースクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ *  @todo       複数テーブルの対応
+ *  @todo       remove dependency on PEAR::DB
+ *  @todo       quoteidentifier は Ethna_AppSQL に持っていくべき
+ */
+class Ethna_AppObject
+{
+    // {{{ properties
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Backend       backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Config        設定オブジェクト */
+    var $config;
+
+    /** @var    object  Ethna_I18N          i18nオブジェクト */
+    var $i18n;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト */
+    var $action_form;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト(省略形) */
+    var $af;
+
+    /** @var    object  Ethna_Session       セッションオブジェクト */
+    var $session;
+
+    /** @var    string  DB定義プレフィクス */
+    var $db_prefix = null;
+
+    /** @var    array   テーブル定義。対応するDB上のテーブル名を指定します。*/
+    var $table_def = null;
+
+    /** @var    array   プロパティ定義。テーブルのカラム定義を記述します。 */
+    var $prop_def = null;
+
+    /** @var    array   プロパティ。各カラムに対応する実際の値です。 */
+    var $prop = null;
+
+    /** @var    array   プロパティ(バックアップ) */
+    var $prop_backup = null;
+
+    /** @var    int     プロパティ定義キャッシュ有効期間(sec) */
+    var $prop_def_cache_lifetime = 86400;
+
+    /** @var    array   プライマリキー定義 */
+    var $id_def = null;
+
+    /** @var    int     オブジェクトID (プライマリーキーの値) */
+    var $id = null;
+
+    /**#@-*/
+    // }}}
+
+    // {{{ Ethna_AppObject
+    /**
+     *  Ethna_AppObjectクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Backend   &$backend   Ethna_Backendオブジェクト
+     *  @param  mixed   $key_type   レコードを特定するためのカラム名
+     *                              (通常はプライマリーキーのフィールド)
+     *  @param  mixed   $key        レコードを特定するためのカラム値
+     *  @param  array   $prop       プロパティ(レコードの値)一覧
+     *  @return mixed   0:正常終了 -1:キー/プロパティ未指定 Ethna_Error:エラー
+     */
+    function Ethna_AppObject(&$backend, $key_type = null, $key = null, $prop = null)
+    {
+        $this->backend =& $backend;
+        $this->config =& $backend->getConfig();
+        $this->action_form =& $backend->getActionForm();
+        $this->af =& $this->action_form;
+        $this->session =& $backend->getSession();
+        $ctl =& $backend->getController();
+
+        // DBオブジェクトの設定
+        $db_list = $this->_getDBList();
+        if (Ethna::isError($db_list)) {
+            return $db_list;
+        } else if (is_null($db_list['rw'])) {
+            return Ethna::raiseError(
+                "Ethna_AppObjectを利用するにはデータベース設定が必要です",
+                E_DB_NODSN);
+        }
+        $this->my_db_rw =& $db_list['rw'];
+        $this->my_db_ro =& $db_list['ro'];
+        // XXX: app objはdb typeを知らなくても動くべき
+        $this->my_db_type = $this->my_db_rw->getType();
+
+        // テーブル定義自動取得
+        // 現在、記述可能なテーブルは常に一つで、primaryはtrue
+        if (is_null($this->table_def)) {
+            $this->table_def = $this->_getTableDef();
+        }
+        if (is_string($this->table_def)) {
+            $this->table_def = array($this->table_def => array('primary' => true));
+        }
+        // プロパティ定義(テーブルのカラム定義)自動取得
+        // データベースから自動取得され、キャッシュされる
+        if (is_null($this->prop_def)) {
+            $this->prop_def = $this->_getPropDef();
+        }
+
+        // プロパティ定義の必須キーを補完
+        foreach (array_keys($this->prop_def) as $k) {
+            if (isset($this->prop_def[$k]['primary']) == false) {
+                $this->prop_def[$k]['primary'] = false;
+            }
+        }
+
+        // オブジェクトのプライマリキー定義構築
+        foreach ($this->prop_def as $k => $v) {
+            if ($v['primary'] == false) {
+                continue;
+            }
+            if (is_null($this->id_def)) {
+                $this->id_def = $k;
+            } else if (is_array($this->id_def)) {
+                $this->id_def[] = $k;
+            } else {  // scalar の場合
+                $this->id_def = array($this->id_def, $k);
+            }
+        }
+        
+        // キー妥当性チェック
+        if (is_null($key_type) && is_null($key) && is_null($prop)) {
+            // perhaps for adding object
+            return 0;
+        }
+
+        // プロパティ設定
+        // $key_type, $key が指定されたらDBから値を取得し、設定する
+        // $prop が設定された場合はそれを設定する
+        if (is_null($prop)) {
+            $this->_setPropByDB($key_type, $key);
+        } else {
+            $this->_setPropByValue($prop);
+        }
+
+        $this->prop_backup = $this->prop;
+
+        //   プライマリーキーの値を設定
+        if (is_array($this->id_def)) {
+            $this->id = array();
+            foreach ($this->id_def as $k) {
+                $this->id[] = $this->prop[$k];
+            }
+        } else {
+            $this->id = $this->prop[$this->id_def];
+        }
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ isValid
+    /**
+     *  有効なオブジェクトかどうかを返す
+     *  プライマリーキーの値が設定されてなければ不正なオブジェクトです。
+     *
+     *  @access public
+     *  @return bool    true:有効 false:無効
+     */
+    function isValid()
+    {
+        if (is_array($this->id)) {
+            return is_null($this->id[0]) ? false : true;
+        } else {
+            return is_null($this->id) ? false : true;
+        }
+    }
+    // }}}
+
+    // {{{ isActive
+    /**
+     *  アクティブなオブジェクトかどうかを返す
+     *
+     *  isValid()メソッドはオブジェクト自体が有効かどうかを判定するのに対し
+     *  isActive()はオブジェクトがアプリケーションとして有効かどうかを返す
+     *
+     *  @access public
+     *  @return bool    true:アクティブ false:非アクティブ
+     */
+    function isActive()
+    {
+        if ($this->isValid() == false) {
+            return false;
+        }
+        return $this->prop['state'] == OBJECT_STATE_ACTIVE ? true : false;
+    }
+    // }}}
+
+    // {{{ getDef
+    /**
+     *  オブジェクトのプロパティ定義(カラム定義)を返す
+     *
+     *  @access public
+     *  @return array   オブジェクトのプロパティ定義
+     */
+    function getDef()
+    {
+        return $this->prop_def;
+    }
+    // }}}
+
+    // {{{ getIdDef
+    /**
+     *  プライマリキー定義を返す
+     *
+     *  @access public
+     *  @return mixed   プライマリキーとなるプロパティ名
+     */
+    function getIdDef()
+    {
+        return $this->id_def;
+    }
+    // }}}
+
+    // {{{ getId
+    /**
+     *  オブジェクトID(primary keyの値)を返す
+     *
+     *  @access public
+     *  @return mixed   オブジェクトID
+     */
+    function getId()
+    {
+        return $this->id;
+    }
+    // }}}
+
+    // {{{ get
+    /**
+     *  オブジェクトプロパティへのアクセサ(R)
+     *
+     *  @access public
+     *  @param  string  $key    プロパティ名(カラム名)
+     *  @return mixed   プロパティ(カラムの値)
+     */
+    function get($key)
+    {
+        if (isset($this->prop_def[$key]) == false) {
+            trigger_error(sprintf("Unknown property [%s]", $key), E_USER_ERROR);
+            return null;
+        }
+        if (isset($this->prop[$key])) {
+            return $this->prop[$key];
+        }
+        return null;
+    }
+    // }}}
+
+    // {{{ getName
+    /**
+     *  オブジェクトプロパティ表示名へのアクセサ
+     *  プロパティ値と、表示用の値が違う場合 (「県」等)に、
+     *  オーバーライドして下さい。
+     *
+     *  表示用の値を返す形で実装します。 
+     *
+     *  @access public
+     *  @param  string  $key    プロパティ(カラム)名
+     *  @return string  プロパティ(カラム)の表示名
+     */
+    function getName($key)
+    {
+        return $this->get($key);
+    }
+    // }}}
+
+    /**
+     *  オブジェクトプロパティ表示名(詳細)へのアクセサ
+     *  プロパティ値と、表示用の値が違う場合 (「県」等)に、
+     *  オーバーライドして下さい。
+     *
+     *  @access public
+     *  @param  string  $key    プロパティ(カラム)名
+     *  @return string  プロパティ(カラム)の表示名(詳細)
+     */
+    function getLongName($key)
+    {
+        return $this->get($key);
+    }
+    // }}}
+
+    // {{{ getNameObject
+    /**
+     *  プロパティ表示名を格納した連想配列を取得する
+     *  すべての getName メソッドの戻り値を配列として返します。
+     *
+     *  @access public
+     *  @return array   プロパティ表示名を格納した連想配列
+     */
+    function getNameObject()
+    {
+        $object = array();
+
+        foreach ($this->prop_def as $key => $elt) {
+            $object[$elt['form_name']] = $this->getName($key);
+        }
+
+        return $object;
+    }
+    // }}}
+
+    // {{{ set
+    /**
+     *  オブジェクトプロパティ(カラムに対応した値)を設定します。
+     *
+     *  @access public
+     *  @param  string  $key    プロパティ(カラム)名
+     *  @param  string  $value  プロパティ値
+     */
+    function set($key, $value)
+    {
+        if (isset($this->prop_def[$key]) == false) {
+            trigger_error(sprintf("Unknown property [%s]", $key), E_USER_ERROR);
+            return null;
+        }
+        $this->prop[$key] = $value;
+    }
+    // }}}
+
+    // {{{ dump
+    /**
+     *  オブジェクトプロパティを指定の形式でダンプする(現在はCSV形式のみサポート)
+     *
+     *  @access public
+     *  @param  string  $type   ダンプ形式("csv"...)
+     *  @return string  ダンプ結果(エラーの場合はnull)
+     */
+    function dump($type = "csv")
+    {
+        $method = "_dump_$type";
+        if (method_exists($this, $method) == false) {
+            return Ethna::raiseError("Undefined Method [%s]", E_APP_NOMETHOD, $method);
+        }
+
+        return $this->$method();
+    }
+    // }}}
+
+    // {{{ importForm
+    /**
+     *  フォーム値からオブジェクトプロパティをインポートする
+     *
+     *  @access public
+     *  @param  int     $option インポートオプション
+     *                  OBJECT_IMPORT_IGNORE_NULL: フォーム値が送信されていない場合はスキップ
+     *                  OBJECT_IMPORT_CONVERT_NULL: フォーム値が送信されていない場合、空文字列に変換
+     */
+    function importForm($option = null)
+    {
+        foreach ($this->getDef() as $k => $def) {
+            $value = $this->af->get($def['form_name']);
+            if (is_null($value)) {
+                // フォームから値が送信されていない場合の振舞い
+                if ($option == OBJECT_IMPORT_IGNORE_NULL) {
+                    // nullはスキップ
+                    continue;
+                } else if ($option == OBJECT_IMPORT_CONVERT_NULL) {
+                    // 空文字列に変換
+                    $value = '';
+                }
+            }
+            $this->set($k, $value);
+        }
+    }
+    // }}}
+
+    // {{{ exportForm
+    /**
+     *  オブジェクトプロパティをフォーム値にエクスポートする
+     *
+     *  @access public
+     */
+    function exportForm()
+    {
+        foreach ($this->getDef() as $k => $def) {
+            $this->af->set($def['form_name'], $this->get($k));
+        }
+    }
+    // }}}
+
+    // {{{ add
+    /**
+     *  オブジェクトを追加する(INSERT)
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     *  @todo remove dependency on PEAR::DB
+     */
+    function add()
+    {
+        // primary key 定義が sequence の場合、
+        // next idの取得: (pgsqlの場合のみ)
+        // 取得できた場合はこのidを使う
+        foreach (to_array($this->id_def) as $id_def) {
+            if (isset($this->prop_def[$id_def]['seq'])
+                && $this->prop_def[$id_def]['seq']) {
+                // NOTE: このapp object以外からinsertがないことが前提
+                $next_id = $this->my_db_rw->getNextId(
+                    $this->prop_def[$id_def]['table'], $id_def);
+                if ($next_id !== null && $next_id >= 0) {
+                    $this->prop[$id_def] = $next_id;
+                }
+                break;
+            }
+        }
+
+        //    INSERT 文を取得し、実行
+        $sql = $this->_getSQL_Add();
+        for ($i = 0; $i < 4; $i++) {
+            $r =& $this->my_db_rw->query($sql);
+            //   エラーの場合 -> 重複キーエラーの場合はリトライ
+            if (Ethna::isError($r)) {
+                if ($r->getCode() == E_DB_DUPENT) {
+                    // 重複エラーキーの判別
+                    $duplicate_key_list = $this->_getDuplicateKeyList();
+                    if (Ethna::isError($duplicate_key_list)) {
+                        return $duplicate_key_list;
+                    }
+                    if (is_array($duplicate_key_list)
+                        && count($duplicate_key_list) > 0) {
+                        foreach ($duplicate_key_list as $k) {
+                            return Ethna::raiseNotice('Duplicate Key Error [%s]',
+                                                      E_APP_DUPENT, $k);
+                        }
+                    }
+                } else {
+                    return $r;
+                }
+            } else {
+                break;
+            }
+        }
+        if ($i == 4) {
+            // cannot be reached
+            return Ethna::raiseError('Cannot detect Duplicate key Error', E_GENERAL);
+        }
+
+        // last insert idの取得: (mysql, sqliteのみ)
+        // primary key の 'seq' フラグがある(最初の)プロパティに入れる
+        $insert_id = $this->my_db_rw->getInsertId(); 
+        if ($insert_id !== null && $insert_id >= 0) {
+            foreach (to_array($this->id_def) as $id_def) {
+                if (isset($this->prop_def[$id_def]['seq'])
+                    && $this->prop_def[$id_def]['seq']) {
+                    $this->prop[$id_def] = $insert_id;
+                    break;
+                }
+            }
+        }
+
+        // ID(Primary Key)の値を設定
+        if (is_array($this->id_def)) {
+            $this->id = array();
+            foreach ($this->id_def as $k) {
+                $this->id[] = $this->prop[$k];
+            }
+        } else if (isset($this->prop[$this->id_def])) {
+            $this->id = $this->prop[$this->id_def];
+        } else {
+            trigger_error("primary key is missing", E_USER_ERROR);
+        }
+
+        // バックアップ/キャッシュ更新
+        $this->prop_backup = $this->prop;
+        $this->_clearPropCache();
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ update
+    /**
+     *  オブジェクトを更新する(UPDATE)
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     *  @todo remove dependency on PEAR::DB
+     */
+    function update()
+    {
+        $sql = $this->_getSQL_Update();
+        //   エラーの場合 -> 重複キーエラーの場合はリトライ(4回)
+        for ($i = 0; $i < 4; $i++) {  //  magic number
+            $r =& $this->my_db_rw->query($sql);
+            if (Ethna::isError($r)) {
+                if ($r->getCode() == E_DB_DUPENT) {
+                    // 重複エラーキーの判別
+                    $duplicate_key_list = $this->_getDuplicateKeyList();
+                    if (Ethna::isError($duplicate_key_list)) {
+                        return $duplicate_key_list;
+                    }
+                    if (is_array($duplicate_key_list)
+                        && count($duplicate_key_list) > 0) {
+                        foreach ($duplicate_key_list as $k) {
+                            return Ethna::raiseNotice('Duplicate Key Error [%s]',
+                                                      E_APP_DUPENT, $k);
+                        }
+                    }
+                } else {
+                    return $r;
+                }
+            } else {
+                break;
+            }
+        }
+        if ($i == 4) {
+            // cannot be reached
+            return Ethna::raiseError('Cannot detect Duplicate key Error', E_GENERAL);
+        }
+
+        $affected_rows = $this->my_db_rw->affectedRows();
+        if ($affected_rows <= 0) {
+            $this->backend->log(LOG_DEBUG, "update query with 0 updated rows");
+        }
+
+        // バックアップ/キャッシュ更新
+        $this->prop_backup = $this->prop;
+        $this->_clearPropCache();
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ replace
+    /**
+     *  オブジェクトを置換する
+     *
+     *  MySQLのREPLACE文に相当する動作を行う(add()で重複エラーが発生したら
+     *  update()を行う)
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 >0:オブジェクトID(追加時) Ethna_Error:エラー
+     *  @todo remove dependency on PEAR::DB
+     */
+    function replace()
+    {
+        $sql = $this->_getSQL_Select($this->getIdDef(), $this->getId());
+
+        //   重複機ーエラーの場合はリトライ(4回) 
+        for ($i = 0; $i < 3; $i++) {  // magic number
+            $r = $this->my_db_rw->query($sql);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+            $n = $r->numRows();
+
+            if ($n > 0) {
+                $r = $this->update();
+                return $r;
+            } else {
+                $r = $this->add();
+                if (Ethna::isError($r) == false) {
+                    return $r;
+                } else if ($r->getCode() != E_APP_DUPENT) {
+                    return $r;
+                }
+            }
+        }
+        
+        return $r;
+    }
+    // }}}
+
+    // {{{ remove
+    /**
+     *  オブジェクト(レコード)を削除する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     *  @todo remove dependency on PEAR::DB
+     */
+    function remove()
+    {
+        $sql = $this->_getSQL_Remove();
+        $r =& $this->my_db_rw->query($sql);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        // プロパティ/バックアップ/キャッシュクリア
+        $this->id = $this->prop = $this->prop_backup = null;
+        $this->_clearPropCache();
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ searchId
+    /**
+     *  オブジェクトID(プライマリーキーの値)を検索する
+     *
+     *  @access public
+     *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
+     *  @param  array   $order      検索結果ソート条件
+     *                              (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
+     *  @param  int     $offset     検索結果取得オフセット
+     *  @param  int     $count      検索結果取得数
+     *  @return mixed   array(0 => 検索条件にマッチした件数,
+     *                  1 => $offset, $countにより指定された件数のオブジェクトID一覧)
+     *                  Ethna_Error:エラー
+     *  TODO: remove dependency on PEAR::DB
+     */
+    function searchId($filter = null, $order = null, $offset = null, $count = null)
+    {
+       //   プライマリーキー件数検索
+       if (is_null($offset) == false || is_null($count) == false) {
+            $sql = $this->_getSQL_SearchLength($filter);
+            $r =& $this->my_db_ro->query($sql);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+            $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
+            $length = $row['id_count'];
+        } else {
+            $length = null;
+        }
+
+        $id_list = array();
+        $sql = $this->_getSQL_SearchId($filter, $order, $offset, $count);
+        $r =& $this->my_db_ro->query($sql);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        $n = $r->numRows();
+        for ($i = 0; $i < $n; $i++) {
+            $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
+
+            // プライマリキーが1カラムならスカラー値に変換
+            if (is_array($this->id_def) == false) {
+                $row = $row[$this->id_def];
+            }
+            $id_list[] = $row;
+        }
+        if (is_null($length)) {
+            $length = count($id_list);
+        }
+
+        return array($length, $id_list);
+    }
+    // }}}
+
+    // {{{ searchProp
+    /**
+     *  オブジェクトプロパティ(レコード)を検索する
+     *
+     *  @access public
+     *  @param  array   $keys       取得するプロパティ(カラム名)
+     *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
+     *  @param  array   $order      検索結果ソート条件
+     *                              (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
+     *  @param  int     $offset     検索結果取得オフセット
+     *  @param  int     $count      検索結果取得数
+     *  @return mixed   array(0 => 検索条件にマッチした件数,
+     *                  1 => $offset, $countにより指定された件数のオブジェクトプロパティ一覧)
+     *                  Ethna_Error:エラー
+     *  TODO: remove dependency on PEAR::DB
+     */
+    function searchProp($keys = null, $filter = null, $order = null,
+                        $offset = null, $count = null)
+    {
+        //   プライマリーキー件数検索
+        if (is_null($offset) == false || is_null($count) == false) {
+            $sql = $this->_getSQL_SearchLength($filter);
+            $r =& $this->my_db_ro->query($sql);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+            $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
+            $length = $row['id_count'];
+        } else {
+            $length = null;
+        }
+
+        $prop_list = array();
+        $sql = $this->_getSQL_SearchProp($keys, $filter, $order, $offset, $count);
+        $r =& $this->my_db_ro->query($sql);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        $n = $r->numRows();
+        for ($i = 0; $i < $n; $i++) {
+            $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
+            $prop_list[] = $row;
+        }
+        if (is_null($length)) {
+            $length = count($prop_list);
+        }
+
+        return array($length, $prop_list);
+    }
+    // }}}
+
+    // {{{ _setDefault
+    /**
+     *  オブジェクトのアプリケーションデフォルトプロパティを設定する
+     *
+     *  コンストラクタにより指定されたキーにマッチするエントリがなかった場合の
+     *  デフォルトプロパティをここで設定することが出来る
+     *
+     *  @access protected
+     *  @param  mixed   $key_type   検索キー名
+     *  @param  mixed   $key        検索キー
+     *  @return int     0:正常終了
+     */
+    function _setDefault($key_type, $key)
+    {
+        return 0;
+    }
+    // }}}
+
+    // {{{ _setPropByDB
+    /**
+     *  オブジェクトプロパティをDBから取得する
+     *
+     *  @access private
+     *  @param  mixed   $key_type   検索キー名
+     *  @param  mixed   $key        検索キー
+     *  TODO: depend on PEAR::DB
+     */
+    function _setPropByDB($key_type, $key)
+    {
+        global $_ETHNA_APP_OBJECT_CACHE;
+
+        $key_type = to_array($key_type);
+        $key = to_array($key);
+        if (count($key_type) != count($key)) {
+            trigger_error(sprintf("Unmatched key_type & key length [%d-%d]",
+                          count($key_type), count($key)), E_USER_ERROR);
+            return;
+        }
+        foreach ($key_type as $elt) {
+            if (isset($this->prop_def[$elt]) == false) {
+                trigger_error("Invalid key_type [$elt]", E_USER_ERROR);
+                return;
+            }
+        }
+
+        // キャッシュチェック
+        $class_name = strtolower(get_class($this));
+        if (is_array($_ETHNA_APP_OBJECT_CACHE) == false
+            || array_key_exists($class_name, $_ETHNA_APP_OBJECT_CACHE) == false) {
+            $_ETHNA_APP_OBJECT_CACHE[$class_name] = array();
+        }
+        $cache_key = serialize(array($key_type, $key));
+        if (array_key_exists($cache_key, $_ETHNA_APP_OBJECT_CACHE[$class_name])) {
+            $this->prop = $_ETHNA_APP_OBJECT_CACHE[$class_name][$cache_key];
+            return;
+        }
+
+        // SQL文構築
+        $sql = $this->_getSQL_Select($key_type, $key);
+
+        // プロパティ取得
+        $r =& $this->my_db_ro->query($sql);
+        if (Ethna::isError($r)) {
+            return;
+        }
+        $n = $r->numRows();
+        if ($n == 0) {
+            // try default
+            if ($this->_setDefault($key_type, $key) == false) {
+                // nop
+            }
+            return;
+        } else if ($n > 1) {
+            trigger_error("Invalid key (multiple rows found) [$key]", E_USER_ERROR);
+            return;
+        }
+        $this->prop = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
+
+        // キャッシュアップデート
+        $_ETHNA_APP_OBJECT_CACHE[$class_name][$cache_key] = $this->prop;
+    }
+    // }}}
+
+    // {{{ _setPropByValue
+    /**
+     *  コンストラクタで指定されたプロパティを設定する
+     *
+     *  @access private
+     *  @param  array   $prop   プロパティ一覧
+     */
+    function _setPropByValue($prop)
+    {
+        $def = $this->getDef();
+        foreach ($def as $key => $value) {
+            if ($value['primary'] && isset($prop[$key]) == false) {
+                // プライマリキーは省略不可
+                trigger_error("primary key is not identical", E_USER_ERROR);
+            }
+            $this->prop[$key] = $prop[$key];
+        }
+    }
+    // }}}
+
+    // {{{ _getPrimaryTable
+    /**
+     *  オブジェクトのプライマリテーブルを取得する
+     *
+     *  @access private
+     *  @return string  オブジェクトのプライマリテーブル名
+     */
+    function _getPrimaryTable()
+    {
+        $tables = array_keys($this->table_def);
+        $table = $tables[0];
+        
+        return $table;
+    }
+    // }}}
+
+    // {{{ _getDuplicateKeyList
+    /**
+     *  重複キーを取得する
+     *
+     *  @access private
+     *  @return mixed   0:重複なし Ethna_Error:エラー array:重複キーのプロパティ名一覧
+     *  TODO: depend on PEAR::DB
+     */
+    function _getDuplicateKeyList()
+    {
+        $duplicate_key_list = array();
+
+        // 現在設定されているプライマリキーにNULLが含まれる場合は検索しない
+        $check_pkey = true;
+        foreach (to_array($this->id_def) as $k) {
+            if (isset($this->prop[$k]) == false || is_null($this->prop[$k])) {
+                $check_pkey = false;
+                break;
+            }
+        }
+
+        // プライマリキーはmulti columnsになり得るので別扱い
+        if ($check_pkey) {
+            $sql = $this->_getSQL_Duplicate($this->id_def);
+            $r =& $this->my_db_rw->query($sql);
+            if (Ethna::isError($r)) {
+                return $r;
+            } else if ($r->numRows() > 0) {
+                // we can overwrite $key_list here
+                $duplicate_key_list = to_array($this->id_def);
+            }
+        }
+
+        // ユニークキー
+        foreach ($this->prop_def as $k => $v) {
+            if ($v['primary'] == true || $v['key'] == false) {
+                continue;
+            }
+            $sql = $this->_getSQL_Duplicate($k);
+            $r =& $this->my_db_rw->query($sql);
+            if (Ethna::isError($r)) {
+                return $r;
+            } else if ($r->NumRows() > 0) {
+                $duplicate_key_list[] = $k;
+            }
+        }
+
+        if (count($duplicate_key_list) > 0) {
+            return $duplicate_key_list;
+        } else {
+            return 0;
+        }
+    }
+    // }}}
+
+    // {{{ _getSQL_Select
+    /**
+     *  オブジェクトプロパティを取得するSQL文を構築する
+     *
+     *  @access private
+     *  @param  array   $key_type   検索キーとなるプロパティ(カラム)名一覧
+     *  @param  array   $key        $key_typeに対応するキー一覧
+     *  @return string  SELECT文
+     */
+    function _getSQL_Select($key_type, $key)
+    {
+        $key_type = to_array($key_type);
+        if (is_null($key)) {
+            // add()前
+            $key = array();
+            for ($i = 0; $i < count($key_type); $i++) {
+                $key[$i] = null;
+            }
+        } else {
+            $key = to_array($key);
+        }
+
+        // SQLエスケープ
+        Ethna_AppSQL::escapeSQL($key, $this->my_db_type);
+
+        $tables = implode(',',
+            $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
+        $columns = implode(',',
+            $this->my_db_ro->quoteIdentifier(array_keys($this->prop_def)));
+
+        // 検索条件
+        $condition = null;
+        for ($i = 0; $i < count($key_type); $i++) {
+            if (is_null($condition)) {
+                $condition = "WHERE ";
+            } else {
+                $condition .= " AND ";
+            }
+            $condition .= Ethna_AppSQL::getCondition(
+                $this->my_db_ro->quoteIdentifier($key_type[$i]), $key[$i]);
+        }
+
+        $sql = "SELECT $columns FROM $tables $condition";
+
+        return $sql;
+    }
+    // }}}
+
+    // {{{ _getSQL_Add
+    /**
+     *  オブジェクトと追加するSQL文を構築する
+     *
+     *  @access private
+     *  @return string  オブジェクトを追加するためのINSERT文
+     */
+    function _getSQL_Add()
+    {
+        $tables = implode(',',
+            $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
+
+        $key_list = array();
+        $set_list = array();
+        $prop_arg_list = $this->prop;
+
+        Ethna_AppSQL::escapeSQL($prop_arg_list, $this->my_db_type);
+        foreach ($this->prop_def as $k => $v) {
+            if (isset($prop_arg_list[$k]) == false) {
+                continue;
+            }
+            $key_list[] = $this->my_db_rw->quoteIdentifier($k);
+            $set_list[] = $prop_arg_list[$k];
+        }
+
+        $key_list = implode(', ', $key_list);
+        $set_list = implode(', ', $set_list);
+        $sql = "INSERT INTO $tables ($key_list) VALUES ($set_list)";
+
+        return $sql;
+    }
+    // }}}
+
+    // {{{ _getSQL_Update
+    /**
+     *  オブジェクトプロパティを更新するSQL文を構築する
+     *
+     *  @access private
+     *  @return オブジェクトプロパティを更新するためのUPDATE文
+     */
+    function _getSQL_Update()
+    {
+        $tables = implode(',',
+            $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
+
+        // SET句構築
+        $set_list = "";
+        $prop_arg_list = $this->prop;
+        Ethna_AppSQL::escapeSQL($prop_arg_list, $this->my_db_type);
+        foreach ($this->prop_def as $k => $v) {
+            if ($set_list != "") {
+                $set_list .= ",";
+            }
+            $set_list .= sprintf("%s=%s",
+                                 $this->my_db_rw->quoteIdentifier($k),
+                                 $prop_arg_list[$k]);
+        }
+
+        // 検索条件(primary key)
+        $condition = null;
+        foreach (to_array($this->id_def) as $k) {
+            if (is_null($condition)) {
+                $condition = "WHERE ";
+            } else {
+                $condition .= " AND ";
+            }
+            $v = $this->prop_backup[$k];    // equals to $this->id
+            Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
+            $condition .= Ethna_AppSQL::getCondition(
+                $this->my_db_rw->quoteIdentifier($k), $v);
+        }
+
+        $sql = "UPDATE $tables SET $set_list $condition";
+
+        return $sql;
+    }
+    // }}}
+
+    // {{{ _getSQL_Remove
+    /**
+     *  オブジェクトを削除するSQL文を構築する
+     *
+     *  @access private
+     *  @return string  オブジェクトを削除するためのDELETE文
+     */
+    function _getSQL_Remove()
+    {
+        $tables = implode(',',
+            $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
+
+        // 検索条件(primary key)
+        $condition = null;
+        foreach (to_array($this->id_def) as $k) {
+            if (is_null($condition)) {
+                $condition = "WHERE ";
+            } else {
+                $condition .= " AND ";
+            }
+            $v = $this->prop_backup[$k];    // equals to $this->id
+            Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
+            $condition .= Ethna_AppSQL::getCondition(
+                $this->my_db_rw->quoteIdentifier($k), $v);
+        }
+        if (is_null($condition)) {
+            trigger_error("DELETE with no conditon", E_USER_ERROR);
+            return null;
+        }
+
+        $sql = "DELETE FROM $tables $condition";
+
+        return $sql;
+    }
+    // }}}
+
+    // {{{ _getSQL_Duplicate
+    /**
+     *  オブジェクトプロパティのユニークチェックを行うSQL文を構築する
+     *
+     *  @access private
+     *  @param  mixed   $key    ユニークチェックを行うプロパティ名
+     *  @return string  ユニークチェックを行うためのSELECT文
+     */
+    function _getSQL_Duplicate($key)
+    {
+        $tables = implode(',',
+            $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
+        $columns = implode(',',
+            $this->my_db_ro->quoteIdentifier(array_keys($this->prop_def)));
+
+        $condition = null;
+        // 検索条件(現在設定されているプライマリキーは検索対象から除く)
+        if (is_null($this->id) == false) {
+            $primary_value = to_array($this->getId());
+            $n = 0;
+            foreach (to_array($this->id_def) as $k) {
+                if (is_null($condition)) {
+                    $condition = "WHERE ";
+                } else {
+                    $condition .= " AND ";
+                }
+                $value = $primary_value[$n];
+                Ethna_AppSQL::escapeSQL($value, $this->my_db_type);
+                $condition .= Ethna_AppSQL::getCondition(
+                    $this->my_db_ro->quoteIdentifier($k), $value, OBJECT_CONDITION_NE);
+                $n++;
+            }
+        }
+
+        foreach (to_array($key) as $k) {
+            if (is_null($condition)) {
+                $condition = "WHERE ";
+            } else {
+                $condition .= " AND ";
+            }
+            $v = $this->prop[$k];
+            Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
+            $condition .= Ethna_AppSQL::getCondition(
+                $this->my_db_ro->quoteIdentifier($k), $v);
+        }
+
+        $sql = "SELECT $columns FROM $tables $condition";
+
+        return $sql;
+    }
+    // }}}
+
+    // {{{ _getSQL_SearchLength
+    /**
+     *  オブジェクト検索総数(offset, count除外)を取得するSQL文を構築する
+     *
+     *  @access private
+     *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
+     *  @return string  検索総数を取得するためのSELECT文
+     *  @todo   my_db_typeの参照を廃止
+     */
+    function _getSQL_SearchLength($filter)
+    {
+        // テーブル名をクォートした上で連結。
+        $tables = implode(',',
+            $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
+
+        // プライマリーキー以外の検索条件が含まれていた
+        // 場合は、追加テーブルがあるとみなし、
+        // その解釈は _SQLPlugin_SearchTable に任せる
+        if ($this->_isAdditionalField($filter)) {
+            $tables .= " " . $this->_SQLPlugin_SearchTable();
+        }
+
+        $id_def = to_array($this->id_def);
+
+        //  テーブル名.プライマリーキー名
+        //  複数あった場合ははじめのものを使う
+        $column_id = $this->my_db_ro->quoteIdentifier($this->_getPrimaryTable())
+             . "." . $this->my_db_ro->quoteIdentifier($id_def[0]);
+        $id_count = $this->my_db_ro->quoteIdentifier('id_count');
+        $condition = $this->_getSQL_SearchCondition($filter);
+
+        if ($this->my_db_type === 'sqlite') {
+            $sql = "SELECT COUNT(*) AS $id_count FROM "
+                . " (SELECT DISTINCT $column_id FROM $tables $condition)";
+        } else {
+            $sql = "SELECT COUNT(DISTINCT $column_id) AS $id_count "
+                . "FROM $tables $condition";
+        }
+
+        return $sql;
+    }
+    // }}}
+
+    // {{{ _getSQL_SearchId
+    /**
+     *  オブジェクトID(プライマリーキー)検索を行うSQL文を構築する
+     *
+     *  @access private
+     *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
+     *  @param  array   $order      検索結果ソート条件
+     *                              (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
+     *  @param  int     $offset     検索結果取得オフセット
+     *  @param  int     $count      検索結果取得数
+     *  @return string  オブジェクト検索を行うSELECT文
+     */
+    function _getSQL_SearchId($filter, $order, $offset, $count)
+    {
+        // テーブル
+        $tables = implode(',',
+            $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
+        if ($this->_isAdditionalField($filter)
+            || $this->_isAdditionalField($order)) {
+            $tables .= " " . $this->_SQLPlugin_SearchTable();
+        }
+
+        $column_id = "";
+        foreach (to_array($this->id_def) as $id) {
+            if ($column_id != "") {
+                $column_id .= ",";
+            }
+            $column_id .= $this->my_db_ro->quoteIdentifier($this->_getPrimaryTable())
+                . "." . $this->my_db_ro->quoteIdentifier($id);
+        }
+        $condition = $this->_getSQL_SearchCondition($filter);
+
+        $sort = "";
+        if (is_array($order)) {
+            foreach ($order as $k => $v) {
+                if ($sort == "") {
+                    $sort = "ORDER BY ";
+                } else {
+                    $sort .= ", ";
+                }
+                $sort .= sprintf("%s %s", $this->my_db_ro->quoteIdentifier($k),
+                                 $v == OBJECT_SORT_ASC ? "ASC" : "DESC");
+            }
+        }
+
+        $limit = "";
+        if (is_null($count) == false) {
+            $limit = sprintf("LIMIT %d", $count);
+            if (is_null($offset) == false) {
+                $limit .= sprintf(" OFFSET %d", $offset);
+            }
+        }
+
+        $sql = "SELECT DISTINCT $column_id FROM $tables $condition $sort $limit";
+
+        return $sql;
+    }
+    // }}}
+
+    // {{{ _getSQL_SearchProp
+    /**
+     *  オブジェクトプロパティ検索を行うSQL文を構築する
+     *
+     *  @access private
+     *  @param  array   $keys       取得プロパティ(カラム名)一覧
+     *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
+     *  @param  array   $order      検索結果ソート条件
+     *                              (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
+     *  @param  int     $offset     検索結果取得オフセット
+     *  @param  int     $count      検索結果取得数
+     *  @return string  オブジェクト検索を行うSELECT文
+     */
+    function _getSQL_SearchProp($keys, $filter, $order, $offset, $count)
+    {
+        // テーブル
+        $tables = implode(',',
+            $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
+        if ($this->_isAdditionalField($filter)
+            || $this->_isAdditionalField($order)) {
+            $tables .= " " . $this->_SQLPlugin_SearchTable();
+        }
+        $p_table = $this->_getPrimaryTable();
+
+        //  検索用追加プロパティ
+        //  プライマリーキー以外の検索キーが含まれていた
+        //  場合は、その解釈を _SQLPlugin_SearchPropDef に任せる
+        //
+        //  これによって、複数のテーブルの条件を指定することが
+        //  できる(一応. ダサいけど)
+        if ($this->_isAdditionalField($filter)
+            || $this->_isAdditionalField($order)) {
+            $search_prop_def = $this->_SQLPlugin_SearchPropDef();
+        } else {
+            $search_prop_def = array();
+        }
+        $def = array_merge($this->getDef(), $search_prop_def);
+
+        // カラム
+        $column = "";
+        $keys = $keys === null ? array_keys($def) : to_array($keys);
+        foreach ($keys as $key) {
+            if ($column != "") {
+                $column .= ", ";
+            }
+            $t = isset($def[$key]['table']) ? $def[$key]['table'] : $p_table;
+            //   テーブル名.カラム名
+            $column .= sprintf("%s.%s",
+                               $this->my_db_ro->quoteIdentifier($t),
+                               $this->my_db_ro->quoteIdentifier($key));
+        }
+
+        // WHERE の条件
+        $condition = $this->_getSQL_SearchCondition($filter);
+
+        // ORDER BY
+        $sort = "";
+        if (is_array($order)) {
+            foreach ($order as $k => $v) {
+                if ($sort == "") {
+                    $sort = "ORDER BY ";
+                } else {
+                    $sort .= ", ";
+                }
+                $sort .= sprintf("%s %s",
+                                 $this->my_db_ro->quoteIdentifier($k),
+                                 $v == OBJECT_SORT_ASC ? "ASC" : "DESC");
+            }
+        }
+
+        // LIMIT, OFFSET
+        $limit = "";
+        if (is_null($count) == false) {
+            $limit = sprintf("LIMIT %d", $count);
+            if (is_null($offset) == false) {
+                $limit .= sprintf(" OFFSET %d", $offset);
+            }
+        }
+
+        $sql = "SELECT $column FROM $tables $condition $sort $limit";
+
+        return $sql;
+    }
+    // }}}
+
+    // {{{ _getSQL_SearchCondition
+    /**
+     *  オブジェクト検索SQLの条件文を構築する
+     *
+     *  @access private
+     *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
+     *  @return string  オブジェクト検索の条件文(エラーならnull)
+     */
+    function _getSQL_SearchCondition($filter)
+    {
+        if (is_array($filter) == false) {
+            return "";
+        }
+
+        $p_table = $this->_getPrimaryTable();
+
+        //  検索用追加プロパティ
+        //  プライマリーキー以外の検索キーが含まれていた
+        //  場合は、その解釈を _SQLPlugin_SearchPropDef に任せる
+        //
+        //  これによって、複数のテーブルの条件を指定することが
+        //  できる(一応. ダサいけど)
+        if ($this->_isAdditionalField($filter)) {
+            $search_prop_def = $this->_SQLPlugin_SearchPropDef();
+        } else {
+            $search_prop_def = array();
+        }
+        $prop_def = array_merge($this->prop_def, $search_prop_def);
+
+        $condition = null;
+        foreach ($filter as $k => $v) {
+            if (isset($prop_def[$k]) == false) {
+                trigger_error(sprintf("Unknown property [%s]", $k), E_USER_ERROR);
+                return null;
+            }
+
+            if (is_null($condition)) {
+                $condition = "WHERE ";
+            } else {
+                $condition .= " AND ";
+            }
+
+            $t = isset($prop_def[$k]['table']) ? $prop_def[$k]['table'] : $p_table;
+
+            // 細かい条件を指定するには、Ethna_AppSearchObject
+            // を使う必要がある  文字列の場合は LIKE, 数値の場合
+            // は = 条件しか指定できないからである。
+            if (is_object($v)) {
+                // Ethna_AppSearchObjectが指定されている場合
+                $condition .= $v->toString(
+                    $this->my_db_ro->quoteIdentifier($t)
+                    .'.'. $this->my_db_ro->quoteIdentifier($k));
+            } else if (is_array($v) && count($v) > 0 && is_object($v[0])) {
+                // Ethna_AppSearchObjectが配列で指定されている場合
+                $n = 0;
+                foreach ($v as $so) {
+                    if ($n > 0) {
+                        $condition .= " AND ";
+                    }
+                    $condition .= $so->toString(
+                        $this->my_db_ro->quoteIdentifier($t)
+                        .'.'. $this->my_db_ro->quoteIdentifier($k));
+                    $n++;
+                }
+            } else if ($prop_def[$k]['type'] == VAR_TYPE_STRING) {
+                // 省略形(文字列)
+                Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
+                $condition .= Ethna_AppSQL::getCondition(
+                    $this->my_db_ro->quoteIdentifier($t)
+                    .'.'. $this->my_db_ro->quoteIdentifier($k),
+                    $v, OBJECT_CONDITION_LIKE);
+            } else {
+                // 省略形(数値)
+                Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
+                $condition .= Ethna_AppSQL::getCondition(
+                    $this->my_db_ro->quoteIdentifier($t)
+                    .'.'. $this->my_db_ro->quoteIdentifier($k),
+                    $v, OBJECT_CONDITION_EQ);
+            }
+        }
+
+        return $condition;
+    }
+    // }}}
+
+    // {{{ _SQLPlugin_SearchTable
+    /**
+     *  オブジェクト検索SQLプラグイン(追加テーブル)
+     *
+     *  sample:
+     *  <code>
+     *  return " LEFT JOIN bar_tbl ON foo_tbl.user_id=bar_tbl.user_id";
+     *  </code>
+     *
+     *  @access protected
+     *  @return string  テーブルJOINのSQL文
+     */
+    function _SQLPlugin_SearchTable()
+    {
+        return "";
+    }
+    // }}}
+
+    // {{{ _SQLPlugin_SearchPropDef
+    /**
+     *  オブジェクト検索SQLプラグイン(追加条件定義)
+     *
+     *  sample:
+     *  <code>
+     *  $search_prop_def = array(
+     *    'group_id' => array(
+     *      'primary' => true, 'key' => true, 'type' => VAR_TYPE_INT,
+     *      'form_name' => 'group_id', 'table' => 'group_user_tbl',
+     *    ),
+     *  );
+     *  return $search_prop_def;
+     *  </code>
+     *
+     *  @access protected
+     *  @return array   追加条件定義
+     */
+    function _SQLPlugin_SearchPropDef()
+    {
+        return array();
+    }
+    // }}}
+
+    // {{{ _dump_csv
+    /**
+     *  オブジェクトプロパティをCSV形式でダンプする
+     *
+     *  @access protected
+     *  @return string  ダンプ結果
+     */
+    function _dump_csv()
+    {
+        $dump = "";
+
+        $n = 0;
+        foreach ($this->getDef() as $k => $def) {
+            if ($n > 0) {
+                $dump .= ",";
+            }
+            $dump .= Ethna_Util::escapeCSV($this->getName($k));
+            $n++;
+        }
+
+        return $dump;
+    }
+    // }}}
+
+    // {{{ _isAdditionalField
+    /**
+     *  (検索条件|ソート条件)フィールドにプライマリーキー以外
+     *  の追加フィールドが含まれるかどうかを返す
+     *
+     *  @access private
+     *  @param  array   $field  (検索条件|ソート条件)定義
+     *  @return bool    true:含まれる false:含まれない
+     */
+    function _isAdditionalField($field)
+    {
+        if (is_array($field) == false) {
+            return false;
+        }
+
+        $def = $this->getDef();
+        foreach ($field as $key => $value) {
+            if (array_key_exists($key, $def) == false) {
+                return true;
+            }
+            if (is_object($value)) {
+                // Ethna_AppSearchObject
+                if ($value->isTarget($key)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    // }}}
+
+    // {{{ _clearPropCache
+    /**
+     *  キャッシュデータを削除する
+     *
+     *  @access private
+     */
+    function _clearPropCache()
+    {
+        $class_name = strtolower(get_class($this));
+        foreach (array('_ETHNA_APP_OBJECT_CACHE',
+                       '_ETHNA_APP_MANAGER_OL_CACHE',
+                       '_ETHNA_APP_MANAGER_OPL_CACHE',
+                       '_ETHNA_APP_MANAGER_OP_CACHE') as $key) {
+            if (array_key_exists($key, $GLOBALS)
+                && array_key_exists($class_name, $GLOBALS[$key])) {
+                unset($GLOBALS[$key][$class_name]);
+            }
+        }
+    }
+    // }}}
+
+    // {{{ _getDBList
+    /**
+     *  DBオブジェクト(read only/read-write)を取得する
+     *
+     *  @access protected
+     *  @return array   array('ro' => {read only db object}, 'rw' => {read-write db object})
+     */
+    function _getDBList()
+    {
+        $r = array('ro' => null, 'rw' => null);
+
+        $db_list = $this->backend->getDBList();
+        if (Ethna::isError($db_list)) {
+            return $r;
+        }
+        foreach ($db_list as $elt) {
+            if ($this->db_prefix) {
+                // 特定のプレフィクスが指定されたDB接続を利用
+                // (テーブルごとにDBが異なる場合など)
+                if (strncmp($this->db_prefix,
+                            $elt['key'],
+                            strlen($this->db_prefix)) != 0) {
+                    continue;
+                }
+            }
+
+            $varname = $elt['varname'];
+
+            // for B.C.
+            $this->$varname =& $elt['db'];
+
+            if ($elt['type'] == DB_TYPE_RW) {
+                $r['rw'] =& $elt['db'];
+            } else if ($elt['type'] == DB_TYPE_RO) {
+                $r['ro'] =& $elt['db'];
+            }
+        }
+        if ($r['ro'] == null && $r['rw'] != null) {
+            $r['ro'] =& $r['rw'];
+        }
+
+        return $r;
+    }
+    // }}}
+
+    // {{{ _getTableDef
+    /**
+     *  テーブル定義を取得する
+     *
+     *  (クラス名→テーブル名のルールを変えたい場合は
+     *  このメソッドをオーバーライドします)
+     *
+     *  @access protected
+     *  @return array   テーブル定義
+     */
+    function _getTableDef()
+    {
+        $class_name = get_class($this);
+        if (preg_match('/(\w+)_(.*)/', $class_name, $match) == 0) {
+            return null;
+        }
+        $table = $match[2];
+
+        // PHP 4は常に小文字を返す...のでPHP 5専用
+        $table = preg_replace('/^([A-Z])/e', "strtolower('\$1')", $table);
+        $table = preg_replace('/([A-Z])/e', "'_' . strtolower('\$1')", $table);
+
+        //   JOIN には対応していないので、記述可能なテーブルは
+        //   常に一つ、かつ primary は trueになる
+        return array($table => array('primary' => true));
+    }
+    // }}}
+
+    // {{{ _getPropDef
+    /**
+     *  プロパティ定義を取得します。キャッシュされている場合は、
+     *  そこから取得します。
+     *
+     *  @access protected
+     *  @return array   プロパティ定義
+     */
+    function _getPropDef()
+    {
+        if (is_null($this->table_def)) {
+            return null;
+        }
+        foreach ($this->table_def as $table_name => $table_attr) {
+            // use 1st one
+            break;
+        }
+
+        $cache_manager =& Ethna_CacheManager::getInstance('localfile');
+        $cache_manager->setNamespace('ethna_app_object');
+        $cache_key = md5($this->my_db_ro->getDSN() . '-' . $table_name);
+
+        if ($cache_manager->isCached($cache_key, $this->prop_def_cache_lifetime)) {
+            $prop_def = $cache_manager->get($cache_key,
+                                            $this->prop_def_cache_lifetime);
+            if (Ethna::isError($prop_def) == false) {
+                return $prop_def;
+            }
+        }
+
+        $r = $this->my_db_ro->getMetaData($table_name);
+        if(Ethna::isError($r)){
+            return null;
+        }
+
+        $prop_def = array();
+        foreach ($r as $i => $field_def) {
+            $primary  = in_array('primary_key', $field_def['flags']);
+            $seq      = in_array('sequence',    $field_def['flags']);
+            $required = in_array('not_null',    $field_def['flags']);
+            $key      = in_array('primary_key', $field_def['flags'])
+                        || in_array('multiple_key', $field_def['flags'])
+                        || in_array('unique_key', $field_def['flags']);
+
+            switch ($field_def['type']) {
+            case 'int':
+                $type = VAR_TYPE_INT;
+                break;
+            case 'boolean':
+                $type = VAR_TYPE_BOOLEAN;
+                break;
+            case 'datetime':
+                $type = VAR_TYPE_DATETIME;
+                break;
+            default:
+                $type = VAR_TYPE_STRING;
+                break;
+            }
+
+            $prop_def[$field_def['name']] = array(
+                'primary'   => $primary,
+                'seq'       => $seq,
+                'key'       => $key,
+                'type'      => $type,
+                'required'  => $required,
+                'length'    => $field_def['len'],
+                'form_name' => $this->_fieldNameToFormName($field_def),
+                'table'     => $table_name,
+            );
+        }
+        
+        $cache_manager->set($cache_key, $prop_def);
+
+        return $prop_def;
+    }
+    // }}}
+
+    // {{{ _fieldNameToFormName
+    /**
+     *  データベースフィールド名に対応するフォーム名を取得する
+     *
+     *  @access protected
+     */
+    function _fieldNameToFormName($field_def)
+    {
+        return $field_def['name'];
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_AppSQL.php b/Idea_Plugin_Extlib/class/Ethna_AppSQL.php
new file mode 100644 (file)
index 0000000..be58860
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_AppSQL.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_AppSQL
+/**
+ *  アプリケーションSQLベースクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_AppSQL
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_AppSQLのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    controllerオブジェクト
+     */
+    function Ethna_AppSQL(&$controller)
+    {
+        $this->controller =& $controller;
+    }
+
+    /**
+     *  適切にエスケープされたSQL文を返す
+     *
+     *  @access public
+     *  @param  string  $sqlfunc    SQL文種別名
+     *  @param  array   $args       引数一覧
+     *  @return string  エスケープされたSQL文
+     */
+    function get($sqlid, $args)
+    {
+        Ethna_AppSQL::escapeSQL($args);
+
+        return call_user_func_array(array(&$this, $sqlid), $args);
+    }
+
+    /**
+     *  SQL引数をエスケープする
+     *
+     *  @access public
+     *  @param  mixed   &$var   エスケープする値
+     *  @static
+     */
+    function escapeSQL(&$var, $type = null)
+    {
+        if (!is_array($var)) {
+            if (is_null($var)) {
+                $var = 'NULL';
+            } else {
+                if ($type === 'sqlite') {
+                    $var = "'" . sqlite_escape_string($var) . "'";
+                } else {
+                    $var = "'" . addslashes($var) . "'";
+                }
+            }
+            return;
+        }
+        foreach (array_keys($var) as $key) {
+            Ethna_AppSQL::escapeSQL($var[$key], $type);
+        }
+    }
+
+    /**
+     *  escapeSQLでエスケープされた文字列をunescapeする
+     *
+     *  @access public
+     *  @param  mixed   &$var   エスケープを復帰する値
+     *  @static
+     */
+    function unescapeSQL(&$var, $type = null)
+    {
+        if (!is_array($var)) {
+            if ($var == 'NULL') {
+                return;
+            }
+            $var = substr($var, 1, strlen($var)-2);
+            $var = stripslashes($var);
+            return;
+        }
+        foreach (array_keys($var) as $key) {
+            Ethna_AppSQL::unescapeSQL($var[$key], $type);
+        }
+    }
+
+    /**
+     *  WHERE条件文を生成する
+     *
+     *  @access public
+     *  @param  string  $field      検索対象のフィールド
+     *  @param  mixed   $value      検索値
+     *  @param  int     $condition  検索条件(OBJECT_CONDITION_NE,...)
+     *  @return string  検索条件文
+     *  @static
+     */
+    function getCondition($field, $value, $condition = OBJECT_CONDITION_EQ)
+    {
+        switch ($condition) {
+        case OBJECT_CONDITION_EQ:
+            $op = "="; break;
+        case OBJECT_CONDITION_NE:
+            $op = "!="; break;
+        case OBJECT_CONDITION_LIKE:
+            $op = "LIKE"; break;
+        case OBJECT_CONDITION_GT:
+            $op = ">"; break;
+        case OBJECT_CONDITION_LT:
+            $op = "<"; break;
+        case OBJECT_CONDITION_GE:
+            $op = ">="; break;
+        case OBJECT_CONDITION_LE:
+            $op = "<="; break;
+        }
+
+        // default operand
+        $operand = $value;
+
+        if (is_array($value)) {
+            if (count($value) > 0) {
+                switch ($condition) {
+                case OBJECT_CONDITION_EQ:
+                    $op = "IN"; break;
+                case OBJECT_CONDITION_NE:
+                    $op = "NOT IN"; break;
+                }
+                $operand = sprintf("(%s)", implode(',', $value));
+            } else {
+                // always be false
+                $op = "=";
+                $operand = "NULL";
+            }
+        } else {
+            if ($value == 'NULL') {
+                switch ($condition) {
+                case OBJECT_CONDITION_EQ:
+                    $op = "IS"; break;
+                case OBJECT_CONDITION_NE:
+                    $op = "IS NOT"; break;
+                }
+            }
+            if ($condition == OBJECT_CONDITION_LIKE) {
+                Ethna_AppSQL::unescapeSQL($value);
+                $value = '%' . str_replace('%', '\\%', $value) . '%';
+                Ethna_AppSQL::escapeSQL($value);
+                $operand = $value;
+            }
+        }
+        return "$field $op $operand";
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_AppSearchObject.php b/Idea_Plugin_Extlib/class/Ethna_AppSearchObject.php
new file mode 100644 (file)
index 0000000..1363fbd
--- /dev/null
@@ -0,0 +1,147 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_AppSearchObject.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/** アプリケーションオブジェクト検索条件: != */
+define('OBJECT_CONDITION_NE', 0);
+
+/** アプリケーションオブジェクト検索条件: == */
+define('OBJECT_CONDITION_EQ', 1);
+
+/** アプリケーションオブジェクト検索条件: LIKE */
+define('OBJECT_CONDITION_LIKE', 2);
+
+/** アプリケーションオブジェクト検索条件: > */
+define('OBJECT_CONDITION_GT', 3);
+
+/** アプリケーションオブジェクト検索条件: < */
+define('OBJECT_CONDITION_LT', 4);
+
+/** アプリケーションオブジェクト検索条件: >= */
+define('OBJECT_CONDITION_GE', 5);
+
+/** アプリケーションオブジェクト検索条件: <= */
+define('OBJECT_CONDITION_LE', 6);
+
+/** アプリケーションオブジェクト検索条件: AND */
+define('OBJECT_CONDITION_AND', 7);
+
+/** アプリケーションオブジェクト検索条件: OR */
+define('OBJECT_CONDITION_OR', 8);
+
+
+
+// {{{ Ethna_AppSearchObject
+/**
+ *  アプリケーションオブジェクト検索条件クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_AppSearchObject
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    string  検索値 */
+    var $value;
+
+    /** @var    int     検索条件 */
+    var $condition;
+
+    /**
+     *  @var    array   追加検索条件を保持したEthna_AppSearchObjectの一覧
+     */
+    var $object_list = array();
+
+    /**#@-*/
+
+
+    /**
+     *  Ethna_AppSearchObjectのコンストラクタ
+     *
+     *  @access public
+     *  @param  string  $value      検索値
+     *  @param  int     $condition  検索条件(OBJECT_CONDITION_NE,...)
+     */
+    function Ethna_AppSearchObject($value, $condition)
+    {
+        $this->value = $value;
+        $this->condition = $condition;
+    }
+
+    /**
+     *  検索条件をOR/ANDで追加する
+     *
+     *  @access public
+     *  @param  string                          $name           検索対象カラム名
+     *  @param  object  Ethna_AppSearchObject   $search_object  追加する検索条件
+     *  @param  int                             $condition      追加条件(OR/AND)
+     */
+    function addObject($name, $search_object, $condition)
+    {
+        $tmp = array();
+        $tmp['name'] = $name;
+        $tmp['object'] =& $search_object;
+        $tmp['condition'] = $condition;
+        $this->object_list[] = $tmp;
+    }
+
+    /**
+     *  指定されたフィールドが検索対象となっているかどうかを返す
+     *
+     *  @access public
+     */
+    function isTarget($field)
+    {
+        foreach ($this->object_list as $object) {
+            if ($object['name'] == $field) {
+                return true;
+            }
+            if (is_object($object['object'])) {
+                $r = $object['object']->isTarget($field);
+                if ($r) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     *  検索条件SQL文を返す
+     *
+     *  @access public
+     *  @param  string  検索対象カラム名
+     *  @return SQL文
+     */
+    function toString($column)
+    {
+        $condition = "(";
+        $tmp_value = $this->value;
+        Ethna_AppSQL::escapeSQL($tmp_value);
+        $condition .= Ethna_AppSQL::getCondition("$column", $tmp_value, $this->condition);
+
+        foreach ($this->object_list as $elt) {
+            if ($elt['condition'] == OBJECT_CONDITION_OR) {
+                $condition .= " OR ";
+            } else {
+                $condition .= " AND ";
+            }
+            $condition .= $elt['object']->toString($elt['name']);
+        }
+
+        return $condition . ")";
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Backend.php b/Idea_Plugin_Extlib/class/Ethna_Backend.php
new file mode 100644 (file)
index 0000000..8b42b1e
--- /dev/null
@@ -0,0 +1,493 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Backend.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Backend
+/**
+ *  バックエンド処理クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Backend
+{
+    /**#@+
+     *  @access     private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    object  Ethna_ClassFactory  クラスファクトリオブジェクト */
+    var $class_factory;
+
+    /** @var    object  Ethna_Config        設定オブジェクト */
+    var $config;
+
+    /** @var    object  Ethna_I18N          i18nオブジェクト */
+    var $i18n;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト */
+    var $action_error;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト($action_errorの省略形) */
+    var $ae;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト */
+    var $action_form;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト($action_formの省略形) */
+    var $af;
+
+    /** @var    object  Ethna_ActionClass   アクションクラスオブジェクト */
+    var $action_class;
+
+    /** @var    object  Ethna_ActionClass   アクションクラスオブジェクト($action_classの省略形) */
+    var $ac;
+
+    /** @var    object  Ethna_Session       セッションオブジェクト */
+    var $session;
+
+    /** @var    object  Ethna_Plugin        プラグインオブジェクト */
+    var $plugin;
+
+    /** @var    array   Ethna_DBオブジェクトを格納した配列 */
+    var $db_list;
+
+    /** @var    object  Ethna_Logger        ログオブジェクト */
+    var $logger;
+
+    /**#@-*/
+
+
+    /**
+     *  Ethna_Backendクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     */
+    function Ethna_Backend(&$controller)
+    {
+        // オブジェクトの設定
+        $this->controller =& $controller;
+        $this->ctl =& $this->controller;
+
+        $this->class_factory =& $controller->getClassFactory();
+
+        $this->config =& $controller->getConfig();
+        $this->i18n =& $controller->getI18N();
+
+        $this->action_error =& $controller->getActionError();
+        $this->ae =& $this->action_error;
+        $this->action_form =& $controller->getActionForm();
+        $this->af =& $this->action_form;
+        $this->action_class = null;
+        $this->ac =& $this->action_class;
+
+        $this->session =& $this->controller->getSession();
+        $this->plugin =& $this->controller->getPlugin();
+        $this->db_list = array();
+        $this->logger =& $this->controller->getLogger();
+    }
+
+    /**
+     *  controllerオブジェクトへのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_Controller    controllerオブジェクト
+     */
+    function &getController()
+    {
+        return $this->controller;
+    }
+
+    /**
+     *  設定オブジェクトへのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_Config        設定オブジェクト
+     */
+    function &getConfig()
+    {
+        return $this->config;
+    }
+
+    /**
+     *  アプリケーションIDを返す
+     *
+     *  @access public
+     *  @return string  アプリケーションID
+     */
+    function getAppId()
+    {
+        return $this->controller->getAppId();
+    }
+
+    /**
+     *  I18Nオブジェクトのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_I18N  i18nオブジェクト
+     */
+    function &getI18N()
+    {
+        return $this->i18n;
+    }
+
+    /**
+     *  アクションエラーオブジェクトのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_ActionError   アクションエラーオブジェクト
+     */
+    function &getActionError()
+    {
+        return $this->action_error;
+    }
+
+    /**
+     *  アクションフォームオブジェクトのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_ActionForm    アクションフォームオブジェクト
+     */
+    function &getActionForm()
+    {
+        return $this->action_form;
+    }
+
+    /**
+     *  アクションフォームオブジェクトのアクセサ(W)
+     *
+     *  @access public
+     */
+    function setActionForm(&$action_form)
+    {
+        $this->action_form =& $action_form;
+        $this->af =& $action_form;
+    }
+
+    /**
+     *  実行中のアクションクラスオブジェクトのアクセサ(R)
+     *
+     *  @access public
+     *  @return mixed   Ethna_ActionClass:アクションクラス null:アクションクラス未定
+     */
+    function &getActionClass()
+    {
+        return $this->action_class;
+    }
+
+    /**
+     *  実行中のアクションクラスオブジェクトのアクセサ(W)
+     *
+     *  @access public
+     */
+    function setActionClass(&$action_class)
+    {
+        $this->action_class =& $action_class;
+        $this->ac =& $action_class;
+    }
+
+    /**
+     *  ログオブジェクトのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_Logger    ログオブジェクト
+     */
+    function &getLogger()
+    {
+        return $this->logger;
+    }
+
+    /**
+     *  セッションオブジェクトのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_Session   セッションオブジェクト
+     */
+    function &getSession()
+    {
+        return $this->session;
+    }
+
+    /**
+     *  プラグインオブジェクトのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_Plugin    プラグインオブジェクト
+     */
+    function &getPlugin()
+    {
+        return $this->plugin;
+    }
+
+    /**
+     *  マネージャオブジェクトへのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_AppManager    マネージャオブジェクト
+     */
+    function &getManager($type, $weak = false)
+    {
+        $_ret_object =& $this->class_factory->getManager($type, $weak);
+        return $_ret_object;
+    }
+
+    /**
+     *  オブジェクトへのアクセサ(R)
+     *
+     *  @access public
+     *  @return mixed   $keyに対応するオブジェクト(or null)
+     */
+    function &getObject($key)
+    {
+        $arg_list = func_get_args();
+        array_shift($arg_list);
+        $_ret_object =& $this->class_factory->getObject($key, $arg_list);
+        return $_ret_object;
+    }
+
+    /**
+     *  アプリケーションのベースディレクトリを取得する
+     *
+     *  @access public
+     *  @return string  ベースディレクトリのパス名
+     */
+    function getBasedir()
+    {
+        return $this->controller->getBasedir();
+    }
+
+    /**
+     *  アプリケーションのテンプレートディレクトリを取得する
+     *
+     *  @access public
+     *  @return string  テンプレートディレクトリのパス名
+     */
+    function getTemplatedir()
+    {
+        return $this->controller->getTemplatedir();
+    }
+
+    /**
+     *  アプリケーションの設定ディレクトリを取得する
+     *
+     *  @access public
+     *  @return string  設定ディレクトリのパス名
+     */
+    function getEtcdir()
+    {
+        return $this->controller->getDirectory('etc');
+    }
+
+    /**
+     *  アプリケーションのテンポラリディレクトリを取得する
+     *
+     *  @access public
+     *  @return string  テンポラリディレクトリのパス名
+     */
+    function getTmpdir()
+    {
+        return $this->controller->getDirectory('tmp');
+    }
+
+    /**
+     *  アプリケーションのテンプレートファイル拡張子を取得する
+     *
+     *  @access public
+     *  @return string  テンプレートファイルの拡張子
+     */
+    function getTemplateext()
+    {
+        return $this->controller->getExt('tpl');
+    }
+
+    /**
+     *  ログを出力する
+     *
+     *  @access public
+     *  @param  int     $level      ログレベル(LOG_DEBUG, LOG_NOTICE...)
+     *  @param  string  $message    ログメッセージ(printf形式)
+     */
+    function log($level, $message)
+    {
+        $args = func_get_args();
+        if (count($args) > 2) {
+            array_splice($args, 0, 2);
+            $message = vsprintf($message, $args);
+        }
+        $this->logger->log($level, $message);
+    }
+
+    /**
+     *  バックエンド処理を実行する
+     *
+     *  @access public
+     *  @param  string  $action_name    実行するアクションの名称
+     *  @return mixed   (string):Forward名(nullならforwardしない) Ethna_Error:エラー
+     */
+    function perform($action_name)
+    {
+        $forward_name = null;
+
+        $action_class_name = $this->controller->getActionClassName($action_name);
+        $this->action_class =& new $action_class_name($this);
+        $this->ac =& $this->action_class;
+
+        // アクションの実行
+        $forward_name = $this->ac->authenticate();
+        if ($forward_name === false) {
+            return null;
+        } else if ($forward_name !== null) {
+            return $forward_name;
+        }
+
+        $forward_name = $this->ac->prepare();
+        if ($forward_name === false) {
+            return null;
+        } else if ($forward_name !== null) {
+            return $forward_name;
+        }
+
+        $forward_name = $this->ac->perform();
+
+        return $forward_name;
+    }
+
+    /**
+     *  DBオブジェクトを返す
+     *
+     *  @access public
+     *  @param  string  $db_key DBキー
+     *  @return mixed   Ethna_DB:DBオブジェクト null:DSN設定なし Ethna_Error:エラー
+     *  @todo   この中でnewしないでclass factoryを利用する
+     */
+    function &getDB($db_key = "")
+    {
+        $null = null;
+        $db_varname =& $this->_getDBVarname($db_key);
+
+        if (Ethna::isError($db_varname)) {
+            return $db_varname;
+        }
+
+        if (isset($this->db_list[$db_varname]) && $this->db_list[$db_varname] != null) {
+            return $this->db_list[$db_varname];
+        }
+
+        $dsn = $this->controller->getDSN($db_key);
+
+        if ($dsn == "") {
+            // DB接続不要
+            return $null;
+        }
+
+        $dsn_persistent = $this->controller->getDSN_persistent($db_key);
+
+        $class_factory =& $this->controller->getClassFactory();
+        $db_class_name = $class_factory->getObjectName('db');
+        
+        // BC: Ethna_DB -> Ethna_DB_PEAR
+        if ($db_class_name == 'Ethna_DB') {
+            $db_class_name = 'Ethna_DB_PEAR';
+        }
+        if (class_exists($db_class_name) === false) {
+            $class_factory->_include($db_class_name);
+        }
+
+        $this->db_list[$db_varname] =& new $db_class_name($this->controller, $dsn, $dsn_persistent);
+        $r = $this->db_list[$db_varname]->connect();
+        if (Ethna::isError($r)) {
+            $this->db_list[$db_varname] = null;
+            return $r;
+        }
+
+        register_shutdown_function(array(&$this, 'shutdownDB'));
+
+        return $this->db_list[$db_varname];
+    }
+
+    /**
+     *  DBオブジェクト(全て)を取得する
+     *
+     *  @access public
+     *  @return mixed   array:Ethna_DBオブジェクトの一覧 Ethan_Error:(いずれか一つ以上の接続で)エラー
+     */
+    function getDBList()
+    {
+        $r = array();
+        $db_define_list = $this->controller->getDBType();
+        foreach ($db_define_list as $db_key => $db_type) {
+            $db =& $this->getDB($db_key);
+            if (Ethna::isError($db)) {
+                return $r;
+            }
+            $elt = array();
+            $elt['db'] =& $db;
+            $elt['key'] = $db_key;
+            $elt['type'] = $db_type;
+            $elt['varname'] = "db";
+            if ($db_key != "") {
+                $elt['varname'] = sprintf("db_%s", strtolower($db_key));
+            }
+            $r[] = $elt;
+        }
+        return $r;
+    }
+
+    /**
+     *  DBコネクションを切断する
+     *
+     *  @access public
+     */
+    function shutdownDB()
+    {
+        foreach (array_keys($this->db_list) as $key) {
+            if ($this->db_list[$key] != null && $this->db_list[$key]->isValid()) {
+                $this->db_list[$key]->disconnect();
+                unset($this->db_list[$key]);
+            }
+        }
+    }
+
+    /**
+     *  指定されたDBキーに対応する(当該DBオブジェクトを格納するための)メンバ変数名を取得する
+     *
+     *  正直もう要らないのですが、後方互換性維持のために一応残してある状態です
+     *  (Ethna_AppManagerクラスなどで、$this->dbとかしている箇所が少なからずあ
+     *  るので)
+     *
+     *  @access private
+     *  @param  string  $db_key DBキー
+     *  @return mixed   string:メンバ変数名 Ethna_Error:不正なDB種別
+     */
+    function &_getDBVarname($db_key = "")
+    {
+        $r = $this->controller->getDBType($db_key);
+        if (is_null($r)) {
+            return Ethna::raiseError("Undefined DB Type [%s]", E_DB_INVALIDTYPE, $db_key);
+        }
+
+        if ($db_key == "") {
+            $db_varname = "";
+        } else {
+            $db_varname = sprintf("%s", strtolower($db_key));
+        }
+
+        return $db_varname;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_CacheManager.php b/Idea_Plugin_Extlib/class/Ethna_CacheManager.php
new file mode 100644 (file)
index 0000000..f24672d
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+// vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
+/**
+ *  Ethna_CacheManager.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/**
+ *  キャッシュマネージャクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_CacheManager
+{
+    /**
+     *  Cachemaanger プラグインのインスタンスを取得する
+     *
+     *  @param  string  $type   キャッシュタイプ('localfile', 'memcache'...)
+     *  @return object  Ethna_Plugin_CacheMaanger   Cachemanager プラグインのインスタンス
+     *  @access public
+     */
+    function &getInstance($type)
+    {
+        $controller =& Ethna_Controller::getInstance();
+        $plugin =& $controller->getPlugin();
+
+        $cache_manager =& $plugin->getPlugin('Cachemanager', ucfirst($type));
+
+        return $cache_manager;
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_ClassFactory.php b/Idea_Plugin_Extlib/class/Ethna_ClassFactory.php
new file mode 100644 (file)
index 0000000..ea586a6
--- /dev/null
@@ -0,0 +1,420 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ClassFactory.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_ClassFactory
+/**
+ *  Ethnaフレームワークのオブジェクト生成ゲートウェイ
+ *
+ *  DIコンテナか、ということも考えましたがEthnaではこの程度の単純なものに
+ *  留めておきます。アプリケーションレベルDIしたい場合はフィルタチェインを
+ *  使って実現することも出来ます。
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_ClassFactory
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト(省略形) */
+    var $ctl;
+    
+    /** @var    array   クラス定義 */
+    var $class = array();
+
+    /** @var    array   生成済みオブジェクトキャッシュ */
+    var $object = array();
+
+    /** @var    array   生成済みアプリケーションマネージャオブジェクトキャッシュ */
+    var $manager = array();
+
+    /** @var    array   メソッド一覧キャッシュ */
+    var $method_list = array();
+
+    /**#@-*/
+
+
+    /**
+     *  Ethna_ClassFactoryクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    controllerオブジェクト
+     *  @param  array                       $class          クラス定義
+     */
+    function Ethna_ClassFactory(&$controller, $class)
+    {
+        $this->controller =& $controller;
+        $this->ctl =& $controller;
+        $this->class = $class;
+    }
+
+    /**
+     *  typeに対応するアプリケーションマネージャオブジェクトを返す
+     *  注意: typeは大文字小文字を区別しない
+     *         (PHP自体が、クラス名の大文字小文字を区別しないため)
+     *
+     *  @access public
+     *  @param  string  $type   アプリケーションマネージャー名
+     *  @param  bool    $weak   オブジェクトが未生成の場合の強制生成フラグ(default: false)
+     *  @return object  Ethna_AppManager    マネージャオブジェクト
+     */
+    function &getManager($type, $weak = false)
+    {
+        $obj = null;
+
+        //  すでにincludeされていなければ、includeを試みる
+        //  ここで返されるクラス名は、AppObjectの命名規約によるもの
+        //
+        //  これは、AppObject のファイル中にAppManagerが含まれる場合が
+        //  あるため必要なルーチンである
+        $obj_class_name = $this->controller->getObjectClassName($type);
+        if (class_exists($obj_class_name) === false) {
+            $this->_include($obj_class_name);
+        }
+
+        //  すでにincludeされていなければ、includeを試みる
+        //  ここで返されるクラス名は、AppManagerの命名規約によるもの
+        $class_name = $this->controller->getManagerClassName($type);
+        if (class_exists($class_name) === false
+            && $this->_include($class_name) === false) {
+            return $obj;  //  include 失敗。戻り値はNULL。
+        }
+
+        //  メソッド情報を集める 
+        if (isset($this->method_list[$class_name]) == false) {
+            $this->method_list[$class_name] = get_class_methods($class_name);
+            for ($i = 0; $i < count($this->method_list[$class_name]); $i++) {
+                $this->method_list[$class_name][$i] = strtolower($this->method_list[$class_name][$i]);
+            }
+        }
+
+        //  PHPのクラス名は大文字小文字を区別しないので、
+        //  同じクラス名と見做されるものを指定した場合には
+        //  同じインスタンスが返るようにする
+        $type = strtolower($type);
+
+        //  以下のルールに従って、キャッシュが利用可能かを判定する
+        //  利用可能と判断した場合、キャッシュされていればそれを返す
+        //
+        //  1. メソッドに getInstance があればキャッシュを利用可能と判断する
+        //     この場合、シングルトンかどうかは getInstance 次第
+        //  2. weak が true であれば、キャッシュは利用不能と判断してオブジェクトを再生成
+        //  3. weak が false であれば、キャッシュは利用可能と判断する(デフォルト) 
+        if ($this->_isCacheAvailable($class_name, $this->method_list[$class_name], $weak)) {
+            if (isset($this->manager[$type]) && is_object($this->manager[$type])) {
+                return $this->manager[$type];
+            }
+        }
+
+        //  インスタンス化のヘルパ(getInstance)があればそれを使う
+        if (in_array("getinstance", $this->method_list[$class_name])) {
+            $obj = call_user_func(array($class_name, 'getInstance'));
+        } else {
+            $backend =& $this->controller->getBackend();
+            $obj =& new $class_name($backend);
+        }
+
+        //  生成したオブジェクトはとりあえずキャッシュする
+        if (isset($this->manager[$type]) == false || is_object($this->manager[$type]) == false) {
+            $this->manager[$type] =& $obj;
+        }
+
+        return $obj;
+    }
+
+    /**
+     *  クラスキーに対応するオブジェクトを返す/クラスキーが未定義の場合はAppObjectを探す
+     *  クラスキーとは、[Appid]_Controller#class に定められたもの。
+     *
+     *  @access public
+     *  @param  string  $key    [Appid]_Controller#class に定められたクラスキー
+     *                          このキーは大文字小文字を区別する 
+     *                          (配列のキーとして使われているため)
+     *  @param  bool    $ext    オブジェクトが未生成の場合の強制生成フラグ(default: false)
+     *  @return object  生成されたオブジェクト(エラーならnull)
+     */
+    function &getObject($key, $ext = false)
+    {
+        $object = null;
+
+        $ext = to_array($ext);
+        if (isset($this->class[$key]) == false) {
+            // app object
+            $class_name = $this->controller->getObjectClassName($key);
+            $ext = array_pad($ext, 3, null);
+            list($key_type, $key_value, $prop) = $ext;
+        } else {
+            // ethna classes
+            $class_name = $this->class[$key];
+            $ext = array_pad($ext, 1, null);
+            list($weak) = $ext;
+        }
+
+        //  すでにincludeされていなければ、includeを試みる
+        if (class_exists($class_name) == false) {
+            if ($this->_include($class_name) == false) {
+                return $object;  //  include 失敗。返り値はnull
+            }
+        }
+
+        //  AppObject をはじめに扱う 
+        //  AppObject はキャッシュされないことに注意
+        if (isset($this->class[$key]) == false) {
+            $backend =& $this->controller->getBackend();
+            $object =& new $class_name($backend, $key_type, $key_value, $prop);
+            return $object;
+        }
+
+        //  Ethna_Controllerで定義されたクラスキーの場合
+        //  はメソッド情報を集める 
+        if (isset($this->method_list[$class_name]) == false) {
+            $this->method_list[$class_name] = get_class_methods($class_name);
+            for ($i = 0; $i < count($this->method_list[$class_name]); $i++) {
+                $this->method_list[$class_name][$i] = strtolower($this->method_list[$class_name][$i]);
+            }
+        }
+
+        //  以下のルールに従って、キャッシュが利用可能かを判定する
+        //  利用可能と判断した場合、キャッシュされていればそれを返す
+        //
+        //  1. メソッドに getInstance があればキャッシュを利用可能と判断する
+        //     この場合、シングルトンかどうかは getInstance 次第
+        //  2. weak が true であれば、キャッシュは利用不能と判断してオブジェクトを再生成
+        //  3. weak が false であれば、キャッシュは利用可能と判断する(デフォルト) 
+        if ($this->_isCacheAvailable($class_name, $this->method_list[$class_name], $weak)) {
+            if (isset($this->object[$key]) && is_object($this->object[$key])) {
+                return $this->object[$key];
+            }
+        }
+
+        //  インスタンス化のヘルパがあればそれを使う
+        $method = sprintf('_getObject_%s', ucfirst($key));
+        if (method_exists($this, $method)) {
+            $object =& $this->$method($class_name);
+        } else if (in_array("getinstance", $this->method_list[$class_name])) {
+            $object = call_user_func(array($class_name, 'getInstance'));
+        } else {
+            $object =& new $class_name();
+        }
+
+        //  クラスキーに定められたクラスのインスタンスは
+        //  とりあえずキャッシュする
+        if (isset($this->object[$key]) == false || is_object($this->object[$key]) == false) {
+            $this->object[$key] =& $object;
+        }
+
+        return $object;
+    }
+
+    /**
+     *  クラスキーに対応するクラス名を返す
+     *
+     *  @access public
+     *  @param  string  $key    クラスキー
+     *  @return string  クラス名
+     */
+    function getObjectName($key)
+    {
+        if (isset($this->class[$key]) == false) {
+            return null;
+        }
+
+        return $this->class[$key];
+    }
+
+    /**
+     *  オブジェクト生成メソッド(backend)
+     *
+     *  @access protected
+     *  @param  string  $class_name     クラス名
+     *  @return object  生成されたオブジェクト(エラーならnull)
+     */
+    function &_getObject_Backend($class_name)
+    {
+        $_ret_object =& new $class_name($this->ctl);
+        return $_ret_object;
+    }
+
+    /**
+     *  オブジェクト生成メソッド(config)
+     *
+     *  @access protected
+     *  @param  string  $class_name     クラス名
+     *  @return object  生成されたオブジェクト(エラーならnull)
+     */
+    function &_getObject_Config($class_name)
+    {
+        $_ret_object =& new $class_name($this->ctl);
+        return $_ret_object;
+    }
+
+    /**
+     *  オブジェクト生成メソッド(i18n)
+     *
+     *  @access protected
+     *  @param  string  $class_name     クラス名
+     *  @return object  生成されたオブジェクト(エラーならnull)
+     */
+    function &_getObject_I18n($class_name)
+    {
+        $_ret_object =& new $class_name($this->ctl->getDirectory('locale'), $this->ctl->getAppId());
+        return $_ret_object;
+    }
+
+    /**
+     *  オブジェクト生成メソッド(logger)
+     *
+     *  @access protected
+     *  @param  string  $class_name     クラス名
+     *  @return object  生成されたオブジェクト(エラーならnull)
+     */
+    function &_getObject_Logger($class_name)
+    {
+        $_ret_object =& new $class_name($this->ctl);
+        return $_ret_object;
+    }
+
+    /**
+     *  オブジェクト生成メソッド(plugin)
+     *
+     *  @access protected
+     *  @param  string  $class_name     クラス名
+     *  @return object  生成されたオブジェクト(エラーならnull)
+     */
+    function &_getObject_Plugin($class_name)
+    {
+        $_ret_object =& new $class_name($this->ctl);
+        return $_ret_object;
+    }
+
+    /**
+     *  オブジェクト生成メソッド(renderer)
+     *
+     *  @access protected
+     *  @param  string  $class_name     クラス名
+     *  @return object  生成されたオブジェクト(エラーならnull)
+     */
+    function &_getObject_Renderer($class_name)
+    {
+        $_ret_object =& new $class_name($this->ctl);
+        return $_ret_object;
+    }
+
+    /**
+     *  オブジェクト生成メソッド(session)
+     *
+     *  @access protected
+     *  @param  string  $class_name     クラス名
+     *  @return object  生成されたオブジェクト(エラーならnull)
+     */
+    function &_getObject_Session($class_name)
+    {
+        $_ret_object =& new $class_name($this->ctl->getAppId(), $this->ctl->getDirectory('tmp'), $this->ctl->getLogger());
+        return $_ret_object;
+    }
+
+    /**
+     *  オブジェクト生成メソッド(sql)
+     *
+     *  @access protected
+     *  @param  string  $class_name     クラス名
+     *  @return object  生成されたオブジェクト(エラーならnull)
+     */
+    function &_getObject_Sql($class_name)
+    {
+        $_ret_object =& new $class_name($this->ctl);
+        return $_ret_object;
+    }
+
+    /**
+     *  指定されたクラスから想定されるファイルをincludeする
+     *
+     *  @access protected
+     */
+    function _include($class_name)
+    {
+        $file = sprintf("%s.%s", $class_name, $this->controller->getExt('php'));
+        if (file_exists_ex($file)) {
+            include_once $file;
+            return true;
+        }
+
+        if (preg_match('/^(\w+?)_(.*)/', $class_name, $match)) {
+            // try ethna app style
+            // App_Foo_Bar_Baz -> Foo/Bar/App_Foo_Bar_Baz.php
+            $tmp = explode("_", $match[2]);
+            $tmp[count($tmp)-1] = $class_name;
+            $file = sprintf('%s.%s',
+                            implode(DIRECTORY_SEPARATOR, $tmp),
+                            $this->controller->getExt('php'));
+            if (file_exists_ex($file)) {
+                include_once $file;
+                return true;
+            }
+
+            // try ethna app & pear mixed style
+            // App_Foo_Bar_Baz -> Foo/Bar/Baz.php
+            $file = sprintf('%s.%s',
+                            str_replace('_', DIRECTORY_SEPARATOR, $match[2]),
+                            $this->controller->getExt('php'));
+            if (file_exists_ex($file)) {
+                include_once $file;
+                return true;
+            }
+
+            // try ethna master style
+            // Ethna_Foo_Bar -> class/Ethna/Foo/Ethna_Foo_Bar.php
+            array_unshift($tmp, 'Ethna', 'class');
+            $file = sprintf('%s.%s',
+                            implode(DIRECTORY_SEPARATOR, $tmp),
+                            $this->controller->getExt('php'));
+            if (file_exists_ex($file)) {
+                include_once $file;
+                return true;
+            }
+
+            // try pear style
+            // Foo_Bar_Baz -> Foo/Bar/Baz.php
+            $file = sprintf('%s.%s',
+                            str_replace('_', DIRECTORY_SEPARATOR, $class_name),
+                            $this->controller->getExt('php'));
+            if (file_exists_ex($file)) {
+                include_once $file;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     *  指定されたクラスがキャッシュを利用可能かどうかをチェックする
+     *
+     *  @access protected
+     */
+    function _isCacheAvailable($class_name, $method_list, $weak)
+    {
+        // if we have getInstance(), use this anyway
+        if (in_array('getinstance', $method_list)) {
+            return false;
+        }
+
+        // if not, see if weak or not
+        return $weak ? false : true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Config.php b/Idea_Plugin_Extlib/class/Ethna_Config.php
new file mode 100644 (file)
index 0000000..c6e7af8
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Config.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Config
+/**
+ *  設定クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Config
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+    
+    /** @var    array   設定内容 */
+    var $config = null;
+
+    /**#@-*/
+
+
+    /**
+     *  Ethna_Configクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    controllerオブジェクト
+     */
+    function Ethna_Config(&$controller)
+    {
+        $this->controller =& $controller;
+
+        // 設定ファイルの読み込み
+        $r = $this->_getConfig();
+        if (Ethna::isError($r)) {
+            // この時点ではlogging等は出来ない(Loggerオブジェクトが生成されていない)
+            $fp = fopen("php://stderr", "r");
+            fputs($fp, sprintf("error occured while reading config file(s) [%s]\n"), $r->getInfo(0));
+            fclose($fp);
+            $this->controller->fatal();
+        }
+    }
+
+    /**
+     *  設定値へのアクセサ(R)
+     *
+     *  @access public
+     *  @param  string  $key    設定項目名
+     *  @return string  設定値
+     */
+    function get($key = null)
+    {
+        if (is_null($key)) {
+            return $this->config;
+        }
+        if (isset($this->config[$key]) == false) {
+            return null;
+        }
+        return $this->config[$key];
+    }
+
+    /**
+     *  設定値へのアクセサ(W)
+     *
+     *  @access public
+     *  @param  string  $key    設定項目名
+     *  @param  string  $value  設定値
+     */
+    function set($key, $value)
+    {
+        $this->config[$key] = $value;
+    }
+
+    /**
+     *  設定ファイルを更新する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function update()
+    {
+        return $this->_setConfig();
+    }
+
+    /**
+     *  設定ファイルを読み込む
+     *
+     *  @access private
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function _getConfig()
+    {
+        $config = array();
+        $file = $this->_getConfigFile();
+        if (file_exists($file)) {
+            $lh = Ethna_Util::lockFile($file, 'r');
+            if (Ethna::isError($lh)) {
+                return $lh;
+            }
+
+            include($file);
+
+            Ethna_Util::unlockFile($lh);
+        }
+
+        // デフォルト値設定
+        if (isset($_SERVER['HTTP_HOST']) && isset($config['url']) == false) {
+            $config['url'] = sprintf("http://%s/", $_SERVER['HTTP_HOST']);
+        }
+        if (isset($config['dsn']) == false) {
+            $config['dsn'] = "";
+        }
+        if (isset($config['log_facility']) == false) {
+            $config['log_facility'] = "";
+        }
+        if (isset($config['log_level']) == false) {
+            $config['log_level'] = "";
+        }
+        if (isset($config['log_option']) == false) {
+            $config['log_option'] = "";
+        }
+
+        $this->config = $config;
+
+        return 0;
+    }
+
+    /**
+     *  設定ファイルに書き込む
+     *
+     *  @access private
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function _setConfig()
+    {
+        $file = $this->_getConfigFile();
+
+        $lh = Ethna_Util::lockFile($file, 'w');
+        if (Ethna::isError($lh)) {
+            return $lh;
+        }
+
+        fwrite($lh, "<?php\n");
+        fwrite($lh, sprintf("/*\n * %s\n *\n * update: %s\n */\n", basename($file), strftime('%Y/%m/%d %H:%M:%S')));
+        fwrite($lh, "\$config = array(\n");
+        foreach ($this->config as $key => $value) {
+            $this->_setConfigValue($lh, $key, $value, 0);
+        }
+        fwrite($lh, ");\n?>\n");
+
+        Ethna_Util::unlockFile($lh);
+
+        return 0;
+    }
+
+    /**
+     *  設定ファイルに設定値を書き込む
+     *
+     *  @access private
+     */
+    function _setConfigValue($fp, $key, $value, $level)
+    {
+        fputs($fp, sprintf("%s'%s' => ", str_repeat("    ", $level+1), $key));
+        if (is_array($value)) {
+            fputs($fp, sprintf("array(\n"));
+            foreach ($value as $k => $v) {
+                $this->_setConfigValue($fp, $k, $v, $level+1);
+            }
+            fputs($fp, sprintf("%s),\n", str_repeat("    ", $level+1)));
+        } else {
+            fputs($fp, sprintf("'%s',\n", $value));
+        }
+    }
+
+    /**
+     *  設定ファイル名を取得する
+     *
+     *  @access private
+     *  @return string  設定ファイルへのフルパス名
+     */
+    function _getConfigFile()
+    {
+        return $this->controller->getDirectory('etc') . '/' . strtolower($this->controller->getAppId()) . '-ini.php';
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Controller.php b/Idea_Plugin_Extlib/class/Ethna_Controller.php
new file mode 100644 (file)
index 0000000..6f0659f
--- /dev/null
@@ -0,0 +1,2279 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Controller.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Controller
+/**
+ *  コントローラクラス
+ *
+ *  @todo       gatewayでswitchしてるところがダサダサ
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Controller
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    string      アプリケーションID */
+    var $appid = 'ETHNA';
+
+    /** @var    string      アプリケーションベースディレクトリ */
+    var $base = '';
+
+    /** @var    string      アプリケーションベースURL */
+    var $url = '';
+
+    /** @var    string      アプリケーションDSN(Data Source Name) */
+    var $dsn;
+
+    /** @var    array       アプリケーションディレクトリ */
+    var $directory = array();
+
+    /** @var    array       アプリケーションディレクトリ(デフォルト) */
+    var $directory_default = array(
+        'action'        => 'app/action',
+        'action_cli'    => 'app/action_cli',
+        'action_xmlrpc' => 'app/action_xmlrpc',
+        'app'           => 'app',
+        'plugin'        => 'app/plugin',
+        'bin'           => 'bin',
+        'etc'           => 'etc',
+        'filter'        => 'app/filter',
+        'locale'        => 'locale',
+        'log'           => 'log',
+        'plugins'       => array(),
+        'template'      => 'template',
+        'template_c'    => 'tmp',
+        'tmp'           => 'tmp',
+        'view'          => 'app/view',
+        'www'           => 'www',
+        'test'          => 'app/test',
+    );
+
+    /** @var    array       DBアクセス定義 */
+    var $db = array(
+        ''              => DB_TYPE_RW,
+    );
+
+    /** @var    array       拡張子設定 */
+    var $ext = array(
+        'php'           => 'php',
+        'tpl'           => 'tpl',
+    );
+
+    /** @var    array       クラス設定 */
+    var $class = array();
+
+    /** @var    array       クラス設定(デフォルト) */
+    var $class_default = array(
+        'class'         => 'Ethna_ClassFactory',
+        'backend'       => 'Ethna_Backend',
+        'config'        => 'Ethna_Config',
+        'db'            => 'Ethna_DB',
+        'error'         => 'Ethna_ActionError',
+        'form'          => 'Ethna_ActionForm',
+        'i18n'          => 'Ethna_I18N',
+        'logger'        => 'Ethna_Logger',
+        'plugin'        => 'Ethna_Plugin',
+        'renderer'      => 'Ethna_Renderer_Smarty',
+        'session'       => 'Ethna_Session',
+        'sql'           => 'Ethna_AppSQL',
+        'view'          => 'Ethna_ViewClass',
+        'url_handler'   => 'Ethna_UrlHandler',
+    );
+
+    /** @var    array       検索対象となるプラグインのアプリケーションIDのリスト */
+    var $plugin_search_appids;
+
+    /** @var    array       フィルタ設定 */
+    var $filter = array(
+    );
+
+    /** @var    string      使用ロケール設定 */
+    var $locale;
+
+    /** @var    string      システム側エンコーディング */
+    var $system_encoding;
+
+    /** @var    string      クライアント側エンコーディング */
+    /**                     ブラウザからのエンコーディングを指す  */
+    var $client_encoding;
+
+    /** @var    string  現在実行中のアクション名 */
+    var $action_name;
+
+    /** @var    string  現在実行中のXMLRPCメソッド名 */
+    var $xmlrpc_method_name;
+
+    /** @var    array   forward定義 */
+    var $forward = array();
+
+    /** @var    array   action定義 */
+    var $action = array();
+
+    /** @var    array   action(CLI)定義 */
+    var $action_cli = array();
+
+    /** @var    array   action(XMLRPC)定義 */
+    var $action_xmlrpc = array();
+
+    /** @var    array   アプリケーションマネージャ定義 */
+    var $manager = array();
+
+    /** @var    object  レンダラー */
+    var $renderer = null;
+
+    /** @var    array   smarty modifier定義 */
+    var $smarty_modifier_plugin = array();
+
+    /** @var    array   smarty function定義 */
+    var $smarty_function_plugin = array();
+
+    /** @var    array   smarty block定義 */
+    var $smarty_block_plugin = array();
+
+    /** @var    array   smarty prefilter定義 */
+    var $smarty_prefilter_plugin = array();
+
+    /** @var    array   smarty postfilter定義 */
+    var $smarty_postfilter_plugin = array();
+
+    /** @var    array   smarty outputfilter定義 */
+    var $smarty_outputfilter_plugin = array();
+
+
+    /** @var    array   フィルターチェイン(Ethna_Filterオブジェクトの配列) */
+    var $filter_chain = array();
+
+    /** @var    object  Ethna_ClassFactory  クラスファクトリオブジェクト */
+    var $class_factory = null;
+
+    /** @var    object  Ethna_ActionForm    フォームオブジェクト */
+    var $action_form = null;
+
+    /** @var    object  Ethna_View          ビューオブジェクト */
+    var $view = null;
+
+    /** @var    object  Ethna_Config        設定オブジェクト */
+    var $config = null;
+
+    /** @var    object  Ethna_Logger        ログオブジェクト */
+    var $logger = null;
+
+    /** @var    object  Ethna_Plugin        プラグインオブジェクト */
+    var $plugin = null;
+
+    /** @var    string  リクエストのゲートウェイ(www/cli/rest/xmlrpc/soap...) */
+    var $gateway = GATEWAY_WWW;
+
+    /**#@-*/
+
+
+    /**
+     *  Ethna_Controllerクラスのコンストラクタ
+     *
+     *  @access     public
+     */
+    function Ethna_Controller($gateway = GATEWAY_WWW)
+    {
+        $GLOBALS['_Ethna_controller'] =& $this;
+        if ($this->base === "") {
+            // EthnaコマンドなどでBASEが定義されていない場合がある
+            if (defined('BASE')) {
+                $this->base = BASE;
+            }
+        }
+
+        $this->gateway = $gateway;
+
+        // クラス設定の未定義値を補完
+        foreach ($this->class_default as $key => $val) {
+            if (isset($this->class[$key]) == false) {
+                $this->class[$key] = $val;
+            }
+        }
+
+        // ディレクトリ設定の未定義値を補完
+        foreach ($this->directory_default as $key => $val) {
+            if (isset($this->directory[$key]) == false) {
+                $this->directory[$key] = $val;
+            }
+        }
+
+        // クラスファクトリオブジェクトの生成
+        $class_factory = $this->class['class'];
+        $this->class_factory =& new $class_factory($this, $this->class);
+
+        // エラーハンドラの設定
+        Ethna::setErrorCallback(array(&$this, 'handleError'));
+
+        // ディレクトリ名の設定(相対パス->絶対パス)
+        foreach ($this->directory as $key => $value) {
+            if ($key == 'plugins') {
+                // Smartyプラグインディレクトリは配列で指定する
+                $tmp = array();
+                foreach (to_array($value) as $elt) {
+                    if (Ethna_Util::isAbsolute($elt) == false) {
+                        $tmp[] = $this->base . (empty($this->base) ? '' : '/') . $elt;
+                    }
+                }
+                $this->directory[$key] = $tmp;
+            } else {
+                if (Ethna_Util::isAbsolute($value) == false) {
+                    $this->directory[$key] = $this->base . (empty($this->base) ? '' : '/') . $value;
+                }
+            }
+        }
+
+        // 初期設定
+        // フレームワークとしての内部エンコーディングはクライアント
+        // エンコーディング(=ブラウザからのエンコーディング)
+        //
+        // @see Ethna_Controller#_getDefaultLanguage
+        list($this->locale, $this->system_encoding, $this->client_encoding) = $this->_getDefaultLanguage();
+        if (mb_enabled()) {
+            mb_internal_encoding($this->client_encoding);
+            mb_regex_encoding($this->client_encoding);
+        }
+        $this->config =& $this->getConfig();
+        $this->dsn = $this->_prepareDSN();
+        $this->url = $this->config->get('url');
+
+        // プラグインオブジェクトの用意
+        $this->plugin =& $this->getPlugin();
+
+        //// assert (experimental)
+        //if ($this->config->get('debug') === false) {
+        //    ini_set('assert.active', 0);
+        //}
+
+        // ログ出力開始
+        $this->logger =& $this->getLogger();
+        $this->plugin->setLogger($this->logger);
+        $this->logger->begin();
+
+        // Ethnaマネージャ設定
+        $this->_activateEthnaManager();
+    }
+
+    /**
+     *  アプリケーション実行後の後始末を行います。 
+     *
+     *  @access protected 
+     */
+    function end()
+    {
+        //  必要に応じてオーバライドして下さい。
+        $this->logger->end();    
+    }
+
+    /**
+     *  (現在アクティブな)コントローラのインスタンスを返す
+     *
+     *  @access public
+     *  @return object  Ethna_Controller    コントローラのインスタンス
+     *  @static
+     */
+    function &getInstance()
+    {
+        if (isset($GLOBALS['_Ethna_controller'])) {
+            return $GLOBALS['_Ethna_controller'];
+        } else {
+            $_ret_object = null;
+            return $_ret_object;
+        }
+    }
+
+    /**
+     *  アプリケーションIDを返す
+     *
+     *  @access public
+     *  @return string  アプリケーションID
+     */
+    function getAppId()
+    {
+        return ucfirst(strtolower($this->appid));
+    }
+
+    /**
+     *  アプリケーションIDをチェックする
+     *
+     *  @access public
+     *  @param  string  $id     アプリケーションID
+     *  @return mixed   true:OK Ethna_Error:NG
+     *  @static
+     */
+    function &checkAppId($id)
+    {
+        $true = true;
+        if (strcasecmp($id, 'ethna') === 0
+            || strcasecmp($id, 'app') === 0) {
+            return Ethna::raiseError("Application Id [$id] is reserved\n");
+        }
+
+        //    アプリケーションIDはクラス名のprefixともなるため、
+        //    数字で始まっていてはいけない
+        //    @see http://www.php.net/manual/en/language.variables.php
+        if (preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $id) === 0) {
+            $msg = (preg_match('/^[0-9]$/', $id[0]))
+                 ? "Application ID must NOT start with Number.\n"
+                 : "Only Numeric(0-9) and Alphabetical(A-Z) is allowed for Application Id\n";
+            return Ethna::raiseError($msg);
+        }
+        return $true;
+    }
+
+    /**
+     *  アクション名をチェックする
+     *
+     *  @access public
+     *  @param  string  $action_name    アクション名
+     *  @return mixed   true:OK Ethna_Error:NG
+     *  @static
+     */
+    function &checkActionName($action_name)
+    {
+        $true = true;
+        if (preg_match('/^[a-zA-Z\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/',
+                       $action_name) === 0) {
+            return Ethna::raiseError("invalid action name [$action_name]");
+        }
+        return $true;
+    }
+
+    /**
+     *  ビュー名をチェックする
+     *
+     *  @access public
+     *  @param  string  $view_name    ビュー名
+     *  @return mixed   true:OK Ethna_Error:NG
+     *  @static
+     */
+    function &checkViewName($view_name)
+    {
+        $true = true;
+        if (preg_match('/^[a-zA-Z\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/',
+                       $view_name) === 0) {
+            return Ethna::raiseError("invalid view name [$view_name]");
+        }
+        return $true;
+    }
+
+    /**
+     *  DSNを返す
+     *
+     *  @access public
+     *  @param  string  $db_key DBキー
+     *  @return string  DSN
+     */
+    function getDSN($db_key = "")
+    {
+        if (isset($this->dsn[$db_key]) == false) {
+            return null;
+        }
+        return $this->dsn[$db_key];
+    }
+
+    /**
+     *  DSNの持続接続設定を返す
+     *
+     *  @access public
+     *  @param  string  $db_key DBキー
+     *  @return bool    true:persistent false:non-persistent(あるいは設定無し)
+     */
+    function getDSN_persistent($db_key = "")
+    {
+        $key = sprintf("dsn%s_persistent", $db_key == "" ? "" : "_$db_key");
+
+        $dsn_persistent = $this->config->get($key);
+        if (is_null($dsn_persistent)) {
+            return false;
+        }
+        return $dsn_persistent;
+    }
+
+    /**
+     *  DB設定を返す
+     *
+     *  @access public
+     *  @param  string  $db_key DBキー("", "r", "rw", "default", "blog_r"...)
+     *  @return string  $db_keyに対応するDB種別定義(設定が無い場合はnull)
+     */
+    function getDBType($db_key = null)
+    {
+        if (is_null($db_key)) {
+            // 一覧を返す
+            return $this->db;
+        }
+
+        if (isset($this->db[$db_key]) == false) {
+            return null;
+        }
+        return $this->db[$db_key];
+    }
+
+    /**
+     *  アプリケーションベースURLを返す
+     *
+     *  @access public
+     *  @return string  アプリケーションベースURL
+     */
+    function getURL()
+    {
+        return $this->url;
+    }
+
+    /**
+     *  アプリケーションベースディレクトリを返す
+     *
+     *  @access public
+     *  @return string  アプリケーションベースディレクトリ
+     */
+    function getBasedir()
+    {
+        return $this->base;
+    }
+
+    /**
+     *  クライアントタイプ/言語からテンプレートディレクトリ名を決定する
+     *  デフォルトでは [appid]/template/ja_JP/ (ja_JPはロケール名)
+     *  ロケール名は _getDefaultLanguage で決定される。
+     *
+     *  @access public
+     *  @return string  テンプレートディレクトリ
+     *  @see    Ethna_Controller#_getDefaultLanguage
+     */
+    function getTemplatedir()
+    {
+        $template = $this->getDirectory('template');
+
+        // 言語別ディレクトリ
+        // _getDerfaultLanguageメソッドでロケールが指定されていた場合は、
+        // テンプレートディレクトリにも自動的にそれを付加する。
+        if (!empty($this->locale)) {
+            $template .= '/' . $this->locale;
+        } 
+
+        return $template;
+    }
+
+    /**
+     *  アクションディレクトリ名を決定する
+     *
+     *  @access public
+     *  @return string  アクションディレクトリ
+     */
+    function getActiondir($gateway = null)
+    {
+        $key = 'action';
+        $gateway = is_null($gateway) ? $this->getGateway() : $gateway;
+        switch ($gateway) {
+        case GATEWAY_WWW:
+            $key = 'action';
+            break;
+        case GATEWAY_CLI:
+            $key = 'action_cli';
+            break;
+        case GATEWAY_XMLRPC:
+            $key = 'action_xmlrpc';
+            break;
+        }
+
+        return (empty($this->directory[$key]) ? ($this->base . (empty($this->base) ? '' : '/')) : ($this->directory[$key] . "/"));
+    }
+
+    /**
+     *  ビューディレクトリ名を決定する
+     *
+     *  @access public
+     *  @return string  ビューディレクトリ
+     */
+    function getViewdir()
+    {
+        return (empty($this->directory['view']) ? ($this->base . (empty($this->base) ? '' : '/')) : ($this->directory['view'] . "/"));
+    }
+
+    /**
+     *  (action,view以外の)テストケースを置くディレクトリ名を決定する
+     *
+     *  @access public
+     *  @return string  テストケースを置くディレクトリ
+     */
+    function getTestdir()
+    {
+        return (empty($this->directory['test']) ? ($this->base . (empty($this->base) ? '' : '/')) : ($this->directory['test'] . "/"));
+    }
+
+    /**
+     *  アプリケーションディレクトリ設定を返す
+     *
+     *  @access public
+     *  @param  string  $key    ディレクトリタイプ("tmp", "template"...)
+     *  @return string  $keyに対応したアプリケーションディレクトリ(設定が無い場合はnull)
+     */
+    function getDirectory($key)
+    {
+        if (isset($this->directory[$key]) == false) {
+            return null;
+        }
+        return $this->directory[$key];
+    }
+
+    /**
+     *  アプリケーション拡張子設定を返す
+     *
+     *  @access public
+     *  @param  string  $key    拡張子タイプ("php", "tpl"...)
+     *  @return string  $keyに対応した拡張子(設定が無い場合はnull)
+     */
+    function getExt($key)
+    {
+        if (isset($this->ext[$key]) == false) {
+            return null;
+        }
+        return $this->ext[$key];
+    }
+
+    /**
+     *  クラスファクトリオブジェクトのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_ClassFactory  クラスファクトリオブジェクト
+     */
+    function &getClassFactory()
+    {
+        return $this->class_factory;
+    }
+
+    /**
+     *  アクションエラーオブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_ActionError   アクションエラーオブジェクト
+     */
+    function &getActionError()
+    {
+        return $this->class_factory->getObject('error');
+    }
+
+    /**
+     *  アクションフォームオブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_ActionForm    アクションフォームオブジェクト
+     */
+    function &getActionForm()
+    {
+        // 明示的にクラスファクトリを利用していない
+        return $this->action_form;
+    }
+
+    /**
+     *  ビューオブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_View          ビューオブジェクト
+     */
+    function &getView()
+    {
+        // 明示的にクラスファクトリを利用していない
+        return $this->view;
+    }
+
+    /**
+     *  backendオブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_Backend   backendオブジェクト
+     */
+    function &getBackend()
+    {
+        return $this->class_factory->getObject('backend');
+    }
+
+    /**
+     *  設定オブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_Config    設定オブジェクト
+     */
+    function &getConfig()
+    {
+        return $this->class_factory->getObject('config');
+    }
+
+    /**
+     *  i18nオブジェクトのアクセサ(R)
+     *
+     *  @access public
+     *  @return object  Ethna_I18N  i18nオブジェクト
+     */
+    function &getI18N()
+    {
+        return $this->class_factory->getObject('i18n');
+    }
+
+    /**
+     *  ログオブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_Logger        ログオブジェクト
+     */
+    function &getLogger()
+    {
+        return $this->class_factory->getObject('logger');
+    }
+
+    /**
+     *  セッションオブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_Session       セッションオブジェクト
+     */
+    function &getSession()
+    {
+        return $this->class_factory->getObject('session');
+    }
+
+    /**
+     *  SQLオブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_AppSQL    SQLオブジェクト
+     */
+    function &getSQL()
+    {
+        return $this->class_factory->getObject('sql');
+    }
+
+    /**
+     *  プラグインオブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_Plugin    プラグインオブジェクト
+     */
+    function &getPlugin()
+    {
+        return $this->class_factory->getObject('plugin');
+    }
+
+    /**
+     *  URLハンドラオブジェクトのアクセサ
+     *
+     *  @access public
+     *  @return object  Ethna_UrlHandler    URLハンドラオブジェクト
+     */
+    function &getUrlHandler()
+    {
+        return $this->class_factory->getObject('url_handler');
+    }
+
+    /**
+     *  マネージャ一覧を返す
+     *
+     *  @access public
+     *  @return array   マネージャ一覧
+     *  @obsolete
+     */
+    function getManagerList()
+    {
+        return $this->manager;
+    }
+
+    /**
+     *  実行中のアクション名を返す
+     *
+     *  @access public
+     *  @return string  実行中のアクション名
+     */
+    function getCurrentActionName()
+    {
+        return $this->action_name;
+    }
+
+    /**
+     *  実行中のXMLRPCメソッド名を返す
+     *
+     *  @access public
+     *  @return string  実行中のXMLRPCメソッド名
+     */
+    function getXmlrpcMethodName()
+    {
+        return $this->xmlrpc_method_name;
+    }
+
+    /**
+     *  ロケール設定、使用言語を取得する
+     *
+     *  @access public
+     *  @return array   ロケール名(e.x ja_JP, en_US 等),
+     *                  システムエンコーディング名,
+     *                  クライアントエンコーディング名 の配列
+     *                  (ロケール名は、ll_cc の形式。ll = 言語コード cc = 国コード)
+     *  @see http://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html 
+     */
+    function getLanguage()
+    {
+        return array($this->locale, $this->system_encoding, $this->client_encoding);
+    }
+
+    /**
+     *  ロケール名へのアクセサ(R)
+     *
+     *  @access public
+     *  @return string  ロケール名(e.x ja_JP, en_US 等),
+     *                  (ロケール名は、ll_cc の形式。ll = 言語コード cc = 国コード)
+     */
+    function getLocale()
+    {
+        return $this->locale;
+    }
+
+    /**
+     *  ロケール名へのアクセサ(W)
+     *
+     *  @access public
+     *  @param $locale ロケール名(e.x ja_JP, en_US 等),
+     *                 (ロケール名は、ll_cc の形式。ll = 言語コード cc = 国コード)
+     */
+    function setLocale($locale)
+    {
+        $this->locale = $locale;
+        $i18n =& $this->getI18N();
+        $i18n->setLanguage($this->locale, $this->system_encoding, $this->client_encoding);
+    }
+
+    /**
+     *  クライアントエンコーディング名へのアクセサ(R)
+     *
+     *  @access public
+     *  @return string  $client_encoding クライアントエンコーディング名
+     */
+    function getClientEncoding()
+    {
+        return $this->client_encoding;
+    }
+
+    /**
+     *  クライアントエンコーディング名へのアクセサ(W)
+     *
+     *  @access public
+     *  @param  string  $client_encoding クライアントエンコーディング名
+     */
+    function setClientEncoding($client_encoding)
+    {
+        $this->client_encoding = $client_encoding;
+        $i18n =& $this->getI18N();
+        $i18n->setLanguage($this->locale, $this->system_encoding, $this->client_encoding);
+    }
+
+    /**
+     *  ゲートウェイを取得する
+     *
+     *  @access public
+     */
+    function getGateway()
+    {
+        return $this->gateway;
+    }
+
+    /**
+     *  ゲートウェイモードを設定する
+     *
+     *  @access public
+     */
+    function setGateway($gateway)
+    {
+        $this->gateway = $gateway;
+    }
+
+    /**
+     *  アプリケーションのエントリポイント
+     *
+     *  @access public
+     *  @param  string  $class_name     アプリケーションコントローラのクラス名
+     *  @param  mixed   $action_name    指定のアクション名(省略可)
+     *  @param  mixed   $fallback_action_name   アクションが決定できなかった場合に実行されるアクション名(省略可)
+     *  @static
+     */
+    function main($class_name, $action_name = "", $fallback_action_name = "")
+    {
+        $c =& new $class_name;
+        $c->trigger($action_name, $fallback_action_name);
+        $c->end();
+    }
+
+    /**
+     *  CLIアプリケーションのエントリポイント
+     *
+     *  @access public
+     *  @param  string  $class_name     アプリケーションコントローラのクラス名
+     *  @param  string  $action_name    実行するアクション名
+     *  @param  bool    $enable_filter  フィルタチェインを有効にするかどうか
+     *  @static
+     */
+    function main_CLI($class_name, $action_name, $enable_filter = true)
+    {
+        $c =& new $class_name(GATEWAY_CLI);
+        $c->action_cli[$action_name] = array();
+        $c->trigger($action_name, "", $enable_filter);
+        $c->end();
+    }
+
+    /**
+     *  XMLRPCアプリケーションのエントリポイント
+     *
+     *  @access public
+     *  @static
+     */
+    function main_XMLRPC($class_name)
+    {
+        if (extension_loaded('xmlrpc') == false) {
+            die("xmlrpc extension is required to enable this gateway");
+        }
+
+        $c =& new $class_name(GATEWAY_XMLRPC);
+        $c->trigger("", "", false);
+        $c->end();
+    }
+
+    /**
+     *  SOAPアプリケーションのエントリポイント
+     *
+     *  @access public
+     *  @param  string  $class_name     アプリケーションコントローラのクラス名
+     *  @param  mixed   $action_name    指定のアクション名(省略可)
+     *  @param  mixed   $fallback_action_name   アクションが決定できなかった場合に実行されるアクション名(省略可)
+     *  @static
+     */
+    function main_SOAP($class_name, $action_name = "", $fallback_action_name = "")
+    {
+        $c =& new $class_name(GATEWAY_SOAP);
+        $c->trigger($action_name, $fallback_action_name);
+        $c->end();
+    }
+
+    /**
+     *  フレームワークの処理を開始する
+     *
+     *  @access public
+     *  @param  mixed   $default_action_name    指定のアクション名
+     *  @param  mixed   $fallback_action_name   アクション名が決定できなかった場合に実行されるアクション名
+     *  @param  bool    $enable_filter  フィルタチェインを有効にするかどうか
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function trigger($default_action_name = "", $fallback_action_name = "", $enable_filter = true)
+    {
+        // フィルターの生成
+        if ($enable_filter) {
+            $this->_createFilterChain();
+        }
+
+        // 実行前フィルタ
+        for ($i = 0; $i < count($this->filter_chain); $i++) {
+            $r = $this->filter_chain[$i]->preFilter();
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+        }
+
+        // trigger
+        switch ($this->getGateway()) {
+        case GATEWAY_WWW:
+            $this->_trigger_WWW($default_action_name, $fallback_action_name);
+            break;
+        case GATEWAY_CLI:
+            $this->_trigger_CLI($default_action_name);
+            break;
+        case GATEWAY_XMLRPC:
+            $this->_trigger_XMLRPC();
+            break;
+        case GATEWAY_SOAP:
+            $this->_trigger_SOAP();
+            break;
+        }
+
+        // 実行後フィルタ
+        for ($i = count($this->filter_chain) - 1; $i >= 0; $i--) {
+            $r = $this->filter_chain[$i]->postFilter();
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+        }
+    }
+
+    /**
+     *  フレームワークの処理を実行する(WWW)
+     *
+     *  引数$default_action_nameに配列が指定された場合、その配列で指定された
+     *  アクション以外は受け付けない(指定されていないアクションが指定された
+     *  場合、配列の先頭で指定されたアクションが実行される)
+     *
+     *  @access private
+     *  @param  mixed   $default_action_name    指定のアクション名
+     *  @param  mixed   $fallback_action_name   アクション名が決定できなかった場合に実行されるアクション名
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function _trigger_WWW($default_action_name = "", $fallback_action_name = "")
+    {
+        // アクション名の取得
+        $action_name = $this->_getActionName($default_action_name, $fallback_action_name);
+
+        // マネージャ実行チェック
+        $this->_ethnaManagerEnabledCheck($action_name);
+
+        // アクション定義の取得
+        $action_obj =& $this->_getAction($action_name);
+        if (is_null($action_obj)) {
+            if ($fallback_action_name != "") {
+                $this->logger->log(LOG_DEBUG, 'undefined action [%s] -> try fallback action [%s]', $action_name, $fallback_action_name);
+                $action_obj =& $this->_getAction($fallback_action_name);
+            }
+            if (is_null($action_obj)) {
+                return Ethna::raiseError("undefined action [%s]", E_APP_UNDEFINED_ACTION, $action_name);
+            } else {
+                $action_name = $fallback_action_name;
+            }
+        }
+
+        // アクション実行前フィルタ
+        for ($i = 0; $i < count($this->filter_chain); $i++) {
+            $r = $this->filter_chain[$i]->preActionFilter($action_name);
+            if ($r != null) {
+                $this->logger->log(LOG_DEBUG, 'action [%s] -> [%s] by %s', $action_name, $r, get_class($this->filter_chain[$i]));
+                $action_name = $r;
+            }
+        }
+        $this->action_name = $action_name;
+
+        // オブジェクト生成
+        $backend =& $this->getBackend();
+        $session =& $this->getSession();
+        $session->restore();
+
+        // 言語切り替えフックを呼ぶ
+        $this->_setLanguage($this->locale, $this->system_encoding, $this->client_encoding);
+
+        // アクションフォーム初期化
+        // フォーム定義、フォーム値設定
+        $form_name = $this->getActionFormName($action_name);
+        $this->action_form =& new $form_name($this);
+        $this->action_form->setFormDef_PreHelper();
+        $this->action_form->setFormVars();
+        $backend->setActionForm($this->action_form);
+
+        // バックエンド処理実行
+        $forward_name = $backend->perform($action_name);
+
+        // アクション実行後フィルタ
+        for ($i = count($this->filter_chain) - 1; $i >= 0; $i--) {
+            $r = $this->filter_chain[$i]->postActionFilter($action_name, $forward_name);
+            if ($r != null) {
+                $this->logger->log(LOG_DEBUG, 'forward [%s] -> [%s] by %s', $forward_name, $r, get_class($this->filter_chain[$i]));
+                $forward_name = $r;
+            }
+        }
+
+        // コントローラで遷移先を決定する(オプション)
+        $forward_name = $this->_sortForward($action_name, $forward_name);
+
+        if ($forward_name != null) {
+            $view_class_name = $this->getViewClassName($forward_name);
+            $this->view =& new $view_class_name($backend, $forward_name, $this->_getForwardPath($forward_name));
+            $this->view->preforward();
+            $this->view->forward();
+        }
+
+        return 0;
+    }
+
+    /**
+     *  フレームワークの処理を実行する(CLI)
+     *
+     *  @access private
+     *  @param  mixed   $default_action_name    指定のアクション名
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function _trigger_CLI($default_action_name = "")
+    {
+        return $this->_trigger_WWW($default_action_name);
+    }
+
+    /**
+     *  フレームワークの処理を実行する(XMLRPC)
+     *
+     *  @access private
+     *  @param  mixed   $action_name    指定のアクション名
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function _trigger_XMLRPC($action_name = "")
+    {
+        // prepare xmlrpc server
+        $xmlrpc_gateway_method_name = "_Ethna_XmlrpcGateway";
+        $xmlrpc_server = xmlrpc_server_create();
+
+        $method = null;
+        $param = xmlrpc_decode_request(file_get_contents('php://input'), $method);
+        $this->xmlrpc_method_name = $method;
+
+        $request = xmlrpc_encode_request(
+            $xmlrpc_gateway_method_name,
+            $param,
+            array(
+                'output_type'   => 'xml',
+                'verbosity'     => 'pretty',
+                'escaping'      => array('markup'),
+                'version'       => 'xmlrpc',
+                'encoding'      => 'utf-8'
+            )
+        ); 
+
+        xmlrpc_server_register_method(
+            $xmlrpc_server,
+            $xmlrpc_gateway_method_name,
+            $xmlrpc_gateway_method_name
+        );
+
+        // send request
+        $r = xmlrpc_server_call_method(
+            $xmlrpc_server,
+            $request,
+            null,
+            array(
+                'output_type'   => 'xml',
+                'verbosity'     => 'pretty',
+                'escaping'      => array('markup'),
+                'version'       => 'xmlrpc',
+                'encoding'      => 'utf-8'
+            )
+        );
+
+        header('Content-Length: ' . strlen($r));
+        header('Content-Type: text/xml; charset=UTF-8');
+        print $r;
+    }
+
+    /**
+     *  _trigger_XMLRPCのコールバックメソッド
+     *
+     *  @access public
+     */
+    function trigger_XMLRPC($method, $param)
+    {
+        // アクション定義の取得
+        $action_obj =& $this->_getAction($method);
+        if (is_null($action_obj)) {
+            return Ethna::raiseError("undefined xmlrpc method [%s]", E_APP_UNDEFINED_ACTION, $method);
+        }
+
+        // オブジェクト生成
+        $backend =& $this->getBackend();
+
+        $form_name = $this->getActionFormName($method);
+        $this->action_form =& new $form_name($this);
+        $def = $this->action_form->getDef();
+        $n = 0;
+        foreach ($def as $key => $value) {
+            if (isset($param[$n]) == false) {
+                $this->action_form->set($key, null);
+            } else {
+                $this->action_form->set($key, $param[$n]);
+            }
+            $n++;
+        }
+
+        // バックエンド処理実行
+        $backend->setActionForm($this->action_form);
+
+        $session =& $this->getSession();
+        $session->restore();
+        $r = $backend->perform($method);
+
+        return $r;
+    }
+
+    /**
+     *  SOAPフレームワークの処理を実行する
+     *
+     *  @access private
+     */
+    function _trigger_SOAP()
+    {
+        // SOAPエントリクラス
+        $gg =& new Ethna_SOAP_GatewayGenerator();
+        $script = $gg->generate();
+        eval($script);
+
+        // SOAPリクエスト処理
+        $server =& new SoapServer(null, array('uri' => $this->config->get('url')));
+        $server->setClass($gg->getClassName());
+        $server->handle();
+    }
+
+    /**
+     *  エラーハンドラ
+     *
+     *  エラー発生時の追加処理を行いたい場合はこのメソッドをオーバーライドする
+     *  (アラートメール送信等−デフォルトではログ出力時にアラートメール
+     *  が送信されるが、エラー発生時に別にアラートメールをここで送信
+     *  させることも可能)
+     *
+     *  @access public
+     *  @param  object  Ethna_Error     エラーオブジェクト
+     */
+    function handleError(&$error)
+    {
+        // ログ出力
+        list ($log_level, $dummy) = $this->logger->errorLevelToLogLevel($error->getLevel());
+        $message = $error->getMessage();
+        $this->logger->log($log_level, sprintf("%s [ERROR CODE(%d)]", $message, $error->getCode()));
+    }
+
+    /**
+     *  エラーメッセージを取得する
+     *
+     *  @access public
+     *  @param  int     $code       エラーコード
+     *  @return string  エラーメッセージ
+     */
+    function getErrorMessage($code)
+    {
+        $message_list =& $GLOBALS['_Ethna_error_message_list'];
+        for ($i = count($message_list)-1; $i >= 0; $i--) {
+            if (array_key_exists($code, $message_list[$i])) {
+                return $message_list[$i][$code];
+            }
+        }
+        return null;
+    }
+
+    /**
+     *  実行するアクション名を返す
+     *
+     *  @access private
+     *  @param  mixed   $default_action_name    指定のアクション名
+     *  @return string  実行するアクション名
+     */
+    function _getActionName($default_action_name, $fallback_action_name)
+    {
+        // フォームから要求されたアクション名を取得する
+        $form_action_name = $this->_getActionName_Form();
+        $form_action_name = preg_replace('/[^a-z0-9\-_]+/i', '', $form_action_name);
+        $this->logger->log(LOG_DEBUG, 'form_action_name[%s]', $form_action_name);
+
+        // Ethnaマネージャへのフォームからのリクエストは拒否
+        if ($form_action_name == "__ethna_info__" ||
+            $form_action_name == "__ethna_unittest__") {
+            $form_action_name = "";
+        }
+
+        // フォームからの指定が無い場合はエントリポイントに指定されたデフォルト値を利用する
+        if ($form_action_name == "" && count($default_action_name) > 0) {
+            $tmp = is_array($default_action_name) ? $default_action_name[0] : $default_action_name;
+            if ($tmp{strlen($tmp)-1} == '*') {
+                $tmp = substr($tmp, 0, -1);
+            }
+            $this->logger->log(LOG_DEBUG, '-> default_action_name[%s]', $tmp);
+            $action_name = $tmp;
+        } else {
+            $action_name = $form_action_name;
+        }
+
+        // エントリポイントに配列が指定されている場合は指定以外のアクション名は拒否する
+        if (is_array($default_action_name)) {
+            if ($this->_isAcceptableActionName($action_name, $default_action_name) == false) {
+                // 指定以外のアクション名で合った場合は$fallback_action_name(or デフォルト)
+                $tmp = $fallback_action_name != "" ? $fallback_action_name : $default_action_name[0];
+                if ($tmp{strlen($tmp)-1} == '*') {
+                    $tmp = substr($tmp, 0, -1);
+                }
+                $this->logger->log(LOG_DEBUG, '-> fallback_action_name[%s]', $tmp);
+                $action_name = $tmp;
+            }
+        }
+
+        $this->logger->log(LOG_DEBUG, '<<< action_name[%s] >>>', $action_name);
+
+        return $action_name;
+    }
+
+    /**
+     *  フォームにより要求されたアクション名を返す
+     *
+     *  アプリケーションの性質に応じてこのメソッドをオーバーライドして下さい。
+     *  デフォルトでは"action_"で始まるフォーム値の"action_"の部分を除いたもの
+     *  ("action_sample"なら"sample")がアクション名として扱われます
+     *
+     *  @access protected
+     *  @return string  フォームにより要求されたアクション名
+     */
+    function _getActionName_Form()
+    {
+        if (isset($_SERVER['REQUEST_METHOD']) == false) {
+            return null;
+        }
+
+        $url_handler =& $this->getUrlHandler();
+        if ($_SERVER['REQUEST_METHOD'] == "GET") {
+            $tmp_vars = $_GET;
+        } else if ($_SERVER['REQUEST_METHOD'] == "POST") {
+            $tmp_vars = $_POST;
+        }
+
+        if (empty($_SERVER['URL_HANDLER']) == false) {
+            $tmp_vars['__url_handler__'] = $_SERVER['URL_HANDLER'];
+            $tmp_vars['__url_info__'] = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : null;
+            $tmp_vars = $url_handler->requestToAction($tmp_vars);
+
+            if ($_SERVER['REQUEST_METHOD'] == "GET") {
+                $_GET = array_merge($_GET, $tmp_vars);
+            } else if ($_SERVER['REQUEST_METHOD'] == "POST") {
+                $_POST = array_merge($_POST, $tmp_vars);
+            }
+            $_REQUEST = array_merge($_REQUEST, $tmp_vars);
+        }
+
+        if (strcasecmp($_SERVER['REQUEST_METHOD'], 'post') == 0) {
+            $http_vars =& $_POST;
+        } else {
+            $http_vars =& $_GET;
+        }
+
+        // フォーム値からリクエストされたアクション名を取得する
+        $action_name = $sub_action_name = null;
+        foreach ($http_vars as $name => $value) {
+            if ($value == "" || strncmp($name, 'action_', 7) != 0) {
+                continue;
+            }
+
+            $tmp = substr($name, 7);
+
+            // type="image"対応
+            if (preg_match('/_x$/', $name) || preg_match('/_y$/', $name)) {
+                $tmp = substr($tmp, 0, strlen($tmp)-2);
+            }
+
+            // value="dummy"となっているものは優先度を下げる
+            if ($value == "dummy") {
+                $sub_action_name = $tmp;
+            } else {
+                $action_name = $tmp;
+            }
+        }
+        if ($action_name == null) {
+            $action_name = $sub_action_name;
+        }
+
+        return $action_name;
+    }
+
+    /**
+     *  アクション名を指定するクエリ/HTMLを生成する
+     *
+     *  @access public
+     *  @param  string  $action action to request
+     *  @param  string  $type   hidden, url...
+     *  @todo   consider gateway
+     */
+    function getActionRequest($action, $type = "hidden")
+    {
+        $s = null; 
+        if ($type == "hidden") {
+            $s = sprintf('<input type="hidden" name="action_%s" value="true" />', htmlspecialchars($action, ENT_QUOTES));
+        } else if ($type == "url") {
+            $s = sprintf('action_%s=true', urlencode($action));
+        }
+        return $s;
+    }
+
+    /**
+     *  フォームにより要求されたアクション名に対応する定義を返す
+     *
+     *  @access private
+     *  @param  string  $action_name    アクション名
+     *  @return array   アクション定義
+     */
+    function &_getAction($action_name, $gateway = null)
+    {
+        $action = array();
+        $gateway = is_null($gateway) ? $this->getGateway() : $gateway;
+        switch ($gateway) {
+        case GATEWAY_WWW:
+            $action =& $this->action;
+            break;
+        case GATEWAY_CLI:
+            $action =& $this->action_cli;
+            break;
+        case GATEWAY_XMLRPC:
+            $action =& $this->action_xmlrpc;
+            break;
+        }
+
+        $action_obj = array();
+        if (isset($action[$action_name])) {
+            $action_obj = $action[$action_name];
+            if (isset($action_obj['inspect']) && $action_obj['inspect']) {
+                return $action_obj;
+            }
+        } else {
+            $this->logger->log(LOG_DEBUG, "action [%s] is not defined -> try default", $action_name);
+        }
+
+        // アクションスクリプトのインクルード
+        $this->_includeActionScript($action_obj, $action_name);
+
+        // 省略値の補正
+        if (isset($action_obj['class_name']) == false) {
+            $action_obj['class_name'] = $this->getDefaultActionClass($action_name);
+        }
+
+        if (isset($action_obj['form_name']) == false) {
+            $action_obj['form_name'] = $this->getDefaultFormClass($action_name);
+        } else if (class_exists($action_obj['form_name']) == false) {
+            // 明示指定されたフォームクラスが定義されていない場合は警告
+            $this->logger->log(LOG_WARNING, 'stated form class is not defined [%s]', $action_obj['form_name']);
+        }
+
+        // 必要条件の確認
+        if (class_exists($action_obj['class_name']) == false) {
+            $this->logger->log(LOG_NOTICE, 'action class is not defined [%s]', $action_obj['class_name']);
+            $_ret_object = null;
+            return $_ret_object;
+        }
+        if (class_exists($action_obj['form_name']) == false) {
+            // フォームクラスは未定義でも良い
+            $class_name = $this->class_factory->getObjectName('form');
+            $this->logger->log(LOG_DEBUG, 'form class is not defined [%s] -> falling back to default [%s]', $action_obj['form_name'], $class_name);
+            $action_obj['form_name'] = $class_name;
+        }
+
+        $action_obj['inspect'] = true;
+        $action[$action_name] = $action_obj;
+        return $action[$action_name];
+    }
+
+    /**
+     *  アクション名とアクションクラスからの戻り値に基づいて遷移先を決定する
+     *
+     *  @access protected
+     *  @param  string  $action_name    アクション名
+     *  @param  string  $retval         アクションクラスからの戻り値
+     *  @return string  遷移先
+     */
+    function _sortForward($action_name, $retval)
+    {
+        return $retval;
+    }
+
+    /**
+     *  フィルタチェインを生成する
+     *
+     *  @access private
+     */
+    function _createFilterChain()
+    {
+        $this->filter_chain = array();
+        foreach ($this->filter as $filter) {
+            $filter_plugin =& $this->plugin->getPlugin('Filter', $filter);
+            if (Ethna::isError($filter_plugin)) {
+                continue;
+            }
+
+            $this->filter_chain[] =& $filter_plugin;
+        }
+    }
+
+    /**
+     *  アクション名が実行許可されているものかどうかを返す
+     *
+     *  @access private
+     *  @param  string  $action_name            リクエストされたアクション名
+     *  @param  array   $default_action_name    許可されているアクション名
+     *  @return bool    true:許可 false:不許可
+     */
+    function _isAcceptableActionName($action_name, $default_action_name)
+    {
+        foreach (to_array($default_action_name) as $name) {
+            if ($action_name == $name) {
+                return true;
+            } else if ($name{strlen($name)-1} == '*') {
+                if (strncmp($action_name, substr($name, 0, -1), strlen($name)-1) == 0) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     *  指定されたアクションのフォームクラス名を返す(オブジェクトの生成は行わない)
+     *
+     *  @access public
+     *  @param  string  $action_name    アクション名
+     *  @return string  アクションのフォームクラス名
+     */
+    function getActionFormName($action_name)
+    {
+        $action_obj =& $this->_getAction($action_name);
+        if (is_null($action_obj)) {
+            return null;
+        }
+
+        return $action_obj['form_name'];
+    }
+
+    /**
+     *  アクションに対応するフォームクラス名が省略された場合のデフォルトクラス名を返す
+     *
+     *  デフォルトでは[プロジェクトID]_Form_[アクション名]となるので好み応じてオーバライドする
+     *
+     *  @access public
+     *  @param  string  $action_name    アクション名
+     *  @return string  アクションフォーム名
+     */
+    function getDefaultFormClass($action_name, $gateway = null)
+    {
+        $gateway_prefix = $this->_getGatewayPrefix($gateway);
+
+        $postfix = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($action_name));
+        $r = sprintf("%s_%sForm_%s", $this->getAppId(), $gateway_prefix ? $gateway_prefix . "_" : "", $postfix);
+        $this->logger->log(LOG_DEBUG, "default action class [%s]", $r);
+
+        return $r;
+    }
+
+    /**
+     *  getDefaultFormClass()で取得したクラス名からアクション名を取得する
+     *
+     *  getDefaultFormClass()をオーバーライドした場合、こちらも合わせてオーバーライド
+     *  することを推奨(必須ではない)
+     *
+     *  @access public
+     *  @param  string  $class_name     フォームクラス名
+     *  @return string  アクション名
+     */
+    function actionFormToName($class_name)
+    {
+        $prefix = sprintf("%s_Form_", $this->getAppId());
+        if (preg_match("/$prefix(.*)/", $class_name, $match) == 0) {
+            // 不明なクラス名
+            return null;
+        }
+        $target = $match[1];
+
+        $action_name = substr(preg_replace('/([A-Z])/e', "'_' . strtolower('\$1')", $target), 1);
+
+        return $action_name;
+    }
+
+    /**
+     *  アクションに対応するフォームパス名が省略された場合のデフォルトパス名を返す
+     *
+     *  デフォルトでは_getDefaultActionPath()と同じ結果を返す(1ファイルに
+     *  アクションクラスとフォームクラスが記述される)ので、好みに応じて
+     *  オーバーライドする
+     *
+     *  @access public
+     *  @param  string  $action_name    アクション名
+     *  @return string  form classが定義されるスクリプトのパス名
+     */
+    function getDefaultFormPath($action_name)
+    {
+        return $this->getDefaultActionPath($action_name);
+    }
+
+    /**
+     *  指定されたアクションのクラス名を返す(オブジェクトの生成は行わない)
+     *
+     *  @access public
+     *  @param  string  $action_name    アクションの名称
+     *  @return string  アクションのクラス名
+     */
+    function getActionClassName($action_name)
+    {
+        $action_obj =& $this->_getAction($action_name);
+        if ($action_obj == null) {
+            return null;
+        }
+
+        return $action_obj['class_name'];
+    }
+
+    /**
+     *  アクションに対応するアクションクラス名が省略された場合のデフォルトクラス名を返す
+     *
+     *  デフォルトでは[プロジェクトID]_Action_[アクション名]となるので好み応じてオーバライドする
+     *
+     *  @access public
+     *  @param  string  $action_name    アクション名
+     *  @return string  アクションクラス名
+     */
+    function getDefaultActionClass($action_name, $gateway = null)
+    {
+        $gateway_prefix = $this->_getGatewayPrefix($gateway);
+
+        $postfix = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($action_name));
+        $r = sprintf("%s_%sAction_%s", $this->getAppId(), $gateway_prefix ? $gateway_prefix . "_" : "", $postfix);
+        $this->logger->log(LOG_DEBUG, "default action class [%s]", $r);
+
+        return $r;
+    }
+
+    /**
+     *  getDefaultActionClass()で取得したクラス名からアクション名を取得する
+     *
+     *  getDefaultActionClass()をオーバーライドした場合、こちらも合わせてオーバーライド
+     *  することを推奨(必須ではない)
+     *
+     *  @access public
+     *  @param  string  $class_name     アクションクラス名
+     *  @return string  アクション名
+     */
+    function actionClassToName($class_name)
+    {
+        $prefix = sprintf("%s_Action_", $this->getAppId());
+        if (preg_match("/$prefix(.*)/", $class_name, $match) == 0) {
+            // 不明なクラス名
+            return null;
+        }
+        $target = $match[1];
+
+        $action_name = substr(preg_replace('/([A-Z])/e', "'_' . strtolower('\$1')", $target), 1);
+
+        return $action_name;
+    }
+
+    /**
+     *  アクションに対応するアクションパス名が省略された場合のデフォルトパス名を返す
+     *
+     *  デフォルトでは"foo_bar" -> "/Foo/Bar.php"となるので好み応じてオーバーライドする
+     *
+     *  @access public
+     *  @param  string  $action_name    アクション名
+     *  @return string  アクションクラスが定義されるスクリプトのパス名
+     */
+    function getDefaultActionPath($action_name)
+    {
+        $r = preg_replace('/_(.)/e', "'/' . strtoupper('\$1')", ucfirst($action_name)) . '.' . $this->getExt('php');
+        $this->logger->log(LOG_DEBUG, "default action path [%s]", $r);
+
+        return $r;
+    }
+
+    /**
+     *  指定された遷移名に対応するビュークラス名を返す(オブジェクトの生成は行わない)
+     *
+     *  @access public
+     *  @param  string  $forward_name   遷移先の名称
+     *  @return string  view classのクラス名
+     */
+    function getViewClassName($forward_name)
+    {
+        if ($forward_name == null) {
+            return null;
+        }
+
+        if (isset($this->forward[$forward_name])) {
+            $forward_obj = $this->forward[$forward_name];
+        } else {
+            $forward_obj = array();
+        }
+
+        if (isset($forward_obj['view_name'])) {
+            $class_name = $forward_obj['view_name'];
+            if (class_exists($class_name)) {
+                return $class_name;
+            }
+        } else {
+            $class_name = null;
+        }
+
+        // viewのインクルード
+        $this->_includeViewScript($forward_obj, $forward_name);
+
+        if (is_null($class_name) == false && class_exists($class_name)) {
+            return $class_name;
+        } else if (is_null($class_name) == false) {
+            $this->logger->log(LOG_WARNING, 'stated view class is not defined [%s] -> try default', $class_name);
+        }
+
+        $class_name = $this->getDefaultViewClass($forward_name);
+        if (class_exists($class_name)) {
+            return $class_name;
+        } else {
+            $class_name = $this->class_factory->getObjectName('view');
+            $this->logger->log(LOG_DEBUG, 'view class is not defined for [%s] -> use default [%s]', $forward_name, $class_name);
+            return $class_name;
+        }
+    }
+
+    /**
+     *  遷移名に対応するビュークラス名が省略された場合のデフォルトクラス名を返す
+     *
+     *  デフォルトでは[プロジェクトID]_View_[遷移名]となるので好み応じてオーバライドする
+     *
+     *  @access public
+     *  @param  string  $forward_name   forward名
+     *  @return string  view classクラス名
+     */
+    function getDefaultViewClass($forward_name, $gateway = null)
+    {
+        $gateway_prefix = $this->_getGatewayPrefix($gateway);
+
+        $postfix = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($forward_name));
+        $r = sprintf("%s_%sView_%s", $this->getAppId(), $gateway_prefix ? $gateway_prefix . "_" : "", $postfix);
+        $this->logger->log(LOG_DEBUG, "default view class [%s]", $r);
+
+        return $r;
+    }
+
+    /**
+     *  遷移名に対応するビューパス名が省略された場合のデフォルトパス名を返す
+     *
+     *  デフォルトでは"foo_bar" -> "/Foo/Bar.php"となるので好み応じてオーバーライドする
+     *
+     *  @access public
+     *  @param  string  $forward_name   forward名
+     *  @return string  view classが定義されるスクリプトのパス名
+     */
+    function getDefaultViewPath($forward_name)
+    {
+        $r = preg_replace('/_(.)/e', "'/' . strtoupper('\$1')", ucfirst($forward_name)) . '.' . $this->getExt('php');
+        $this->logger->log(LOG_DEBUG, "default view path [%s]", $r);
+
+        return $r;
+    }
+
+    /**
+     *  遷移名に対応するテンプレートパス名が省略された場合のデフォルトパス名を返す
+     *
+     *  デフォルトでは"foo_bar"というforward名が"foo/bar" + テンプレート拡張子となる
+     *  ので好み応じてオーバライドする
+     *
+     *  @access public
+     *  @param  string  $forward_name   forward名
+     *  @return string  forwardパス名
+     */
+    function getDefaultForwardPath($forward_name)
+    {
+        return str_replace('_', '/', $forward_name) . '.' . $this->ext['tpl'];
+    }
+    
+    /**
+     *  テンプレートパス名から遷移名を取得する
+     *
+     *  getDefaultForwardPath()をオーバーライドした場合、こちらも合わせてオーバーライド
+     *  することを推奨(必須ではない)
+     *
+     *  @access public
+     *  @param  string  $forward_path   テンプレートパス名
+     *  @return string  遷移名
+     */
+    function forwardPathToName($forward_path)
+    {
+        $forward_path = preg_replace('/^\/+/', '', $forward_path);
+        $forward_path = preg_replace(sprintf('/\.%s$/', $this->getExt('tpl')), '', $forward_path);
+
+        return str_replace('/', '_', $forward_path);
+    }
+
+    /**
+     *  遷移名からテンプレートファイルのパス名を取得する
+     *
+     *  @access private
+     *  @param  string  $forward_name   forward名
+     *  @return string  テンプレートファイルのパス名
+     */
+    function _getForwardPath($forward_name)
+    {
+        $forward_obj = null;
+
+        if (isset($this->forward[$forward_name]) == false) {
+            // try default
+            $this->forward[$forward_name] = array();
+        }
+        $forward_obj =& $this->forward[$forward_name];
+        if (isset($forward_obj['forward_path']) == false) {
+            // 省略値補正
+            $forward_obj['forward_path'] = $this->getDefaultForwardPath($forward_name);
+        }
+
+        return $forward_obj['forward_path'];
+    }
+
+    /**
+     *  レンダラを取得する(getTemplateEngine()はそのうち廃止されgetRenderer()に統合される予定)
+     *
+     *  @access public
+     *  @return object  Ethna_Renderer  レンダラオブジェクト
+     */
+    function &getRenderer()
+    {
+        $_ret_object =& $this->getTemplateEngine();
+        return $_ret_object;
+    }
+
+    /**
+     *  テンプレートエンジン取得する
+     *
+     *  @access public
+     *  @return object  Ethna_Renderer  レンダラオブジェクト
+     *  @obsolete
+     */
+    function &getTemplateEngine()
+    {
+        if (is_object($this->renderer)) {
+            return $this->renderer;
+        }
+        
+        $this->renderer =& $this->class_factory->getObject('renderer');
+       
+        // {{{ for B.C.
+        if (strtolower(get_class($this->renderer)) == "ethna_renderer_smarty") {
+            // user defined modifiers
+            foreach ($this->smarty_modifier_plugin as $modifier) {
+                if (!is_array($modifier)) {
+                    $name = str_replace('smarty_modifier_', '', $modifier);
+                    $this->renderer->setPlugin($name,'modifier', $modifier);
+                } else {
+                    $this->renderer->setPlugin($modifier[1], 'modifier', $modifier);
+                }
+            }
+
+            // user defined functions
+            foreach ($this->smarty_function_plugin as $function) {
+                if (!is_array($function)) {
+                    $name = str_replace('smarty_function_', '', $function);
+                    $this->renderer->setPlugin($name, 'function', $function);
+                } else {
+                    $this->renderer->setPlugin($function[1], 'function', $function);
+                }
+            }
+
+            // user defined blocks
+            foreach ($this->smarty_block_plugin as $block) {
+                if (!is_array($block)) {
+                    $name = str_replace('smarty_block_', '', $block);
+                    $this->renderer->setPlugin($name,'block', $block);
+                } else {
+                    $this->renderer->setPlugin($block[1],'block', $block);
+                }
+            }
+
+            // user defined prefilters
+            foreach ($this->smarty_prefilter_plugin as $prefilter) {
+                if (!is_array($prefilter)) {
+                    $name = str_replace('smarty_prefilter_', '', $prefilter);
+                    $this->renderer->setPlugin($name,'prefilter', $prefilter);
+                } else {
+                    $this->renderer->setPlugin($prefilter[1],'prefilter', $prefilter);
+                }
+            }
+
+            // user defined postfilters
+            foreach ($this->smarty_postfilter_plugin as $postfilter) {
+                if (!is_array($postfilter)) {
+                    $name = str_replace('smarty_postfilter_', '', $postfilter);
+                    $this->renderer->setPlugin($name,'postfilter', $postfilter);
+                } else {
+                    $this->renderer->setPlugin($postfilter[1],'postfilter', $postfilter);
+                }
+            }
+
+            // user defined outputfilters
+            foreach ($this->smarty_outputfilter_plugin as $outputfilter) {
+                if (!is_array($outputfilter)) {
+                    $name = str_replace('smarty_outputfilter_', '', $outputfilter);
+                    $this->renderer->setPlugin($name,'outputfilter', $outputfilter);
+                } else {
+                    $this->renderer->setPlugin($outputfilter[1],'outputfilter', $outputfilter);
+                }
+            }
+        }
+
+        //テンプレートエンジンのデフォルトの設定
+        $this->_setDefaultTemplateEngine($this->renderer);
+        // }}}
+
+        return $this->renderer;
+    }
+
+    /**
+     *  テンプレートエンジンのデフォルト状態を設定する
+     *
+     *  @access protected
+     *  @param  object  Ethna_Renderer  レンダラオブジェクト
+     *  @obsolete
+     */
+    function _setDefaultTemplateEngine(&$renderer)
+    {
+    }
+
+    /**
+     *  使用言語、ロケールを設定する
+     *  条件によって使用言語、ロケールを切り替えたい場合は、
+     *  このメソッドをオーバーライドする。
+     *
+     *  @access protected
+     *  @param  string  $locale             ロケール名(ja_JP, en_US等)
+     *                                      (ll_cc の形式。ll = 言語コード cc = 国コード)
+     *  @param  string  $system_encoding    システムエンコーディング名
+     *  @param  string  $client_encoding    クライアントエンコーディング(テンプレートのエンコーディングと考えれば良い)
+     *  @see    http://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html 
+     *  @see    Ethna_Controller#_getDefaultLanguage
+     */
+    function _setLanguage($locale, $system_encoding = null, $client_encoding = null)
+    {
+        $this->locale = $locale;
+        $this->system_encoding = $system_encoding;
+        $this->client_encoding = $client_encoding;
+
+        //   $this->locale, $this->client_encoding を書き換えた場合は
+        //   必ず Ethna_I18N クラスの setLanguageメソッドも呼ぶこと!
+        //   さもないとカタログその他が再ロードされない!
+        $i18n =& $this->getI18N();
+        $i18n->setLanguage($locale, $system_encoding, $client_encoding);
+    }
+
+    /**
+     *  デフォルト状態での使用言語を取得する
+     *  外部に出力されるEthnaのエラーメッセージ等のエンコーディングを
+     *  切り替えたい場合は、このメソッドをオーバーライドする。
+     *
+     *  @access protected
+     *  @return array   ロケール名(e.x ja_JP, en_US 等),
+     *                  システムエンコーディング名,
+     *                  クライアントエンコーディング名
+     *                  (= テンプレートのエンコーディングと考えてよい) の配列
+     *                  (ロケール名は ll_cc の形式。ll = 言語コード cc = 国コード)
+     *
+     *  WARNING!! : クライアントエンコーディング名が、フレームワークの内部エンコーデ
+     *              ィングとして設定されます。つまり、クライアントエンコーディングで
+     *              ブラウザからの入力は入ってくるものと想定しています!
+     */
+    function _getDefaultLanguage()
+    {
+        return array('ja_JP', 'UTF-8', 'UTF-8');
+    }
+
+    /**
+     *  デフォルト状態でのゲートウェイを取得する
+     *
+     *  @access protected
+     *  @return int     ゲートウェイ定義(GATEWAY_WWW, GATEWAY_CLI...)
+     */
+    function _getDefaultGateway($gateway)
+    {
+        if (is_null($GLOBALS['_Ethna_gateway']) == false) {
+            return $GLOBALS['_Ethna_gateway'];
+        }
+        return GATEWAY_WWW;
+    }
+
+    /**
+     *  ゲートウェイに対応したクラス名のプレフィクスを取得する
+     *
+     *  @access public
+     *  @param  string  $gateway    ゲートウェイ
+     *  @return string  ゲートウェイクラスプレフィクス
+     */
+    function _getGatewayPrefix($gateway = null)
+    {
+        $gateway = is_null($gateway) ? $this->getGateway() : $gateway;
+        switch ($gateway) {
+        case GATEWAY_WWW:
+            $prefix = '';
+            break;
+        case GATEWAY_CLI:
+            $prefix = 'Cli';
+            break;
+        case GATEWAY_XMLRPC:
+            $prefix = 'Xmlrpc';
+            break;
+        default:
+            $prefix = '';
+            break;
+        }
+
+        return $prefix;
+    }
+
+    /**
+     *  マネージャクラス名を取得する
+     *
+     *  @access public
+     *  @param  string  $name   マネージャキー
+     *  @return string  マネージャクラス名
+     */
+    function getManagerClassName($name)
+    {
+        //   アプリケーションIDと、渡された名前のはじめを大文字にして、
+        //   組み合わせたものが返される 
+        return sprintf('%s_%sManager', $this->getAppId(), ucfirst($name));
+    }
+
+    /**
+     *  アプリケーションオブジェクトクラス名を取得する
+     *
+     *  @access public
+     *  @param  string  $name   アプリケーションオブジェクトキー
+     *  @return string  マネージャクラス名
+     */
+    function getObjectClassName($name)
+    {
+        //  引数のはじめの一文字目と、アンダーバー直後の
+        //  1文字を必ず大文字にする。アンダーバーは削除される。
+        $name = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($name));
+        
+        //  $name に foo_bar を渡し、AppID が Hogeの場合
+        //  [Appid]_FooBar が返される
+        return sprintf('%s_%s', $this->getAppId(), $name);
+    }
+
+    /**
+     *  アクションスクリプトをインクルードする
+     *
+     *  ただし、インクルードしたファイルにクラスが正しく定義されているかどうかは保証しない
+     *
+     *  @access private
+     *  @param  array   $action_obj     アクション定義
+     *  @param  string  $action_name    アクション名
+     */
+    function _includeActionScript($action_obj, $action_name)
+    {
+        $class_path = $form_path = null;
+
+        $action_dir = $this->getActiondir();
+
+        // class_path属性チェック
+        if (isset($action_obj['class_path'])) {
+            // フルパス指定サポート
+            $tmp_path = $action_obj['class_path'];
+            if (Ethna_Util::isAbsolute($tmp_path) == false) {
+                $tmp_path = $action_dir . $tmp_path;
+            }
+
+            if (file_exists($tmp_path) == false) {
+                $this->logger->log(LOG_WARNING, 'class_path file not found [%s] -> try default', $tmp_path);
+            } else {
+                include_once $tmp_path;
+                $class_path = $tmp_path;
+            }
+        }
+
+        // デフォルトチェック
+        if (is_null($class_path)) {
+            $class_path = $this->getDefaultActionPath($action_name);
+            if (file_exists($action_dir . $class_path)) {
+                include_once $action_dir . $class_path;
+            } else {
+                $this->logger->log(LOG_DEBUG, 'default action file not found [%s] -> try all files', $class_path);
+                $class_path = null;
+            }
+        }
+        
+        // 全ファイルインクルード
+        if (is_null($class_path)) {
+            $this->_includeDirectory($this->getActiondir());
+            return;
+        }
+
+        // form_path属性チェック
+        if (isset($action_obj['form_path'])) {
+            // フルパス指定サポート
+            $tmp_path = $action_obj['form_path'];
+            if (Ethna_Util::isAbsolute($tmp_path) == false) {
+                $tmp_path = $action_dir . $tmp_path;
+            }
+
+            if ($tmp_path == $class_path) {
+                return;
+            }
+            if (file_exists($tmp_path) == false) {
+                $this->logger->log(LOG_WARNING, 'form_path file not found [%s] -> try default', $tmp_path);
+            } else {
+                include_once $tmp_path;
+                $form_path = $tmp_path;
+            }
+        }
+
+        // デフォルトチェック
+        if (is_null($form_path)) {
+            $form_path = $this->getDefaultFormPath($action_name);
+            if ($form_path == $class_path) {
+                return;
+            }
+            if (file_exists($action_dir . $form_path)) {
+                include_once $action_dir . $form_path;
+            } else {
+                $this->logger->log(LOG_DEBUG, 'default form file not found [%s] -> maybe falling back to default form class', $form_path);
+            }
+        }
+    }
+
+    /**
+     *  ビュースクリプトをインクルードする
+     *
+     *  ただし、インクルードしたファイルにクラスが正しく定義されているかどうかは保証しない
+     *
+     *  @access private
+     *  @param  array   $forward_obj    遷移定義
+     *  @param  string  $forward_name   遷移名
+     */
+    function _includeViewScript($forward_obj, $forward_name)
+    {
+        $view_dir = $this->getViewdir();
+
+        // view_path属性チェック
+        if (isset($forward_obj['view_path'])) {
+            // フルパス指定サポート
+            $tmp_path = $forward_obj['view_path'];
+            if (Ethna_Util::isAbsolute($tmp_path) == false) {
+                $tmp_path = $view_dir . $tmp_path;
+            }
+
+            if (file_exists($tmp_path) == false) {
+                $this->logger->log(LOG_WARNING, 'view_path file not found [%s] -> try default', $tmp_path);
+            } else {
+                include_once $tmp_path;
+                return;
+            }
+        }
+
+        // デフォルトチェック
+        $view_path = $this->getDefaultViewPath($forward_name);
+        if (file_exists($view_dir . $view_path)) {
+            include_once $view_dir . $view_path;
+            return;
+        } else {
+            $this->logger->log(LOG_DEBUG, 'default view file not found [%s]', $view_path);
+            $view_path = null;
+        }
+    }
+
+    /**
+     *  ディレクトリ以下の全てのスクリプトをインクルードする
+     *
+     *  @access private
+     */
+    function _includeDirectory($dir)
+    {
+        $ext = "." . $this->ext['php'];
+        $ext_len = strlen($ext);
+
+        if (is_dir($dir) == false) {
+            return;
+        }
+
+        $dh = opendir($dir);
+        if ($dh) {
+            while (($file = readdir($dh)) !== false) {
+                if ($file != '.' && $file != '..' && is_dir("$dir/$file")) {
+                    $this->_includeDirectory("$dir/$file");
+                }
+                if (substr($file, -$ext_len, $ext_len) != $ext) {
+                    continue;
+                }
+                include_once $dir . '/' . $file;
+            }
+        }
+        closedir($dh);
+    }
+
+    /**
+     *  設定ファイルのDSN定義から使用するデータを再構築する(スレーブアクセス分岐等)
+     *
+     *  DSNの定義方法(デフォルト:設定ファイル)を変えたい場合はここをオーバーライドする
+     *
+     *  @access protected
+     *  @return array   DSN定義(array('DBキー1' => 'dsn1', 'DBキー2' => 'dsn2', ...))
+     */
+    function _prepareDSN()
+    {
+        $r = array();
+
+        foreach ($this->db as $key => $value) {
+            $config_key = "dsn";
+            if ($key != "") {
+                $config_key .= "_$key";
+            }
+            $dsn = $this->config->get($config_key);
+            if (is_array($dsn)) {
+                // 種別1つにつき複数DSNが定義されている場合はアクセス分岐
+                $dsn = $this->_selectDSN($key, $dsn);
+            }
+            $r[$key] = $dsn;
+        }
+        return $r;
+    }
+
+    /**
+     *  DSNのアクセス分岐を行う
+     *  
+     *  スレーブサーバへの振分け処理(デフォルト:ランダム)を変更したい場合はこのメソッドをオーバーライドする
+     *
+     *  @access protected
+     *  @param  string  $type       DB種別
+     *  @param  array   $dsn_list   DSN一覧
+     *  @return string  選択されたDSN
+     */
+    function _selectDSN($type, $dsn_list)
+    {
+        if (is_array($dsn_list) == false) {
+            return $dsn_list;
+        }
+
+        // デフォルト:ランダム
+        list($usec, $sec) = explode(' ', microtime());
+        mt_srand($sec + ((float) $usec * 100000));
+        $n = mt_rand(0, count($dsn_list)-1);
+        
+        return $dsn_list[$n];
+    }
+
+    /**
+     *  Ethnaマネージャを設定する
+     *
+     *  不要な場合は空のメソッドとしてオーバーライドしてもよい
+     *
+     *  @access protected
+     */
+    function _activateEthnaManager()
+    {
+        if ($this->config->get('debug') == false) {
+            return;
+        }
+
+        require_once ETHNA_BASE . '/class/Ethna_InfoManager.php';
+        
+        // see if we have simpletest
+        if (file_exists_ex('simpletest/unit_tester.php', true)) {
+            require_once ETHNA_BASE . '/class/Ethna_UnitTestManager.php';
+        }
+
+        // action設定
+        $this->action['__ethna_info__'] = array(
+            'form_name' =>  'Ethna_Form_Info',
+            'form_path' =>  sprintf('%s/class/Action/Ethna_Action_Info.php', ETHNA_BASE),
+            'class_name' => 'Ethna_Action_Info',
+            'class_path' => sprintf('%s/class/Action/Ethna_Action_Info.php', ETHNA_BASE),
+        );
+
+        // forward設定
+        $this->forward['__ethna_info__'] = array(
+            'forward_path'  => sprintf('%s/tpl/info.tpl', ETHNA_BASE),
+            'view_name'     => 'Ethna_View_Info',
+            'view_path'     => sprintf('%s/class/View/Ethna_View_Info.php', ETHNA_BASE),
+        );
+        
+        
+        // action設定
+        $this->action['__ethna_unittest__'] = array(
+            'form_name' =>  'Ethna_Form_UnitTest',
+            'form_path' =>  sprintf('%s/class/Action/Ethna_Action_UnitTest.php', ETHNA_BASE),
+            'class_name' => 'Ethna_Action_UnitTest',
+            'class_path' => sprintf('%s/class/Action/Ethna_Action_UnitTest.php', ETHNA_BASE),
+        );
+
+        // forward設定
+        $this->forward['__ethna_unittest__'] = array(
+            'forward_path'  => sprintf('%s/tpl/unittest.tpl', ETHNA_BASE),
+            'view_name'     => 'Ethna_View_UnitTest',
+            'view_path'     => sprintf('%s/class/View/Ethna_View_UnitTest.php', ETHNA_BASE),
+        );
+
+    }
+
+    /**
+     *  Ethnaマネージャが実行可能かをチェックする
+     *
+     *  Ethnaマネージャを実行するよう指示されているにも関わらず、
+     *  debug が trueでない場合は実行を停止する。
+     *
+     *  @access private
+     */
+    function _ethnaManagerEnabledCheck($action_name)
+    {
+        if ($this->config->get('debug') == false
+         && ($action_name == '__ethna_info__' || $action_name == '__ethna_unittest__')) {
+            $this->ethnaManagerCheckErrorMsg($action_name);
+            exit(0);
+        }
+    }
+
+    /**
+     *  Ethnaマネージャが実行不能な場合のエラーメッセージを
+     *  表示する。運用上の都合でこのメッセージを出力したくない
+     *  場合は、このメソッドをオーバーライドせよ
+     *
+     *  @access protected
+     */
+     function ethnaManagerCheckErrorMsg($action_name)
+     {
+         $appid = strtolower($this->getAppId());
+         $run_action = ($action_name == '__ethna_info__')
+                     ? ' show Application Info List '
+                     : ' run Unit Test ';
+         echo "Ethna cannot {$run_action} under your application setting.<br>";
+         echo "HINT: You must set {$appid}/etc/{$appid}-ini.php debug setting 'true'.<br>";
+         echo "<br>";
+         echo "In {$appid}-ini.php, please set as follows :<br><br>";
+         echo "\$config = array ( 'debug' => true, );";
+     } 
+
+    /**
+     *  CLI実行中フラグを取得する
+     *
+     *  @access public
+     *  @return bool    CLI実行中フラグ
+     *  @obsolete
+     */
+    function getCLI()
+    {
+        return $this->gateway == GATEWAY_CLI ? true : false;
+    }
+
+    /**
+     *  CLI実行中フラグを設定する
+     *
+     *  @access public
+     *  @param  bool    CLI実行中フラグ
+     *  @obsolete
+     */
+    function setCLI($cli)
+    {
+        $this->gateway = $cli ? GATEWAY_CLI : $this->_getDefaultGateway();
+    }
+}
+// }}}
+
+/**
+ *  XMLRPCゲートウェイのスタブクラス
+ *
+ *  @access     public
+ */
+function _Ethna_XmlrpcGateway($method_stub, $param)
+{
+    $ctl =& Ethna_Controller::getInstance();
+    $method = $ctl->getXmlrpcMethodName();
+    $r = $ctl->trigger_XMLRPC($method, $param);
+    if (Ethna::isError($r)) {
+        return array(
+            'faultCode' => $r->getCode(),
+            'faultString' => $r->getMessage(),
+        );
+    }
+    return $r;
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_DB.php b/Idea_Plugin_Extlib/class/Ethna_DB.php
new file mode 100644 (file)
index 0000000..42718ad
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_DB.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_DB
+/**
+ *  Ethna用DB抽象クラス
+ *
+ *  EthnaのフレームワークでDBオブジェクトを扱うための抽象クラス
+ *  (のつもり...あぁすばらしきPHP 4)
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_DB
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  DB              DBオブジェクト */
+    var $db;
+
+    /** @var    array   トランザクション管理スタック */
+    var $transaction = array();
+
+    /**#@-*/
+
+
+    /**
+     *  Ethna_DBクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     *  @param  string  $dsn                                DSN
+     *  @param  bool    $persistent                         持続接続設定
+     */
+    function Ethna_DB(&$controller, $dsn, $persistent)
+    {
+        $this->dsn = $dsn;
+        $this->persistent = $persistent;
+    }
+
+    /**
+     *  DBに接続する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function connect()
+    {
+    }
+
+    /**
+     *  DB接続を切断する
+     *
+     *  @access public
+     */
+    function disconnect()
+    {
+    }
+
+    /**
+     *  DB接続状態を返す
+     *
+     *  @access public
+     *  @return bool    true:正常(接続済み) false:エラー/未接続
+     */
+    function isValid()
+    {
+    }
+
+    /**
+     *  DBトランザクションを開始する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function begin()
+    {
+    }
+
+    /**
+     *  DBトランザクションを中断する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function rollback()
+    {
+    }
+
+    /**
+     *  DBトランザクションを終了する
+     *
+     *  @access public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function commit()
+    {
+    }
+
+    /**
+     *  テーブル定義情報を取得する
+     *
+     *  @access public
+     *  @return mixed   array: PEAR::DBに準じたメタデータ
+     *                  Ethna_Error::エラー
+     */
+    function getMetaData()
+    {
+        //   このメソッドはAppObject
+        //   との連携に必要。
+    }
+
+    /**
+     *  DSNを取得する
+     *
+     *  @access public
+     *  @return string  DSN
+     */
+    function getDSN()
+    {
+        return $this->dsn;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Error.php b/Idea_Plugin_Extlib/class/Ethna_Error.php
new file mode 100644 (file)
index 0000000..22ec850
--- /dev/null
@@ -0,0 +1,265 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Error.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ ethna_error_handler
+/**
+ *  エラーコールバック関数
+ *
+ *  @param  int     $errno      エラーレベル
+ *  @param  string  $errstr     エラーメッセージ
+ *  @param  string  $errfile    エラー発生箇所のファイル名
+ *  @param  string  $errline    エラー発生箇所の行番号
+ */
+function ethna_error_handler($errno, $errstr, $errfile, $errline)
+{
+    if ($errno === E_STRICT || ($errno & error_reporting()) === 0) {
+        return;
+    }
+
+    list($level, $name) = Ethna_Logger::errorLevelToLogLevel($errno);
+    switch ($errno) {
+    case E_ERROR:
+    case E_CORE_ERROR:
+    case E_COMPILE_ERROR:
+    case E_USER_ERROR:
+        $php_errno = 'Fatal error'; break;
+    case E_WARNING:
+    case E_CORE_WARNING:
+    case E_COMPILE_WARNING:
+    case E_USER_WARNING:
+        $php_errno = 'Warning'; break;
+    case E_PARSE:
+        $php_errno = 'Parse error'; break;
+    case E_NOTICE:
+    case E_USER_NOTICE:
+    case E_STRICT:
+        $php_errno = 'Notice'; break;
+    default:
+        $php_errno = 'Unknown error'; break;
+    }
+    $php_errstr = sprintf('PHP %s: %s in %s on line %d',
+                          $php_errno, $errstr, $errfile, $errline);
+
+    // error_log()
+    if (ini_get('log_errors')) {
+        $locale = setlocale(LC_TIME, 0);
+        setlocale(LC_TIME, 'C');
+        error_log($php_errstr, 0);
+        setlocale(LC_TIME, $locale);
+    }
+
+    // $logger->log()
+    $c =& Ethna_Controller::getInstance();
+    if ($c !== null) {
+        $logger =& $c->getLogger();
+        $logger->log($level, sprintf("[PHP] %s: %s in %s on line %d",
+                                     $name, $errstr, $errfile, $errline));
+    }
+
+    // printf()
+    if (ini_get('display_errors')) {
+        $is_debug = true;
+        $has_echo = false;
+        if ($c !== null) {
+            $config =& $c->getConfig();
+            $is_debug = $config->get('debug');
+            $facility = $logger->getLogFacility();
+            $has_echo = is_array($facility)
+                        ? in_array('echo', $facility) : $facility === 'echo';
+        }
+        if ($is_debug == true && $has_echo === false) {
+            if ($c !== null && $c->getGateway() === GATEWAY_WWW) {
+                $format = "<b>%s</b>: %s in <b>%s</b> on line <b>%d</b><br />\n";
+            } else {
+                $format = "%s: %s in %s on line %d\n";
+            }
+            printf($format, $php_errno, $errstr, $errfile, $errline);
+        }
+    }
+}
+set_error_handler('ethna_error_handler');
+// }}}
+
+// {{{ ethna_exception_handler
+    //  TODO: Implement ethna_exception_handler function.
+// }}}
+
+// {{{ Ethna_Error
+/**
+ *  エラークラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Error
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_I18N  i18nオブジェクト */
+    var $i18n;
+
+    /** @var    object  Ethna_Logger    loggerオブジェクト */
+    var $logger;
+
+    /** @var    string  エラーメッセージ */
+    var $message;
+
+    /** @var    integer エラーコード */
+    var $code;
+
+    /** @var    integer エラーモード */
+    var $mode;
+    
+    /** @var    array   エラーモード依存のオプション */
+    var $options;
+
+    /** @var    string  ユーザー定義もしくはデバッグ関連の追加情報を記した文字列。 */
+    var $userinfo;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_Errorクラスのコンストラクタ
+     *  $userinfo は第5引数に設定すること。 
+     *
+     *  @access public
+     *  @param  string  $message            エラーメッセージ
+     *  @param  int     $code               エラーコード
+     *  @param  int     $mode               エラーモード(Ethna_Errorはコールバックを
+     *                                      常に使用するので実質無視される)
+     *  @param  array   $options            エラーモード依存のオプション
+     *  @param  array   $userinfo           エラー追加情報($options より後の全ての引数)
+     *  @see http://pear.php.net/manual/ja/core.pear.pear-error.pear-error.php
+     */
+    function Ethna_Error($message = null, $code = null, $mode = null, $options = null)
+    {
+        $controller =& Ethna_Controller::getInstance();
+        if ($controller !== null) {
+            $this->i18n =& $controller->getI18N();
+        }
+
+        // $options 以降の引数 -> $userinfo
+        if (func_num_args() > 4) {
+            $userinfo = array_slice(func_get_args(), 4);
+            if (count($userinfo) == 1) {
+                if (is_array($userinfo[0])) {
+                    $this->userinfo = $userinfo[0];
+                } else if (is_null($userinfo[0])) {
+                    $this->userinfo = array();
+                }
+            } else {
+                $this->userinfo = $userinfo[0];
+            }
+        } else {
+            $this->userinfo = array();
+        }
+
+        // メッセージ補正処理 ($message)
+        if (is_null($message)) {
+            // $codeからメッセージを取得する
+            $message = $controller->getErrorMessage($code);
+            if (is_null($message)) {
+                $message = 'unknown error';
+            }
+        }
+        $this->message = $message;
+
+        //  その他メンバ変数設定
+        $this->code = $code;
+        $this->mode = $mode;
+        $this->options = $options; 
+        $this->level = ($this->options === NULL) ? E_USER_NOTICE : $options;
+
+        //  Ethnaフレームワークのエラーハンドラ(callback)
+        Ethna::handleError($this);
+    }
+
+    /**
+     * エラーオブジェクトに関連付けられたエラーコードを返します。
+     *
+     * @return integer - エラー番号
+     */
+    function getCode()
+    {
+        return $this->code;
+    }
+
+    /**
+     *  levelへのアクセサ(R)
+     *
+     *  @access public
+     *  @return int     エラーレベル
+     */
+    function getLevel()
+    {
+        return $this->level;
+    }
+
+    /**
+     *  messageへのアクセサ(R)
+     *
+     *  以下の処理を行う
+     *  - エラーメッセージのi18n処理
+     *  - $userinfoとして渡されたデータによるvsprintf()処理
+     *
+     *  @access public
+     *  @return string  エラーメッセージ
+     */
+    function getMessage()
+    {
+        $tmp_message = $this->i18n ? $this->i18n->get($this->message) : $this->message;
+        $tmp_userinfo = to_array($this->userinfo);
+        $tmp_message_arg_list = array();
+        for ($i = 0; $i < count($tmp_userinfo); $i++) {
+            $tmp_message_arg_list[] = $this->i18n ? $this->i18n->get($tmp_userinfo[$i]) : $tmp_userinfo[$i];
+        }
+        return vsprintf($tmp_message, $tmp_message_arg_list);
+    }
+
+    /**
+     *  エラー追加情報へのアクセサ(R)
+     *
+     *  エラー追加情報配列の個々のエントリへのアクセスをサポート
+     *
+     *  @access public
+     *  @param  int     $n      エラー追加情報のインデックス(省略可)
+     *  @return mixed   message引数
+     */
+    function getUserInfo($n = null)
+    {
+        if (is_null($n)) {
+            return $this->userinfo;
+        }
+
+        if (isset($this->userinfo[$n])) {
+            return $this->userinfo[$n];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     *  エラー追加情報へのアクセサ(W)
+     *
+     *  @access public
+     *  @param  string  $info   追加するエラー情報
+     */
+    function addUserInfo($info)
+    {
+        $this->userinfo[] = $info;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Filter.php b/Idea_Plugin_Extlib/class/Ethna_Filter.php
new file mode 100644 (file)
index 0000000..0fecd4b
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Filter.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Filter
+/**
+ *  フレームワークのフィルタ基底クラス
+ *
+ *  Mojaviの真似です(きっぱり)。アクション実行前に各種処理を行うことが
+ *  出来ます。
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ *  @obsolete
+ */
+class Ethna_Filter
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    object  Ethna_Config        設定オブジェクト */
+    var $config;
+
+    /** @var    object  Ethna_Logger        ログオブジェクト */
+    var $logger;
+
+    /**#@-*/
+
+
+    /**
+     *  Ethna_Filterのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     */
+    function Ethna_Filter(&$controller)
+    {
+        // オブジェクトの設定
+        $this->controller =& $controller;
+        $this->ctl =& $this->controller;
+
+        $this->config =& $controller->getConfig();
+        $this->logger =& $this->controller->getLogger();
+    }
+
+    /**
+     *  実行前フィルタ
+     *
+     *  @access public
+     *  @return Ethna_Error:実行中止 any:正常終了
+     */
+    function preFilter()
+    {
+    }
+
+    /**
+     *  アクション実行前フィルタ
+     *
+     *  @access public
+     *  @param  string  $action_name    実行されるアクション名
+     *  @return string  null:正常終了 (string):実行するアクション名を変更
+     */
+    function preActionFilter($action_name)
+    {
+        return null;
+    }
+
+    /**
+     *  アクション実行後フィルタ
+     *
+     *  @access public
+     *  @param  string  $action_name    実行されたアクション名
+     *  @param  string  $forward_name   実行されたアクションからの戻り値
+     *  @return string  null:正常終了 (string):遷移名を変更
+     */
+    function postActionFilter($action_name, $forward_name)
+    {
+        return null;
+    }
+
+    /**
+     *  実行後フィルタ
+     *
+     *  @access public
+     *  @return Ethna_Error:実行中止 any:正常終了
+     */
+    function postFilter()
+    {
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Generator.php b/Idea_Plugin_Extlib/class/Ethna_Generator.php
new file mode 100644 (file)
index 0000000..eb18949
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Generator.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Generator
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Generator
+{
+    /**
+     *  スケルトンを生成する
+     *
+     *  @access public
+     *  @param  string  $type       生成する対象
+     *  @param  string  $app_dir    アプリケーションのディレクトリ
+     *                              (nullのときはアプリケーションを特定しない)
+     *  @param  mixed   residue     プラグインのgenerate()にそのまま渡す
+     *  @static
+     */
+    function &generate()
+    {
+        $arg_list   = func_get_args();
+        $type       = array_shift($arg_list);
+        $app_dir    = array_shift($arg_list);
+
+        if ($app_dir === null) {
+            $ctl =& Ethna_Handle::getEthnaController();
+        } else {
+            $ctl =& Ethna_Handle::getAppController($app_dir);
+        }
+        if (Ethna::isError($ctl)) {
+            return $ctl;
+        }
+
+        $plugin_manager =& $ctl->getPlugin();
+        if (Ethna::isError($plugin_manager)) {
+            return $plugin_manager;
+        }
+
+        $generator =& $plugin_manager->getPlugin('Generator', $type);
+        if (Ethna::isError($generator)) {
+            return $generator;
+        }
+        
+        // 引数はプラグイン依存とする
+        $ret = call_user_func_array(array(&$generator, 'generate'), $arg_list);
+        return $ret;
+    }
+
+    /**
+     *  スケルトンを削除する
+     *
+     *  @access public
+     *  @param  string  $type       生成する対象
+     *  @param  string  $app_dir    アプリケーションのディレクトリ
+     *                              (nullのときはアプリケーションを特定しない)
+     *  @param  mixed   residue     プラグインのremove()にそのまま渡す
+     *  @static
+     */
+    function &remove()
+    {
+        $arg_list   = func_get_args();
+        $type       = array_shift($arg_list);
+        $app_dir    = array_shift($arg_list);
+
+        if ($app_dir === null) {
+            $ctl =& Ethna_Handle::getEthnaController();
+        } else {
+            $ctl =& Ethna_Handle::getAppController($app_dir);
+        }
+        if (Ethna::isError($ctl)) {
+            return $ctl;
+        }
+
+        $plugin_manager =& $ctl->getPlugin();
+        if (Ethna::isError($plugin_manager)) {
+            return $plugin_manager;
+        }
+
+        $generator =& $plugin_manager->getPlugin('Generator', $type);
+        if (Ethna::isError($generator)) {
+            return $generator;
+        }
+        
+        // 引数はプラグイン依存とする
+        $ret = call_user_func_array(array(&$generator, 'remove'), $arg_list);
+        return $ret;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Getopt.php b/Idea_Plugin_Extlib/class/Ethna_Getopt.php
new file mode 100644 (file)
index 0000000..3af565b
--- /dev/null
@@ -0,0 +1,332 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Getopt.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @license    Public Domain
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+if (!defined('ETHNA_OPTVALUE_IS_DISABLED')) {
+    define('ETHNA_OPTVALUE_IS_DISABLED', 1);
+}
+if (!defined('ETHNA_OPTVALUE_IS_REQUIRED')) {
+    define('ETHNA_OPTVALUE_IS_REQUIRED', 2);
+}
+if (!defined('ETHNA_OPTVALUE_IS_OPTIONAL')) {
+    define('ETHNA_OPTVALUE_IS_OPTIONAL', 3);
+}
+
+// {{{ Ethna_Getopt
+/**
+ *  コマンドラインオプション解釈クラス
+ *  PEAR への依存を排除するため、 Console_Getopt クラスを最実装したもの
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ *  @see        http://pear.php.net/manual/en/package.console.console-getopt.php
+ */
+class Ethna_Getopt
+{
+    /**
+     *  PHP 設定を考慮して、$argv 配列を読みます。 
+     *  ini ディレクティブ中の register_argc_argv を考慮します。
+     *
+     *  注意: PHP 4.2.0 以前では、$argv を読むためには
+     *         register_globals が ON になっている必要が
+     *         ありました。Ethna は この設定がoffであるこ
+     *         とを前提にして書かれているため、ここでは考
+     *         慮していません。
+     *
+     *  @return array - オプションとパラメータを含む配列、
+     *                  もしくは Ethna_Error
+     */
+    function readPHPArgv()
+    {
+        global $argv;
+
+        if (ini_get('register_argc_argv') == false) {
+            return Ethna::raiseError(
+                       'Could not read cmd args (register_argc_argv=Off?'
+                   );
+        }
+        return $argv;
+    }
+
+    /**
+     *  コマンドラインオプションをパースし、結果を返します。
+     *
+     *  @param array  $args - コマンドライン引数の配列
+     *  @param string $shortoptions - 使用できる短いオプション目録を指定します。
+     *  @param array  $longoptions - 使用できる長いオプション目録を指定します。
+     *
+     *  @return array - パースされたオプションと非オプションのコマンドライン引数
+     *                  の 2つの要素からなる配列、もしくは Ethna_Error 。 
+     */
+    function getopt($args, $shortoptions, $longoptions = NULL)
+    {
+        $shortopts = $this->_parseShortOption($shortoptions);
+        if (Ethna::isError($shortopts)) {
+            return $shortopts;
+        }
+        $longopts = $this->_parseLongOption($longoptions);
+        if (Ethna::isError($longopts)) {
+            return $longopts;
+        }
+
+        $parsed_arguments = array();
+        $nonparsed_arguments = array();
+
+        for ($pos = 0; $pos < count($args); $pos++) {
+
+             $arg = $args[$pos];
+             $next_arg = isset($args[$pos + 1]) ? $args[$pos + 1] : NULL;
+             $is_nextarg_is_value = false;
+             $required = false;
+
+             if (strpos($arg, '--') === 0) { //  long option
+
+                 //
+                 // GNU getopt(3) の場合は、長いオプションは他と重なら
+                 // ない限りにおいて短縮できる。たとえば --foo, --fuji
+                 // というオプションが定義された場合、 --fo や --fu と
+                 // いう短縮指定も可能というものである。
+                 //
+                 // PEAR の Console_Getopt はこの短縮指定に対応していな
+                 // い。よって、それを使用してきた Ethna でもそこまでは
+                 // 頑張らないことにする。
+                 //
+
+                 //    オプションの値を処理する
+                 $lopt = str_replace('--', '', $arg);
+                 $opt_and_value = explode('=', $lopt);
+                 $opt = $opt_and_value[0];
+                 if (!array_key_exists($opt, $longopts)) {
+                     return Ethna::raiseError("unrecognized option --$opt");
+                 }
+                 
+                 //  オプションの値を取り出す 
+                 $required = $longopts[$opt];
+                 $value = NULL;
+                 if (count($opt_and_value) == 2) {
+                     $value = $opt_and_value[1];   // --foo=bar
+                 } elseif (strpos('-', $next_arg) !== 0
+                        && $required == ETHNA_OPTVALUE_IS_REQUIRED) {
+                     if (!empty($next_arg)) {      // --foo bar
+                         // 次の $argv を値として解釈
+                         // == が設定されていた場合は値として解釈「しない」
+                         $value = $next_arg;
+                         $pos++;
+                     }
+                 }
+
+                 //  オプション設定チェック 
+                 switch ($required) {
+                     case ETHNA_OPTVALUE_IS_REQUIRED:
+                         if ($value === NULL) {
+                             return Ethna::raiseError(
+                                        "option --$opt requires an argument"
+                                    );
+                         }
+                         break;
+                     case ETHNA_OPTVALUE_IS_DISABLED:
+                         if ($value !== NULL) {
+                             return Ethna::raiseError(
+                                        "option --$opt doesn't allow an argument"
+                                    );
+                         }    
+                         break;
+                 }
+
+                 //  長いオプションの場合は、-- 付きでオプション名を記録する
+                 //  Console_Getopt 互換にするため。
+                 $parsed_arguments[] = array("--$opt", $value);
+
+             } elseif (strpos($arg, '-') === 0) {  // short option
+
+                 //
+                 // -abcd のように、オプションと値が続けて
+                 // 入力される場合がある。この場合どうオプションを解釈
+                 // するかの仕様は、GNU getopt(3) の仕様に従う
+                 //
+                 // 1. abcd を1文字ずつに分解し、a, b, c, d にする
+                 //
+                 // 2. ':' (値必須) として設定されていた場合は、次の文字以降は
+                 //    全て値として解釈する。この場合は次のargvは値として解釈し
+                 //    ない。また、次の文字がなく、次の argv が値だった場合は、
+                 //    それを値として解釈する
+                 // 3. '::'(値が任意) として設定されていた場合も次の文字以降を
+                 //    全て値として解釈するが、次の文字がない場合でも次のargvは
+                 //    値として解釈「しない」
+                 //
+                 // 4. 無設定(値設定禁止)の場合は、次の文字もオプションとして解
+                 //    釈する。また、次のargvは値として解釈しない
+                 //
+                 // @see LANG=C; man 3 getopt (日本語マニュアルは見ない方がいいかも)
+                 // @see http://www.gnu.org/software/libtool/manual/libc/Using-Getopt.html
+                 //
+                 //  TODO: ambiguous なオプションを検出できるようにする
+                 //
+                 $sopt = str_replace('-', '', $arg);
+                 $sopt_len = strlen($sopt);
+
+                 for ($sopt_pos = 0; $sopt_pos < $sopt_len; $sopt_pos++) {
+
+                     //  オプションを取り出す
+                     $opt = $sopt[$sopt_pos];
+
+                     $value = NULL;
+                     $do_next_arg = false;
+                     $required = isset($shortopts[$opt]) ? $shortopts[$opt] : NULL;
+                     switch ($required) {
+                         case ETHNA_OPTVALUE_IS_REQUIRED:
+                         case ETHNA_OPTVALUE_IS_OPTIONAL:
+                            if ($sopt_len == 1
+                             && $required == ETHNA_OPTVALUE_IS_REQUIRED) {
+                                if ($next_arg[0] != '-') { // -a hoge
+                                    // 次の $argv を値として解釈
+                                    // 但し、:: の場合は解釈しない
+                                    $value = $next_arg;
+                                    $pos++;
+                                }
+                            } else {
+                                //  残りの文字を値として解釈
+                                $value = substr($sopt, $sopt_pos + 1);
+                                $value = (empty($value)) ? NULL : $value;
+                            } 
+                            if ($required == ETHNA_OPTVALUE_IS_REQUIRED
+                              && empty($value)) {
+                                 return Ethna::raiseError(
+                                            "option -$opt requires an argument"
+                                        );
+                             }
+                             // ':' または '::' が設定された場合は、次の文字
+                             // 以降を全て値として解釈するため、次のargv要素に
+                             // 解釈を移す
+                             $do_next_arg = true;
+                             break;
+                         case ETHNA_OPTVALUE_IS_DISABLED:
+                             //   値を設定禁止にした場合は、値が解釈されなく
+                             //   なるので、値設定のチェックは不要
+                             break;
+                         default:
+                             return Ethna::raiseError("unrecognized option -$opt");
+                             break;
+                     }
+
+                     //  短いオプションの場合は、- を付けないでオプション名を記録する
+                     //  Console_Getopt 互換にするため。
+                     $parsed_arguments[] = array($opt, $value);
+
+                     if ($do_next_arg === true) {
+                         break;
+                     }
+                 } 
+
+             } else {  // オプションとして解釈されない
+
+                 //   non-parsed なオプションに辿り着いた
+                 //   ら、それ以降の解釈を停止する
+                 //   つまり、それ以降は全て値として解釈する
+                 //
+                 //   これは POSIX_CORRECT な実装であって
+                 //   GNU Getopt な実装ではないが、実際に
+                 //   Console_Getopt で行われている以上、
+                 //   それに従った実装
+                 $nonparsed_arguments = array_slice($args, $pos);
+                 break;
+             }
+        }
+  
+        return array($parsed_arguments, $nonparsed_arguments);
+    }
+
+    /**
+     *  短いオプション目録を解析します。
+     *
+     *  @param  string $sopts 短いオプション目録
+     *  @return array  オプションと引数指定種別の配列
+     *                 エラーの場合は Ethna_Error
+     *  @access private
+     */
+    function _parseShortOption($sopts)
+    {
+        if (empty($sopts)) {
+            return array();
+        }
+
+        if (!preg_match('/^[A-Za-z:]+$/', $sopts)) {
+            return Ethna::raiseError('invalid short options.');
+        }
+
+        $analyze_result = array();
+
+        for ($pos = 0; $pos < strlen($sopts); $pos++) {
+            $char = $sopts[$pos];
+            $next_char = (isset($sopts[$pos + 1]))
+                       ? $sopts[$pos + 1]
+                       : NULL;
+            $next2_char = (isset($sopts[$pos + 2]))
+                        ? $sopts[$pos + 2]
+                        : NULL;
+
+            if ($char == ':') {
+                continue;
+            }
+
+            //   $sopts[$pos] is character.
+            if ($next_char == ':' && $next2_char == ':') {
+                $analyze_result[$char] = ETHNA_OPTVALUE_IS_OPTIONAL; // 値は任意
+            } elseif ($next_char == ':' && $next2_char != ':') { 
+                $analyze_result[$char] = ETHNA_OPTVALUE_IS_REQUIRED; // 値は必須
+            } else {
+                $analyze_result[$char] = ETHNA_OPTVALUE_IS_DISABLED; // 値は不要
+            }
+        }
+
+        return $analyze_result;
+    }
+
+    /**
+     *  長いオプション目録を解析します。
+     *
+     *  @param  array $lopts 長いオプション目録
+     *  @return array オプションと引数指定種別の配列
+     *                エラーの場合は Ethna_Error
+     *  @access private
+     */
+    function _parseLongOption($lopts)
+    {
+        if (empty($lopts)) {
+            return array();
+        }
+
+        if (!is_array($lopts)) {
+            return Ethna::raiseError('invalid long options.');
+        }
+
+        $analyze_result = array();
+         
+        foreach ($lopts as $opt) {
+            if (preg_match('/==$/', $opt) > 0) {
+                $opt = substr($opt, 0, -2); 
+                $analyze_result[$opt] = ETHNA_OPTVALUE_IS_OPTIONAL; // 値は任意
+            } elseif (preg_match('/=$/', $opt) > 0) {
+                $opt = substr($opt, 0, -1); 
+                $analyze_result[$opt] = ETHNA_OPTVALUE_IS_REQUIRED; // 値は必須
+            } else {
+                $analyze_result[$opt] = ETHNA_OPTVALUE_IS_DISABLED; // 値は不要
+            }
+        }
+
+        return $analyze_result;
+    }
+}
+
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Handle.php b/Idea_Plugin_Extlib/class/Ethna_Handle.php
new file mode 100644 (file)
index 0000000..65ea20c
--- /dev/null
@@ -0,0 +1,211 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Handle.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Handle
+/**
+ *  Manager class of Ethna (Command Line) Handlers
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Handle
+{
+    /**#@+
+     *  @access     private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    object  Ethna_Pluguin       pluginオブジェクト */
+    var $plugin;
+
+    /**#@-*/
+
+    // {{{ constructor
+    /**
+     *  Ethna_Handle constructor (stub for php4)
+     *
+     *  @access public
+     */
+    function Ethna_Handle()
+    {
+        $this->controller =& new Ethna_Controller(GATEWAY_CLI);
+        Ethna::clearErrorCallback();
+        Ethna::setErrorCallback(array('Ethna_Handle', 'handleError'));
+
+        $this->ctl =& $this->controller;
+        $this->plugin =& $this->controller->getPlugin();
+    }
+    // }}}
+
+    // {{{ getHandler
+    /**
+     *  get handler object
+     *
+     *  @access public
+     */
+    function &getHandler($id)
+    {
+        $name = preg_replace('/\-(.)/e', "strtoupper('\$1')", ucfirst($id));
+        $handler =& $this->plugin->getPlugin('Handle', $name);
+        if (Ethna::isError($handler)) {
+            return $handler;
+        }
+
+        return $handler;
+    }
+    // }}}
+
+    // {{{ getHandlerList
+    /**
+     *  get an object list of all available handlers
+     *
+     *  @access public
+     */
+    function getHandlerList()
+    {
+        $handler_list = $this->plugin->getPluginList('Handle');
+        usort($handler_list, array($this, "_handler_sort_callback"));
+
+        return $handler_list;
+    }
+
+    /**
+     *  sort callback method
+     */
+    function _handler_sort_callback($a, $b)
+    {
+        return strcmp($a->getId(), $b->getId());
+    }
+    // }}}
+
+    // {{{ getEthnaController
+    /**
+     *  Ethna_Controllerのインスタンスを取得する
+     *  (Ethna_Handlerの文脈で呼び出されることが前提)
+     *
+     *  @access public
+     *  @static
+     */
+    function &getEthnaController()
+    {
+        return Ethna_Controller::getInstance();
+    }
+    // }}}
+
+    // {{{ getAppController
+    /**
+     *  アプリケーションのコントローラファイル/クラスを検索する
+     *
+     *  @access public
+     *  @static
+     */
+    function &getAppController($app_dir = null)
+    {
+        static $app_controller = array();
+
+        if (isset($app_controller[$app_dir])) {
+            return $app_controller[$app_dir];
+        } else if ($app_dir === null) {
+            return Ethna::raiseError('$app_dir not specified.');
+        }
+
+        $ini_file = null;
+        while (is_dir($app_dir)) {
+            if (is_file("$app_dir/.ethna")) {
+                $ini_file = "$app_dir/.ethna";
+                break;
+            }
+            $app_dir = dirname($app_dir);
+            if (Ethna_Util::isRootDir($app_dir)) {
+                break;
+            }
+        }
+
+        if ($ini_file === null) {
+            return Ethna::raiseError('no .ethna file found');
+        }
+        
+        $macro = parse_ini_file($ini_file);
+        if (isset($macro['controller_file']) == false
+            || isset($macro['controller_class']) == false) {
+            return Ethna::raiseError('invalid .ethna file');
+        }
+        $file = $macro['controller_file'];
+        $class = $macro['controller_class'];
+
+        $controller_file = "$app_dir/$file";
+        if (is_file($controller_file) == false) {
+            return Ethna::raiseError("no such file $controller_file");
+        }
+
+        include_once $controller_file;
+        if (class_exists($class) == false) {
+            return Ethna::raiseError("no such class $class");
+        }
+
+        $global_controller =& $GLOBALS['_Ethna_controller'];
+        $app_controller[$app_dir] =& new $class(GATEWAY_CLI);
+        $GLOBALS['_Ethna_controller'] =& $global_controller;
+        Ethna::clearErrorCallback();
+        Ethna::setErrorCallback(array('Ethna_Handle', 'handleError'));
+
+        return $app_controller[$app_dir];
+    }
+    // }}}
+
+    // {{{ getMasterSetting
+    /**
+     *  Ethna 本体の設定を取得する (ethnaコマンド用)
+     *
+     *  @param  $section    ini ファイルの section
+     *  @access public
+     */
+    function &getMasterSetting($section = null)
+    {
+        static $setting = null;
+        if ($setting === null) {
+            $ini_file = ETHNA_BASE . "/.ethna";
+            if (is_file($ini_file) && is_readable($ini_file)) {
+                $setting = parse_ini_file($ini_file, true);
+            } else {
+                $setting = array();
+            }
+        }
+
+        if ($section === null) {
+            return $setting;
+        } else if (array_key_exists($section, $setting)) {
+            return $setting[$section];
+        } else {
+            $array = array();
+            return $array;
+        }
+    }
+    // }}}
+
+    // {{{ handleError
+    /**
+     *  Ethna コマンドでのエラーハンドリング
+     */
+    function handleError(&$eobj)
+    {
+        // do nothing.
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_I18N.php b/Idea_Plugin_Extlib/class/Ethna_I18N.php
new file mode 100644 (file)
index 0000000..e02b27d
--- /dev/null
@@ -0,0 +1,377 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_I18N.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{  mbstring enabled check
+function mb_enabled()
+{
+    return (extension_loaded('mbstring')) ? true : false;
+}
+// }}}
+
+// {{{ I18N shortcut
+/**
+ *  メッセージカタログからロケールに適合するメッセージを取得します。
+ *  Ethna_I18N#get のショートカットです。
+ *
+ *  @access public
+ *  @param  string  $message    メッセージ
+ *  @return string  ロケールに適合するメッセージ
+ *  @see    Ethna_I18N#get
+ */
+function _et($message) 
+{
+    $ctl =& Ethna_Controller::getInstance();
+    $i18n =& $ctl->getI18N();
+    return $i18n->get($message);
+}
+// }}}
+// {{{ Ethna_I18N
+/**
+ *  i18n関連の処理を行うクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_I18N
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    Ethna_Controller  コントローラーオブジェクト  */
+    var $ctl;
+
+    /** @var    bool    gettextフラグ */
+    var $use_gettext;
+
+    /** @var    string  ロケール */
+    var $locale;
+
+    /** @var    string  プロジェクトのロケールディレクトリ */
+    var $locale_dir;
+
+    /** @var    string  アプリケーションID */
+    var $appid;
+
+    /** @var    string  システム側エンコーディング */
+    var $systemencoding;
+
+    /** @var    string  クライアント側エンコーディング */
+    var $clientencoding;
+
+    /** @var    mixed   Ethna独自のメッセージカタログ */
+    var $messages;
+
+    /** @var    mixed   ロガーオブジェクト */
+    var $logger;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_I18Nクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  string  $locale_dir プロジェクトのロケールディレクトリ
+     *  @param  string  $appid      アプリケーションID
+     */
+    function Ethna_I18N($locale_dir, $appid)
+    {
+        $this->locale_dir = $locale_dir;
+        $this->appid = $appid;
+
+        $this->ctl =& Ethna_Controller::getInstance();
+        $config =& $this->ctl->getConfig();
+        $this->logger =& $this->ctl->getLogger();
+        $this->use_gettext = $config->get('use_gettext') ? true : false;
+
+        //    gettext load check. 
+        if ($this->use_gettext === true
+         && !extension_loaded("gettext")) {
+            $this->logger->log(LOG_WARNING,
+                "You specify to use gettext in ${appid}/etc/${appid}-ini.php, "
+              . "but gettext extension was not installed !!!"
+            );
+        }
+
+        $this->messages = false;  //  not initialized yet.
+    }
+
+    /**
+     *  ロケール、言語設定を設定する
+     *
+     *  @access public
+     *  @param  string  $locale         ロケール名(e.x ja_JP, en_US 等)
+     *                                  (ll_cc の形式。ll = 言語コード cc = 国コード)
+     *  @param  string  $systemencoding システムエンコーディング名
+     *  @param  string  $clientencoding クライアントエンコーディング名
+     *                                  (=テンプレートのエンコーディングと考えてよい)
+     *  @see    http://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html 
+     */
+    function setLanguage($locale, $systemencoding = null, $clientencoding = null)
+    {
+        setlocale(LC_ALL, $locale);
+
+        if ($this->use_gettext) {
+            bind_textdomain_codeset($locale, $clientencoding);
+            bindtextdomain($locale, $this->locale_dir);
+            textdomain($locale);
+        }
+
+        $this->locale = $locale;
+        $this->systemencoding = $systemencoding;
+        $this->clientencoding = $clientencoding;
+
+        //  強制的にメッセージカタログ再生成
+        if (!$this->use_gettext) {
+            $this->messages = $this->_makeEthnaMsgCatalog();
+        }
+    }
+
+    /**
+     *  メッセージカタログからロケールに適合するメッセージを取得する
+     *
+     *  @access public
+     *  @param  string  $msg    メッセージ
+     *  @return string  ロケールに適合するメッセージ
+     */
+    function get($msg)
+    {
+
+        if ($this->use_gettext) {
+
+            //
+            //    gettext から返されるメッセージは、
+            //    [appid]/locale/[locale_name]/LC_MESSAGES/[locale].mo から
+            //    返される。エンコーディング変換はgettext任せである
+            //
+            return gettext($msg);
+
+        } else {
+
+            //
+            //  初期化されてない場合は、
+            //  Ethna独自のメッセージカタログを初期化
+            //
+            if ($this->messages === false) {
+                $this->messages = $this->_makeEthnaMsgCatalog();
+            }
+
+            //
+            //  Ethna独自のメッセージは、
+            //  [appid]/locale/[locale_name]/LC_MESSAGES/*.ini から
+            //  返される。
+            //
+            if (isset($this->messages[$msg]) && !empty($this->messages[$msg])) {
+
+                $ret_message = $this->messages[$msg];
+
+                //
+                //  convert message in case $client_encoding
+                //  setting IS NOT UTF-8.
+                //
+                //  @see Ethna_Controller#_getDefaultLanguage
+                // 
+                if (strcasecmp($this->clientencoding, 'UTF-8') !== 0) {
+                    return mb_convert_encoding($ret_message, $this->clientencoding, 'UTF-8');
+                }
+
+                return $ret_message;
+            }
+
+        }
+
+        return $msg;
+    }
+
+    /**
+     *  Ethna独自のメッセージカタログを読み込んで生成する
+     *
+     *  1. [appid]/locale/[locale_name]/LC_MESSAGES/*.ini
+     *     からメッセージを読み込む。
+     *  2. Ethnaが吐くメッセージカタログファイル名は ethna_sysmsg.ini とし、
+     *     skel化して ETHNA_HOME/skel/locale/[locale_name]/ethna_sysmsg.ini に置く
+     *  3. "ethna i18n" コマンドでは、1. のファイルとプロジェクトファイル
+     *     内の _et('xxxx') を全て走査し、メッセージカタログを作る。gettext を利用
+     *     するのであれば、potファイルを生成する。
+     *  4. ethna_sysmsg.ini は単純な ini ファイル形式とし、
+     *     "msgid" = "translation" の形式とする。エンコーディングは一律 UTF-8
+     * 
+     *  @access  private 
+     *  @return  array     読み込んだメッセージカタログ。失敗した場合は空の配列 
+     */
+    function _makeEthnaMsgCatalog()
+    {
+        $ret_messages = array();
+
+        //    Ethna_I18N#setLanguage を呼び出さず
+        //    このメソッドを呼び出すと、ロケール名が空になる
+        //    その場合は Ethna_Controller の設定を補う
+        if (empty($this->locale)) {
+            list($this->locale, $sys_enc, $cli_enc) = $this->ctl->getLanguage();
+        }
+
+        //    ロケールディレクトリが存在しない場合は、E_NOTICEを出し、
+        //    デフォルトの skelton ファイルを使う
+        $msg_dir = sprintf("%s/%s/LC_MESSAGES", $this->locale_dir, $this->locale);
+        if (!file_exists($msg_dir)) {
+            //   use skelton.
+            $this->logger->log(LOG_NOTICE,
+                               "Message directory was not found!! : $msg_dir,"
+                             . " Use skelton file Instead"); 
+            $msg_dir = sprintf("%s/skel/locale/%s", ETHNA_BASE, $this->locale);
+            if (!file_exists($msg_dir)) {  // last fallback.
+                $msg_dir = sprintf("%s/skel/locale", ETHNA_BASE);
+            }
+        }
+                     
+        //  localeディレクトリ内のファイルを読み込み、parseする
+        $msg_dh = opendir($msg_dir);
+        while (($file = readdir($msg_dh)) !== false) {
+            if (is_dir($file) || !preg_match("/[A-Za-z0-9\-_]+\.ini$/", $file)) {
+                continue;
+            }
+            $msg_file = sprintf("%s/%s", $msg_dir, $file);
+            $messages = $this->parseEthnaMsgCatalog($msg_file);
+            $ret_messages = array_merge($ret_messages, $messages);
+        }
+        
+        return $ret_messages;
+    }
+
+    /**
+     *  Ethna独自のメッセージカタログをparseする
+     *
+     *  @access  public
+     *  @param   string    メッセージカタログファイル名
+     *  @return  array     読み込んだメッセージカタログ。失敗した場合は空の配列 
+     */
+    function parseEthnaMsgCatalog($file)
+    {
+        $messages = array();
+
+        //
+        //    ファイルフォーマットは ini ファイルライクだが、
+        //    parse_ini_file 関数は使わない。
+        //
+        //    キーに含められないキーワードや文字があるため。
+        //    e.x yes, no {}|&~![() 等
+        //    @see http://www.php.net/manual/en/function.parse-ini-file.php
+        //
+        $contents = file($file);
+        if ($contents === false) {
+            return $messages;
+        }
+
+        $quote = 0;                   // ダブルクオートの数 
+        $in_translation_line = false; // 翻訳行をパース中か否か
+        $before_is_quote = false;     // 直前の文字がクォート文字(\)か否か
+        $equal_op = 0;                // 等値演算子の数
+        $is_end = false;              // 終了フラグ
+        $msgid = $msgstr = '';
+
+        foreach ($contents as $idx => $line) {
+
+            //  コメント行または空行は無視する。
+            //  ホワイトスペースを除いた上で、それと看做される行も無視する
+            $ltrimed_line = ltrim($line);
+            if ($in_translation_line == false
+            && (strpos($ltrimed_line, ';') === 0 || preg_match('/^$/', $ltrimed_line))) {
+                continue;
+            }
+
+            //    1文字ずつ、ダブルクォートの数
+            //    を基準にしてパースする
+            $length = strlen($line);
+            for ($pos = 0; $pos < $length; $pos++) {
+
+                //    特別な文字で分岐
+                switch ($line[$pos]) {
+                    case '"':
+                        if ($in_translation_line == false && $pos == 0) {
+                            $in_translation_line = true;  // 翻訳行開始
+                        }
+                        if (!$before_is_quote) {
+                            $quote++;
+                            continue 2;  // switch 文を抜けるのではなく、
+                                         // for文に戻る = 次の文字へ
+                        }
+                        //  クォートされた「"」
+                        $before_is_quote = false;         
+                        break; 
+                    case '=':
+                        //  等値演算子は文法的にvalidかどうかを確
+                        //  認する手段でしかない 
+                        if ($quote == 2) {
+                            $equal_op++;
+                        }
+                    case '\\': // backslash
+                        //   クォート用のバックスラッシュと看做す
+                        if ($quote == 1 || $quote == 3) {
+                            $before_is_quote = true;
+                        }
+                        break;
+                    default:
+                        if ($before_is_quote) {
+                            $before_is_quote = false;
+                        }
+                        if ($quote == 4) {
+                            $is_end = true;   
+                        }
+                }
+
+                if ($is_end == true) {
+                    $in_translation_line = false;  //  翻訳行終了
+                    break;
+                }
+
+                //  パース済みの文字列を追加
+                if ($quote == 1) {
+                    $msgid .= $line[$pos];
+                }
+                if ($quote == 3) {
+                    $msgstr .= $line[$pos];
+                }
+            }
+        
+            //  一行分のパース終了
+            if ($is_end == false) {
+                //  翻訳行がまだ終わってない場合次の行へ
+                continue;
+            } elseif ($equal_op != 1 || $quote != 4) { 
+                //  終わっているが、valid な翻訳行でない場合
+                $this->logger->log(LOG_WARNING,
+                                   "invalid message catalog in {$file}, line " . ($idx + 1)
+                );
+                continue; 
+            } 
+
+            //  カタログに追加
+            $msgid = preg_replace('#\\\"#', '"', $msgid);
+            $msgstr = preg_replace('#\\\"#', '"', $msgstr);
+            $messages[$msgid] = $msgstr; 
+
+            //  パース用変数をリセット
+            $quote = 0;                   
+            $in_translation_line = false;
+            $before_is_quote = false;
+            $equal_op = 0;
+            $is_end = false;
+            $msgid = $msgstr = '';
+        }
+        
+        return $messages;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_InfoManager.php b/Idea_Plugin_Extlib/class/Ethna_InfoManager.php
new file mode 100644 (file)
index 0000000..c3957be
--- /dev/null
@@ -0,0 +1,742 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_InfoManager.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_InfoManager
+/**
+ *  Ethnaマネージャクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_InfoManager extends Ethna_AppManager
+{
+    /**#@+
+     *  @access private
+     */
+    
+    /** @var    object  Ethna_Controller    コントローラオブジェクト */
+    var $ctl;
+
+    /** @var    object  Ethna_ClassFactory  クラスファクトリオブジェクト */
+    var $class_factory;
+
+    /** @var    array   アクションスクリプト解析結果キャッシュファイル */
+    var $cache_class_list_file;
+
+    /** @var    array   アクションスクリプト解析結果キャッシュ */
+    var $cache_class_list;
+
+    /** @var    array   [属性]DBタイプ一覧 */
+    var $db_type_list = array(
+        DB_TYPE_RW      => array('name' => 'DB_TYPE_RW'),
+        DB_TYPE_RO      => array('name' => 'DB_TYPE_RO'),
+        DB_TYPE_MISC    => array('name' => 'DB_TYPE_MISC'),
+    );
+
+    /** @var    array   [属性]フォーム型一覧 */
+    var $form_type_list;
+    /** @var    array   [属性]変数型一覧 */
+    var $var_type_list;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_InfoManagerのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Backend   &$backend   Ethna_Backendオブジェクト
+     */
+    function Ethna_InfoManager(&$backend)
+    {
+        $this->form_type_list = array(
+                                    FORM_TYPE_TEXT => array('name' => _et('TextBox')),
+                                    FORM_TYPE_PASSWORD  => array('name' => _et('Password')),
+                                    FORM_TYPE_TEXTAREA  => array('name' => _et('TextArea')),
+                                    FORM_TYPE_SELECT    => array('name' => _et('SelectBox')),
+                                    FORM_TYPE_RADIO     => array('name' => _et('RadioButton')),
+                                    FORM_TYPE_CHECKBOX  => array('name' => _et('CheckBox')),
+                                    FORM_TYPE_SUBMIT    => array('name' => _et('SubmitButton')),
+                                    FORM_TYPE_FILE      => array('name' => _et('File')),
+                                );
+
+        $this->var_type_list = array(
+                                    VAR_TYPE_INT        => array('name' => _et('Integer')),
+                                    VAR_TYPE_FLOAT      => array('name' => _et('Float')),
+                                    VAR_TYPE_STRING     => array('name' => _et('String')),
+                                    VAR_TYPE_DATETIME   => array('name' => _et('Datetime')),
+                                    VAR_TYPE_BOOLEAN    => array('name' => _et('Boolean')),
+                                    VAR_TYPE_FILE       => array('name' => _et('File')),
+                               );
+
+        parent::Ethna_AppManager($backend);
+        $this->ctl =& Ethna_Controller::getInstance();
+        $this->class_factory =& $this->ctl->getClassFactory();
+
+        // アクションスクリプト解析結果キャッシュ取得
+        $this->cache_class_list_file = sprintf('%s/ethna_info_class_list',
+                                               $this->ctl->getDirectory('tmp')
+                                       );
+        if (file_exists($this->cache_class_list_file)
+         && filesize($this->cache_class_list_file) > 0) {
+            $fp = fopen($this->cache_class_list_file, 'r');
+            $s = fread($fp, filesize($this->cache_class_list_file));
+            fclose($fp);
+            $this->cache_class_list = unserialize($s);
+        }
+    }
+
+    /**
+     *  定義済みアクション一覧を取得する
+     *
+     *  @access public
+     *  @return array   アクション一覧
+     */
+    function getActionList()
+    {
+        $r = array();
+
+        // アクションスクリプトを解析する
+        $class_list = $this->_analyzeActionList();
+
+        // アクション定義エントリ一覧
+        list($manifest_action_list, $manifest_class_list) = $this->_getActionList_Manifest($class_list);
+
+        // アクション定義省略エントリ一覧
+        $implicit_action_list = $this->_getActionList_Implicit($class_list, $manifest_action_list, $manifest_class_list);
+
+        $r = array_merge($manifest_action_list, $implicit_action_list);
+        ksort($r);
+
+        // アクション定義情報補完
+        $r = $this->_addActionList($r);
+
+        return $r;
+    }
+
+    /**
+     *  定義済み遷移先一覧を取得する
+     *
+     *  @access public
+     *  @return array   遷移先一覧
+     */
+    function getForwardList()
+    {
+        $r = array();
+
+        // テンプレート/ビュースクリプトを解析する
+        $forward_list = $this->_analyzeForwardList();
+
+        // ビュー定義エントリ一覧
+        $manifest_forward_list = $this->_getForwardList_Manifest();
+
+        // ビュー定義省略エントリ一覧
+        $implicit_forward_list = $this->_getForwardList_Implicit($forward_list, $manifest_forward_list);
+
+        $r = array_merge($manifest_forward_list, $implicit_forward_list);
+        ksort($r);
+
+        return $r;
+    }
+
+    /**
+     *  ディレクトリ以下のアクションスクリプトを解析する
+     *
+     *  @access private
+     *  @param  string  $action_dir     解析対象のディレクトリ
+     *  @return array   アクションクラス定義一覧
+     */
+    function _analyzeActionList($action_dir = null)
+    {
+        $r = array();
+        $cache_update = false;
+
+        if (is_null($action_dir)) {
+            $cache_update = true;
+            $action_dir = $this->ctl->getActiondir();
+        }
+        $prefix_len = strlen($this->ctl->getActiondir());
+
+        $child_dir_list = array();
+
+        $dh = opendir($action_dir);
+        if ($dh == false) {
+            return;
+        }
+
+        $ext = $this->ctl->getExt('php');
+        while (($file = readdir($dh)) !== false) {
+            if ($file == "." || $file == "..") {
+                continue;
+            }
+            $file = $action_dir . $file;
+
+            if (is_dir($file)) {
+                $child_dir_list[] = $file;
+                continue;
+            }
+
+            if (preg_match("/\.$ext\$/", $file) == 0) {
+                continue;
+            }
+
+            $key = substr($file, $prefix_len);
+            
+            // キャッシュチェック
+            include_once $file;
+            if ($this->cache_class_list[$key]['.mtime'] >= filemtime($file)) {
+                $class_list = $this->cache_class_list[$key];
+            } else {
+                $class_list = $this->_analyzeActionScript($file);
+            }
+            if (is_null($class_list) == false) {
+                $r[$key] = $class_list;
+            }
+        }
+
+        closedir($dh);
+
+        foreach ($child_dir_list as $child_dir) {
+            $tmp = $this->_analyzeActionList($child_dir . "/");
+            $r = array_merge($r, $tmp);
+        }
+
+        if ($cache_update) {
+            // キャッシュファイル更新
+            $fp = fopen($this->cache_class_list_file, 'w');
+            fwrite($fp, serialize($r));
+            fclose($fp);
+        }
+
+        return $r;
+    }
+
+    /**
+     *  アクションスクリプトを解析する
+     *
+     *  @access private
+     *  @param  string  $script ファイル名
+     *  @return array   アクションクラス定義一覧
+     */
+    function _analyzeActionScript($script)
+    {
+        $class_list = array();
+        $class_list['.mtime'] = filemtime($script);
+
+        $source = "";
+        $fp = fopen($script, 'r');
+        if ($fp == false) {
+            return null;
+        }
+        while (feof($fp) == false) {
+            $source .= fgets($fp, 8192);
+        }
+        fclose($fp);
+
+        // トークンに分割してクラス定義情報を取得
+        $token_list = token_get_all($source);
+        $state = 'T_OUT';
+        $nest = 0;
+        $method_nest = 0;
+        $current = null;
+        for ($i = 0; $i < count($token_list); $i++) {
+            $token = $token_list[$i];
+
+            if (is_string($token)) {
+                if ($token == '{') {
+                    $nest++;
+                } else if ($token == '}') {
+                    $nest--;
+                    if ($state == 'T_PREPARE' || $state == 'T_PERFORM') {
+                        if ($nest == $method_nest) {
+                            $state = 'T_ACTION_CLASS';
+                        }
+                    } else if ($nest == 0) {
+                        $state = 'T_OUT';
+                    }
+                }
+                continue;
+            }
+
+            if ($token[0] == T_CLASS) {
+                // クラス定義開始
+                $i += 2;
+                $class_name = $token_list[$i][1];       // should be T_STRING
+                if ($this->_isSubclassOf($class_name, 'Ethna_ActionClass')) {
+                    $state = 'T_ACTION_CLASS';
+                    $current = $class_name;
+                    $class_list[$current] = array('type' => 'action_class');
+                } else if ($this->_isSubclassOf($class_name, 'Ethna_ActionForm')) {
+                    $state = 'T_ACTION_FORM';
+                    $current = $class_name;
+                    $class_list[$current] = array('type' => 'action_form');
+                }
+                $nest = 0;  // for safe
+            } else if ($token[0] == T_COMMENT && strncmp($token[1], "/**", 3) == 0 && is_array($token_list[$i+2]) && $token_list[$i+2][0] == T_CLASS) {
+                // DocComment for class
+            } else if ($state == 'T_ACTION_CLASS' && $token[0] == T_FUNCTION) {
+                $i += 2;
+                $method_name = $token_list[$i][1];
+                if (strcasecmp($method_name, 'prepare') == 0) {
+                    $state = 'T_PREPARE';
+                    $method_nest = $nest;
+                } else if (strcasecmp($method_name, 'perform') == 0) {
+                    $state = 'T_PERFORM';
+                    $method_nest = $nest;
+                }
+            } else if (($state == 'T_PREPARE' || $state == 'T_PERFORM') && $token[0] == T_RETURN) {
+                $s = "";
+                $n = 1;
+                while ($token_list[$i+$n] !== ";") {
+                    $s .= is_string($token_list[$i+$n]) ? $token_list[$i+$n] : $token_list[$i+$n][1];
+                    $n++;
+                }
+                $key = $state == 'T_PREPARE' ? 'prepare' : 'perform';
+                $class_list[$current]['return'][$key][] = $s;
+            }
+        }
+
+        if (count($class_list) == 0) {
+            return null;
+        }
+        return $class_list;
+    }
+
+    /**
+     *  指定されたクラス名を継承しているかどうかを返す
+     *
+     *  @access private
+     *  @param  string  $class_name     チェック対象のクラス名
+     *  @param  string  $parent_name    親クラス名
+     *  @return bool    true:継承している false:いない
+     */
+    function _isSubclassOf($class_name, $parent_name)
+    {
+        while ($tmp = get_parent_class($class_name)) {
+            if (strcasecmp($tmp, $parent_name) == 0) {
+                return true;
+            }
+            $class_name = $tmp;
+        }
+        return false;
+    }
+
+    /**
+     *  コントローラに明示的に定義されているアクション一覧を取得する
+     *
+     *  @access private
+     *  @param  array   定義されているクラス一覧
+     *  @return array   array(アクション一覧, クラス一覧)
+     */
+    function _getActionList_Manifest($class_list)
+    {
+        $manifest_action_list = array();
+        $manifest_class_list = array();
+        foreach ($this->ctl->action as $action_name => $action) {
+            if ($action_name == '__ethna_info__') {
+                continue;
+            }
+            $action = $this->ctl->_getAction($action_name);
+
+            $elt = array();
+            // _analyzeActionList()で取得したクラス定義データから対応関係を取得
+            foreach ($class_list as $file => $elts) {
+                foreach ($elts as $class_name => $def) {
+                    if ($def['type'] == 'action_class' && strcasecmp($class_name, $action['class_name']) == 0) {
+                        $elt['action_class'] = $class_name;
+                        $elt['action_class_file'] = $file;
+                        $elt['action_class_info'] = $def;
+                    } else if ($def['type'] == 'action_form' && strcasecmp($class_name, $action['form_name']) == 0) {
+                        $elt['action_form'] = $class_name;
+                        $elt['action_form_file'] = $file;
+                        $elt['action_form_info'] = $def;
+                    }
+                }
+            }
+
+            // 未定義チェック
+            if (isset($elt['action_class']) == false) {
+                $elt['action_class'] = $action['class_name'];
+                if (class_exists($action['class_name']) == false) {
+                    $elt['action_class_info'] = array('undef' => true);
+                }
+            }
+            if (isset($elt['action_form']) == false && $action['form_name'] != 'Ethna_ActionForm') {
+                $elt['action_form'] = $action['form_name'];
+                if (class_exists($action['form_name']) == false) {
+                    $elt['action_form_info'] = array('undef' => true);
+                }
+            }
+            $manifest_action_list[$action_name] = $elt;
+            $manifest_class_list[] = strtolower($elt['action_class']);
+        }
+
+        return array($manifest_action_list, $manifest_class_list);
+    }
+
+    /**
+     *  暗黙に定義されているアクション一覧を取得する
+     *
+     *  @access private
+     *  @param  array   $class_list             定義されているクラス一覧
+     *  @param  array   $manifest_action_list   明示的に定義済みのアクション一覧
+     *  @param  array   $manifest_class_list    明示的に定義済みのクラス一覧
+     *  @return array   array:アクション一覧
+     */
+    function _getActionList_Implicit($class_list, $manifest_action_list, $manifest_class_list)
+    {
+        $implicit_action_list = array();
+
+        foreach ($class_list as $file => $elts) {
+            foreach ($elts as $class_name => $def) {
+                if (in_array(strtolower($class_name), $manifest_class_list)) {
+                    continue;
+                }
+
+                // クラス名からアクション名を取得
+                if ($def['type'] == 'action_class') {
+                    $action_name = $this->ctl->actionClassToName($class_name);
+                    if (array_key_exists($action_name, $manifest_action_list)) {
+                        continue;
+                    }
+                    $implicit_action_list[$action_name]['action_class'] = $class_name;
+                    $implicit_action_list[$action_name]['action_class_file'] = $file;
+                    $implicit_action_list[$action_name]['action_class_info'] = $def;
+                } else if ($def['type'] == 'action_form') {
+                    $action_name = $this->ctl->actionFormToName($class_name);
+                    if (array_key_exists($action_name, $manifest_action_list)) {
+                        continue;
+                    }
+                    $implicit_action_list[$action_name]['action_form'] = $class_name;
+                    $implicit_action_list[$action_name]['action_form_file'] = $file;
+                    $implicit_action_list[$action_name]['action_form_info'] = $def;
+                } else {
+                    continue;
+                }
+            }
+        }
+
+        return $implicit_action_list;
+    }
+    
+    /**
+     *  アクション定義一覧を補完する
+     *
+     *  @access private
+     *  @param  array   $action_list    取得したアクション一覧
+     *  @return array   修正後のアクション一覧
+     */
+    function _addActionList($action_list)
+    {
+        foreach ($action_list as $action_name => $action) {
+            // アクションフォームにフォーム定義情報を追加
+            $form_name = $action['action_form'];
+            if (class_exists($form_name) == false) {
+                continue;
+            }
+            $af =& new $form_name($this->ctl);
+
+            $form = array();
+            foreach ($af->getDef() as $name => $def) {
+                $form[$name]['required'] = $def['required'] ? 'true' : 'false';
+                foreach (array('name', 'max', 'min', 'regexp', 'custom') as $key) {
+                    $form[$name][$key] = $def[$key];
+                }
+                $form[$name]['filter'] = str_replace(",", "\n", $def['filter']);
+                $form[$name]['form_type'] = $this->getAttrName('form_type', $def['form_type']);
+                $form[$name]['type_is_array'] = is_array($def['type']);
+                $form[$name]['type'] = $this->getAttrName('var_type', is_array($def['type'])
+                                     ? $def['type'][0] : $def['type']);
+            }
+            $action['action_form_info']['form'] = $form;
+            $action_list[$action_name] = $action;
+        }
+
+        return $action_list;
+    }
+
+    /**
+     *  ディレクトリ以下のテンプレートを解析する
+     *
+     *  @access private
+     *  @param  string  $action_dir     解析対象のディレクトリ
+     *  @return array   遷移定義一覧
+     */
+    function _analyzeForwardList($template_dir = null)
+    {
+        $r = array();
+
+        if (is_null($template_dir)) {
+            $template_dir = $this->ctl->getTemplatedir();
+        }
+        $prefix_len = strlen($this->ctl->getTemplatedir());
+
+        $child_dir_list = array();
+
+        $dh = opendir($template_dir);
+        if ($dh == false) {
+            return;
+        }
+
+        $ext = $this->ctl->getExt('tpl');
+        while (($file = readdir($dh)) !== false) {
+            if ($file == "." || $file == "..") {
+                continue;
+            }
+            $file = $template_dir . '/' . $file;
+
+            if (is_dir($file)) {
+                $child_dir_list[] = $file;
+                continue;
+            }
+
+            if (preg_match("/\.$ext\$/", $file) == 0) {
+                continue;
+            }
+
+            $tpl = substr($file, $prefix_len);
+            $r[] = $this->ctl->forwardPathToName($tpl);
+        }
+
+        closedir($dh);
+
+        foreach ($child_dir_list as $child_dir) {
+            $tmp = $this->_analyzeForwardList($child_dir);
+            $r = array_merge($r, $tmp);
+        }
+
+        return $r;
+    }
+
+    /**
+     *  コントローラに明示的に定義されている遷移先一覧を取得する
+     *
+     *  @access private
+     *  @return array   ビュー一覧
+     */
+    function _getForwardList_Manifest()
+    {
+        $manifest_forward_list = array();
+        foreach ($this->ctl->forward as $forward_name => $forward) {
+            if ($forward_name == '__ethna_info__') {
+                continue;
+            }
+
+            $elt = array();
+            $elt['template_file'] = $this->ctl->_getForwardPath($forward_name);
+            if (file_exists(sprintf("%s/%s", $this->ctl->getTemplatedir(), $elt['template_file'])) == false) {
+                $elt['template_file_info'] = array('undef' => true);
+            }
+
+            $elt['view_class'] = $this->ctl->getViewClassName($forward_name);
+            if ($elt['view_class'] == 'Ethna_ViewClass') {
+                $elt['view_class'] = null;
+            } else if (class_exists($elt['view_class']) == false) {
+                $elt['view_class_info'] = array('undef' => true);
+            }
+
+            if (isset($forward['view_path']) && $forward['view_path']) {
+                $elt['view_path'] = $forward['view_path'];
+            } else if ($this->_isSubclassOf($elt['view_class'], 'Ethna_ViewClass')) {
+                $elt['view_class_file'] = $this->ctl->getDefaultViewPath($forward_name);
+            } else {
+                foreach ($this->cache_class_list as $file => $elts) {
+                    foreach ($elts as $name => $def) {
+                        if (strcasecmp($elt['view_class'], $name) == 0) {
+                            $elt['view_class_file'] = $file;
+                            break 2;
+                        }
+                    }
+                }
+            }
+
+            $manifest_forward_list[$forward_name] = $elt;
+        }
+
+        return $manifest_forward_list;
+    }
+
+    /**
+     *  暗黙に定義されているビュー一覧を取得する
+     *
+     *  @access private
+     *  @param  array   $forward_list           定義されている遷移名一覧
+     *  @param  array   $manifest_forward_list  明示的に定義済みのビュー一覧
+     *  @return array   array:ビュー一覧
+     */
+    function _getForwardList_Implicit($forward_list, $manifest_forward_list)
+    {
+        $implicit_forward_list = array();
+        $manifest_forward_name_list = array_keys($manifest_forward_list);
+
+        foreach ($forward_list as $forward_name) {
+            if (in_array($forward_name, $manifest_forward_name_list)) {
+                continue;
+            }
+
+            $elt = array();
+            $elt['template_file'] = $this->ctl->_getForwardPath($forward_name);
+            $elt['view_class'] = $this->ctl->getViewClassName($forward_name);
+            if ($elt['view_class'] == 'Ethna_ViewClass') {
+                $elt['view_class'] = null;
+            } else if (class_exists($elt['view_class']) == false) {
+                $elt['view_class'] = null;
+            } else {
+                $elt['view_class_file'] = $this->ctl->getDefaultViewPath($forward_name);
+            }
+
+            $implicit_forward_list[$forward_name] = $elt;
+        }
+
+        return $implicit_forward_list;
+    }
+
+    /**
+     *  Ethnaの設定一覧を取得する
+     *
+     *  @access public
+     *  @return array   設定一覧を格納した配列
+     *  @todo   respect access controll
+     */
+    function getConfiguration()
+    {
+        $r = array();
+
+        // core
+        $elts = array();
+        $elts[_et('Application ID')] = $this->ctl->getAppId();
+        $elts[_et('Application URL')] = $this->ctl->getURL();
+        $elts[_et('Ethna Version')] = ETHNA_VERSION;
+        $elts[_et('Ethna Base Directory')] = ETHNA_BASE;
+        $r['Core'] = $elts;
+
+        // class
+        $elts = array();
+        $elts[_et('Backend')] = $this->class_factory->getObjectName('backend');
+        $elts[_et('ClassFactory')] = $this->class_factory->getObjectName('class');
+        $elts[_et('Config')] = $this->class_factory->getObjectName('config');
+        $elts['DB'] = $this->class_factory->getObjectName('db');
+        $elts[_et('Error')] = $this->class_factory->getObjectName('error');
+        $elts[_et('Form')] = $this->class_factory->getObjectName('form');
+        $elts[_et('Log')] = $this->class_factory->getObjectName('logger');
+        $elts['i18n'] = $this->class_factory->getObjectName('i18n');
+        $elts[_et('Plugin')] = $this->class_factory->getObjectName('plugin');
+        $elts[_et('Session')] = $this->class_factory->getObjectName('session');
+        $elts['SQL'] = $this->class_factory->getObjectName('sql');
+        $elts[_et('View')] = $this->class_factory->getObjectName('view');
+        $r[_et('Class')] = $elts;
+
+        // DB
+        $elts = array();
+        $db_list = array();
+        foreach ($this->ctl->db as $key => $db) {
+            if ($key == "") {
+                $tmp = '$db';
+            } else {
+                $tmp = sprintf('$db_%s', $key);
+            }
+            $elts[$tmp] = $this->getAttrName('db_type', $db);
+            $db_list[$key] = $tmp;
+        }
+        $r[_et('DB Type')] = $elts;
+
+        // DSN
+        $elts = array();
+        foreach ($db_list as $key => $name) {
+            $config_key = "dsn";
+            if ($key != "") {
+                $config_key .= "_$key";
+            }
+            $dsn = $this->config->get($config_key);
+            if ($dsn) {
+                $elts[$name] = implode("\n", to_array($dsn));
+            }
+        }
+        $r['DSN'] = $elts;
+
+        // directory
+        $elts = array();
+        $elts[_et('Application')] = $this->ctl->getBasedir();
+        $elts[_et('Action')] = $this->ctl->getActiondir();
+        $elts[_et('View')] = $this->ctl->getViewdir();
+        $elts[_et('Filter')] = $this->ctl->getDirectory('filter');
+        $elts[_et('Plugin')] = $this->ctl->getDirectory('plugin');
+        $elts[_et('Template')] = $this->ctl->getTemplatedir();
+        $elts[_et('Template Cache')] = $this->ctl->getDirectory('template_c');
+        $elts[_et('Smarty Plugin')] = implode(',', $this->ctl->getDirectory('plugins'));
+        $elts[_et('Configuration File')] = $this->ctl->getDirectory('etc');
+        $elts[_et('Locale')] = $this->ctl->getDirectory('locale');
+        $elts[_et('Logging')] = $this->ctl->getDirectory('log');
+        $elts[_et('Temporary File')] = $this->ctl->getDirectory('tmp');
+        $r[_et('Directory')] = $elts;
+
+        // ext
+        $elts = array();
+        $elts[_et('Template')] = $this->ctl->getExt('tpl');
+        $elts[_et('PHP Script')] = $this->ctl->getExt('php');
+        $r[_et('File Extention')] = $elts;
+
+        // filter
+        $elts = array();
+        $n = 1;
+        foreach ($this->ctl->filter as $filter) {
+            $key = sprintf(_et('Filter(%d)'), $n);
+            if (class_exists($filter)) {
+                $elts[$key] = $filter;
+                $n++;
+            }
+        }
+        $r[_et('Filter')] = $elts;
+
+        // manager
+        $elts = array();
+        foreach ($this->ctl->getManagerList() as $key => $manager) {
+            $name = sprintf('$%s', $key);
+            $elts[$name] = $this->ctl->getManagerClassName($manager);
+        }
+        $r[_et('Application Manager')] = $elts;
+
+        return $r;
+    }
+
+    /**
+     *  プラグインの一覧を取得する
+     *
+     *  @access public
+     *  @return array   設定一覧を格納した配列
+     *  @todo   respect access controll
+     */
+    function getPluginList()
+    {
+        $r = array();
+        $plugin = $this->ctl->getPlugin();
+        foreach ($plugin->searchAllPluginType() as $type) {
+            $plugin->searchAllPluginSrc($type);
+            if (isset($plugin->src_registry[$type])) {
+                $elts = array();
+                foreach ($plugin->src_registry[$type] as $name => $src) {
+                    if (empty($src)) {
+                        continue;
+                    }
+                    $elts[$name] = $src[2];
+                }
+                ksort($elts);
+                $r[$type] = $elts;
+            }
+        }
+        ksort($r);
+        return $r;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Logger.php b/Idea_Plugin_Extlib/class/Ethna_Logger.php
new file mode 100644 (file)
index 0000000..23c7e3d
--- /dev/null
@@ -0,0 +1,569 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Logger.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/**
+ *  拡張ログプロパティ: ファイル出力
+ */
+define('LOG_FILE', 1 << 16);
+
+/**
+ *  拡張ログプロパティ: 標準出力
+ */
+define('LOG_ECHO', 1 << 17);
+
+// {{{ Ethna_Logger
+/**
+ *  ログ管理クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Logger extends Ethna_AppManager
+{
+    // {{{ properties
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    array   ログファシリティ一覧 */
+    var $log_facility_list = array(
+        'auth'      => array('name' => 'LOG_AUTH'),
+        'cron'      => array('name' => 'LOG_CRON'),
+        'daemon'    => array('name' => 'LOG_DAEMON'),
+        'kern'      => array('name' => 'LOG_KERN'),
+        'lpr'       => array('name' => 'LOG_LPR'),
+        'mail'      => array('name' => 'LOG_MAIL'),
+        'news'      => array('name' => 'LOG_NEWS'),
+        'syslog'    => array('name' => 'LOG_SYSLOG'),
+        'user'      => array('name' => 'LOG_USER'),
+        'uucp'      => array('name' => 'LOG_UUCP'),
+        'file'      => array('name' => 'LOG_FILE'),
+        'echo'      => array('name' => 'LOG_ECHO'),
+    );
+
+    /** @var    array   ログレベル一覧 */
+    var $log_level_list = array(
+        'emerg'     => array('name' => 'LOG_EMERG',     'value' => 7),
+        'alert'     => array('name' => 'LOG_ALERT',     'value' => 6),
+        'crit'      => array('name' => 'LOG_CRIT',      'value' => 5),
+        'err'       => array('name' => 'LOG_ERR',       'value' => 4),
+        'warning'   => array('name' => 'LOG_WARNING',   'value' => 3),
+        'notice'    => array('name' => 'LOG_NOTICE',    'value' => 2),
+        'info'      => array('name' => 'LOG_INFO',      'value' => 1),
+        'debug'     => array('name' => 'LOG_DEBUG',     'value' => 0),
+    );
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    array   ログファシリティ */
+    var $facility = array();
+
+    /** @var    array   ログレベル */
+    var $level = array();
+
+    /** @var    array   ログオプション */
+    var $option = array();
+
+    /** @var    array   メッセージフィルタ(出力) */
+    var $message_filter_do = array();
+
+    /** @var    array   メッセージフィルタ(無視) */
+    var $message_filter_ignore = array();
+
+    /** @var    int     アラートレベル */
+    var $alert_level;
+
+    /** @var    string  アラートメールアドレス */
+    var $alert_mailaddress;
+
+    /** @var    array   Ethna_LogWriter ログ出力オブジェクト */
+    var $writer = array();
+
+    /** @var    bool    ログ出力開始フラグ */
+    var $is_begin = false;
+
+    /** @var    array   ログスタック(begin()前にlog()が呼び出された場合のスタック) */
+    var $log_stack = array();
+
+    /**#@-*/
+    // }}}
+    
+    // {{{ Ethna_Logger
+    /**
+     *  Ethna_Loggerクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    $controller controllerオブジェクト
+     */
+    function Ethna_Logger(&$controller)
+    {
+        $this->controller =& $controller;
+        $this->ctl =& $this->controller;
+        $config =& $controller->getConfig();
+
+        // ログファシリティテーブル補完(LOCAL0〜LOCAL8)
+        for ($i = 0; $i < 8; $i++) {
+            if (defined("LOG_LOCAL$i")) {
+                $this->log_facility_list["local$i"] = array('name' => "LOG_LOCAL$i");
+            }
+        }
+
+        $config_log = $config->get('log');
+
+        // ログファシリティ
+        if (is_array($config_log)) {
+            $this->facility = array_keys($config_log);
+        } else {
+            $this->facility = $this->_parseLogFacility($config->get('log_facility'));
+        }
+
+        foreach ($this->facility as $f) {
+            // ログレベル
+            if (isset($config_log[$f]['level'])) {
+                $this->level[$f] = $this->_parseLogLevel($config_log[$f]['level']);
+            } else if (($level = $config->get("log_level_$f")) !== null) {
+                $this->level[$f] = $this->_parseLogLevel($level);
+            } else if (($level = $config->get("log_level")) !== null) {
+                $this->level[$f] = $this->_parseLogLevel($level);
+            } else {
+                $this->level[$f] = LOG_WARNING;
+            }
+
+            // メッセージフィルタ(filter_do)
+            if (isset($config_log[$f]['filter_do'])) {
+                $this->message_filter_do[$f] = $config_log[$f]['filter_do'];
+            } else if (($filter = $config->get("log_filter_do_$f")) !== null) {
+                $this->message_filter_do[$f] = $filter;
+            } else if (($filter = $config->get("log_filter_do")) !== null) {
+                $this->message_filter_do[$f] = $filter;
+            } else {
+                $this->message_filter_do[$f] = '';
+            }
+
+            // メッセージフィルタ(filter_ignore)
+            if (isset($config_log[$f]['filter_ignore'])) {
+                $this->message_filter_ignore[$f] = $config_log[$f]['filter_ignore'];
+            } else if (($filter = $config->get("log_filter_ignore_$f")) !== null) {
+                $this->message_filter_ignore[$f] = $filter;
+            } else if (($filter = $config->get("log_filter_ignore")) !== null) {
+                $this->message_filter_ignore[$f] = $filter;
+            } else {
+                $this->message_filter_ignore[$f] = '';
+            }
+
+            // そのたオプション (unsetはせずにそのまま渡す)
+            if (isset($config_log[$f])) {
+                $this->option[$f] = $config_log[$f];
+            } else {
+                $this->option[$f] = array();
+            }
+
+            // 'option' によるオプション指定 (for B.C.)
+            if (isset($config_log[$f]['option'])) {
+                $option = $this->_parseLogOption($config_log[$f]['option']);
+            } else if (($option = $config->get("log_option_$f")) !== null) {
+                $option = $this->_parseLogOption($option);
+            } else if (($option = $config->get("log_option")) !== null) {
+                $option = $this->_parseLogOption($option);
+            }
+            if ($option !== null) {
+                $this->option[$f] = array_merge($this->option[$f], $option);
+            }
+        }
+
+        // アラートオプション
+        $this->alert_level =
+            $this->_parseLogLevel($config->get('log_alert_level'));
+        $this->alert_mailaddress
+            = preg_split('/\s*,\s*/', $config->get('log_alert_mailaddress'));
+    }
+    // }}}
+
+    // {{{ getLogFacility
+    /**
+     *  ログファシリティを取得する
+     *
+     *  @access public
+     *  @return mixed   ログファシリティ(ファシリティが1つ以下ならscalar、
+     *                  2つ以上なら配列を返す for B.C.)
+     */
+    function getLogFacility()
+    {
+        if (is_array($this->facility)) {
+            if (count($this->facility) == 0) {
+                return null;
+            } else if (count($this->facility) == 1) {
+                return $this->facility[0];
+            }
+        }
+        return $this->facility;
+    }
+    // }}}
+
+    // {{{ errorLevelToLogLevel
+    /**
+     *  PHPエラーレベルをログレベルに変換する
+     *
+     *  @access public
+     *  @param  int     $errno  PHPエラーレベル
+     *  @return array   ログレベル(LOG_NOTICE,...), エラーレベル表示名("E_NOTICE"...)
+     *  @static
+     */
+    function errorLevelToLogLevel($errno)
+    {
+        switch ($errno) {
+        case E_ERROR:           $code = "E_ERROR"; $level = LOG_ERR; break;
+        case E_WARNING:         $code = "E_WARNING"; $level = LOG_WARNING; break;
+        case E_PARSE:           $code = "E_PARSE"; $level = LOG_CRIT; break;
+        case E_NOTICE:          $code = "E_NOTICE"; $level = LOG_NOTICE; break;
+        case E_USER_ERROR:      $code = "E_USER_ERROR"; $level = LOG_ERR; break;
+        case E_USER_WARNING:    $code = "E_USER_WARNING"; $level = LOG_WARNING; break;
+        case E_USER_NOTICE:     $code = "E_USER_NOTICE"; $level = LOG_NOTICE; break;
+        case E_STRICT:          $code = "E_STRICT"; $level = LOG_NOTICE; return;
+        default:                $code = "E_UNKNOWN"; $level = LOG_DEBUG; break;
+        }
+        return array($level, $code);
+    }
+    // }}}
+
+    // {{{ begin
+    /**
+     *  ログ出力を開始する
+     *
+     *  @access public
+     */
+    function begin()
+    {
+        // LogWriterクラスの生成
+        foreach ($this->facility as $f) {
+            $this->writer[$f] =& $this->_getLogWriter($this->option[$f], $f);
+            if (Ethna::isError($this->writer[$f])) {
+                // use default
+                $this->writer[$f] =& $this->_getLogWriter($this->option[$f],
+                                                          "default");
+            }
+        }
+
+        foreach (array_keys($this->writer) as $key) {
+            $this->writer[$key]->begin();
+        }
+        
+        $this->is_begin = true;
+
+        // begin()以前のlog()を処理
+        if (count($this->log_stack) > 0) {
+            // copy and clear for recursive calls
+            $tmp_stack = $this->log_stack;
+            $this->log_stack = array();
+
+            while (count($tmp_stack) > 0) {
+                $log = array_shift($tmp_stack);
+                $this->log($log[0], $log[1]);
+            }
+        }
+    }
+    // }}}
+
+    // {{{ log
+    /**
+     *  ログを出力する
+     *
+     *  @access public
+     *  @param  int     $level      ログレベル(LOG_DEBUG, LOG_NOTICE...)
+     *  @param  string  $message    ログメッセージ(+引数)
+     */
+    function log($level, $message)
+    {
+        if ($this->is_begin == false) {
+            $args = func_get_args();
+            if (count($args) > 2) {
+                array_splice($args, 0, 2);
+                $message = vsprintf($message, $args);
+            }
+            $this->log_stack[] = array($level, $message);
+            return;
+        }
+
+        foreach (array_keys($this->writer) as $key) {
+            // ログメッセージフィルタ(レベルフィルタに優先する)
+            $r = $this->_evalMessageMask($this->message_filter_do[$key], $message);
+            if (is_null($r)) {
+                $r = $this->_evalMessageMask($this->message_filter_ignore[$key],
+                                             $message);
+                if ($r) {
+                    continue;
+                }
+            }
+
+            // ログレベルフィルタ
+            if ($this->_evalLevelMask($this->level[$key], $level)) {
+                continue;
+            }
+
+            // ログ出力
+            $args = func_get_args();
+            if (count($args) > 2) {
+                array_splice($args, 0, 2);
+                $message = vsprintf($message, $args);
+            }
+            $output = $this->writer[$key]->log($level, $message);
+        }
+
+        // アラート処理
+        if ($this->_evalLevelMask($this->alert_level, $level) == false) {
+            if (count($this->alert_mailaddress) > 0) {
+                $this->_alert($output);
+            }
+        }
+    }
+    // }}}
+
+    // {{{ end
+    /**
+     *  ログ出力を終了する
+     *
+     *  @access public
+     */
+    function end()
+    {
+        foreach (array_keys($this->writer) as $key) {
+            $this->writer[$key]->end();
+        }
+
+        $this->is_begin = false;
+    }
+    // }}}
+
+    // {{{ _getLogWriter
+    /**
+     *  LogWriterオブジェクトを取得する
+     *
+     *  @access protected
+     *  @param  array   $option     ログオプション
+     *  @param  string  $facility   ログファシリティ
+     *  @return object  LogWriter   LogWriterオブジェクト
+     */
+    function &_getLogWriter($option, $facility = null)
+    {
+        if ($facility == null) {
+            $facility = $this->getLogFacility();
+            if (is_array($facility)) {
+                $facility = $facility[0];
+            }
+        }
+
+        if (is_null($facility)) {
+            $plugin = "default";
+        } else if (isset($this->log_facility_list[$facility])) {
+            if ($facility == "file" || $facility == "echo") {
+                $plugin = $facility;
+
+            } else {
+                $plugin = "syslog";
+            }
+        } else {
+            $plugin = $facility;
+        }
+
+        $plugin_manager =& $this->controller->getPlugin();
+        $plugin_object = $plugin_manager->getPlugin('Logwriter',
+                                                    ucfirst(strtolower($plugin)));
+        if (Ethna::isError($plugin_object)) {
+            return $plugin_object;
+        }
+
+        if (isset($option['ident']) == false) {
+            $option['ident'] = $this->controller->getAppId();
+        }
+        if (isset($option['facility']) == false) {
+            $option['facility'] = $facility;
+        }
+        $plugin_object->setOption($option);
+
+        return $plugin_object;
+    }
+    // }}}
+
+    // {{{ _alert
+    /**
+     *  アラートメールを送信する
+     *
+     *  @access protected
+     *  @param  string  $message    ログメッセージ
+     *  @return int     0:正常終了
+     *  @deprecated
+     */
+    function _alert($message)
+    {
+        restore_error_handler();
+
+        // ヘッダ
+        $header = "Mime-Version: 1.0\n";
+        $header .= "Content-Type: text/plain; charset=ISO-2022-JP\n";
+        $header .= "X-Alert: " . $this->controller->getAppId();
+        $subject = sprintf("[%s] alert (%s%s)\n",
+                           $this->controller->getAppId(),
+                           substr($message, 0, 12),
+                           strlen($message) > 12 ? "..." : "");
+        
+        // 本文
+        $mail = sprintf("--- [log message] ---\n%s\n\n", $message);
+        if (function_exists("debug_backtrace")) {
+            $bt = debug_backtrace();
+            $mail .= sprintf("--- [backtrace] ---\n%s\n",
+                             Ethna_Util::FormatBacktrace($bt));
+        }
+
+        foreach ($this->alert_mailaddress as $mailaddress) {
+            mail($mailaddress,
+                 $subject,
+                 mb_convert_encoding($mail, "ISO-2022-JP"),
+                 $header);
+        }
+
+        set_error_handler("ethna_error_handler");
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ _evalMessageMask
+    /**
+     *  ログメッセージのマスクチェックを行う
+     *
+     *  @access private
+     *  @param  string  $filter     フィルタ
+     *  @param  string  $message    ログメッセージ
+     *  @return mixed   true:match, null:skip
+     */
+    function _evalMessageMask($filter, $message)
+    {
+        $regexp = sprintf("/%s/", $filter);
+
+        if ($filter && preg_match($regexp, $message)) {
+            return true;
+        }
+
+        return null;
+    }
+    // }}}
+
+    // {{{ _evalLevelMask
+    /**
+     *  ログレベルのマスクチェックを行う
+     *
+     *  @access private
+     *  @param  int     $src    ログレベルマスク
+     *  @param  int     $dst    ログレベル
+     *  @return bool    true:閾値以下 false:閾値以上
+     */
+    function _evalLevelMask($src, $dst)
+    {
+        static $log_level_table = null;
+
+        if (is_null($log_level_table)) {
+            $log_level_table = array();
+
+            // ログレベルテーブル(逆引き)作成
+            foreach ($this->log_level_list as $key => $def) {
+                if (defined($def['name']) == false) {
+                    continue;
+                }
+                $log_level_table[constant($def['name'])] = $def['value'];
+            }
+        }
+
+        // 知らないレベルなら出力しない
+        if (isset($log_level_table[$src]) == false
+            || isset($log_level_table[$dst]) == false) {
+            return true;
+        }
+
+        if ($log_level_table[$dst] >= $log_level_table[$src]) {
+            return false;
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _parseLogOption
+    /**
+     *  ログオプション(設定ファイル値)を解析する
+     *
+     *  @access private
+     *  @param  mixed   $option ログオプション(設定ファイル値)
+     *  @return array   解析された設定ファイル値(アラート通知メールアドレス,
+     *                  アラート対象ログレベル, ログオプション)
+     */
+    function _parseLogOption($option)
+    {
+        if (is_null($option)) {
+            return null;
+        } else if (is_array($option)) {
+            return $option;
+        }
+
+        $ret = array();
+        $elts = preg_split('/\s*,\s*/', $option);
+        foreach ($elts as $elt) {
+            if (preg_match('/^(.*?)\s*:\s*(.*)/', $elt, $match)) {
+                $ret[$match[1]] = $match[2];
+            } else {
+                $ret[$elt] = true;
+            }
+        }
+
+        return $ret;
+    }
+    // }}}
+
+    // {{{ _parseLogFacility
+    /**
+     *  ログファシリティ(設定ファイル値)を解析する
+     *
+     *  @access private
+     *  @param  string  $facility   ログファシリティ(設定ファイル値)
+     *  @return array   ログファシリティ(LOG_LOCAL0, LOG_FILE...)を格納した配列
+     */
+    function _parseLogFacility($facility)
+    {
+        $facility_list = preg_split('/\s*,\s*/', $facility, -1, PREG_SPLIT_NO_EMPTY);
+        return $facility_list;
+    }
+    // }}}
+
+    // {{{ _parseLogLevel
+    /**
+     *  ログレベル(設定ファイル値)を解析する
+     *
+     *  @access private
+     *  @param  string  $level  ログレベル(設定ファイル値)
+     *  @return int     ログレベル(LOG_DEBUG, LOG_NOTICE...)
+     */
+    function _parseLogLevel($level)
+    {
+        if (isset($this->log_level_list[strtolower($level)]) == false) {
+            return null;
+        }
+        $constant_name = $this->log_level_list[strtolower($level)]['name'];
+
+        return constant($constant_name);
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_MailSender.php b/Idea_Plugin_Extlib/class/Ethna_MailSender.php
new file mode 100644 (file)
index 0000000..58bec6d
--- /dev/null
@@ -0,0 +1,289 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_MailSender.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/** メールテンプレートタイプ: 直接送信 */
+define('MAILSENDER_TYPE_DIRECT', 0);
+
+
+// {{{ Ethna_MailSender
+/**
+ *  メール送信クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_MailSender
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    array   メールテンプレート定義 */
+    var $def = array(
+    );
+
+    /** @var    string  メールテンプレートディレクトリ */
+    var $mail_dir = 'mail';
+
+    /** @var    int     送信メールタイプ */
+    var $type;
+
+    /** @var    string  送信オプション */
+    var $option = '';
+
+    /** @var    object  Ethna_Backend   backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Config    設定オブジェクト */
+    var $config;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_MailSenderクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Backend   &$backend       backendオブジェクト
+     */
+    function Ethna_MailSender(&$backend)
+    {
+        $this->backend =& $backend;
+        $this->config =& $this->backend->getConfig();
+    }
+
+    /**
+     *  メールオプションを設定する
+     *
+     *  @access public
+     *  @param  string  $option メール送信オプション
+     */
+    function setOption($option)
+    {
+        $this->option = $option;
+    }
+
+    /**
+     *  メールを送信する
+     *
+     *  $attach の指定方法:
+     *  - 既存のファイルを添付するとき
+     *  <code>
+     *  array('filename' => '/tmp/hoge.xls', 'content-type' => 'application/vnd.ms-excel')
+     *  </code>
+     *  - 文字列に名前を付けて添付するとき
+     *  <code>
+     *  array('name' => 'foo.txt', 'content' => 'this is foo.')
+     *  </code>
+     *  'content-type' 省略時は 'application/octet-stream' となる。
+     *  複数添付するときは上の配列を添字0から始まるふつうの配列に入れる。
+     *
+     *  @access public
+     *  @param  string  $to         メール送信先アドレス (nullのときは送信せずに内容を return する)
+     *  @param  string  $template   メールテンプレート名 or タイプ
+     *  @param  array   $macro      テンプレートマクロ or $templateがMAILSENDER_TYPE_DIRECTのときはメール送信内容)
+     *  @param  array   $attach     添付ファイル
+     */
+    function send($to, $template, $macro, $attach = null)
+    {
+        // メール内容を作成
+        if ($template === MAILSENDER_TYPE_DIRECT) {
+            $mail = $macro;
+        } else {
+            $renderer =& $this->getTemplateEngine();
+
+            // 基本情報設定
+            $env_datetime = _et('%Y/%m/%d %H:%M:%S');
+            $renderer->setProp("env_datetime", strftime($env_datetime));
+            $renderer->setProp("env_useragent", $_SERVER["HTTP_USER_AGENT"]);
+            $renderer->setProp("env_remoteaddr", $_SERVER["REMOTE_ADDR"]);
+
+            // デフォルトマクロ設定
+            $macro = $this->_setDefaultMacro($macro);
+
+            // ユーザ定義情報設定
+            if (is_array($macro)) {
+                foreach ($macro as $key => $value) {
+                    $renderer->setProp($key, $value);
+                }
+            }
+            if (isset($this->def[$template])) {
+                $template = $this->def[$template];
+            }
+            $mail = $renderer->perform(sprintf('%s/%s', $this->mail_dir, $template), true);
+            if (Ethna::isError($mail)) {
+                return $mail;
+            }
+        }
+        if ($to === null) {
+            return $mail;
+        }
+
+        // メール内容をヘッダと本文に分離
+        $mail = str_replace("\r\n", "\n", $mail);
+        list($header, $body) = $this->_parse($mail);
+
+        // 添付ファイル (multipart)
+        if ($attach !== null) {
+            $attach = isset($attach[0]) ? $attach : array($attach);
+            $boundary = Ethna_Util::getRandom(); 
+            $body = "This is a multi-part message in MIME format.\n\n" .
+                "--$boundary\n" .
+                "Content-Type: text/plain; charset=iso-2022-jp\n" .
+                "Content-Transfer-Encoding: 7bit\n\n" .
+                "$body\n";
+            foreach ($attach as $part) {
+                if (isset($part['content']) === false
+                    && isset($part['filename']) && is_readable($part['filename'])) {
+                    $part['content'] = file_get_contents($part['filename']);
+                    $part['filename'] = basename($part['filename']);
+                }
+                if (isset($part['content']) === false) {
+                    continue;
+                }
+                if (isset($part['content-type']) === false) {
+                    $part['content-type'] = 'application/octet-stream';
+                }
+                if (isset($part['name']) === false) {
+                    $part['name'] = $part['filename'];
+                }
+                if (isset($part['filename']) === false) {
+                    $part['filename'] = $part['name'];
+                }
+                $part['name'] = preg_replace('/([^\x00-\x7f]+)/e',
+                    "Ethna_Util::encode_MIME('$1')", $part['name']); // XXX: rfc2231
+                $part['filename'] = preg_replace('/([^\x00-\x7f]+)/e',
+                    "Ethna_Util::encode_MIME('$1')", $part['filename']);
+
+                $body .=
+                    "--$boundary\n" .
+                    "Content-Type: " . $part['content-type'] . ";\n" .
+                        "\tname=\"" . $part['name'] . "\"\n" .
+                    "Content-Transfer-Encoding: base64\n" . 
+                    "Content-Disposition: attachment;\n" .
+                        "\tfilename=\"" . $part['filename'] . "\"\n\n";
+                $body .= chunk_split(base64_encode($part['content']));
+            }
+            $body .= "--$boundary--";
+        }
+
+        // ヘッダ
+        if (isset($header['mime-version']) === false) {
+            $header['mime-version'] = array('Mime-Version', '1.0');
+        }
+        if (isset($header['subject']) === false) {
+            $header['subject'] = array('Subject', 'no subject in original');
+        }
+        if (isset($header['content-type']) === false) {
+            $header['content-type'] = array(
+                'Content-Type',
+                $attach === null ? 'text/plain; charset=iso-2022-jp'
+                                 : "multipart/mixed; \n\tboundary=\"$boundary\"",
+            );
+        }
+
+        $header_line = "";
+        foreach ($header as $key => $value) {
+            if ($key == 'subject') {
+                // should be added by mail()
+                continue;
+            }
+            if ($header_line != "") {
+                $header_line .= "\n";
+            }
+            $header_line .= $value[0] . ": " . $value[1];
+        }
+
+        // 改行コードを CRLF に
+        if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
+            $body = str_replace("\n", "\r\n", $body);
+        }
+        $header_line = str_replace("\n", "\r\n", $header_line);
+
+        // 送信
+        foreach (to_array($to) as $rcpt) {
+            if (is_string($this->option)) {
+                mail($rcpt, $header['subject'][1], $body, $header_line, $this->option);
+            } else {
+                mail($rcpt, $header['subject'][1], $body, $header_line);
+            }
+        }
+    }
+
+    /**
+     *  アプリケーション固有のマクロを設定する
+     *
+     *  @access protected
+     *  @param  array   $macro  ユーザ定義マクロ
+     *  @return array   アプリケーション固有処理済みマクロ
+     */
+    function _setDefaultMacro($macro)
+    {
+        return $macro;
+    }
+
+    /**
+     *  テンプレートメールのヘッダ情報を取得する
+     *
+     *  @access private
+     *  @param  string  $mail   メールテンプレート
+     *  @return array   ヘッダ, 本文
+     */
+    function _parse($mail)
+    {
+        list($header_line, $body) = preg_split('/\r?\n\r?\n/', $mail, 2);
+        $header_line .= "\n";
+
+        $header_lines = explode("\n", $header_line);
+        $header = array();
+        foreach ($header_lines as $h) {
+            if (strstr($h, ':') == false) {
+                continue;
+            }
+            list($key, $value) = preg_split('/\s*:\s*/', $h, 2);
+            $i = strtolower($key);
+            $header[$i] = array();
+            $header[$i][] = $key;
+            $header[$i][] = preg_replace('/([^\x00-\x7f]+)/e', "Ethna_Util::encode_MIME('$1')", $value);
+        }
+
+        $body = mb_convert_encoding($body, "ISO-2022-JP");
+
+        return array($header, $body);
+    }
+
+    /**
+     *  メールフォーマット用レンダラオブジェクト取得する
+     *
+     *  @access public
+     *  @return object  Ethna_Renderer  レンダラオブジェクト
+     */
+    function &getRenderer()
+    {
+        $_ret_object =& $this->getTemplateEngine();
+        return $_ret_object;
+    }
+
+    /**
+     *  メールフォーマット用レンダラオブジェクト取得する
+     *
+     *  @access public
+     *  @return object  Ethna_Renderer  レンダラオブジェクト
+     */
+    function &getTemplateEngine()
+    {
+        $c =& $this->backend->getController();
+        $renderer =& $c->getRenderer();
+        return $renderer;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_PearWrapper.php b/Idea_Plugin_Extlib/class/Ethna_PearWrapper.php
new file mode 100644 (file)
index 0000000..39ba453
--- /dev/null
@@ -0,0 +1,677 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_PearWrapper.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once 'PEAR.php';
+require_once 'PEAR/Config.php';
+require_once 'PEAR/Command.php';
+require_once 'PEAR/PackageFile.php';
+
+// {{{ Ethna_PearWrapper
+/**
+ *  wrapper class for PEAR_Command
+ *  This class should be instantiated in ethna handler.
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_PearWrapper
+{
+    // {{{ properties
+    /**#@+
+     *  @access     private
+     */
+
+    /** @var    string  channel url of ethna repositry */
+    var $channel;
+
+    /** @var    string  target, 'master' or 'local' */
+    var $target;
+
+    /** @var    object  controller object collesponding to the target */
+    var $target_ctl;
+
+    /** @var    object  PEAR_Config     PEAR_Config object */
+    var $config;
+
+    /** @var    object  PEAR_Registry   PEAR_Registry object */
+    var $registry;
+
+    /** @var    object  PEAR_Frontend   PEAR_Frontend(_CLI) object */
+    var $ui;
+
+    /** @var    array   options for pearcmd */
+    var $_pearopt;
+
+    /**#@-*/
+    // }}}
+
+    // {{{ constructor, initializer
+    /**
+     *  Ethna_PearWrapper constructor
+     *
+     *  @access public
+     */
+    function Ethna_PearWrapper()
+    {
+        $this->channel = null;
+        $this->config = null;
+        $this->registry = null;
+        $this->ui = null;
+        $this->target = null;
+        $this->target_ctl = null;
+    }
+
+    /**
+     *  setup PEAR_Config and so on.
+     *
+     *  @param  string      $target     whether 'master' or 'local'
+     *  @param  string|null $app_dir    local application directory.
+     *  @param  string|null $channel    channel for the package repository.
+     *  @return true|Ethna_Error
+     */
+    function &init($target, $app_dir = null, $channel = null)
+    {
+        $true = true;
+        if ($target == 'master') {
+            $this->target = 'master';
+        } else {
+            // default target is 'local'.
+            $this->target = 'local';
+        }
+
+        // setup PEAR_Frontend
+        PEAR_Command::setFrontendType('CLI');
+        $this->ui =& PEAR_Command::getFrontendObject();
+
+        // set PEAR's error handling
+        // TODO: if PEAR/Command/Install.php is newer than 1.117, displayError goes well.
+        PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array(&$this->ui, 'displayFatalError'));
+
+        // set channel
+        $master_setting = Ethna_Handle::getMasterSetting('repositry');
+        if ($channel !== null) {
+            $this->channel = $channel;
+        } else if (isset($master_setting["channel_{$target}"])) {
+            $this->channel = $master_setting["channel_{$target}"];
+        } else {
+            $this->channel = 'pear.ethna.jp';
+        }
+
+        // set target controller
+        if ($target == 'master') {
+            $this->target_ctl =& Ethna_Handle::getEthnaController();
+        } else {
+            $this->target_ctl =& Ethna_Handle::getAppController($app_dir);
+        }
+        if (Ethna::isError($this->target_ctl)) {
+            return $this->target_ctl;
+        }
+
+        // setup PEAR_Config
+        if ($target == 'master') {
+            $ret =& $this->_setMasterConfig();
+        } else {
+            $ret =& $this->_setLocalConfig();
+        }
+        if (Ethna::isError($ret)) {
+            return $ret;
+        }
+        $this->ui->setConfig($this->config);
+
+        // setup PEAR_Registry
+        $this->registry =& $this->config->getRegistry();
+
+        return $true;
+    }
+
+    /**
+     *  config for master.
+     *
+     *  @return true|Ethna_Error
+     *  @access private 
+     */
+    function &_setMasterConfig()
+    {
+        $true = true;
+
+        // setup config
+        $this->config =& PEAR_Config::singleton();
+
+        // setup channel
+        $reg =& $this->config->getRegistry();
+        if ($reg->channelExists($this->channel) == false) {
+            $ret =& $this->doChannelDiscover();
+            if (Ethna::isError($ret)) {
+                return $ret;
+            }
+        }
+
+        return $true;
+    }
+
+    /**
+     *  config for local.
+     *
+     *  @return true|Ethna_Error
+     *  @access private 
+     */
+    function &_setLocalConfig()
+    {
+        $true = true;
+
+        // determine dirs
+        $base = $this->target_ctl->getBaseDir();
+        $bin  = $this->target_ctl->getDirectory('bin');
+        $tmp  = $this->target_ctl->getDirectory('tmp');
+        $dirs = array(
+                'php_dir'       => "{$base}/skel",
+                'bin_dir'       => "{$bin}",
+                'cache_dir'     => "{$tmp}/.pear/cache",
+                'download_dir'  => "{$tmp}/.pear/download",
+                'temp_dir'      => "{$tmp}/.pear/temp",
+                'doc_dir'       => "{$tmp}/.pear/doc",
+                'ext_dir'       => "{$tmp}/.pear/ext",
+                'data_dir'      => "{$tmp}/.pear/data",
+                'test_dir'      => "{$tmp}/.pear/test",
+                );
+
+        // mkdir
+        foreach ($dirs as $key => $dir) {
+            if (is_dir($dir) == false) {
+                Ethna_Util::mkdir($dir, 0755);
+            }
+        }
+
+        $pearrc = "{$base}/skel/.pearrc";
+        $this->config =& PEAR_Config::singleton($pearrc);
+
+        // read local .pearrc if exists.
+        if (is_file($pearrc) && is_readable($pearrc)) {
+            $this->config->readConfigFile($pearrc);
+        }
+
+        // set dirs to config
+        foreach ($dirs as $key => $dir) {
+            $this->config->set($key, $dir);
+        }
+
+        // setup channel
+        $reg =& $this->config->getRegistry();
+        if ($reg->channelExists($this->channel) == false) {
+            $ret =& $this->doChannelDiscover();
+            if (Ethna::isError($ret)) {
+                return $ret;
+            }
+        }
+        $this->config->set('default_channel', $this->channel);
+
+        // write local .pearrc
+        $this->config->writeConfigFile();
+
+        return $true;
+    }
+    // }}}
+
+    // {{{ doClearCache
+    /**
+     *  do clear-cache
+     *
+     *  @return true|Ethna_Error
+     */
+    function &doClearCache()
+    {
+        $true = true;
+        $r =& $this->_run('clear-cache', array(), array());
+        if (PEAR::isError($r)) {
+            return $r;
+        }
+        return $true;
+    }
+    // }}}
+
+    // {{{ doChannelDiscover
+    /**
+     *  do channel-discover
+     *
+     *  @return true|Ethna_Error
+     */
+    function &doChannelDiscover()
+    {
+        $true = true;
+        $r =& $this->_run('channel-discover', array(), array($this->channel));
+        if (PEAR::isError($r)) {
+            return $r;
+        }
+        return $true;
+    }
+    // }}}
+
+    // {{{ isChannelExists
+    /**
+     *  whether channel discovered or not
+     *
+     *  @return bool
+     */
+    function isChannelExists()
+    {
+        return $this->registry->channelExists($this->channel);
+    }
+    // }}}
+
+    // {{{ doChannelUpdate
+    /**
+     *  do channel-update
+     *
+     *  @return true|Ethna_Error
+     */
+    function &doChannelUpdate()
+    {
+        $true = true;
+        if ($this->isChannelExists() == false) {
+            $r =& $this->doChannelDiscover();
+            if (PEAR::isError($r)) {
+                return $r;
+            }
+        }
+        $r =& $this->_run('channel-update', array(), array($this->channel));
+        if (PEAR::isError($r)) {
+            return $r;
+        }
+        return $true;
+    }
+    // }}}
+
+    // {{{ _doInstallOrUpgrade
+    /**
+     *  do install
+     *
+     *  @param  string  $command    'install' or 'upgrade'
+     *  @param  string  $package    package string
+     *  @return true|Ethna_Error
+     *  @access private 
+     */
+    function &_doInstallOrUpgrade($command, $package)
+    {
+        $true = true;
+        $r =& $this->_run($command, array(), array($package));
+        if (PEAR::isError($r)) {
+            return $r;
+        }
+        return $true;
+    }
+    // }}}
+        
+    // {{{ doInstall
+    /**
+     *  do install
+     *
+     *  @param  string  $pkg_name   package name.
+     *  @param  string  $state      package state.
+     *  @return true|Ethna_Error
+     */
+    function &doInstall($pkg_name, $state = null)
+    {
+        $pkg = "{$this->channel}/{$pkg_name}";
+        if ($state !== null) {
+            $pkg = "{$pkg}-{$state}";
+        }
+        $r =& $this->_doInstallOrUpgrade('install', $pkg); 
+        return $r;
+    }
+    // }}}
+
+    // {{{ doInstallFromTgz
+    /**
+     *  do install from local tgz file
+     *
+     *  @param  string  $pkg_file   package filename
+     *  @return true|Ethna_Error
+     */
+    function &doInstallFromTgz($pkg_file)
+    {
+        $r =& $this->_doInstallOrUpgrade('install', $pkg_file); 
+        return $r;
+    }
+    // }}}
+
+    // {{{ doUpgrade
+    /**
+     *  do upgrade
+     *
+     *  @param  string  $pkg_name   package name.
+     *  @param  string  $state      package state.
+     *  @return true|Ethna_Error
+     */
+    function &doUpgrade($pkg_name, $state = null)
+    {
+        $pkg = "{$this->channel}/{$pkg_name}";
+        if ($state !== null) {
+            $pkg = "{$pkg}-{$state}";
+        }
+        $r =& $this->_doInstallOrUpgrade('upgrade', $pkg);
+        return $r;
+    }
+    // }}}
+
+    // {{{ doUpgradeFromTgz
+    /**
+     *  do upgrade from local tgz file
+     *
+     *  @param  string  $pkg_file   package filename
+     *  @return true|Ethna_Error
+     */
+    function &doUpgradeFromTgz($pkg_file)
+    {
+        $r =& $this->_doInstallOrUpgrade('upgrade', $pkg_file); 
+        return $r;
+    }
+    // }}}
+
+    // {{{ isInstalled
+    /**
+     *  check package installed
+     *
+     *  @param  string  $package package name
+     *  @return bool
+     */
+    function isInstalled($package)
+    {
+        return $this->registry->packageExists($package, $this->channel);
+    }
+    // }}}
+
+    // {{{ getVersion
+    /**
+     *  get package version
+     *
+     *  @param  string  $package package name
+     *  @return string  version string
+     */
+    function getVersion($package)
+    {
+        $pobj =& $this->registry->getPackage($package, $this->channel);
+        return $pobj->getVersion();
+    }
+    // }}}
+
+    // {{{ getState
+    /**
+     *  get package version
+     *
+     *  @param  string  $package package name
+     *  @return string  version string
+     */
+    function getState($package)
+    {
+        $pobj =& $this->registry->getPackage($package, $this->channel);
+        return $pobj->getState();
+    }
+    // }}}
+
+    // {{{ doUninstall
+    /**
+     *  do uninstall (packages installed with ethna command)
+     *
+     *  @return true|Ethna_Error
+     */
+    function &doUninstall($package)
+    {
+        $true = true;
+        if ($this->isInstalled($package) == false) {
+            return Ethna::raiseNotice("{$this->channel}/{$package} is not installed.");
+        }
+        $r =& $this->_run('uninstall', array(), array("{$this->channel}/{$package}"));
+        if (PEAR::isError($r)) {
+            return $r;
+        }
+        if ($this->isInstalled($package)) {
+            return Ethna::raiseNotice("uninstall failed: {$this->channel}/{$package}");
+        }
+        return $true;
+    }
+    // }}}
+
+    // {{{ getPackageNameFromTgz
+    /**
+     *  get package info from tar/tgz file.
+     *
+     *  @param  string  $filename   package file name.
+     *  @return string  package name
+     *  @access public
+     *  @static
+     */
+    function &getPackageNameFromTgz($filename)
+    {
+        $config =& PEAR_Config::singleton();
+        $packagefile =& new PEAR_PackageFile($config);
+        $info =& $packagefile->fromTgzFile($filename, PEAR_VALIDATE_NORMAL);
+        if (PEAR::isError($info)) {
+            return $info;
+        }
+        $info_array = $info->toArray();
+        return $info_array['name'];
+    }
+    // }}}
+
+    // {{{ getCanonicalPackageName
+    /**
+     *  get canonical package name (case sensitive)
+     *
+     *  @param  string  $package    package name.
+     *  @return string  canonical name
+     *  @access public
+     */
+    function &getCanonicalPackageName($package)
+    {
+        if ($this->isInstalled($package) == false) {
+            return Ethna::raiseNotice("{$this->channel}/{$package} is not installed.");
+        }
+        $pobj =& $this->registry->getPackage($package, $this->channel);
+        $cname = $pobj->getName();
+        return $cname;
+    }
+    // }}}
+
+    // {{{ getInstalledPackageList
+    /**
+     *  get installed package list
+     *
+     *  @return array   installed package list
+     *  @access public
+     */
+    function &getInstalledPackageList()
+    {
+        $ret = array();
+        foreach ($this->registry->listPackages($this->channel) as $pkg) {
+            $ret[] = $this->getCanonicalPackageName($pkg);
+        }
+        return $ret;
+    }
+    // }}}
+
+    // {{{ doInfo
+    /**
+     *  do info (packages installed with ethna command)
+     *
+     *  @param  string  $package    package name.
+     *  @return true|Ethna_Error
+     */
+    function &doInfo($package)
+    {
+        return $this->_run('info', array(), array("{$this->channel}/{$package}"));
+    }
+    // }}}
+
+    // {{{ doRemoteInfo
+    /**
+     *  do info (packages installable with ethna command)
+     *
+     *  @param  string  $package    package name.
+     *  @return true|Ethna_Error
+     */
+    function &doRemoteInfo($package)
+    {
+        return $this->_run('remote-info', array(), array("{$this->channel}/{$package}"));
+    }
+    // }}}
+
+    // {{{ doUpgradeAll
+    /**
+     *  do upgrade-all
+     *
+     *  @return true|Ethna_Error
+     */
+    function &doUpgradeAll()
+    {
+        return $this->_run('upgrade-all', array('channel' => "{$this->channel}"), array());
+    }
+    // }}}
+
+    // {{{ doList
+    /**
+     *  do list (packages installed with ethna command)
+     *
+     *  @return true|Ethna_Error
+     */
+    function &doList()
+    {
+        return $this->_run('list', array('channel' => $this->channel), array());
+    }
+    // }}}
+
+    // {{{ doRemoteList
+    /**
+     *  do remote-list (packages installable with ethna command)
+     *
+     *  @return true|Ethna_Error
+     */
+    function &doRemoteList()
+    {
+        return $this->_run('remote-list', array('channel' => $this->channel), array());
+    }
+    // }}}
+
+    // {{{ subroutines.
+    /**
+     *  run PEAR_Command.
+     *
+     *  @param  string  $command    command name
+     *  @param  array   $options    options
+     *  @param  array   $params     parameters
+     *  @return true|Ethna_Error
+     *  @access private 
+     *  @see PEAR_Command_Common::run, etc.
+     */
+    function &_run($command, $options, $params)
+    {
+        if ($this->config === null) {
+            return Ethna::raiseError('configuration not initialized.');
+        }
+
+        $true = true;
+
+        $cmd =& PEAR_Command::factory($command, $this->config);
+        if (PEAR::isError($cmd)) {
+            return $cmd;
+        }
+
+        // pear command options
+        if (is_array($this->_pearopt) && count($this->_pearopt) > 0) {
+            $pearopts = $this->_getPearOpt($cmd, $command, $this->_pearopt);
+            $options = array_merge($pearopts, $options);
+        }
+
+        $ret = $cmd->run($command, $options, $params);
+        if (PEAR::isError($ret)) {
+            return $ret;
+        }
+
+        return $true;
+    }
+
+    /**
+     *  provide yes-or-no dialog.
+     *
+     *  @return bool
+     *  @access public
+     */
+    function confirmDialog($message, $default = 'yes')
+    {
+        $ret = $this->ui->userConfirm($message);
+        return $ret;
+    }
+
+    /**
+     *  provide table layout
+     *
+     *  @param  array   $headline   headline
+     *  @param  array   $rows       rows which have the same size as headline's.
+     *  @access public
+     */
+    function displayTable($caption, $headline, $rows)
+    {
+        // spacing
+        foreach (array_keys($headline) as $k) {
+            $headline[$k] = sprintf('% -8s', $headline[$k]);
+        }
+
+        $data = array('caption'  => $caption,
+                      'border'   => true,
+                      'headline' => $headline,
+                      'data'     => $rows);
+        $this->ui->outputData($data);
+    }
+
+    /**
+     *  (experimental)
+     *  @access public
+     */
+    function setPearOpt($pearopt)
+    {
+        $this->_pearopt = $pearopt;
+    }
+
+    /**
+     *  (experimental)
+     *  @return array
+     */
+    function _getPearOpt(&$cmd_obj, $cmd_str, $opt_array)
+    {
+        $short_args = $long_args = null;
+        PEAR_Command::getGetOptArgs($cmd_str, $short_args, $long_args);
+        $opt = new Ethna_Getopt();
+        $opt_arg = $opt->getopt($opt_array, $short_args, $long_args);
+        if (Ethna::isError($opt_arg)) return array();
+        $opts = array();
+        foreach ($opt_arg[0] as $tmp) {
+            list($opt, $val) = $tmp;
+            if ($val === null) $val = true;
+            if (strlen($opt) == 1) {
+                $cmd_opts = $cmd_obj->getOptions($cmd_str);
+                foreach ($cmd_opts as $o => $d) {
+                    if (isset($d['shortopt']) && $d['shortopt'] == $opt) {
+                        $opts[$o] = $val;
+                    }
+                }
+            } else {
+                if (substr($opt, 0, 2) == '--') $opts[substr($opt, 2)] = $val;
+            }
+        }
+        return $opts;
+    }
+                
+
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Plugin.php b/Idea_Plugin_Extlib/class/Ethna_Plugin.php
new file mode 100644 (file)
index 0000000..b6e471b
--- /dev/null
@@ -0,0 +1,466 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @author     Kazuhiro Hosoi <hosoi@gree.co.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin
+/**
+ *  プラグインクラス
+ *  
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @author     Kazuhiro Hosoi <hosoi@gree.co.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    コントローラオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    コントローラオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    object  Ethna_Logger        ログオブジェクト */
+    var $logger;
+
+    /** @var    array   プラグインのオブジェクト(インスタンス)を保存する配列 */
+    var $obj_registry = array();
+
+    /** @var    array   プラグインのクラス名、ソースファイル名を保存する配列 */
+    var $src_registry = array();
+
+    /** @var    array       検索対象となるプラグインのアプリケーションIDのリスト */
+    var $appid_list;
+
+    /**#@-*/
+
+    // {{{ コンストラクタ
+    /**
+     *  Ethna_Pluginのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    $controller コントローラオブジェクト
+     */
+    function Ethna_Plugin(&$controller)
+    {
+        $this->controller =& $controller;
+        $this->ctl =& $this->controller;
+        $this->logger = null;
+        if (isset($this->controller->plugin_search_appids)
+            && is_array($this->controller->plugin_search_appids)) {
+            $this->appid_list =& $this->controller->plugin_search_appids;
+        } else {
+            $this->appid_list = array($this->controller->getAppId(), 'Ethna');
+        }
+    }
+
+    /**
+     *  loggerをsetする。
+     *
+     *  LogWriterはpluginなので、pluginインスタンス作成時点では
+     *  loggerに依存しないようにする。
+     *
+     *  @access public
+     *  @param  object  Ethna_Logger    $logger ログオブジェクト
+     */
+    function setLogger(&$logger)
+    {
+        if ($this->logger === null && is_object($logger)) {
+            $this->logger =& $logger;
+        }
+    }
+    // }}}
+
+    // {{{ プラグイン呼び出しインタフェース
+    /**
+     *  プラグインのインスタンスを取得
+     *
+     *  @access public
+     *  @param  string  $type   プラグインの種類
+     *  @param  string  $name   プラグインの名前
+     *  @return object  プラグインのインスタンス
+     */
+    function &getPlugin($type, $name)
+    {
+        return $this->_getPlugin($type, $name);
+    }
+
+    /**
+     *  ある種類 ($type) のプラグイン ($name) の全リストを取得
+     *
+     *  @access public
+     *  @param  string  $type   プラグインの種類
+     *  @return array   プラグインオブジェクトの配列
+     */
+    function getPluginList($type)
+    {
+        $plugin_list = array();
+
+        $this->searchAllPluginSrc($type);
+        if (isset($this->src_registry[$type]) == false) {
+            return $plugin_list;
+        }
+        foreach ($this->src_registry[$type] as $name => $value) {
+            if (is_null($value)) {
+                continue;
+            }
+            $plugin_list[$name] =& $this->getPlugin($type, $name);
+        }
+        return $plugin_list;
+    }
+    // }}}
+
+    // {{{ obj_registry のアクセサ
+    /**
+     *  プラグインのインスタンスをレジストリから取得
+     *
+     *  @access private
+     *  @param  string  $type   プラグインの種類
+     *  @param  string  $name   プラグインの名前
+     *  @return object  プラグインのインスタンス
+     */
+    function &_getPlugin($type, $name)
+    {
+        if (isset($this->obj_registry[$type]) == false) {
+            $this->obj_registry[$type] = array();
+
+            // プラグインの親クラスを(存在すれば)読み込み
+            foreach ($this->appid_list as $appid) {
+                list($class, $dir, $file) = $this->getPluginNaming($type, null, $appid);
+                $this->_includePluginSrc($class, $dir, $file, true);
+            }
+        }
+
+        // key がないときはプラグインをロードする
+        if (array_key_exists($name, $this->obj_registry[$type]) == false) {
+            $this->_loadPlugin($type, $name);
+        }
+
+        // null のときはロードに失敗している
+        if (is_null($this->obj_registry[$type][$name])) {
+            return Ethna::raiseWarning('plugin [type=%s, name=%s] is not found',
+                E_PLUGIN_NOTFOUND, $type, $name);
+        }
+
+        // プラグインのインスタンスを返す
+        return $this->obj_registry[$type][$name];
+    }
+
+    /**
+     *  プラグインをincludeしてnewし,レジストリに登録
+     *
+     *  @access private
+     *  @param  string  $type   プラグインの種類
+     *  @param  string  $name   プラグインの名前
+     */
+    function _loadPlugin($type, $name)
+    {
+        // プラグインのファイル名を取得
+        $plugin_src = $this->_getPluginSrc($type, $name);
+        if (is_null($plugin_src)) {
+            $this->obj_registry[$type][$name] = null;
+            return;
+        }
+        list($plugin_class, $plugin_dir, $plugin_file) = $plugin_src;
+
+        // プラグインのファイルを読み込み
+        $r =& $this->_includePluginSrc($plugin_class, $plugin_dir, $plugin_file);
+        if (Ethna::isError($r)) {
+            $this->obj_registry[$type][$name] = null;
+            return;
+        }
+
+        // プラグイン作成
+        $instance =& new $plugin_class($this->controller, $type, $name);
+        if (is_object($instance) == false
+            || strcasecmp(get_class($instance), $plugin_class) != 0) {
+            $this->logger->log(LOG_WARNING, 'plugin [%s::%s] instantiation failed', $type, $name);
+            $this->obj_registry[$type][$name] = null;
+            return;
+        }
+        $this->obj_registry[$type][$name] =& $instance;
+    }
+
+    /**
+     *  プラグインのインスタンスをレジストリから消す
+     *
+     *  @access private
+     *  @param  string  $type   プラグインの種類
+     *  @param  string  $name   プラグインの名前
+     */
+    function _unloadPlugin($type, $name)
+    {
+        unset($this->obj_registry[$type][$name]);
+    }
+    // }}}
+
+    // {{{ src_registry のアクセサ
+    /**
+     *  プラグインのソースファイル名とクラス名をレジストリから取得
+     *
+     *  @access private
+     *  @param  string  $type   プラグインの種類
+     *  @param  string  $name   プラグインの名前
+     *  @return array   ソースファイル名とクラス名からなる配列
+     */
+    function _getPluginSrc($type, $name)
+    {
+        if (isset($this->src_registry[$type]) == false) {
+            $this->src_registry[$type] = array();
+        }
+
+        // key がないときはプラグインの検索をする
+        if (array_key_exists($name, $this->src_registry[$type]) == false) {
+            $this->_searchPluginSrc($type, $name);
+        }
+
+        // プラグインのソースを返す
+        return $this->src_registry[$type][$name];
+    }
+    // }}}
+
+    // {{{ プラグインファイル検索部分
+    /**
+     *  プラグインのクラス名、ディレクトリ、ファイル名を決定
+     *
+     *  @access public
+     *  @param  string  $type   プラグインの種類
+     *  @param  string  $name   プラグインの名前 (nullのときは親クラス)
+     *  @param  string  $appid  アプリケーションID
+     *  @return array   プラグインのクラス名、ディレクトリ、ファイル名の配列
+     *  @todo   class factoryのnaming ruleと整合させる
+     */
+    function getPluginNaming($type, $name, $appid)
+    {
+        if ($appid == 'Ethna') {
+            if ($name === null) {
+                $ext = 'php';
+                $dir = ETHNA_BASE . "/class/Plugin";
+                $class = "Ethna_Plugin_{$type}";
+            } else {
+                $ext = 'php';
+                $dir = ETHNA_BASE . "/class/Plugin/{$type}";
+                $class = "Ethna_Plugin_{$type}_{$name}";
+            }
+        } else {
+            if ($name === null) {
+                $ext = $this->controller->getExt('php');
+                $dir = $this->controller->getDirectory('plugin');
+                $class = "{$appid}_Plugin_{$type}";
+            } else {
+                $ext = $this->controller->getExt('php');
+                $dir = $this->controller->getDirectory('plugin') . "/{$type}";
+                $class = "{$appid}_Plugin_{$type}_{$name}";
+            }
+        }
+
+        $file  = "{$class}.{$ext}";
+        return array($class, $dir, $file);
+    }
+
+    /**
+     *  プラグインのソースを include する
+     *
+     *  @access private
+     *  @param  string  $class  クラス名
+     *  @param  string  $dir    ディレクトリ名
+     *  @param  string  $file   ファイル名
+     *  @param  bool    $parent 親クラスかどうかのフラグ
+     *  @return true|Ethna_Error
+     */
+    function &_includePluginSrc($class, $dir, $file, $parent = false)
+    {
+        $true = true;
+        if (class_exists($class)) {
+            return $true;
+        }
+
+        $file = $dir . '/' . $file;
+        if (file_exists($file) === false) {
+            if ($parent === false) {
+                return Ethna::raiseWarning('plugin file is not found: [%s]',
+                                           E_PLUGIN_NOTFOUND, $file);
+            } else {
+                return $true;
+            }
+        }
+
+        include_once $file;
+
+        if (class_exists($class) === false) {
+            if ($parent === false) {
+                return Ethna::raiseWarning('plugin class [%s] is not defined',
+                    E_PLUGIN_NOTFOUND, $class);
+            } else {
+                return $true;
+            }
+        }
+
+        if ($parent === false) {
+            $this->logger->log(LOG_DEBUG, 'plugin class [%s] is defined', $class);
+        }
+        return $true;
+    }
+
+    /**
+     *  アプリケーション, Ethna の順でプラグインのソースを検索する
+     *
+     *  @access private
+     *  @param  string  $type   プラグインの種類
+     *  @param  string  $name   プラグインの名前
+     */
+    function _searchPluginSrc($type, $name)
+    {
+        // コントローラで指定されたアプリケーションIDの順に検索
+        foreach ($this->appid_list as $appid) {
+            list($class, $dir, $file) = $this->getPluginNaming($type, $name, $appid);
+            if (class_exists($class)) {
+                // すでにクラスが存在する場合は特別にスキップ
+                if (isset($this->src_registry[$type]) == false) {
+                    $this->src_registry[$type] = array();
+                }
+                $this->src_registry[$type][$name] = array($class, null, null);
+                return;
+            }
+            if (file_exists("{$dir}/{$file}")) {
+                $this->logger->log(LOG_DEBUG, 'plugin file is found in search: [%s]',
+                                   "{$dir}/{$file}");
+                if (isset($this->src_registry[$type]) == false) {
+                    $this->src_registry[$type] = array();
+                }
+                $this->src_registry[$type][$name] = array($class, $dir, $file);
+                return;
+            }
+        }
+
+        // 見つからなかった場合 (nullで記憶しておく)
+        $this->logger->log(LOG_WARNING, 'plugin file for [type=%s, name=%s] is not found in search', $type, $name);
+        $this->src_registry[$type][$name] = null;
+    }
+
+    /**
+     *  プラグインの種類 ($type) をすべて検索する
+     *
+     *  @access public
+     *  @return array
+     */
+    function searchAllPluginType()
+    {
+        $type_list = array();
+        foreach (array_reverse($this->appid_list) as $appid) {
+            list(, $dir, ) = $this->getPluginNaming('', null, $appid);
+            if (is_dir($dir) == false) {
+                continue;
+            }
+            $dh = opendir($dir);
+            if (is_resource($dh) == false) {
+                continue;
+            }
+            while (($type_dir = readdir($dh)) !== false) {
+                if ($type_dir{0} != '.' && is_dir("{$dir}/{$type_dir}")) {
+                    $type_list[$type_dir] = 0;
+                }
+            }
+            closedir($dh);
+        }
+        return array_keys($type_list);
+    }
+
+    /**
+     *  指定された $type のプラグイン (のソース) をすべて検索する
+     *
+     *  @access public
+     *  @param  string  $type   プラグインの種類
+     */
+    function searchAllPluginSrc($type)
+    {
+        // 後で見付かったもので上書きするので $this->appid_list の逆順とする
+        $name_list = array();
+        foreach (array_reverse($this->appid_list) as $appid) {
+            //  クラス名として許可された文字であればOKとする
+            //  アンダーバーを拒む理由はないし、命名規約からも禁止されていない
+            //  @see http://ethna.jp/ethna-document-dev_guide-plugin.html
+            //  @see http://www.php.net/manual/en/language.variables.php
+            list($class_regexp, $dir, $file_regexp) = $this->getPluginNaming($type, '([a-zA-Z0-9_\x7f-\xff]+)', $appid);
+
+            //ディレクトリの存在のチェック
+            if (is_dir($dir) == false) {
+                // アプリ側で見付からないのは正常
+                continue;
+            }
+
+            // ディレクトリを開く
+            $dh = opendir($dir);
+            if (is_resource($dh) == false) {
+                $this->logger->log(LOG_DEBUG, 'cannot open plugin directory: [%s]', $dir);
+                continue;
+            }
+            $this->logger->log(LOG_DEBUG, 'plugin directory opened: [%s]', $dir);
+
+            // 条件にあう $name をリストに追加
+            while (($file = readdir($dh)) !== false) {
+                if (preg_match('#^'.$file_regexp.'$#', $file, $matches)
+                    && file_exists("{$dir}/{$file}")) {
+                    $name_list[$matches[1]] = true;
+                }
+            }
+
+            closedir($dh);
+        }
+
+        foreach (array_keys($name_list) as $name) {
+            // 冗長だがもう一度探しなおす
+            $this->_searchPluginSrc($type, $name);
+        }
+    }
+    // }}}
+
+    // {{{ static な include メソッド
+    /**
+     *  Ethna 本体付属のプラグインのソースを include する
+     *
+     *  @access public
+     *  @param  string  $type   プラグインの種類
+     *  @param  string  $name   プラグインの名前
+     *  @static
+     */
+    function includeEthnaPlugin($type, $name)
+    {
+        Ethna_Plugin::includePlugin($type, $name, 'Ethna');
+    }
+
+    /**
+     *  プラグインのソースを include する
+     *
+     *  @access public
+     *  @param  string  $type   プラグインの種類
+     *  @param  string  $name   プラグインの名前
+     *  @param  string  $appid  アプリケーションID
+     *  @static
+     */
+    function includePlugin($type, $name, $appid = null)
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+
+        if ($appid === null) {
+            $appid = $ctl->getAppId();
+        }
+        list($class, $dir, $file) = $plugin->getPluginNaming($type, $name, $appid);
+        $plugin->_includePluginSrc($class, $dir, $file);
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Renderer.php b/Idea_Plugin_Extlib/class/Ethna_Renderer.php
new file mode 100644 (file)
index 0000000..ded414d
--- /dev/null
@@ -0,0 +1,300 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Renderer.php
+ *
+ *  @author     Kazuhiro Hosoi <hosoi@gree.co.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Renderer
+/**
+ *  レンダラクラス(Mojaviのまね)
+ *
+ *  @author     Kazuhiro Hosoi <hosoi@gree.co.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Renderer
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    string  template directory  */
+    var $template_dir;
+
+    /** @var    string  template engine */
+    var $engine;
+
+    /** @var    string  template file */
+    var $template;
+
+    /** @var    string  テンプレート変数 */
+    var $prop;
+    
+    /** @var    string  レンダラプラグイン(Ethna_Pluginとは関係なし) */
+    var $plugin_registry;
+    
+    /**
+     *  Ethna_Rendererクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_Renderer(&$controller)
+    {
+        $this->controller =& $controller;
+        $this->ctl =& $this->controller;
+        $this->template_dir = null;
+        $this->engine = null;
+        $this->template = null;
+        $this->prop = array();
+        $this->plugin_registry = array();
+    }
+
+    /**
+     *  ビューを出力する
+     *
+     *  @param string   $template   テンプレート
+     *  @param  bool    $capture    true ならば出力を表示せずに返す
+     *
+     *  @access public
+     */
+    function perform($template = null, $capture = false)
+    {
+        if ($template == null && $this->template == null) {
+            return Ethna::raiseWarning('template is not defined');
+        }
+
+        if ($template != null) {
+            $this->template = $template;
+        }
+
+        // テンプレートの有無のチェック
+        if (is_readable($this->template_dir . $this->template) === false) {
+            return Ethna::raiseWarning("template is not found: " . $this->template);
+        }
+
+        if ($capture === true) {
+            ob_start();
+            include_once $this->template_dir . $this->template;
+            $captured = ob_get_contents();
+            ob_end_clean();
+            return $captured;
+        } else {
+            include_once $this->template_dir . $this->template;
+            return true;
+        }
+    }
+
+    /**
+     *  テンプレートエンジンを取得する
+     * 
+     *  @return object   Template Engine.
+     * 
+     *  @access public
+     */
+    function &getEngine()
+    {
+        return $this->engine;
+    }
+
+    /**
+     *  テンプレートディレクトリを取得する
+     * 
+     *  @return string   Template Directory
+     * 
+     *  @access public
+     */
+    function getTemplateDir()
+    {
+        return $this->template_dir;
+    }
+
+    /**
+     *  テンプレート変数を取得する
+     * 
+     *  @param string $name  変数名
+     * 
+     *  @return mixed    変数
+     * 
+     *  @access public
+     */
+    function &getProp($name)
+    {
+        if (isset($this->prop[$name])) {
+            return $this->prop[$name];
+        }
+
+        return null;
+    }
+
+    /**
+     *  テンプレート変数を削除する
+     * 
+     *  @param name    変数名
+     * 
+     *  @access public
+     */
+    function &removeProp($name)
+    {
+        if (isset($this->prop[$name])) {
+            unset($this->prop[$name]);
+        }
+    }
+
+    /**
+     *  テンプレート変数に配列を割り当てる
+     * 
+     *  @param array $array
+     * 
+     *  @access public
+     */
+    function setPropArray($array)
+    {
+        $this->prop = array_merge($this->prop, $array);
+    }
+
+    /**
+     *  テンプレート変数に配列を参照として割り当てる
+     * 
+     *  @param array $array
+     * 
+     *  @access public
+     */
+    function setPropArrayByRef(&$array)
+    {
+        $keys  = array_keys($array);
+        $count = sizeof($keys);
+
+        for ($i = 0; $i < $count; $i++) {
+            $this->prop[$keys[$i]] =& $array[$keys[$i]];
+        }
+    }
+
+    /**
+     * テンプレート変数を割り当てる
+     * 
+     * @param string $name 変数名
+     * @param mixed $value 値
+     * 
+     * @access public
+     */
+    function setProp($name, $value)
+    {
+        $this->prop[$name] = $value;
+    }
+
+    /**
+     *  テンプレート変数に参照を割り当てる
+     * 
+     *  @param string $name 変数名
+     *  @param mixed $value 値
+     * 
+     *  @access public
+     */
+    function setPropByRef($name, &$value)
+    {
+        $this->prop[$name] =& $value;
+    }
+
+    /**
+     *  テンプレートを割り当てる
+     * 
+     *  @param string $template テンプレート名
+     * 
+     *  @access public
+     */
+    function setTemplate($template)
+    {
+        $this->template = $template;
+    }
+
+    /**
+     *  テンプレートディレクトリを割り当てる
+     * 
+     *  @param string $dir ディレクトリ名
+     * 
+     *  @access public
+     */
+    function setTemplateDir($dir)
+    {
+        $this->template_dir = $dir;
+
+        if (substr($this->template_dir, -1) != '/') {
+            $this->template_dir .= '/';
+        }
+    }
+    
+    /**
+     *  テンプレートの有無をチェックする
+     * 
+     *  @param string $template テンプレート名
+     * 
+     *  @access public
+     */
+    function templateExists($template)
+    {
+        if (substr($this->template_dir, -1) != '/') {
+            $this->template_dir .= '/';
+        }
+
+        return (is_readable($this->template_dir . $template));
+    }
+
+    /**
+     *  プラグインをセットする
+     * 
+     *  @param string $name プラグイン名
+     *  @param string $type プラグインタイプ
+     *  @param string $plugin プラグイン本体
+     * 
+     *  @access public
+     */
+    function setPlugin($name, $type, $plugin)
+    {
+        $this->plugin_registry[$type][$name] = $plugin;
+    }
+
+    // {{{ proxy methods (for B.C.)
+    /**
+     *  テンプレート変数を割り当てる(後方互換)
+     *
+     *  @access public
+     */
+    function assign($name, $value)
+    {
+        $this->setProp($name, $value);
+    }
+
+    /**
+     *  テンプレート変数に参照を割り当てる(後方互換)
+     *
+     *  @access public
+     */
+    function assign_by_ref($name, &$value)
+    {
+        $this->setPropByRef($name, $value);
+    }
+
+    /**
+     *  ビューを出力する
+     *
+     *  @access public
+     */
+    function display($template = null)
+    {
+        return $this->perform($template);
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Session.php b/Idea_Plugin_Extlib/class/Ethna_Session.php
new file mode 100644 (file)
index 0000000..108428b
--- /dev/null
@@ -0,0 +1,324 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Session.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Session
+/**
+ *  セッションクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Session
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Logger    loggerオブジェクト */
+    var $logger;
+
+    /** @var    string  セッション名 */
+    var $session_name;
+
+    /** @var    string  セッションデータ保存ディレクトリ */
+    var $session_save_dir;
+
+    /** @var    bool    セッション開始フラグ */
+    var $session_start = false;
+
+    /** @var    bool    匿名セッションフラグ */
+    var $anonymous = false;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_Sessionクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  string  $appid      アプリケーションID(セッション名として使用)
+     *  @param  string  $save_dir   セッションデータを保存するディレクトリ
+     */
+    function Ethna_Session($appid, $save_dir, $logger)
+    {
+        $this->session_name = "${appid}SESSID";
+        $this->session_save_dir = $save_dir;
+        $this->logger =& $logger;
+
+        if ($this->session_save_dir != "") {
+            session_save_path($this->session_save_dir);
+        }
+
+        session_name($this->session_name);
+        session_cache_limiter('private, must-revalidate');
+
+        $this->session_start = false;
+        if (isset($_SERVER['REQUEST_METHOD']) == false) {
+            return;
+        }
+
+        if (strcasecmp($_SERVER['REQUEST_METHOD'], 'post') == 0) {
+            $http_vars =& $_POST;
+        } else {
+            $http_vars =& $_GET;
+        }
+        if (array_key_exists($this->session_name, $http_vars)
+            && $http_vars[$this->session_name] != null) {
+            $_COOKIE[$this->session_name] = $http_vars[$this->session_name];
+        }
+    }
+
+    /**
+     *  セッションを復帰する
+     *
+     *  @access public
+     */
+    function restore()
+    {
+        if (!empty($_COOKIE[$this->session_name]) ||
+        (ini_get("session.use_trans_sid") == 1 &&
+        !empty($_REQUEST[$this->session_name]))
+        ) {
+            session_start();
+            $this->session_start = true;
+
+            // check session
+            if ($this->isValid() == false) {
+                setcookie($this->session_name, "", 0, "/");
+                $this->session_start = false;
+            }
+
+            // check anonymous
+            if ($this->get('__anonymous__')) {
+                $this->anonymous = true;
+            }
+        }
+    }
+
+    /**
+     *  セッションの正当性チェック
+     *
+     *  @access public
+     *  @return bool    true:正当なセッション false:不当なセッション
+     */
+    function isValid()
+    {
+        if (!$this->session_start) {
+            if (!empty($_COOKIE[$this->session_name]) || session_id() != null) {
+                setcookie($this->session_name, "", 0, "/");
+            }
+            return false;
+        }
+
+        // check remote address
+        if (!isset($_SESSION['REMOTE_ADDR'])
+            || $this->_validateRemoteAddr($_SESSION['REMOTE_ADDR'],
+                                          $_SERVER['REMOTE_ADDR']) == false) {
+            // we do not allow this
+            setcookie($this->session_name, "", 0, "/");
+            session_destroy();
+            $this->session_start = false;
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     *  セッションを開始する
+     *
+     *  @access public
+     *  @param  int     $lifetime   セッション有効期間(秒単位, 0ならセッションクッキー)
+     *  @return bool    true:正常終了 false:エラー
+     */
+    function start($lifetime = 0, $anonymous = false)
+    {
+        if ($this->session_start) {
+            // we need this?
+            $_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
+            $_SESSION['__anonymous__'] = $anonymous;
+            return true;
+        }
+
+        if (is_null($lifetime)) {
+            ini_set('session.use_cookies', 0);
+        } else {
+            ini_set('session.use_cookies', 1);
+        }
+
+        session_set_cookie_params($lifetime);
+        session_id(Ethna_Util::getRandom());
+        session_start();
+        $_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
+        $_SESSION['__anonymous__'] = $anonymous;
+        $this->session_start = true;
+
+        return true;
+    }
+
+    /**
+     *  セッションを破棄する
+     *
+     *  @access public
+     *  @return bool    true:正常終了 false:エラー
+     */
+    function destroy()
+    {
+        if (!$this->session_start) {
+            return true;
+        }
+        
+        session_destroy();
+        $this->session_start = false;
+        setcookie($this->session_name, "", 0, "/");
+
+        return true;
+    }
+
+    /**
+     *  セッションIDを再生成する
+     *
+     *  @access public
+     *  @return bool    true:正常終了 false:エラー
+     */
+    function regenerateId($lifetime = 0, $anonymous = false)
+    {
+        if (! $this->session_start) {
+            return false;
+        }
+       
+        $tmp = $_SESSION;
+
+        $this->destroy();
+        $this->start($lifetime, $anonymous);
+        
+        unset($tmp['REMOTE_ADDR']);
+        unset($tmp['__anonymous__']);
+        foreach ($tmp as $key => $value) {
+            $_SESSION[$key] = $value;
+        }
+
+        return true;
+    }
+
+    /**
+     *  セッション値へのアクセサ(R)
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @return mixed   取得した値(null:セッションが開始されていない)
+     */
+    function get($name)
+    {
+        if (!$this->session_start) {
+            return null;
+        }
+
+        if (!isset($_SESSION[$name])) {
+            return null;
+        }
+        return $_SESSION[$name];
+    }
+
+    /**
+     *  セッション値へのアクセサ(W)
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @param  string  $value  値
+     *  @return bool    true:正常終了 false:エラー(セッションが開始されていない)
+     */
+    function set($name, $value)
+    {
+        if (!$this->session_start) {
+            // no way
+            return false;
+        }
+
+        $_SESSION[$name] = $value;
+
+        return true;
+    }
+
+    /**
+     *  セッションの値を破棄する
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @return bool    true:正常終了 false:エラー(セッションが開始されていない)
+     */
+    function remove($name)
+    {
+        if (!$this->session_start) {
+            return false;
+        }
+
+        unset($_SESSION[$name]);
+
+        return true;
+    }
+
+    /**
+     *  セッションが開始されているかどうかを返す
+     *
+     *  @access public
+     *  @param  string  $anonymous  匿名セッションを「開始」とみなすかどうか(default: false)
+     *  @return bool    true:開始済み false:開始されていない
+     */
+    function isStart($anonymous = false)
+    {
+        if ($anonymous) {
+            return $this->session_start;
+        } else {
+            if ($this->session_start && $this->isAnonymous() != true) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     *  匿名セッションかどうかを返す
+     *
+     *  @access public
+     *  @return bool    true:匿名セッション false:非匿名セッション/セッション開始されていない
+     */
+    function isAnonymous()
+    {
+        return $this->anonymous;
+    }
+
+    /**
+     *  セッションに保存されたIPアドレスとアクセス元のIPアドレスが
+     *  同一ネットワーク範囲かどうかを判別する(16bit mask)
+     *
+     *  @access private
+     *  @param  string  $src_ip     セッション開始時のアクセス元IPアドレス
+     *  @param  string  $dst_ip     現在のアクセス元IPアドレス
+     *  @return bool    true:正常終了 false:不正なIPアドレス
+     */
+    function _validateRemoteAddr($src_ip, $dst_ip)
+    {
+        $src = ip2long($src_ip);
+        $dst = ip2long($dst_ip);
+
+        if (($src & 0xffff0000) == ($dst & 0xffff0000)) {
+            return true;
+        } else {
+            $this->logger->log(LOG_NOTICE, "session IP validation failed [%s] - [%s]",
+                               $src_ip, $dst_ip);
+            return false;
+        }
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_UnitTestCase.php b/Idea_Plugin_Extlib/class/Ethna_UnitTestCase.php
new file mode 100644 (file)
index 0000000..fe56d01
--- /dev/null
@@ -0,0 +1,181 @@
+<?php
+/**
+ *  Ethna_UnitTestCase.php
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/**
+ *  UnitTestCase実行クラス
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_UnitTestCase extends UnitTestCase
+{
+    /** @var    object  Ethna_Backend       backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    object  Ethna_Session       セッションオブジェクト */
+    var $session;
+
+    /** @var    string                      アクション名 */
+    var $action_name;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト */
+    var $action_form;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト($action_formの省略形) */
+    var $af;
+
+    /** @var    object  Ethna_ActionClass   アクションクラスオブジェクト */
+    var $action_class;
+
+    /** @var    object  Ethna_ActionClass   アクションクラスオブジェクト($action_classの省略形) */
+    var $ac;
+
+    /** @var    string                      ビュー名 */
+    var $forward_name;
+
+    /** @var    object  Ethna_ViewClass     viewクラスオブジェクト */
+    var $view_class;
+
+    /** @var    object  Ethna_ViewClass     viewクラスオブジェクト($view_classの省略形) */
+    var $vc;
+
+    /**
+     *  Ethna_UnitTestCaseのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     */
+    function Ethna_UnitTestCase(&$controller)
+    {
+        parent::UnitTestCase();
+
+        // オブジェクトの設定
+        $this->controller =& $controller;
+        $this->ctl =& $this->controller;
+        $this->backend =& $this->ctl->getBackend();
+        $this->session =& $this->backend->getSession();
+
+        // 変数の初期化
+        $this->action_form = $this->af = null;
+        $this->action_class = $this->ac = null;
+        $this->view_class = $this->vc = null;
+    }
+
+    /**
+     *  アクションフォームの作成と関連付け
+     *
+     *  @access public
+     */
+    function _createActionForm($form_name)
+    {
+        $this->action_form =& new $form_name($this->ctl);
+        $this->af =& $this->action_form;
+
+        // controler&backendにafを関連付け
+        $this->ctl->action_name = $this->action_name;
+        $this->ctl->action_form =& $this->af;
+        $this->backend->action_form =& $this->af;
+        $this->backend->af =& $this->af;
+
+        // action_error, validator の初期化
+        // これにより、直前のテスト結果をひきずらない
+        // ようにする
+        $ae =& $this->ctl->getActionError();
+        $ae->clear();
+        unset($ae->action_form);
+        unset($this->ctl->class_factory->object['plugin']->obj_registry["Validator"]);
+    }
+
+    /**
+     *  アクションフォームの作成
+     *
+     *  @access public
+     */
+    function createActionForm()
+    {
+        $form_name = $this->ctl->getActionFormName($this->action_name);
+        $this->_createActionForm($form_name);
+    }
+
+    /**
+     *  validateOneTime()
+     *
+     *  @access public
+     *  @return int $result
+     */
+    function validateOneTime()
+    {
+        if ($this->af == null) {
+            $this->createActionForm();
+        }
+
+        $result = $this->af->validate();
+        $this->af->ae->clear();
+
+        return $result;
+    }
+
+    /**
+     *  単純なアクションフォームの作成
+     *
+     *  @access public
+     */
+    function createPlainActionForm()
+    {
+        $form_name = 'Ethna_ActionForm';
+        $this->_createActionForm($form_name);
+    }
+
+    /**
+     *  アクションの作成
+     *
+     *  @access public
+     */
+    function createActionClass()
+    {
+        if ($this->af == null) {
+            $this->createActionForm();
+        }
+
+        // オブジェクト生成
+        $action_class_name = $this->ctl->getActionClassName($this->action_name);
+        $this->action_class =& new $action_class_name($this->backend);
+        $this->ac =& $this->action_class;
+
+        // backendにacを関連付け
+        $this->backend->action_class =& $this->ac;
+        $this->backend->ac =& $this->ac;
+    }
+
+    /**
+     *  ビューの作成
+     *
+     *  @access public
+     */
+    function createViewClass()
+    {
+        if ($this->af == null) {
+            $this->createPlainActionForm();
+        }
+
+        // オブジェクト生成
+        $view_class_name = $this->ctl->getViewClassName($this->forward_name);
+        $this->view_class =& new $view_class_name($this->backend, $this->forward_name, $this->ctl->_getForwardPath($this->forward_name));
+        $this->vc =& $this->view_class;
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_UnitTestManager.php b/Idea_Plugin_Extlib/class/Ethna_UnitTestManager.php
new file mode 100644 (file)
index 0000000..a997940
--- /dev/null
@@ -0,0 +1,374 @@
+<?php
+/**
+ *  Ethna_UnitTestManager.php
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once('simpletest/unit_tester.php');
+require_once('Ethna_UnitTestCase.php');
+require_once('Ethna_UnitTestReporter.php');
+
+/**
+ *  Ethnaユニットテストマネージャクラス
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_UnitTestManager extends Ethna_AppManager
+{
+    /** @var    object  Ethna_Controller    コントローラオブジェクト */
+    var $ctl;
+
+    /** @var    array                       一般テストケース定義 */
+    var $testcase = array();
+
+    /**
+     *  Ethna_UnitTestManagerのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Backend   &$backend   Ethna_Backendオブジェクト
+     */
+    function Ethna_UnitTestManager(&$backend)
+    {
+        parent::Ethna_AppManager($backend);
+        $this->ctl =& Ethna_Controller::getInstance();
+        $this->class_factory =& $this->ctl->getClassFactory();
+        $this->testcase = array_merge($this->testcase, $this->_getTestCaseList()); 
+    }
+
+    /**
+     *  action, view 以外のテストケースの一覧を取得する
+     *
+     *  @access private
+     *  @param  テストケースが含まれているディレクトリ名
+     */
+    function _getTestCaseList($test_dir = NULL)
+    {
+        $r = array();
+
+        if (is_null($test_dir)) {
+            $test_dir = $this->ctl->getTestdir();
+        }
+        $base = $this->ctl->getBasedir();
+
+        //  テストディレクトリはユーザが変更できる
+        //  ため、実行時の変更のタイミング次第では
+        //  WARNING が出る可能性があるのをケアする
+        if (!is_dir($test_dir)) {
+            return array();
+        }
+
+        $child_dir_list = array();
+
+        $dh = opendir($test_dir);
+        if ($dh == false) {
+            return;
+        }
+
+        $ext = $this->ctl->getExt('php');
+        while (($file = readdir($dh)) !== false) {
+            if ($file == "." || $file == "..") {
+                continue;
+            }
+            $file = $test_dir . $file;
+
+            if (is_dir($file)) {
+                $child_dir_list[] = $file;
+                continue;
+            }
+
+            if (preg_match("/\.$ext\$/", $file) == 0) {
+                continue;
+            }
+
+            $file = str_replace($this->ctl->getTestdir(), '', $file);
+
+            $key = ereg_replace("^(.*)Test\.$ext", '\1', $file);
+            $key = str_replace('/', '', $key);
+
+            $r[$key] = str_replace($base . '/', '', $this->ctl->getTestdir() . $file);
+        }
+
+        closedir($dh);
+
+        foreach ($child_dir_list as $child_dir) {
+            $tmp = $this->_getTestCaseList($child_dir . "/");
+            $r = array_merge($r, $tmp);
+        }
+
+        return $r;
+    }
+
+    /**
+     *  定義済みアクション一覧を取得する
+     *
+     *  @access public
+     *  @return array   アクション一覧
+     */
+    function _getActionList()
+    {
+        $im =& new Ethna_InfoManager($this->backend);
+        return $im->getActionList();
+    }
+
+    /**
+     *  クラス名からビュー名を取得する
+     *
+     *  @access public
+     *  @param  string  $class_name     ビュークラス名
+     *  @return string  アクション名
+     */
+    function viewClassToName($class_name)
+    {
+        $prefix = sprintf("%s_View_", $this->ctl->getAppId());
+        if (preg_match("/$prefix(.*)/", $class_name, $match) == 0) {
+            // 不明なクラス名
+            return null;
+        }
+        $target = $match[1];
+
+        $action_name = substr(preg_replace('/([A-Z])/e', "'_' . strtolower('\$1')", $target), 1);
+
+        return $action_name;
+    }
+
+    /**
+     *  指定されたクラス名を継承しているかどうかを返す
+     *
+     *  @access private
+     *  @param  string  $class_name     チェック対象のクラス名
+     *  @param  string  $parent_name    親クラス名
+     *  @return bool    true:継承している false:いない
+     */
+    function _isSubclassOf($class_name, $parent_name)
+    {
+        while ($tmp = get_parent_class($class_name)) {
+            if (strcasecmp($tmp, $parent_name) == 0) {
+                return true;
+            }
+            $class_name = $tmp;
+        }
+        return false;
+    }
+
+    /**
+     *  ビュースクリプトを解析する
+     *
+     *  @access private
+     *  @param  string  $script ファイル名
+     *  @return array   ビュークラス定義一覧
+     */
+    function __analyzeViewScript($script)
+    {
+        $class_list = array();
+
+        $source = "";
+        $fp = fopen($script, 'r');
+        if ($fp == false) {
+            return null;
+        }
+        while (feof($fp) == false) {
+            $source .= fgets($fp, 8192);
+        }
+        fclose($fp);
+
+        // トークンに分割してクラス定義情報を取得
+        $token_list = token_get_all($source);
+        for ($i = 0; $i < count($token_list); $i++) {
+            $token = $token_list[$i];
+
+            if ($token[0] == T_CLASS) {
+                // クラス定義開始
+                $i += 2;
+                $class_name = $token_list[$i][1];       // should be T_STRING
+                if ($this->_isSubclassOf($class_name, 'Ethna_ViewClass')) {
+                    $view_name = $this->viewClassToName($class_name);
+                    $class_list[$view_name] = array(
+                        'template_file' => $this->ctl->_getForwardPath($view_name),
+                        'view_class' => $class_name,
+                        'view_class_file' => $this->ctl->getDefaultViewPath($view_name),
+                    );
+                }
+            }
+        }
+
+        if (count($class_list) == 0) {
+            return null;
+        }
+        return $class_list;
+    }
+
+    /**
+     *  ディレクトリ以下のビュースクリプトを解析する
+     *
+     *  @access private
+     *  @param  string  $action_dir     解析対象のディレクトリ
+     *  @return array   ビュークラス定義一覧
+     */
+    function __analyzeViewList($view_dir = null)
+    {
+        $r = array();
+
+        if (is_null($view_dir)) {
+            $view_dir = $this->ctl->getViewdir();
+        }
+        $prefix_len = strlen($this->ctl->getViewdir());
+
+        $ext = '.' . $this->ctl->getExt('php');
+        $ext_len = strlen($ext);
+
+        $dh = opendir($view_dir);
+        if ($dh) {
+            while (($file = readdir($dh)) !== false) {
+                $path = "$view_dir/$file";
+                if ($file != '.' && $file != '..' && is_dir($path)) {
+                    $tmp = $this->__analyzeViewList($path);
+                    $r = array_merge($r, $tmp);
+                    continue;
+                }
+                if (substr($file, -$ext_len, $ext_len) != $ext) {
+                    continue;
+                }
+
+                include_once($path);
+                $class_list = $this->__analyzeViewScript($path);
+                if (is_null($class_list) == false) {
+                    $r = array_merge($r, $class_list);
+                }
+            }
+        }
+        closedir($dh);
+
+        return $r;
+    }
+
+    /**
+     *  定義済みビュー一覧を取得する
+     *
+     *  @access public
+     *  @return array   ビュー一覧
+     */
+    function _getViewList()
+    {
+        $im =& new Ethna_InfoManager($this->backend);
+        //$view_class_list = array_keys($im->getForwardList());
+
+        $r = array();
+
+        // テンプレート/ビュースクリプトを解析する
+        $forward_list = $im->_analyzeForwardList();
+        $view_list = $this->__analyzeViewList();
+
+        // ビュー定義エントリ一覧
+        $manifest_forward_list = $im->_getForwardList_Manifest($forward_list);
+
+        // ビュー定義省略エントリ一覧
+        $implicit_forward_list = $im->_getForwardList_Implicit($forward_list, $manifest_forward_list);
+
+        $r = array_merge($view_list, $manifest_forward_list, $implicit_forward_list);
+        ksort($r);
+
+        return $r;
+    }
+
+    /**
+     *  アクションテストクラスを取得する
+     *
+     *  @access private
+     *  @return array
+     */
+    function _getTestAction()
+    {
+        $action_class_list = array_keys($this->_getActionList());
+
+        // テストの存在するアクション
+        foreach ($action_class_list as $key => $action_name) {
+            $action_class = $this->ctl->getDefaultActionClass($action_name, false).'_TestCase';
+            if (!class_exists($action_class)) {
+                unset($action_class_list[$key]);
+            }
+        }
+
+        return $action_class_list;
+    }
+
+    /**
+     *  ビューテストクラスを取得する
+     *
+     *  @access private
+     *  @return array
+     */
+    function _getTestView()
+    {
+        $view_class_list = array_keys($this->_getViewList());
+
+        // テストの存在するビュー
+        foreach ($view_class_list as $key => $view_name) {
+            $view_class = $this->ctl->getDefaultViewClass($view_name, false).'_TestCase';
+            if (!class_exists($view_class)) {
+                unset($view_class_list[$key]);
+            }
+        }
+
+        return $view_class_list;
+    }
+
+    /**
+     *  ユニットテストを実行する
+     *
+     *  @access private
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function run()
+    {
+        $action_class_list = $this->_getTestAction();
+        $view_class_list = $this->_getTestView();
+
+        $test =& new GroupTest("Ethna UnitTest");
+
+        // アクション
+        foreach ($action_class_list as $action_name) {
+            $action_class = $this->ctl->getDefaultActionClass($action_name, false).'_TestCase';
+            $action_form = $this->ctl->getDefaultFormClass($action_name, false).'_TestCase';
+
+            $test->addTestCase(new $action_class($this->ctl));
+            $test->addTestCase(new $action_form($this->ctl));
+        }
+
+        // ビュー
+        foreach ($view_class_list as $view_name) {
+            $view_class = $this->ctl->getDefaultViewClass($view_name, false).'_TestCase';
+
+            $test->addTestCase(new $view_class($this->ctl));
+        }
+
+        // 一般
+        foreach ($this->testcase as $class_name => $file_name) {
+            $dir = $this->ctl->getBasedir().'/';
+            include_once $dir . $file_name;
+            $testcase_name = $class_name.'_TestCase';
+            $test->addTestCase(new $testcase_name($this->ctl));
+        }
+
+        // ActionFormのバックアップ
+        $af =& $this->ctl->getActionForm();
+
+        //出力したい形式にあわせて切り替える
+        $cli_enc = $this->ctl->getClientEncoding();
+        $reporter = new Ethna_UnitTestReporter($cli_enc);
+        $test->run($reporter);
+
+        // ActionFormのリストア
+        $this->ctl->action_form =& $af;
+        $this->backend->action_form =& $af;
+        $this->backend->af =& $af;
+
+        return array($reporter->report, $reporter->result);
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_UnitTestReporter.php b/Idea_Plugin_Extlib/class/Ethna_UnitTestReporter.php
new file mode 100644 (file)
index 0000000..73dc841
--- /dev/null
@@ -0,0 +1,173 @@
+<?php
+/**
+ *  Ethna_UnitTestReporter.php
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once 'simpletest/scorer.php';
+
+/**
+ *  Ethnaマネージャクラス
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_UnitTestReporter extends SimpleReporter {
+    
+    var $_character_set;
+
+    var $report;
+    var $result;
+
+    /**
+     *  Ethna_UnitTestReporterのコンストラクタ
+     *
+     *  @access public
+     *  @param  string  $character_set  キャラクタセット
+     */
+    function Ethna_UnitTestReporter($character_set = 'UTF-8')
+    {
+        $this->SimpleReporter();
+        $this->_character_set = $character_set;
+        $this->report= array();
+        $this->result= array();
+    }
+
+    /**
+     *  結果
+     *
+     *  @access public
+     *  @param string   $test_name  テスト名称
+     */
+    function paintFooter($test_name)
+    {
+        $colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green");
+        $this->result = array(
+            'TestCaseProgress' => $this->getTestCaseProgress(),
+            'TestCaseCount' => $this->getTestCaseCount(),
+            'PassCount' => $this->getPassCount(),
+            'FailCount' => $this->getFailCount(),
+            'ExceptionCount' => $this->getExceptionCount(),
+        );
+    }
+
+    /**
+     *  パス
+     *
+     *  @access public
+     * @param string   $message    メッセージ
+     */
+    function paintPass($message)
+    {
+        parent::paintPass($message);
+            
+        $test_list = $this->getTestList();
+        $this->report[] = array(
+            'type' => 'Pass',
+            'test' => $test_list[2],
+            'message' => $message,
+        );
+    }
+
+    /**
+     *  失敗
+     *
+     *  @access public
+     * @param string   $message    メッセージ
+     */
+    function paintFail($message)
+    {
+        parent::paintFail($message);
+
+        $test_list = $this->getTestList();
+        $this->report[] = array(
+            'type' => 'Fail',
+            'test' => $test_list[2],
+            'message' => $message,
+        );
+    }
+
+    /**
+     *  例外
+     *
+     *  @access public
+     * @param string   $message    メッセージ
+     */
+    function paintException($message)
+    {
+        parent::paintException($message);
+
+        $breadcrumb = $this->getTestList();
+        $test = $breadcrumb[2];
+        array_shift($breadcrumb);
+        $this->report[] = array(
+            'type' => 'Exception',
+            'test' => $test,
+            'breadcrumb' => $breadcrumb,
+            'message' => $message,
+        );
+    }
+
+    /**
+     *  テストケース開始
+     *
+     *  @access public
+     *  @param string   $test_name  テスト名称
+     */
+    function paintCaseStart($test_name)
+    {
+        parent::paintCaseStart($test_name);
+
+        $this->report[] = array(
+            'type' => 'CaseStart',
+            'test_name' => $test_name,
+        );
+    }
+
+    /**
+     *  テストケース終了
+     *
+     *  @access public
+     *  @param string   $test_name  テスト名称
+     */
+    function paintCaseEnd($test_name)
+    {
+        parent::paintCaseEnd($test_name);
+
+        $this->report[] = array(
+            'type' => 'CaseEnd',
+        );
+    }
+
+    /**
+     *  フォーマット済みメッセージ
+     *
+     *  @access public
+     * @param string   $message    メッセージ
+     */
+    function paintFormattedMessage($message)
+    {
+        $this->report[] = array(
+            'type' => 'FormattedMessage',
+            'message' => $this->_htmlEntities($message),
+        );
+    }
+
+    /**
+     *  HTMLエンティティ変換
+     *
+     * @access protected
+     * @param string   $message    プレーンテキスト
+     * @return string              HTMLエンティティ変換済みメッセージ
+     */
+    function _htmlEntities($message)
+    {
+        return htmlentities($message, ENT_COMPAT, $this->_character_set);
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_UrlHandler.php b/Idea_Plugin_Extlib/class/Ethna_UrlHandler.php
new file mode 100644 (file)
index 0000000..90eafa2
--- /dev/null
@@ -0,0 +1,400 @@
+<?php
+// vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
+/**
+ *  Ethna_UrlHandler.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/**
+ *  URLハンドラクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_UrlHandler
+{
+    /** @var    array   アクションマッピング */
+    var $action_map = array(
+        /*
+         * 'user'   => array(
+         *  'user_login' => array(
+         *      'path'          => 'login',
+         *      'path_regexp'   => false,
+         *      'path_ext'      => false,
+         *      'option'        => array(),
+         *  ),
+         * ),
+         */
+    );
+
+    /**
+     *  Ethna_UrlHandlerクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_UrlHandler()
+    {
+    }
+
+    /**
+     *  Ethna_UrlHandlerクラスのインスタンスを取得する
+     *
+     *  $name がクラス名 ('_'を含む) の場合はそのクラスを、
+     *  そうでないときはプラグイン名とみなしてインスタンスを返す
+     *
+     *  @access public
+     */
+    function &getInstance($name = null)
+    {
+        static $instance = array();
+        if ($name === null) {
+            $name = __CLASS__;
+        }
+        if (isset($instance[$name])) {
+            return $instance[$name];
+        }
+
+        if (strpos($name, '_') !== false) {
+            $instance[$name] = &new $name();
+        } else {
+            // get instance with plugin
+            $ctl = &Ethna_Controller::getInstance();
+            $plugin = &$ctl->getPlugin();
+            $instance[$name] = &$plugin->getPlugin('Urlhandler', $name);
+        }
+
+        return $instance[$name];
+    }
+
+    /**
+     *  アクションをユーザリクエストに変換する
+     *
+     *  @access public
+     */
+    function actionToRequest($action, $param)
+    {
+        $url_handler = null;
+        $action_value = null;
+        foreach ($this->action_map as $key => $value) {
+            if (isset($value[$action])) {
+                $url_handler = $key;
+                $action_value = $value[$action];
+                break;
+            }
+        }
+        if (is_null($url_handler)) {
+            return null;
+        }
+
+        // url_handler specific path
+        $method = sprintf("_getPath_%s", ucfirst($url_handler));
+        if (method_exists($this, $method) === false) {
+            return null;
+        }
+        list($path, $path_key) = $this->$method($action, $param);
+        if ($path == "") {
+            return null;
+        }
+
+        // append action path
+        if ($action_value['path']) {
+            $path .= "/" . $action_value['path'];
+        }
+
+        // path_ext candidate list
+        if (is_array($action_value['path_regexp'])) {
+            // try most matcher
+            $tmp = array_map('count', $action_value['path_ext']);
+            arsort($tmp);
+            $path_ext_list_indices = array_keys($tmp);
+            $path_ext_list = $action_value['path_ext'];
+        } else {
+            $path_ext_list_indices = array(0);
+            $path_ext_list = array(0 => $action_value['path_ext']);
+        }
+
+        // fix path_ext to use
+        foreach ($path_ext_list_indices as $index) {
+            if (is_array($path_ext_list[$index]) === false) {
+                // no parameters needed.
+                $path_ext = $path_ext_list[$index];
+                break;
+            }
+            $path_ext_match = true;
+            foreach ($path_ext_list[$index] as $key => $value) {
+                if (isset($param[$key]) === false) {
+                    $path_ext_match = false;
+                    break;
+                }
+            }
+            if ($path_ext_match) {
+                $path_ext = $path_ext_list[$index];
+                break;
+            }
+        }
+        if (isset($path_ext) === false) {
+            return null;
+        }
+
+        // append extra parameters to path.
+        if (is_array($path_ext)) {
+            foreach ($path_ext as $key => $value) {
+                $path_key[] = $key;
+                $ext_param = $param[$key];
+
+                // output filter
+                if (isset($value['output_filter']) && $value['output_filter'] != "") {
+                    $method = $value['output_filter'];
+                    if (method_exists($this, $method)) {
+                        $ext_param = $this->$method($ext_param);
+                    }
+                }
+
+                // remove form (pre|suf)fix
+                if (isset($value['form_prefix']) && $value['form_prefix'] != "") {
+                    $s = $value['form_prefix'];
+                    if (substr($ext_param, 0, strlen($s)) == $s) {
+                        $ext_param = substr($ext_param, strlen($s));
+                    }
+                }
+                if (isset($value['form_suffix']) && $value['form_suffix'] != "") {
+                    $s = $value['form_suffix'];
+                    if (substr($ext_param, -strlen($s)) == $s) {
+                        $ext_param = substr($ext_param, 0, -strlen($s));
+                    }
+                }
+
+                // rawurlencode (url (pre|suf)fixes need not to be encoded.)
+                $ext_param = rawurlencode($ext_param);
+
+                // add url (pre|suf)fix
+                if (isset($value['url_prefix']) && $value['url_prefix'] != "") {
+                    $ext_param = $value['url_prefix'] . $ext_param;
+                }
+                if (isset($value['url_suffix']) && $value['url_suffix'] != "") {
+                    $ext_param = $ext_param . $value['url_suffix'];
+                }
+
+                $path .= '/' . $ext_param;
+            }
+        }
+
+        list($path, $is_slash) = $this->_normalizePath($path);
+        return array($path, $path_key);
+    }
+
+    /**
+     *  ユーザリクエストをアクションに変換する
+     *
+     *  @access public
+     */
+    function requestToAction($http_vars)
+    {
+        if (isset($http_vars['__url_handler__']) == false
+            || isset($this->action_map[$http_vars['__url_handler__']]) == false) {
+            return array();
+        }
+
+        $url_handler = $http_vars['__url_handler__'];
+        $action_map = $this->action_map[$url_handler];
+
+        // parameter fix
+        $method = sprintf("_normalizeRequest_%s", ucfirst($url_handler));
+        if (method_exists($this, $method)) {
+            $http_vars = $this->$method($http_vars);
+        }
+
+        // normalize
+        if (isset($http_vars['__url_info__'])) {
+            $path = $http_vars['__url_info__'];
+        } else {
+            $path = "";
+        }
+        list($path, $is_slash) = $this->_normalizePath($path);
+
+        // match
+        $action = null;
+        $action_value = null;
+        $action_match = null;
+        $action_regexp_index = null;
+        foreach ($action_map as $key => $value) {
+            $match_length = strlen($value['path']);
+
+            // check necessary match
+            if (strncmp($path, $value['path'], $match_length) != 0) {
+                continue;
+            }
+
+            // try exact match
+            if ($path == $value['path']) {
+                $action = $key;
+                break;
+            }
+
+            // continue in case w/ incomplete match
+            if ($path != "" && $match_length > 0 && $path{$match_length} != "/") {
+                continue;
+            }
+            if ($is_slash && $path{strlen($path)-1} == "/") {
+                continue;
+            }
+
+            // try regexp
+            if ($value['path_regexp']) {
+                if (is_array($value['path_regexp'])) {
+                    foreach ($value['path_regexp'] as $index => $regexp) {
+                        if (preg_match($regexp, $path, $tmp)) {
+                            $action = $key;
+                            $action_match = $tmp;
+                            $action_regexp_index = $index;
+                            break;
+                        }
+                    }
+                } else {
+                    if (preg_match($value['path_regexp'], $path, $tmp)) {
+                        $action = $key;
+                        $action_match = $tmp;
+                        break;
+                    }
+                }
+            }
+        }
+        if (is_null($action)) {
+            return array();
+        }
+        $action_value = $action_map[$action];
+
+        // build parameters
+        $http_vars = $this->buildActionParameter($http_vars, $action);
+
+        // extra parameters
+        $path_ext = is_null($action_regexp_index)
+                    ? $action_value['path_ext']
+                    : $action_value['path_ext'][$action_regexp_index];
+        if (is_array($path_ext) && is_array($action_match)) {
+            $n = 1;
+            foreach ($path_ext as $key => $value) {
+                if (isset($action_match[$n]) == false) {
+                    break;
+                }
+
+                // remove url (pre|suf)fix
+                if (isset($value['url_prefix']) && $value['url_prefix'] != "") {
+                    $s = $value['url_prefix'];
+                    if (substr($action_match[$n], 0, strlen($s)) == $s) {
+                        $action_match[$n] = substr($action_match[$n], strlen($s));
+                    }
+                }
+                if (isset($value['url_suffix']) && $value['url_suffix'] != "") {
+                    $s = $value['url_suffix'];
+                    if (substr($action_match[$n], -strlen($s)) == $s) {
+                        $action_match[$n] = substr($action_match[$n], 0, -strlen($s));
+                    }
+                }
+
+                // add form (pre|suf)fix
+                if (isset($value['form_prefix']) && $value['form_prefix'] != "") {
+                    $action_match[$n] = $value['form_prefix'] . $action_match[$n];
+                }
+                if (isset($value['form_suffix']) && $value['form_suffix'] != "") {
+                    $action_match[$n] = $action_match[$n] . $value['form_suffix'];
+                }
+
+                // input filter
+                if (isset($value['input_filter']) && $value['input_filter'] != "") {
+                    $method = $value['input_filter'];
+                    if (method_exists($this, $method)) {
+                        $action_match[$n] = $this->$method($action_match[$n]);
+                    }
+                }
+
+                $http_vars[$key] = $action_match[$n];
+                $n++;
+            }
+        }
+
+        return $http_vars;
+    }
+
+    /**
+     *  ゲートウェイパスを正規化する
+     *
+     *  @access private
+     */
+    function _normalizePath($path)
+    {
+        if ($path == "") {
+            return array($path, false);
+        }
+
+        $is_slash = false;
+        $path = preg_replace('|/+|', '/', $path);
+
+        if ($path{0} == '/') {
+            $path = substr($path, 1);
+        }
+        if ($path{strlen($path)-1} == '/') {
+            $path = substr($path, 0, strlen($path)-1);
+            $is_slash = true;
+        }
+
+        return array($path, $is_slash);
+    }
+
+    /**
+     *  アクションをリクエストパラメータに変換する
+     *
+     *  @access public
+     */
+    function buildActionParameter($http_vars, $action)
+    {
+        if ($action == "") {
+            return $http_vars;
+        }
+        $key = sprintf('action_%s', $action);
+        $http_vars[$key] = 'true';
+        return $http_vars;
+    }
+
+    /**
+     *  パラメータをURLに変換する
+     *
+     *  @access public
+     */
+    function buildQueryParameter($query)
+    {
+        $param = '';
+
+        foreach ($query as $key => $value) {
+            if (is_array($value)) {
+                foreach ($value as $k => $v) {
+                    if (is_numeric($k)) {
+                        $k = '';
+                    }
+                    $param .= sprintf('%s=%s&',
+                                      urlencode(sprintf('%s[%s]', $key, $k)),
+                                      urlencode($v));
+                }
+            } else if (is_null($value) == false) {
+                $param .= sprintf('%s=%s&', urlencode($key), urlencode($value));
+            }
+        }
+
+        return substr($param, 0, -1);
+    }
+
+    // {{{ ゲートウェイリクエスト正規化
+    // }}}
+
+    // {{{ ゲートウェイパス生成
+    // }}}
+
+    // {{{ フィルタ
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_Util.php b/Idea_Plugin_Extlib/class/Ethna_Util.php
new file mode 100644 (file)
index 0000000..7ba5a51
--- /dev/null
@@ -0,0 +1,938 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Util.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ to_array
+/**
+ *  グローバルユーティリティ関数: スカラー値を要素数1の配列として返す
+ *
+ *  @param  mixed   $v  配列として扱う値
+ *  @return array   配列に変換された値
+ */
+function to_array($v)
+{
+    if (is_array($v)) {
+        return $v;
+    } else {
+        return array($v);
+    }
+}
+// }}}
+
+// {{{ is_error
+/**
+ *  グローバルユーティリティ関数: 指定されたフォーム項目にエラーがあるかどうかを返す
+ *
+ *  @param  string  $name   フォーム項目名
+ *  @return bool    true:エラー有り false:エラー無し
+ */
+function is_error($name = null)
+{
+    $c =& Ethna_Controller::getInstance();
+    $action_error =& $c->getActionError();
+    if ($name !== null) {
+        return $action_error->isError($name);
+    } else {
+        return $action_error->count() > 0;
+    }
+}
+// }}}
+
+// {{{ file_exists_ex
+/**
+ *  グローバルユーティリティ関数: include_pathを検索しつつfile_exists()する
+ *
+ *  @param  string  $path               ファイル名
+ *  @param  bool    $use_include_path   include_pathをチェックするかどうか
+ *  @return bool    true:有り false:無し
+ */
+function file_exists_ex($path, $use_include_path = true)
+{
+    if ($use_include_path == false) {
+        return file_exists($path);
+    }
+
+    // check if absolute
+    if (is_absolute_path($path)) {
+        return file_exists($path);
+    }
+
+    $include_path_list = explode(PATH_SEPARATOR, get_include_path());
+    if (is_array($include_path_list) == false) {
+        return file_exists($path);
+    }
+
+    foreach ($include_path_list as $include_path) {
+        if (file_exists($include_path . DIRECTORY_SEPARATOR . $path)) {
+            return true;
+        }
+    }
+    return false;
+}
+// }}}
+
+// {{{ is_absolute_path
+/**
+ *  グローバルユーティリティ関数: 絶対パスかどうかを返す
+ *
+ *  @param  string  $path               ファイル名
+ *  @return bool    true:絶対 false:相対
+ */
+function is_absolute_path($path)
+{
+    if (ETHNA_OS_WINDOWS) {
+        if (preg_match('/^[a-z]:/i', $path) && $path{2} == DIRECTORY_SEPARATOR) {
+            return true;
+        }
+    } else {
+        if ($path{0} == DIRECTORY_SEPARATOR) {
+            return true;
+        }
+    }
+    return false;
+}
+// }}}
+
+// {{{ Ethna_Util
+/**
+ *  ユーティリティクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Util
+{
+    // {{{ isDuplicatePost
+    /**
+     *  POSTのユニークチェックを行う
+     *
+     *  @access public
+     *  @return bool    true:2回目以降のPOST false:1回目のPOST
+     */
+    function isDuplicatePost()
+    {
+        $c =& Ethna_Controller::getInstance();
+
+        // use raw post data
+        if (isset($_POST['uniqid'])) {
+            $uniqid = $_POST['uniqid'];
+        } else if (isset($_GET['uniqid'])) {
+            $uniqid = $_GET['uniqid'];
+        } else {
+            return false;
+        }
+
+        // purge old files
+        Ethna_Util::purgeTmp("uniqid_", 60*60*1);
+
+        $filename = sprintf("%s/uniqid_%s_%s",
+                            $c->getDirectory('tmp'),
+                            $_SERVER['REMOTE_ADDR'],
+                            $uniqid);
+        if (file_exists($filename) == false) {
+            touch($filename);
+            return false;
+        }
+
+        $st = stat($filename);
+        if ($st[9] + 60*60*1 < time()) {
+            // too old
+            return false;
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ clearDuplicatePost
+    /**
+     *  POSTのユニークチェックフラグをクリアする
+     *
+     *  @acccess public
+     *  @return mixed   0:正常終了 Ethna_Error:エラー
+     */
+    function clearDuplicatePost()
+    {
+        $c =& Ethna_Controller::getInstance();
+
+        // use raw post data
+        if (isset($_POST['uniqid'])) {
+            $uniqid = $_POST['uniqid'];
+        } else {
+            return 0;
+        }
+
+        $filename = sprintf("%s/uniqid_%s_%s",
+                            $c->getDirectory('tmp'),
+                            $_SERVER['REMOTE_ADDR'],
+                            $uniqid);
+        if (file_exists($filename)) {
+            if (unlink($filename) == false) {
+                return Ethna::raiseWarning("File Write Error [%s]", E_APP_WRITE, $filename);
+            }
+        }
+
+        return 0;
+    }
+    // }}}
+
+    // {{{ isCsrfSafeValid
+    /**
+     *  CSRFをチェックする
+     *
+     *  @access public
+     *  @return bool    true:正常なPOST false:不正なPOST
+     */
+    function isCsrfSafe()
+    {
+        $c =& Ethna_Controller::getInstance();
+        $name = $c->config->get('csrf');
+        
+        if (is_null($name)) {
+            $name = 'Session';
+        }
+        
+        $plugin =& $c->getPlugin('Csrf', $name);
+        $csrf =& $plugin->getPlugin('Csrf', $name);
+        return $csrf->isValid();
+    }
+    // }}}
+
+    // {{{ setCsrfID
+    /**
+     *  CSRFをチェックする
+     *
+     *  @access public
+     *  @return bool    true:成功
+     */
+    function setCsrfID()
+    {
+        $c =& Ethna_Controller::getInstance();
+        $name = $c->config->get('csrf');
+        
+        if (is_null($name)) {
+            $name = 'Session';
+        }
+        
+        $plugin =& $c->getPlugin('Csrf', $name);
+        $csrf =& $plugin->getPlugin('Csrf', $name);
+        return $csrf->set();
+    }
+    // }}}
+
+    // {{{ checkMailAddress
+    /**
+     *  メールアドレスが正しいかどうかをチェックする
+     *
+     *  @access public
+     *  @param  string  $mailaddress    チェックするメールアドレス
+     *  @return bool    true: 正しいメールアドレス false: 不正な形式
+     */
+    function checkMailAddress($mailaddress)
+    {
+        if (preg_match('/^([a-z0-9_]|\-|\.|\+)+@(([a-z0-9_]|\-)+\.)+[a-z]{2,6}$/i',
+                       $mailaddress)) {
+            return true;
+        }
+        return false;
+    }
+    // }}}
+
+    // {{{ explodeCSV
+    /**
+     *  CSV形式の文字列を配列に分割する
+     *
+     *  @access public
+     *  @param  string  $csv        CSV形式の文字列(1行分)
+     *  @param  string  $delimiter  フィールドの区切り文字
+     *  @return mixed   (array):分割結果 Ethna_Error:エラー(行継続)
+     */
+    function explodeCSV($csv, $delimiter = ",")
+    {
+        $space_list = '';
+        foreach (array(" ", "\t", "\r", "\n") as $c) {
+            if ($c != $delimiter) {
+                $space_list .= $c;
+            }
+        }
+
+        $line_end = "";
+        if (preg_match("/([$space_list]+)\$/sS", $csv, $match)) {
+            $line_end = $match[1];
+        }
+        $csv = substr($csv, 0, strlen($csv)-strlen($line_end));
+        $csv .= ' ';
+
+        $field = '';
+        $retval = array();
+
+        $index = 0;
+        $csv_len = strlen($csv);
+        do {
+            // 1. skip leading spaces
+            if (preg_match("/^([$space_list]+)/sS", substr($csv, $index), $match)) {
+                $index += strlen($match[1]);
+            }
+            if ($index >= $csv_len) {
+                break;
+            }
+
+            // 2. read field
+            if ($csv{$index} == '"') {
+                // 2A. handle quote delimited field
+                $index++;
+                while ($index < $csv_len) {
+                    if ($csv{$index} == '"') {
+                        // handle double quote
+                        if ($csv{$index+1} == '"') {
+                            $field .= $csv{$index};
+                            $index += 2;
+                        } else {
+                            // must be end of string
+                            while ($csv{$index} != $delimiter && $index < $csv_len) {
+                                $index++;
+                            }
+                            if ($csv{$index} == $delimiter) {
+                                $index++;
+                            }
+                            break;
+                        }
+                    } else {
+                        // normal character
+                        if (preg_match("/^([^\"]*)/S", substr($csv, $index), $match)) {
+                            $field .= $match[1];
+                            $index += strlen($match[1]);
+                        }
+
+                        if ($index == $csv_len) {
+                            $field = substr($field, 0, strlen($field)-1);
+                            $field .= $line_end;
+
+                            // request one more line
+                            return Ethna::raiseNotice('CSV Split Error (line continue)', E_UTIL_CSV_CONTINUE);
+                        }
+                    }
+                }
+            } else {
+                // 2B. handle non-quoted field
+                if (preg_match("/^([^$delimiter]*)/S", substr($csv, $index), $match)) {
+                    $field .= $match[1];
+                    $index += strlen($match[1]);
+                }
+
+                // remove trailing spaces
+                $field = preg_replace("/[$space_list]+\$/S", '', $field);
+                if ($csv{$index} == $delimiter) {
+                    $index++;
+                }
+            }
+            $retval[] = $field;
+            $field = '';
+        } while ($index < $csv_len);
+
+        return $retval;
+    }
+    // }}}
+
+    // {{{ escapeCSV
+    /**
+     *  CSVエスケープ処理を行う
+     *
+     *  @access public
+     *  @param  string  $csv        エスケープ対象の文字列(CSVの各要素)
+     *  @param  bool    $escape_nl  改行文字(\r/\n)のエスケープフラグ
+     *  @return string  CSVエスケープされた文字列
+     */
+    function escapeCSV($csv, $escape_nl = false)
+    {
+        if (preg_match('/[,"\r\n]/', $csv)) {
+            if ($escape_nl) {
+                $csv = preg_replace('/\r/', "\\r", $csv);
+                $csv = preg_replace('/\n/', "\\n", $csv);
+            }
+            $csv = preg_replace('/"/', "\"\"", $csv);
+            $csv = "\"$csv\"";
+        }
+
+        return $csv;
+    }
+    // }}}
+
+    // {{{ escapeHtml
+    /**
+     *  配列の要素を全てHTMLエスケープして返す
+     *
+     *  @access public
+     *  @param  array   $target     HTMLエスケープ対象となる配列
+     *  @return array   エスケープされた配列
+     */
+    function escapeHtml($target)
+    {
+        $r = array();
+        Ethna_Util::_escapeHtml($target, $r);
+        return $r;
+    }
+
+    /**
+     *  配列の要素を全てHTMLエスケープして返す
+     *
+     *  @access public
+     *  @param  mixed   $vars   HTMLエスケープ対象となる配列
+     *  @param  mixed   $retval HTMLエスケープ対象となる子要素
+     */
+    function _escapeHtml(&$vars, &$retval)
+    {
+        foreach (array_keys($vars) as $name) {
+            if (is_array($vars[$name])) {
+                $retval[$name] = array();
+                Ethna_Util::_escapeHtml($vars[$name], $retval[$name]);
+            } else if (!is_object($vars[$name])) {
+                $retval[$name] = htmlspecialchars($vars[$name], ENT_QUOTES);
+            }
+        }
+    }
+    // }}}
+
+    // {{{ encode_MIME
+    /**
+     *  文字列をMIMEエンコードする
+     *
+     *  @access public
+     *  @param  string  $string     MIMEエンコードする文字列
+     *  @return エンコード済みの文字列
+     */
+    function encode_MIME($string)
+    {
+        $pos = 0;
+        $split = 36;
+        $_string = "";
+        while ($pos < mb_strlen($string))
+        {
+            $tmp = mb_strimwidth($string, $pos, $split, "");
+            $pos += mb_strlen($tmp);
+            $_string .= (($_string)? ' ' : '') . mb_encode_mimeheader($tmp, 'ISO-2022-JP');
+        }
+        return $_string;
+    }
+    // }}}
+
+    // {{{ getDirectLinkList
+    /**
+     *  Google風リンクリストを返す
+     *
+     *  @access public
+     *  @param  int     $total      検索総件数
+     *  @param  int     $offset     表示オフセット
+     *  @param  int     $count      表示件数
+     *  @return array   リンク情報を格納した配列
+     */
+    function getDirectLinkList($total, $offset, $count)
+    {
+        $direct_link_list = array();
+
+        if ($total == 0) {
+            return array();
+        }
+
+        // backwards
+        $current = $offset - $count;
+        while ($current > 0) {
+            array_unshift($direct_link_list, $current);
+            $current -= $count;
+        }
+        if ($offset != 0 && $current <= 0) {
+            array_unshift($direct_link_list, 0);
+        }
+
+        // current
+        $backward_count = count($direct_link_list);
+        array_push($direct_link_list, $offset);
+
+        // forwards
+        $current = $offset + $count;
+        for ($i = 0; $i < 10; $i++) {
+            if ($current >= $total) {
+                break;
+            }
+            array_push($direct_link_list, $current);
+            $current += $count;
+        }
+        $forward_count = count($direct_link_list) - $backward_count - 1;
+
+        $backward_count -= 4;
+        if ($forward_count < 5) {
+            $backward_count -= 5 - $forward_count;
+        }
+        if ($backward_count < 0) {
+            $backward_count = 0;
+        }
+
+        // add index
+        $n = 1;
+        $r = array();
+        foreach ($direct_link_list as $direct_link) {
+            $v = array('offset' => $direct_link, 'index' => $n);
+            $r[] = $v;
+            $n++;
+        }
+
+        return array_splice($r, $backward_count, 10);
+    }
+    // }}}
+
+    // {{{ getEra
+    /**
+     *  元号制での年を返す
+     *
+     *  @access public
+     *  @param  int     $t      unix time
+     *  @return string  元号(不明な場合はnull)
+     */
+    function getEra($t)
+    {
+        $tm = localtime($t, true);
+        $year = $tm['tm_year'] + 1900;
+
+        if ($year >= 1989) {
+            $heisei_str = _et('Heisei');
+            return array($heisei_str, $year - 1988);
+        } else if ($year >= 1926) {
+            $showa_str = _et('Showa');
+            return array($showa_str, $year - 1925);
+        }
+
+        return null;
+    }
+    // }}}
+
+    // {{{ getImageExtName
+    /**
+     *  getimagesize()の返すイメージタイプに対応する拡張子を返す
+     *
+     *  @access public
+     *  @param  int     $type   getimagesize()関数の返すイメージタイプ
+     *  @return string  $typeに対応する拡張子
+     */
+    function getImageExtName($type)
+    {
+        $ext_list = array(
+            1   => 'gif',
+            2   => 'jpg',
+            3   => 'png',
+            4   => 'swf',
+            5   => 'psd',
+            6   => 'bmp',
+            7   => 'tiff',
+            8   => 'tiff',
+            9   => 'jpc',
+            10  => 'jp2',
+            11  => 'jpx',
+            12  => 'jb2',
+            13  => 'swc',
+            14  => 'iff',
+            15  => 'wbmp',
+            16  => 'xbm',
+        );
+
+        return @$ext_list[$type];
+    }
+    // }}}
+
+    // {{{ getRandom
+    /**
+     *  ランダムなハッシュ値を生成する
+     *
+     *  決して高速ではないので乱用は避けること
+     *
+     *  @access public
+     *  @param  int     $length ハッシュ値の長さ(〜64)
+     *  @return string  ハッシュ値
+     */
+    function getRandom($length = 64)
+    {
+        static $srand = false;
+
+        if ($srand == false) {
+            list($usec, $sec) = explode(' ', microtime());
+            mt_srand((float) $sec + ((float) $usec * 100000) + getmypid());
+            $srand = true;
+        }
+
+        // open_basedir がオンで、かつ /proc が許可されているか?
+        // open_basedir が空なら許可されていると看做す
+        $devfile = '/proc/net/dev';
+        $open_basedir_conf = ini_get('open_basedir');
+        $devfile_enabled = (empty($open_basedir_conf) 
+                        || (preg_match('#:/proc#', $open_basedir_conf) > 0
+                        ||  preg_match('#^/proc#', $open_basedir_conf) > 0));
+
+        $value = "";
+        for ($i = 0; $i < 2; $i++) {
+            // for Linux
+            if ($devfile_enabled && file_exists($devfile)) {
+                $rx = $tx = 0;
+                $fp = fopen($devfile, 'r');
+                if ($fp != null) {
+                    $header = true;
+                    while (feof($fp) === false) {
+                        $s = fgets($fp, 4096);
+                        if ($header) {
+                            $header = false;
+                            continue;
+                        }
+                        $v = preg_split('/[:\s]+/', $s);
+                        if (is_array($v) && count($v) > 10) {
+                            $rx += $v[2];
+                            $tx += $v[10];
+                        }
+                    }
+                }
+                $platform_value = $rx . $tx . mt_rand() . getmypid();
+            } else {
+                $platform_value = mt_rand() . getmypid();
+            }
+            $now = strftime('%Y%m%d %T');
+            $time = gettimeofday();
+            $v = $now . $time['usec'] . $platform_value . mt_rand(0, time());
+            $value .= md5($v);
+        }
+
+        if ($length < 64) {
+            $value = substr($value, 0, $length);
+        }
+        return $value;
+    }
+    // }}}
+
+    // {{{ get2dArray
+    /**
+     *  1次元配列をm x nに再構成する
+     *
+     *  @access public
+     *  @param  array   $array  処理対象の1次元配列
+     *  @param  int     $m      軸の要素数
+     *  @param  int     $order  $mをX軸と見做すかY軸と見做すか(0:X軸 1:Y軸)
+     *  @return array   m x nに再構成された配列
+     */
+    function get2dArray($array, $m, $order)
+    {
+        $r = array();
+        
+        $n = intval(count($array) / $m);
+        if ((count($array) % $m) > 0) {
+            $n++;
+        }
+        for ($i = 0; $i < $n; $i++) {
+            $elts = array();
+            for ($j = 0; $j < $m; $j++) {
+                if ($order == 0) {
+                    // 横並び(横:$m列 縦:無制限)
+                    $key = $i*$m+$j;
+                } else {
+                    // 縦並び(横:無制限 縦:$m行)
+                    $key = $i+$n*$j;
+                }
+                if (array_key_exists($key, $array) == false) {
+                    $array[$key] = null;
+                }
+                $elts[] = $array[$key];
+            }
+            $r[] = $elts;
+        }
+
+        return $r;
+    }
+    // }}}
+
+    // {{{ isAbsolute
+    /**
+     *  パス名が絶対パスかどうかを返す
+     *
+     *  port from File in PEAR (for BC)
+     *
+     *  @access public
+     *  @param  string  $path
+     *  @return bool    true:絶対パス false:相対パス
+     */
+    function isAbsolute($path)
+    {
+        if (preg_match("/\.\./", $path)) {
+            return false;
+        }
+
+        if (DIRECTORY_SEPARATOR == '/'
+            && (substr($path, 0, 1) == '/' || substr($path, 0, 1) == '~')) {
+            return true;
+        } else if (DIRECTORY_SEPARATOR == '\\' && preg_match('/^[a-z]:\\\/i', $path)) {
+            return true;
+        }
+
+        return false;
+    }
+    // }}}
+
+    // {{{ isRootDir
+    /**
+     *  パス名がルートディレクトリかどうかを返す
+     *
+     *  @access public
+     *  @param  string  $path
+     *  @static
+     */
+    function isRootDir($path)
+    {
+        if ($path === DIRECTORY_SEPARATOR) {
+            // avoid stat().
+            return true;
+        }
+        if (is_dir($path) === false) {
+            return false;
+        }
+        return $path === basename($path) . DIRECTORY_SEPARATOR;
+    }
+    // }}}
+
+    // {{{ mkdir
+    /**
+     *  mkdir -p
+     *
+     *  @access public
+     *  @param  string  $dir    作成するディレクトリ
+     *  @param  int     $mode   パーミッション
+     *  @return bool    true:成功 false:失敗
+     *  @static
+     */
+    function mkdir($dir, $mode)
+    {
+        if (file_exists($dir)) {
+            return is_dir($dir);
+        }
+
+        $parent = dirname($dir);
+        if ($dir === $parent) {
+            return true;
+        }
+
+        if (is_dir($parent) === false) {
+            if (Ethna_Util::mkdir($parent, $mode) === false) {
+                return false;
+            }
+        }
+
+        return mkdir($dir, $mode) && Ethna_Util::chmod($dir, $mode);
+    }
+    // }}}
+
+    // {{{ chmod
+    /**
+     *  ファイルのパーミッションを変更する
+     */
+    function chmod($file, $mode)
+    {
+        $st = stat($file);
+        if (($st[2] & 0777) == $mode) {
+            return true;
+        }
+        return chmod($file, $mode);
+    }
+    // }}}
+
+    // {{{ purgeDir
+    /**
+     *  ディレクトリを再帰的に削除する
+     *  (途中で失敗しても中断せず、削除できるものはすべて消す)
+     *
+     *  @access public
+     *  @param  string  $file   削除するファイルまたはディレクトリ
+     *  @return bool    true:成功 false:失敗
+     *  @static
+     */
+    function purgeDir($dir)
+    {
+        if (file_exists($dir) === false) {
+            return false;
+        }
+        if (is_dir($dir) === false) {
+            return unlink($dir);
+        }
+
+        $dh = opendir($dir);
+        if ($dh === false) {
+            return false;
+        }
+        $ret = true;
+        while (($entry = readdir($dh)) !== false) {
+            if ($entry === '.' || $entry === '..') {
+                continue;
+            }
+            $ret = $ret && Ethna_Util::purgeDir("{$dir}/{$entry}");
+        }
+        closedir($dh);
+        if ($ret) {
+            return rmdir($dir);
+        } else {
+            return false;
+        }
+    }
+    // }}}
+
+    // {{{ purgeTmp
+    /**
+     *  テンポラリディレクトリのファイルを削除する
+     *
+     *  @access public
+     *  @param  string  $prefix     ファイルのプレフィクス
+     *  @param  int     $timeout    削除対象閾値(秒−60*60*1なら1時間)
+     */
+    function purgeTmp($prefix, $timeout)
+    {
+        $c =& Ethna_Controller::getInstance();
+
+        $dh = opendir($c->getDirectory('tmp'));
+        if ($dh) {
+            while (($file = readdir($dh)) !== false) {
+                if ($file == '.' || $file == '..') {
+                    continue;
+                } else if (is_dir($c->getDirectory('tmp') . '/' . $file)) {
+                    continue;
+                } else if (strncmp($file, $prefix, strlen($prefix)) == 0) {
+                    $f = $c->getDirectory('tmp') . "/" . $file;
+                    $st = stat($f);
+                    if ($st[9] + $timeout < time()) {
+                        unlink($f);
+                    }
+                }
+            }
+            closedir($dh);
+        }
+    }
+    // }}}
+
+    // {{{ lockFile
+    /**
+     *  ファイルをロックする
+     *
+     *  @access public
+     *  @param  string  $file       ロックするファイル名
+     *  @param  int     $mode       ロックモード('r', 'rw')
+     *  @param  int     $timeout    ロック待ちタイムアウト(秒−0なら無限)
+     *  @return int     ロックハンドル(falseならエラー)
+     */
+    function lockFile($file, $mode, $timeout = 0)
+    {
+        if (file_exists($file) === false) {
+            touch($file);
+        }
+        $lh = fopen($file, $mode);
+        if ($lh == null) {
+            return Ethna::raiseError("File Read Error [%s]", E_APP_READ, $file);
+        }
+
+        $lock_mode = $mode == 'r' ? LOCK_SH : LOCK_EX;
+
+        for ($i = 0; $i < $timeout || $timeout == 0; $i++) {
+            $r = flock($lh, $lock_mode | LOCK_NB);
+            if ($r == true) {
+                break;
+            }
+            sleep(1);
+        }
+        if ($timeout > 0 && $i == $timeout) {
+            // timed out
+            return Ethna::raiseError("File lock get error [%s]", E_APP_LOCK, $file);
+        }
+
+        return $lh;
+    }
+    // }}}
+
+    // {{{ unlockFile
+    /**
+     *  ファイルのロックを解除する
+     *
+     *  @access public
+     *  @param  int     $lh     ロックハンドル
+     */
+    function unlockFile($lh)
+    {
+        fclose($lh);
+    }
+    // }}}
+
+    // {{{ formatBacktrace
+    /**
+     *  バックトレースをフォーマットして返す
+     *
+     *  @access public
+     *  @param  array   $bt     debug_backtrace()関数で取得したバックトレース
+     *  @return string  文字列にフォーマットされたバックトレース
+     */
+    function formatBacktrace($bt) 
+    {
+        $r = "";
+        $i = 0;
+        foreach ($bt as $elt) {
+            $r .= sprintf("[%02d] %s:%d:%s.%s\n", $i,
+                          isset($elt['file']) ? $elt['file'] : 'unknown file',
+                          isset($elt['line']) ? $elt['line'] : 'unknown line',
+                          isset($elt['class']) ? $elt['class'] : 'global',
+                          $elt['function']);
+            $i++;
+
+            if (isset($elt['args']) == false || is_array($elt['args']) == false) {
+                continue;
+            }
+
+            // 引数のダンプ
+            foreach ($elt['args'] as $arg) {
+                $r .= Ethna_Util::_formatBacktrace($arg);
+            }
+        }
+
+        return $r;
+    }
+
+    /**
+     *  バックトレース引数をフォーマットして返す
+     *
+     *  @access private
+     *  @param  string  $arg    バックトレースの引数
+     *  @param  int     $level  バックトレースのネストレベル
+     *  @param  int     $wrap   改行フラグ
+     *  @return string  文字列にフォーマットされたバックトレース
+     */
+    function _formatBacktrace($arg, $level = 0, $wrap = true)
+    {
+        $pad = str_repeat("  ", $level);
+        if (is_array($arg)) {
+            $r = sprintf("     %s[array] => (\n", $pad);
+            if ($level+1 > 4) {
+                $r .= sprintf("     %s  *too deep*\n", $pad);
+            } else {
+                foreach ($arg as $key => $elt) {
+                    $r .= Ethna_Util::_formatBacktrace($key, $level, false);
+                    $r .= " => \n";
+                    $r .= Ethna_Util::_formatBacktrace($elt, $level+1);
+                }
+            }
+            $r .= sprintf("     %s)\n", $pad);
+        } else if (is_object($arg)) {
+            $r = sprintf("     %s[object]%s%s", $pad, get_class($arg), $wrap ? "\n" : "");
+        } else {
+            $r = sprintf("     %s[%s]%s%s", $pad, gettype($arg), $arg, $wrap ? "\n" : "");
+        }
+
+        return $r;
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Ethna_ViewClass.php b/Idea_Plugin_Extlib/class/Ethna_ViewClass.php
new file mode 100644 (file)
index 0000000..3aa4964
--- /dev/null
@@ -0,0 +1,984 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ViewClass.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_ViewClass
+/**
+ *  viewクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_ViewClass
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    Controllerオブジェクト */
+    var $ctl;
+
+    /** @var    object  Ethna_Backend       backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Config        設定オブジェクト    */
+    var $config;
+
+    /** @var    object  Ethna_I18N          i18nオブジェクト */
+    var $i18n;
+
+    /** @var    object  Ethna_Logger    ログオブジェクト */
+    var $logger;
+
+    /** @var    object  Ethna_Plugin    プラグインオブジェクト */
+    var $plugin;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト */
+    var $action_error;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト(省略形) */
+    var $ae;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト */
+    var $action_form;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト(省略形) */
+    var $af;
+
+    /** @var    array   アクションフォームオブジェクト(helper) */
+    var $helper_action_form = array();
+
+    /** @var    array   helperでhtmlのattributeにはしなパラメータの一覧 */
+    var $helper_parameter_keys = array('default', 'option', 'separator');
+
+    /** @var    object  Ethna_Session       セッションオブジェクト */
+    var $session;
+
+    /** @var    string  遷移名 */
+    var $forward_name;
+
+    /** @var    string  遷移先テンプレートファイル名 */
+    var $forward_path;
+
+    /** @var    boolean  配列フォームを呼んだカウンタをリセットするか否か */
+    var $reset_counter = false;
+
+    /**#@-*/
+
+    // {{{ Ethna_ViewClass
+    /**
+     *  Ethna_ViewClassのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Backend   $backend    backendオブジェクト
+     *  @param  string  $forward_name   ビューに関連付けられている遷移名
+     *  @param  string  $forward_path   ビューに関連付けられているテンプレートファイル名
+     */
+    function Ethna_ViewClass(&$backend, $forward_name, $forward_path)
+    {
+        $c =& $backend->getController();
+        $this->ctl =& $c;
+        $this->backend =& $backend;
+        $this->config =& $this->backend->getConfig();
+        $this->i18n =& $this->backend->getI18N();
+        $this->logger =& $this->backend->getLogger();
+        $this->plugin =& $this->backend->getPlugin();
+
+        $this->action_error =& $this->backend->getActionError();
+        $this->ae =& $this->action_error;
+
+        $this->action_form =& $this->backend->getActionForm();
+        $this->af =& $this->action_form;
+
+        $this->session =& $this->backend->getSession();
+
+        $this->forward_name = $forward_name;
+        $this->forward_path = $forward_path;
+
+        foreach (array_keys($this->helper_action_form) as $action) {
+            $this->addActionFormHelper($action);
+        }
+    }
+    // }}}
+
+    // {{{ preforward
+    /**
+     *  画面表示前処理
+     *
+     *  テンプレートに設定する値でコンテキストに依存しないものは
+     *  ここで設定する(例:セレクトボックス等)
+     *
+     *  @access public
+     */
+    function preforward()
+    {
+    }
+    // }}}
+
+    // {{{ forward
+    /**
+     *  遷移名に対応する画面を出力する
+     *
+     *  特殊な画面を表示する場合を除いて特にオーバーライドする必要は無い
+     *  (preforward()のみオーバーライドすれば良い)
+     *
+     *  @access public
+     */
+    function forward()
+    {
+        $renderer =& $this->_getRenderer();
+        $this->_setDefault($renderer);
+        $renderer->perform($this->forward_path);
+    }
+    // }}}
+
+    // {{{ addActionFormHelper
+    /**
+     *  helperアクションフォームオブジェクトを設定する
+     *
+     *  @param  string $action アクション名
+     *  @param  boolean $dynamic_helper 動的フォームヘルパを呼ぶか否か
+     *  @access public
+     */
+    function addActionFormHelper($action, $dynamic_helper = false)
+    {
+        //
+        //  既に追加されている場合は処理をしない
+        //
+        if (isset($this->helper_action_form[$action])
+            && is_object($this->helper_action_form[$action])) {
+            return;
+        }
+
+        //    現在のアクションと等しければ、対応する
+        //    アクションフォームを設定
+        $ctl =& Ethna_Controller::getInstance();
+        if ($action === $ctl->getCurrentActionName()) {
+            $this->helper_action_form[$action] =& $this->af;
+        } else {
+            //    アクションが異なる場合
+            $form_name = $ctl->getActionFormName($action);
+            if ($form_name === null) {
+                $this->logger->log(LOG_WARNING,
+                    'action form for the action [%s] not found.', $action);
+                return;
+            }
+            $this->helper_action_form[$action] =& new $form_name($ctl);
+        }
+
+        //   動的フォームを設定するためのヘルパメソッドを呼ぶ
+        if ($dynamic_helper) {
+            $af =& $this->helper_action_form[$action];
+            $af->setFormDef_ViewHelper();
+        }
+    }
+    // }}}
+
+    // {{{ clearActionFormHelper
+    /**
+     *  helperアクションフォームオブジェクトを削除する
+     *
+     *  @access public
+     */
+    function clearActionFormHelper($action)
+    {
+        unset($this->helper_action_form[$action]);
+    }
+    // }}}
+
+    // {{{ _getHelperActionForm
+    /**
+     *  アクションフォームオブジェクト(helper)を取得する
+     *  $action === null で $name が指定されているときは、$nameの定義を
+     *  含むものを探す
+     *
+     *  @access protected
+     *  @param  string  action  取得するアクション名
+     *  @param  string  name    定義されていることを期待するフォーム名
+     *  @return object  Ethna_ActionFormまたは継承オブジェクト
+     */
+    function &_getHelperActionForm($action = null, $name = null)
+    {
+        // $action が指定されている場合
+        if ($action !== null) {
+            if (isset($this->helper_action_form[$action])
+                && is_object($this->helper_action_form[$action])) {
+                return $this->helper_action_form[$action];
+            } else {
+                $this->logger->log(LOG_WARNING,
+                    'helper action form for action [%s] not found',
+                    $action);
+                return null;
+            }
+        }
+
+        // 最初に $this->af を調べる
+        $def = $this->af->getDef($name);
+        if ($def !== null) {
+            return $this->af;
+        }
+
+        // $this->helper_action_form を順に調べる
+        foreach (array_keys($this->helper_action_form) as $action) {
+            if (is_object($this->helper_action_form[$action]) === false) {
+                continue;
+            }
+            $af =& $this->helper_action_form[$action];
+            $def = $af->getDef($name);
+            if (is_null($def) === false) {
+                return $af;
+            }
+        }
+
+        // 見付からなかった
+        $this->logger->log(LOG_WARNING,
+            'action form defining form [%s] not found', $name);
+        return null;
+    }
+    // }}}
+
+    // {{{ resetFormCounter
+    /**
+     *  フォームヘルパ用、内部フォームカウンタをリセットする
+     *
+     *  @access public
+     */
+    function resetFormCounter()
+    {
+        $this->reset_counter = true;
+    }
+    // }}}
+
+    // {{{ getFormName
+    /**
+     *  指定されたフォーム項目に対応するフォーム名(w/ レンダリング)を取得する
+     *
+     *  @access public
+     */
+    function getFormName($name, $action, $params)
+    {
+        $af =& $this->_getHelperActionForm($action, $name);
+        if ($af === null) {
+            return $name;
+        }
+
+        $def = $af->getDef($name);
+        if ($def === null || isset($def['name']) === false) {
+            return $name;
+        }
+
+        return $def['name'];
+    }
+    // }}}
+
+    // {{{ getFormSubmit
+    /**
+     *  submitボタンを取得する(送信先アクションで受け取るよう
+     *  定義されていないときに、たんにsubmitボタンを作るのに使う)
+     *
+     *  @access public
+     */
+    function getFormSubmit($params)
+    {
+        if (isset($params['type']) === false) {
+            $params['type'] = 'submit';
+        }
+        return $this->_getFormInput_Html('input', $params);
+    }
+    // }}}
+
+    // {{{ getFormInput
+    /**
+     *  指定されたフォーム項目に対応するフォームタグを取得する
+     *
+     *  @access public
+     *  @todo   JavaScript対応
+     */
+    function getFormInput($name, $action, $params)
+    {
+        $af =& $this->_getHelperActionForm($action, $name);
+        if ($af === null) {
+            return '';
+        }
+
+        $def = $af->getDef($name);
+        if ($def === null) {
+            return '';
+        }
+
+        if (isset($def['form_type']) === false) {
+            $def['form_type'] = FORM_TYPE_TEXT;
+        }
+
+        // 配列フォームが何回呼ばれたかを保存するカウンタ
+        if (isset($def['type']) && is_array($def['type'])) {
+            static $form_counter = array();
+            if ($this->reset_counter) {
+                $form_counter = array();
+                $this->reset_counter = false;
+            }
+
+            if (isset($form_counter[$action]) === false) {
+                $form_counter[$action] = array();
+            }
+            if (isset($form_counter[$action][$name]) === false) {
+                $form_counter[$action][$name] = 0;
+            }
+            $def['_form_counter'] = $form_counter[$action][$name]++;
+        }
+
+        switch ($def['form_type']) {
+        case FORM_TYPE_BUTTON:
+            $input = $this->_getFormInput_Button($name, $def, $params);
+            break;
+
+        case FORM_TYPE_CHECKBOX:
+            $def['option'] = $this->_getSelectorOptions($af, $def, $params);
+            $input = $this->_getFormInput_Checkbox($name, $def, $params);
+            break;
+
+        case FORM_TYPE_FILE:
+            $input = $this->_getFormInput_File($name, $def, $params);
+            break;
+
+        case FORM_TYPE_HIDDEN:
+            $input = $this->_getFormInput_Hidden($name, $def, $params);
+            break;
+
+        case FORM_TYPE_PASSWORD:
+            $input = $this->_getFormInput_Password($name, $def, $params);
+            break;
+
+        case FORM_TYPE_RADIO:
+            $def['option'] = $this->_getSelectorOptions($af, $def, $params);
+            $input = $this->_getFormInput_Radio($name, $def, $params);
+            break;
+
+        case FORM_TYPE_SELECT:
+            $def['option'] = $this->_getSelectorOptions($af, $def, $params);
+            $input = $this->_getFormInput_Select($name, $def, $params);
+            break;
+
+        case FORM_TYPE_SUBMIT:
+            $input = $this->_getFormInput_Submit($name, $def, $params);
+            break;
+
+        case FORM_TYPE_TEXTAREA:
+            $input = $this->_getFormInput_Textarea($name, $def, $params);
+            break;
+
+        case FORM_TYPE_TEXT:
+        default:
+            $input = $this->_getFormInput_Text($name, $def, $params);
+            break;
+        }
+
+        return $input;
+    }
+    // }}}
+
+    // {{{ getFormBlock
+    /**
+     *  フォームタグを取得する(type="form")
+     *
+     *  @access protected
+     */
+    function getFormBlock($content, $params)
+    {
+        // method
+        if (isset($params['method']) === false) {
+            $params['method'] = 'post';
+        }
+
+        return $this->_getFormInput_Html('form', $params, $content, false);
+    }
+    // }}}
+
+    // {{{ _getSelectorOptions
+    /**
+     *  select, radio, checkbox の選択肢を取得する
+     *
+     *  @access protected
+     */
+    function _getSelectorOptions(&$af, $def, $params)
+    {
+        // $params, $def の順で調べる
+        $source = null;
+        if (isset($params['option'])) {
+            $source = $params['option'];
+        } else if (isset($def['option'])) {
+            $source = $def['option'];
+        }
+
+        // 未定義 or 定義済みの場合はそのまま
+        if ($source === null) {
+            return null;
+        } else if (is_array($source)) {
+            return $source;
+        }
+        
+        // 選択肢を取得
+        $options = null;
+        $split = array_map("trim", explode(',', $source));
+        if (count($split) === 1) {
+            // アクションフォームから取得
+            $method_or_property = $split[0];
+            if (method_exists($af, $method_or_property)) {
+                $options = $af->$method_or_property();
+            } else {
+                $options = $af->$method_or_property;
+            }
+        } else {
+            // マネージャから取得
+            $mgr =& $this->backend->getManager($split[0]);
+            $attr_list = $mgr->getAttrList($split[1]);
+            if (is_array($attr_list)) {
+                foreach ($attr_list as $key => $val) {
+                    $options[$key] = $val['name'];
+                }
+            }
+        }
+
+        if (is_array($options) === false) {
+            $this->logger->log(LOG_WARNING,
+                'selector option is not valid. [actionform=%s, option=%s]',
+                get_class($af), $source);
+            return null;
+        }
+
+        return $options;
+    }
+    // }}}
+
+    // {{{ _getFormInput_Button
+    /**
+     *  フォームタグを取得する(type="button")
+     *
+     *  @access protected
+     */
+    function _getFormInput_Button($name, $def, $params)
+    {
+        $params['type'] = 'button';
+        
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+        if (isset($params['value']) === false) {
+            if (isset($def['name'])) {
+                $params['value'] = $def['name'];
+            }
+        }
+        if (isset($params['value']) && is_array($params['value'])) {
+            $params['value'] = $params['value'][0];
+        }
+
+        return $this->_getFormInput_Html('input', $params);
+    }
+    // }}}
+
+    // {{{ _getFormInput_Checkbox
+    /**
+     *  チェックボックスタグを取得する(type="check")
+     *
+     *  @access protected
+     */
+    function _getFormInput_Checkbox($name, $def, $params)
+    {
+        $params['type'] = 'checkbox';
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+
+        // オプションの一覧(alist)を取得
+        if (isset($def['option']) && is_array($def['option'])) {
+            $options = $def['option'];
+        } else {
+            $options = array();
+        }
+
+        // default値の設定
+        if (isset($params['default'])) {
+            $current_value = $params['default'];
+        } else if (isset($def['default'])) {
+            $current_value = $def['default'];
+        } else {
+            $current_value = array();
+        }
+        $current_value = array_map('strval', to_array($current_value));
+
+        // タグのセパレータ
+        if (isset($params['separator'])) {
+            $separator = $params['separator'];
+        } else {
+            $separator = "\n";
+        }
+
+        $ret = array();
+        $i = 1;
+        foreach ($options as $key => $value) {
+            $params['value'] = $key;
+            $params['id'] = $name . '_' . $i++;
+
+            // checked
+            if (in_array((string) $key, $current_value, true)) {
+                $params['checked'] = 'checked';
+            } else {
+                unset($params['checked']);
+            }
+
+            // <input type="checkbox" />
+            $input_tag = $this->_getFormInput_Html('input', $params);
+
+            // <label for="id">..</label>
+            $ret[] = $this->_getFormInput_Html('label', array('for' => $params['id']),
+                                               $input_tag . $value, false);
+        }
+
+        return implode($separator, $ret);
+    }
+    // }}}
+
+    // {{{ _getFormInput_File
+    /**
+     *  フォームタグを取得する(type="file")
+     *
+     *  @access protected
+     */
+    function _getFormInput_File($name, $def, $params)
+    {
+        $params['type'] = 'file';
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+        $params['value'] = '';
+
+        return $this->_getFormInput_Html('input', $params);
+    }
+    // }}}
+
+    // {{{ _getFormInput_Hidden
+    /**
+     *  フォームタグを取得する(type="hidden")
+     *
+     *  @access protected
+     */
+    function _getFormInput_Hidden($name, $def, $params)
+    {
+        $params['type'] = 'hidden';
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+
+        // value
+        $value = '';
+        if (isset($params['value'])) {
+            $value = $params['value'];
+        } else if (isset($params['default'])) {
+            $value = $params['default'];
+        } else if (isset($def['default'])) {
+            $value = $def['default'];
+        }
+        if (is_array($value)) {
+            if ($def['_form_counter'] < count($value)) {
+                $params['value'] = $value[$def['_form_counter']];
+            } else {
+                $params['value'] = '';
+            }
+        } else {
+            $params['value'] = $value;
+        }
+
+        return $this->_getFormInput_Html('input', $params);
+    }
+    // }}}
+
+    // {{{ _getFormInput_Password
+    /**
+     *  フォームタグを取得する(type="password")
+     *
+     *  @access protected
+     */
+    function _getFormInput_Password($name, $def, $params)
+    {
+        $params['type'] = 'password';
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+
+        // value
+        $value = '';
+        if (isset($params['value'])) {
+            $value = $params['value'];
+        } else if (isset($params['default'])) {
+            $value = $params['default'];
+        } else if (isset($def['default'])) {
+            $value = $def['default'];
+        }
+        if (is_array($value)) {
+            if ($def['_form_counter'] < count($value)) {
+                $params['value'] = $value[$def['_form_counter']];
+            } else {
+                $params['value'] = '';
+            }
+        } else {
+            $params['value'] = $value;
+        }
+
+        // maxlength
+        if (isset($def['max']) && $def['max']) {
+            $params['maxlength'] = $def['max'];
+        }
+
+        return $this->_getFormInput_Html('input', $params);
+    }
+    // }}}
+
+    // {{{ _getFormInput_Radio
+    /**
+     *  ラジオボタンタグを取得する(type="radio")
+     *
+     *  @access protected
+     */
+    function _getFormInput_Radio($name, $def, $params)
+    {
+        $params['type'] = 'radio';
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+
+        // オプションの一覧(alist)を取得
+        if (isset($def['option']) && is_array($def['option'])) {
+            $options = $def['option'];
+        } else {
+            $options = array();
+        }
+
+        // default値の設定
+        if (isset($params['default'])) {
+            $current_value = $params['default'];
+        } else if (isset($def['default'])) {
+            $current_value = $def['default'];
+        } else {
+            $current_value = null;
+        }
+
+        // タグのセパレータ
+        if (isset($params['separator'])) {
+            $separator = $params['separator'];
+        } else {
+            $separator = "\n";
+        }
+
+        $ret = array();
+        $i = 1;
+        foreach ($options as $key => $value) {
+            $params['value'] = $key;
+            $params['id'] = $name . '_' . $i++;
+
+            // checked
+            if (strcmp($current_value,$key) === 0) {
+                $params['checked'] = 'checked';
+            } else {
+                unset($params['checked']);
+            }
+
+            // <input type="radio" />
+            $input_tag = $this->_getFormInput_Html('input', $params);
+
+            // <label for="id">..</label>
+            $ret[] = $this->_getFormInput_Html('label', array('for' => $params['id']),
+                                               $input_tag . $value, false);
+        }
+
+        return implode($separator, $ret);
+    }
+    // }}}
+
+    // {{{ _getFormInput_Select
+    /**
+     *  セレクトボックスタグを取得する(type="select")
+     *
+     *  @access protected
+     */
+    function _getFormInput_Select($name, $def, $params)
+    {
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+
+        // オプションの一覧(alist)を取得
+        if (isset($def['option']) && is_array($def['option'])) {
+            $options = $def['option'];
+        } else {
+            $options = array();
+        }
+
+        // default値の設定
+        if (isset($params['default'])) {
+            $current_value = $params['default'];
+        } else if (isset($def['default'])) {
+            $current_value = $def['default'];
+        } else {
+            $current_value = array();
+        }
+        $current_value = array_map('strval', to_array($current_value));
+
+        // タグのセパレータ
+        if (isset($params['separator'])) {
+            $separator = $params['separator'];
+        } else {
+            $separator = "\n";
+        }
+
+        // selectタグの中身を作る
+        $contents = array();
+        $selected = false;
+        foreach ($options as $key => $value) {
+            $attr = array('value' => $key);
+            $def['_form_counter'] = empty($def['_form_counter']) ? 0 : $def['_form_counter'];
+            if (isset($params['multiple']) &&
+                    in_array((string)$key, $current_value, true) ||
+               !isset($params['multiple']) && $selected === false &&
+                    strcmp($current_value[$def['_form_counter']], $key) === 0) {
+                $attr['selected'] = 'selected';
+                $selected = true;
+            }
+            $contents[] = $this->_getFormInput_Html('option', $attr, $value);
+        }
+
+        // 空エントリ
+        if (isset($params['emptyoption'])) {
+            $attr = array('value' => '');
+            if ($selected === false) {
+                $attr['selected'] = 'selected';
+            }
+            array_unshift($contents,
+                          $this->_getFormInput_Html('option',
+                                                    $attr,
+                                                    $params['emptyoption']));
+            unset($params['emptyoption']);
+        }
+
+        $element = $separator . implode($separator, $contents) . $separator;
+        return $this->_getFormInput_Html('select', $params, $element, false);
+    }
+    // }}}
+
+    // {{{ _getFormInput_Submit
+    /**
+     *  フォームタグを取得する(type="submit")
+     *
+     *  @access protected
+     */
+    function _getFormInput_Submit($name, $def, $params)
+    {
+        $params['type'] = 'submit';
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+        if (isset($params['value']) === false) {
+            if (isset($def['name'])) {
+                $params['value'] = $def['name'];
+            }
+        }
+        if (is_array($params['value'])) {
+            $params['value'] = $params['value'][0];
+        }
+
+        return $this->_getFormInput_Html('input', $params);
+    }
+    // }}}
+
+    // {{{ _getFormInput_Textarea
+    /**
+     *  フォームタグを取得する(textarea)
+     *
+     *  @access protected
+     */
+    function _getFormInput_Textarea($name, $def, $params)
+    {
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+
+        // element
+        $element = '';
+        if (isset($params['value'])) {
+            $element = $params['value'];
+            unset($params['value']);
+        } else if (isset($params['default'])) {
+            $element = $params['default'];
+        } else if (isset($def['default'])) {
+            $element = $def['default'];
+        }
+        if (is_array($element)) {
+            if ($def['_form_counter'] < count($element)) {
+                $element = $element[$def['_form_counter']];
+            } else {
+                $element = '';
+            }
+        } else {
+            $params['value'] = $element;
+        }
+
+        return $this->_getFormInput_Html('textarea', $params, $element);
+    }
+    // }}}
+
+    // {{{ _getFormInput_Text
+    /**
+     *  フォームタグを取得する(type="text")
+     *
+     *  @access protected
+     */
+    function _getFormInput_Text($name, $def, $params)
+    {
+        // type
+        $params['type'] = 'text';
+
+        // name
+        if (isset($def['type'])) {
+            $params['name'] = is_array($def['type']) ? $name . '[]' : $name;
+        } else {
+            $params['name'] = $name;
+        }
+
+        // value
+        $value = '';
+        if (isset($params['value'])) {
+            $value = $params['value'];
+        } else if (isset($params['default'])) {
+            $value = $params['default'];
+        } else if (isset($def['default'])) {
+            $value = $def['default'];
+        }
+        if (is_array($value)) {
+            if ($def['_form_counter'] < count($value)) {
+                $params['value'] = $value[$def['_form_counter']];
+            } else {
+                $params['value'] = '';
+            }
+        } else {
+            $params['value'] = $value;
+        }
+
+        // maxlength
+        if (isset($def['max']) && $def['max']) {
+            $params['maxlength'] = $def['max'];
+        }
+
+        return $this->_getFormInput_Html('input', $params);
+    }
+    // }}}
+
+    // {{{ _getFormInput_Html
+    /**
+     *  HTMLタグを取得する
+     *
+     *  @access protected
+     */
+    function _getFormInput_Html($tag, $attr, $element = null, $escape_element = true)
+    {
+        // 不要なパラメータは消す
+        foreach ($this->helper_parameter_keys as $key) {
+            unset($attr[$key]);
+        }
+
+        $r = "<$tag";
+
+        foreach ($attr as $key => $value) {
+            if ($value === null) {
+                $r .= sprintf(' %s', $key);
+            } else {
+                $r .= sprintf(' %s="%s"', $key, htmlspecialchars($value, ENT_QUOTES));
+            }
+        }
+
+        if ($element === null) {
+            $r .= ' />';
+        } else if ($escape_element) {
+            $r .= sprintf('>%s</%s>', htmlspecialchars($element, ENT_QUOTES), $tag);
+        } else {
+            $r .= sprintf('>%s</%s>', $element, $tag);
+        }
+
+        return $r;
+    }
+    // }}}
+
+    // {{{ _getRenderer
+    /**
+     *  レンダラオブジェクトを取得する
+     *
+     *  @access protected
+     *  @return object  Ethna_Renderer  レンダラオブジェクト
+     */
+    function &_getRenderer()
+    {
+        $c =& $this->backend->getController();
+        $renderer =& $c->getRenderer();
+
+        $form_array =& $this->af->getArray();
+        $app_array =& $this->af->getAppArray();
+        $app_ne_array =& $this->af->getAppNEArray();
+        $renderer->setPropByRef('form', $form_array);
+        $renderer->setPropByRef('app', $app_array);
+        $renderer->setPropByRef('app_ne', $app_ne_array);
+        $message_list = Ethna_Util::escapeHtml($this->ae->getMessageList());
+        $renderer->setPropByRef('errors', $message_list);
+        if (isset($_SESSION)) {
+            $tmp_session = Ethna_Util::escapeHtml($_SESSION);
+            $renderer->setPropByRef('session', $tmp_session);
+        }
+        $renderer->setProp('script',
+            htmlspecialchars(basename($_SERVER['SCRIPT_NAME']), ENT_QUOTES));
+        $renderer->setProp('request_uri',
+            isset($_SERVER['REQUEST_URI'])
+            ? htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES)
+            : '');
+        $renderer->setProp('config', $this->config->get());
+
+        return $renderer;
+    }
+    // }}}
+
+    // {{{ _setDefault
+    /**
+     *  共通値を設定する
+     *
+     *  @access protected
+     *  @param  object  Ethna_Renderer  レンダラオブジェクト
+     */
+    function _setDefault(&$renderer)
+    {
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Cachemanager/Ethna_Plugin_Cachemanager_Localfile.php b/Idea_Plugin_Extlib/class/Plugin/Cachemanager/Ethna_Plugin_Cachemanager_Localfile.php
new file mode 100644 (file)
index 0000000..baf0427
--- /dev/null
@@ -0,0 +1,282 @@
+<?php
+// vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
+/**
+ *  Ethna_Plugin_Cachemanager_Localfile.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/**
+ *  キャッシュマネージャクラス(ローカルファイルキャッシュ版)
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Cachemanager_Localfile extends Ethna_Plugin_Cachemanager
+{
+    /**#@+  @access private */
+
+    /**#@-*/
+
+    /**
+     *  キャッシュに設定された値を取得する
+     *
+     *  キャッシュに値が設定されている場合はキャッシュ値
+     *  が戻り値となる。キャッシュに値が無い場合やlifetime
+     *  を過ぎている場合、エラーが発生した場合はEthna_Error
+     *  オブジェクトが戻り値となる。
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  int     $lifetime   キャッシュ有効期間
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return array   キャッシュ値
+     */
+    function get($key, $lifetime = null, $namespace = null)
+    {
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+        $cache_file = $this->_getCacheFile($namespace, $key);
+
+        // ライフタイムチェック
+        clearstatcache();
+        if (is_readable($cache_file) === false
+            || ($st = stat($cache_file)) === false) {
+            return Ethna::raiseError('fopen failed', E_CACHE_NO_VALUE);
+        }
+        if (is_null($lifetime) == false) {
+            if (($st[9]+$lifetime) < time()) {
+                return Ethna::raiseError('fopen failed', E_CACHE_EXPIRED);
+            }
+        }
+
+        $fp = fopen($cache_file, "r");
+        if ($fp == false) {
+            return Ethna::raiseError('fopen failed', E_CACHE_NO_VALUE);
+        }
+        // ロック
+        $timeout = 3;
+        while ($timeout > 0) {
+            $r = flock($fp, LOCK_EX|LOCK_NB);
+            if ($r) {
+                break;
+            }
+            $timeout--;
+            sleep(1);
+        }
+        if ($timeout <= 0) {
+            fclose($fp);
+            return Ethna::raiseError('fopen failed', E_CACHE_GENERAL);
+        }
+
+        $n = 0;
+        while ($st[7] == 0) {
+            clearstatcache();
+            $st = stat($cache_file);
+            usleep(1000*1);
+            $n++;
+            if ($n > 5) {
+                break;
+            }
+        }
+
+        if ($st == false || $n > 5) {
+            fclose($fp);
+            return Ethna::raiseError('stat failed', E_CACHE_NO_VALUE);
+        }
+        $value = fread($fp, $st[7]);
+        fclose($fp);
+
+        return unserialize($value);
+    }
+
+    /**
+     *  キャッシュの最終更新日時を取得する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return int     最終更新日時(unixtime)
+     */
+    function getLastModified($key, $namespace = null)
+    {
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+        $cache_file = $this->_getCacheFile($namespace, $key);
+
+        clearstatcache();
+        if (is_readable($cache_file) === false
+            || ($st = stat($cache_file)) === false) {
+            return Ethna::raiseError('fopen failed', E_CACHE_NO_VALUE);
+        }
+        return $st[9];
+    }
+
+    /**
+     *  値がキャッシュされているかどうかを取得する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  int     $lifetime   キャッシュ有効期間
+     *  @param  string  $namespace  キャッシュネームスペース
+     */
+    function isCached($key, $lifetime = null, $namespace = null)
+    {
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+        $cache_file = $this->_getCacheFile($namespace, $key);
+
+        // ライフタイムチェック
+        clearstatcache();
+        if (is_readable($cache_file) === false
+            || ($st = stat($cache_file)) === false) {
+            return false;
+        }
+        if (is_null($lifetime) == false) {
+            if (($st[9]+$lifetime) < time()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     *  キャッシュに値を設定する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  mixed   $value      キャッシュ値
+     *  @param  int     $timestamp  キャッシュ最終更新時刻(unixtime)
+     *  @param  string  $namespace  キャッシュネームスペース
+     */
+    function set($key, $value, $timestamp = null, $namespace = null)
+    {
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+        $dir = $this->_getCacheDir($namespace, $key);
+
+        // キャッシュディレクトリチェック
+        $r = Ethna_Util::mkdir($dir, 0777);
+        if ($r == false && is_dir($dir) == false) {
+            return Ethna::raiseError('mkdir(%s) failed', E_USER_WARNING, $dir);
+        }
+
+        $cache_file = $this->_getCacheFile($namespace, $key);
+        $fp = fopen($cache_file, "a+");
+        if ($fp == false) {
+            return Ethna::raiseError('fopen failed', E_CACHE_GENERAL);
+        }
+
+        // ロック
+        $timeout = 3;
+        while ($timeout > 0) {
+            $r = flock($fp, LOCK_EX|LOCK_NB);
+            if ($r) {
+                break;
+            }
+            $timeout--;
+            sleep(1);
+        }
+        if ($timeout <= 0) {
+            fclose($fp);
+            return Ethna::raiseError('fopen failed', E_CACHE_GENERAL);
+        }
+        rewind($fp);
+        ftruncate($fp, 0);
+        fwrite($fp, serialize($value));
+        fclose($fp);
+        Ethna_Util::chmod($cache_file, 0666);
+
+        if (is_null($timestamp)) {
+            // this could suppress warning
+            touch($cache_file);
+        } else {
+            touch($cache_file, $timestamp);
+        }
+
+        return 0;
+    }
+
+    /**
+     *  キャッシュ値を削除する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  string  $namespace  キャッシュネームスペース
+     */
+    function clear($key, $namespace = null)
+    {
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+        $cache_file = $this->_getCacheFile($namespace, $key);
+
+        if (file_exists($cache_file)) {
+            unlink($cache_file);
+        }
+    }
+
+    /**
+     *  キャッシュ対象ディレクトリを取得する
+     *
+     *  @access private
+     */
+    function _getCacheDir($namespace, $key)
+    {
+        $safe_mode = ini_get('safe_mode');
+        if ($safe_mode) {
+            return sprintf("%s", $this->backend->getTmpdir());
+        }
+
+        $len = strlen($key);
+        // intentionally avoid using -2 or -4
+        $dir1 = substr($key, $len-4, 2);
+        if ($len-4 < 0 || strlen($dir1) < 2) {
+            $dir1 = "__dir1";
+        }
+        $dir2 = substr($key, $len-2, 2);
+        if ($len-2 < 0 || strlen($dir2) < 2) {
+            $dir2 = "__dir2";
+        }
+
+        $map = $this->config->get('cachemanager_localfile');
+        $tmp_key = $namespace . "::" . $key;
+        // PHP依存:)
+        $dir = "default";
+
+        if (is_array($map)) {
+            foreach ($map as $key => $value) {
+                if (strncmp($key, $tmp_key, strlen($key)) == 0) {
+                    $dir = $value;
+                    break;
+                }
+            }
+        }
+        return sprintf("%s/cache/%s/cache_%s/%s/%s", $this->backend->getTmpdir(), $dir, $this->_escape($namespace), $this->_escape($dir1), $this->_escape($dir2));
+    }
+
+    /**
+     *  キャッシュファイルを取得する
+     *
+     *  @access private
+     */
+    function _getCacheFile($namespace, $key)
+    {
+        $safe_mode = ini_get('safe_mode');
+        if ($safe_mode) {
+            return sprintf("%s/cache_%s_%s", $this->_getCacheDir($namespace, $key), $this->_escape($namespace), $this->_escape($key));
+        }
+
+        return sprintf("%s/%s", $this->_getCacheDir($namespace, $key), $this->_escape($key));
+    }
+
+    /**
+     *  キーをファイルシステム用にエスケープする
+     *
+     *  @access private
+     */
+    function _escape($string)
+    {
+        return preg_replace('/([^0-9A-Za-z_])/e', "sprintf('%%%02X', ord('\$1'))", $string);
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Cachemanager/Ethna_Plugin_Cachemanager_Memcache.php b/Idea_Plugin_Extlib/class/Plugin/Cachemanager/Ethna_Plugin_Cachemanager_Memcache.php
new file mode 100644 (file)
index 0000000..0620530
--- /dev/null
@@ -0,0 +1,356 @@
+<?php
+// vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
+/**
+ *  Ethna_Plugin_Cachemanager_Memcache.php
+ *
+ *  - Point Cutしたいと思った!
+ *  - キャッシュキーには250文字までしか使用できないので注意して下さい
+ *
+ *  @todo   ネームスペース/キャッシュキー長のエラーハンドリング
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/**
+ *  キャッシュマネージャクラス(memcache版)
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Cachemanager_Memcache extends Ethna_Plugin_Cachemanager
+{
+    /**#@+  @access private */
+
+    /** @var    object  MemCache    MemCacheオブジェクト */
+    var $memcache = null;
+
+    /** @var bool 圧縮フラグ */
+    var $compress = true;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_Plugin_Cachemanager_Memcacheクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_Plugin_Cachemanager_Memcache(&$controller)
+    {
+        parent::Ethna_Plugin_Cachemanager($controller);
+        $this->memcache_pool = array();
+    }
+
+    /**
+     *  memcacheキャッシュオブジェクトを生成、取得する
+     *
+     *  @access protected
+     */
+    function _getMemcache($cache_key, $namespace = null)
+    {
+        $retry = $this->config->get('memcache_retry');
+        if ($retry == "") {
+            $retry = 3;
+        }
+        $timeout = $this->config->get('memcache_timeout');
+        if ($timeout == "") {
+            $timeout = 3;
+        }
+        $r = false;
+
+        list($host, $port) = $this->_getMemcacheInfo($cache_key, $namespace);
+        if (isset($this->memcache_pool["$host:$port"])) {
+            // activate
+            $this->memcache = $this->memcache_pool["$host:$port"];
+            return $this->memcache;
+        }
+        $this->memcache_pool["$host:$port"] =& new MemCache();
+
+        while ($retry > 0) {
+            if ($this->config->get('memcache_use_pconnect')) {
+                $r = $this->memcache_pool["$host:$port"]->pconnect($host, $port, $timeout);
+            } else {
+                $r = $this->memcache_pool["$host:$port"]->connect($host, $port, $timeout);
+            }
+            if ($r) {
+                break;
+            }
+            sleep(1);
+            $retry--;
+        }
+        if ($r == false) {
+            trigger_error("memcache: connection failed");
+            $this->memcache_pool["$host:$port"] = null;
+        }
+
+        $this->memcache = $this->memcache_pool["$host:$port"];
+        return $this->memcache;
+    }
+
+    /**
+     *  memcache接続情報を取得する
+     *
+     *  @access protected
+     *  @todo   $cache_keyから$indexを決める方法を変更できるようにする
+     */
+    function _getMemcacheInfo($cache_key, $namespace)
+    {
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+
+        $memcache_info = $this->config->get('memcache');
+        $default_memcache_host = $this->config->get('memcache_host');
+        if ($default_memcache_host == "") {
+            $default_memcache_host = "localhost";
+        }
+        $default_memcache_port = $this->config->get('memcache_port');
+        if ($default_memcache_port == "") {
+            $default_memcache_port = 11211;
+        }
+        if ($memcache_info == null || isset($memcache_info[$namespace]) == false) {
+            return array($default_memcache_host, $default_memcache_port);
+        }
+
+        // namespace/cache_keyで接続先を決定
+        $n = count($memcache_info[$namespace]);
+
+        $index = $cache_key % $n;
+        return array(
+            isset($memcache_info[$namespace][$index]['memcache_host']) ?
+                $memcache_info[$namespace][$index]['memcache_host'] :
+                'localhost',
+            isset($memcache_info[$namespace][$index]['memcache_port']) ?
+                $memcache_info[$namespace][$index]['memcache_port'] :
+                11211,
+        );
+
+        // for safe
+        return array($default_memcache_host, $default_memcache_port);
+    }
+
+    /**
+     *  キャッシュに設定された値を取得する
+     *
+     *  キャッシュに値が設定されている場合はキャッシュ値
+     *  が戻り値となる。キャッシュに値が無い場合やlifetime
+     *  を過ぎている場合、エラーが発生した場合はEthna_Error
+     *  オブジェクトが戻り値となる。
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  int     $lifetime   キャッシュ有効期間
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return array   キャッシュ値
+     */
+    function get($key, $lifetime = null, $namespace = null)
+    {
+        $this->_getMemcache($key, $namespace);
+        if ($this->memcache == null) {
+            return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
+        }
+
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+
+        $cache_key = $this->_getCacheKey($namespace, $key);
+        if ($cache_key == null) {
+            return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
+        }
+
+        $value = $this->memcache->get($cache_key);
+        if ($value == null) {
+            return Ethna::raiseError('no such cache', E_CACHE_NO_VALUE);
+        }
+        $time = $value['time'];
+        $data = $value['data'];
+
+        // ライフタイムチェック
+        if (is_null($lifetime) == false) {
+            if (($time+$lifetime) < time()) {
+                return Ethna::raiseError('lifetime expired', E_CACHE_EXPIRED);
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     *  キャッシュの最終更新日時を取得する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return int     最終更新日時(unixtime)
+     */
+    function getLastModified($key, $namespace = null)
+    {
+        $this->_getMemcache($key, $namespace);
+        if ($this->memcache == null) {
+            return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
+        }
+
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+
+        $cache_key = $this->_getCacheKey($namespace, $key);
+        if ($cache_key == null) {
+            return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
+        }
+
+        $value = $this->memcache->get($cache_key);
+
+        return $value['time'];
+    }
+
+    /**
+     *  値がキャッシュされているかどうかを取得する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  int     $lifetime   キャッシュ有効期間
+     *  @param  string  $namespace  キャッシュネームスペース
+     */
+    function isCached($key, $lifetime = null, $namespace = null)
+    {
+        $r = $this->get($key, $lifetime, $namespace);
+
+        return Ethna::isError($r) ? false: true;
+    }
+
+    /**
+     *  キャッシュに値を設定する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  mixed   $value      キャッシュ値
+     *  @param  int     $timestamp  キャッシュ最終更新時刻(unixtime)
+     *  @param  string  $namespace  キャッシュネームスペース
+     */
+    function set($key, $value, $timestamp = null, $namespace = null)
+    {
+        $this->_getMemcache($key, $namespace);
+        if ($this->memcache == null) {
+            return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
+        }
+
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+
+        $cache_key = $this->_getCacheKey($namespace, $key);
+        if ($cache_key == null) {
+            return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
+        }
+
+        $time = $timestamp ? $timestamp : time();
+        $this->memcache->set($cache_key, array('time' => $time, 'data' => $value), $this->compress ? MEMCACHE_COMPRESSED : null);
+    }
+
+    /**
+     *  キャッシュ値を削除する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  string  $namespace  キャッシュネームスペース
+     */
+    function clear($key, $namespace = null)
+    {
+        $this->_getMemcache($key, $namespace);
+        if ($this->memcache == null) {
+            return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
+        }
+
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+
+        $cache_key = $this->_getCacheKey($namespace, $key);
+        if ($cache_key == null) {
+            return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
+        }
+
+        $this->memcache->delete($cache_key, -1);
+    }
+
+    /**
+     *  キャッシュデータをロックする
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  int     $timeout    ロックタイムアウト
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return bool    true:成功 false:失敗
+     */
+    function lock($key, $timeout = 5, $namespace = null)
+    {
+        $this->_getMemcache($key, $namespace);
+        if ($this->memcache == null) {
+            return Ethna::raiseError('memcache server not available', E_CACHE_LOCK_ERROR);
+        }
+
+        // ロック用キャッシュデータを利用する
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+        $cache_key = "lock::" . $this->_getCacheKey($namespace, $key);
+        $lock_lifetime = 30;
+
+        do {
+            $r = $this->memcache->add($cache_key, true, false, $lock_lifetime);
+            if ($r != false) {
+                break;
+            }
+            sleep(1);
+            $timeout--;
+        } while ($timeout > 0);
+
+        if ($r == false) {
+            return Ethna::raiseError('lock timeout', E_CACHE_LOCK_TIMEOUT);
+        }
+
+        return true;
+    }
+
+    /**
+     *  キャッシュデータのロックを解除する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return bool    true:成功 false:失敗
+     */
+    function unlock($key, $namespace = null)
+    {
+        $this->_getMemcache($key, $namespace);
+        if ($this->memcache == null) {
+            return Ethna::raiseError('memcache server not available', E_CACHE_LOCK_ERROR);
+        }
+
+        $namespace = is_null($namespace) ? $this->namespace : $namespace;
+        $cache_key = "lock::" . $this->_getCacheKey($namespace, $key);
+
+        $this->memcache->delete($cache_key, -1);
+    }
+
+    /**
+     *  ネームスペースからキャッシュキーを生成する
+     *
+     *  @access private
+     */
+    function _getCacheKey($namespace, $key)
+    {
+        // 少し乱暴だけど...
+        $key = str_replace(":", "_", $key);
+        $cache_key = $namespace . "::" . $key;
+        if (strlen($cache_key) > 250) {
+            return null;
+        }
+        return $cache_key;
+    }
+
+    /**
+     * 圧縮フラグを立てる
+     *
+     * MySQLなどいくつかの子クラスで有効
+     * 
+     * @access public
+     * @param bool $flag フラグ
+     */
+    function setCompress($flag) {
+        $this->compress = $flag;
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Csrf/Ethna_Plugin_Csrf_Session.php b/Idea_Plugin_Extlib/class/Plugin/Csrf/Ethna_Plugin_Csrf_Session.php
new file mode 100644 (file)
index 0000000..93bfd51
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Csrf_Session.php
+ *
+ *  @author     Keita Arai <cocoiti@comio.info>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Csrf_Session
+/**
+ *  CSRF対策
+ *
+ *  CSRF対策をトークンを用いて対策するためのコード
+ *
+ *  @author     Keita Arai <cocoiti@comio.info>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Csrf_Session extends Ethna_Plugin_Csrf
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Session    セッションオブジェクト */
+    var $session;
+    
+    /**#@-*/
+
+
+    /**
+     *  Ethna_Plugin_Csrfのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     */
+    function Ethna_Plugin_Csrf_Session(&$controller)
+    {
+        parent::Ethna_Plugin_Csrf($controller);
+
+        // オブジェクトの設定
+        $this->session =& $this->controller->getSession();
+    }
+    
+    /**
+     *  トークンをViewとローカルファイルにセットする
+     *
+     *  @access public
+     *  @return boolean  成功か失敗か
+     */
+    function set()
+    {
+        if (! $this->session->isStart()) {
+            $this->session->start();
+        }
+
+        $token = $this->session->get($this->token_name);
+        if ($token !== null) {
+            return true;
+        }
+
+        $key = $this->_generateKey();
+        $this->session->set($this->token_name, $key); 
+
+        return true;       
+    }
+
+    /**
+     *  トークンIDを取得する
+     *
+     *  @access public
+     *  @return string トークンIDを返す。
+     */
+    function get()
+    {
+        if (! $this->session->isStart()) {
+            $this->session->start();
+        }
+        
+        return $this->session->get($this->token_name);
+    }
+
+    /**
+     *  トークンIDを削除する
+     *
+     *  @access public
+     */
+    function remove()
+    {
+        if (! $this->session->isStart()) {
+            $this->session->start();
+        }
+        $this->session->remove($this->token_name);        
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Cachemanager.php b/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Cachemanager.php
new file mode 100644 (file)
index 0000000..1d86bbd
--- /dev/null
@@ -0,0 +1,173 @@
+<?php
+// vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
+/**
+ *  Ethna_Plugin_Cachemanager.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/**
+ *  キャッシュマネージャプラグインクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Cachemanager
+{
+    /**#@+  @access private */
+
+    /** @var    string  現在のネームスペース */
+    var $namespace = '';
+
+    /** @var    object  Ethna_Backend       backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Config        設定オブジェクト    */
+    var $config;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_Plugin_Cachemanagerクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_Plugin_Cachemanager(&$controller)
+    {
+        $this->controller =& $controller;
+        $this->backend =& $this->controller->getBackend();
+        $this->config =& $this->controller->getConfig();
+    }
+
+    /**
+     *  キャッシュネームスペースを取得する
+     *
+     *  @access public
+     *  @return string  現在のキャッシュネームスペース
+     */
+    function getNamespace($namespace)
+    {
+        return $this->namespace;
+    }
+
+    /**
+     *  キャッシュネームスペースを設定する
+     *
+     *  @access public
+     *  @param  string  $namespace  ネームスペース
+     */
+    function setNamespace($namespace)
+    {
+        $this->namespace = $namespace;
+    }
+
+    /**
+     *  キャッシュに設定された値を取得する
+     *
+     *  キャッシュに値が設定されている場合はキャッシュ値
+     *  が戻り値となる。キャッシュに値が無い場合やlifetime
+     *  を過ぎている場合、エラーが発生した場合はEthna_Error
+     *  オブジェクトが戻り値となる。
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  int     $lifetime   キャッシュ有効期間
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return mixed   キャッシュ値
+     */
+    function get($key, $lifetime = null, $namespace = null)
+    {
+    }
+
+    /**
+     *  キャッシュの最終更新日時を取得する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return int     最終更新日時(unixtime)
+     */
+    function getLastModified($key, $namespace = null)
+    {
+    }
+
+    /**
+     *  キャッシュに値を設定する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  mixed   $value      キャッシュ値
+     *  @param  int     $timestamp  キャッシュ最終更新時刻(unixtime)
+     *  @param  string  $namespace  キャッシュネームスペース
+     */
+    function set($key, $value, $timestamp = null, $namespace = null)
+    {
+    }
+
+    /**
+     *  値がキャッシュされているかどうかを取得する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  int     $lifetime   キャッシュ有効期間
+     *  @param  string  $namespace  キャッシュネームスペース
+     */
+    function isCached($key, $timestamp = null, $namespace = null)
+    {
+    }
+
+    /**
+     *  キャッシュから値を削除する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  string  $namespace  キャッシュネームスペース
+     */
+    function clear($key, $namespace = null)
+    {
+    }
+
+    /**
+     *  キャッシュデータをロックする
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  int     $timeout    ロックタイムアウト
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return bool    true:成功 false:失敗
+     */
+    function lock($key, $timeout = 5, $namespace = null)
+    {
+        return false;
+    }
+
+    /**
+     *  キャッシュデータのロックを解除する
+     *
+     *  @access public
+     *  @param  string  $key        キャッシュキー
+     *  @param  string  $namespace  キャッシュネームスペース
+     *  @return bool    true:成功 false:失敗
+     */
+    function unlock($key, $namespace = null)
+    {
+        return false;
+    }
+
+    /**
+     * 圧縮フラグを立てる
+     *
+     * MySQLなどいくつかの子クラスで有効
+     * 
+     * @access public
+     * @param bool $flag フラグ
+     */
+    function setCompress($flag) {
+        return false;
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Csrf.php b/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Csrf.php
new file mode 100644 (file)
index 0000000..2cbcba3
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Csrf.php
+ *
+ *  @author     Keita Arai <cocoiti@comio.info>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Csrf
+/**
+ *  CSRF対策基底クラス
+ *
+ *  CSRF対策をトークンを用いて対策するためのコード
+ *
+ *  @author     Keita Arai <cocoiti@comio.info>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Csrf
+{
+    /**#@+
+     *  @access private
+     */
+
+    var $controller;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    object  Ethna_Config        設定オブジェクト */
+    var $config;
+
+    /** @var    object  Ethna_Logger        ログオブジェクト */
+    var $logger;
+    
+    /** @var    string  共有トークン名 */
+    var $token_name = 'ethna_csrf';
+    
+    /**#@-*/
+
+
+    /**
+     *  Ethna_Plugin_Csrfのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     */
+    function Ethna_Plugin_Csrf(&$controller)
+    {
+        // オブジェクトの設定
+        $this->controller =& $controller;
+        $this->ctl =& $this->controller;
+
+        $this->config =& $controller->getConfig();
+        $this->logger =& $this->controller->getLogger();
+    }
+    
+    /**
+     *  トークンをViewとローカルファイルにセットする
+     *
+     *  @access public
+     *  @return string  トークンのKey
+     */
+    function set()
+    {
+
+    }
+
+    /**
+     *  トークンIDを取得する
+     *
+     *  @access public
+     *  @return string トークンIDを返す。
+     */
+    function get()
+    {
+
+    }
+
+    /**
+     *  トークンIDを削除する
+     *
+     *  @access public
+     *  @return string トークンIDを返す。
+     */
+    function remove()
+    {
+
+    }
+
+    /**
+     *  トークン名を取得する
+     *
+     *  @access public
+     *  @return string トークン名を返す。
+     */
+    function getName()
+    {
+        return $this->token_name;
+    }
+
+    /**
+     *  トークンIDを検証する
+     *
+     *  @access public
+     *  @return mixed  正常の場合はtrue, 不正の場合はfalse
+     */
+    function isValid()
+    {
+        $token = $this->_get_token();
+
+        $local_token = $this->get();
+
+        if (is_null($local_token)) {
+            return false;
+        }
+
+        if ($token === $local_token) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     *  キーを生成する
+     *
+     *  @access public
+     *  @return string  keyname
+     */
+    function _generateKey()
+    {
+        return Ethna_Util::getRandom(32);
+    }
+
+    /**
+     *  リクエストからトークンIDとリクエストIDを抜き出す
+     *
+     *  @access public
+     *  @return mixed  正常の場合はトークン名, 不正の場合はfalse
+     */
+    function _get_token()
+    {
+        $token_name = $this->getName();
+        if (strcasecmp($_SERVER['REQUEST_METHOD'], 'post') === 0) {
+            return isset($_POST[$token_name]) ? $_POST[$token_name] : null;
+        } else {
+            return isset($_GET[$token_name]) ? $_GET[$token_name] : null;
+        }
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Filter.php b/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Filter.php
new file mode 100644 (file)
index 0000000..2fa74a5
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Filter.php
+ *
+ *  @author     Kazuhiro Hosoi <hosoi@gree.co.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Filter
+/**
+ *  プラグインフィルタ基底クラス
+ *
+ *  Plugin実装により,Ethna_Filterの後継として,
+ *  Ethna_Plugin_Filterに追加しました.基本的にEthna_Filterと同じです.
+ *  
+ *  Mojaviの真似です(きっぱり)。アクション実行前に各種処理を行うことが
+ *  出来ます。
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Filter
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    object  Ethna_Config        設定オブジェクト */
+    var $config;
+
+    /** @var    object  Ethna_Logger        ログオブジェクト */
+    var $logger;
+
+    /**#@-*/
+
+
+    /**
+     *  Ethna_Plugin_Filterのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
+     */
+    function Ethna_Plugin_Filter(&$controller)
+    {
+        // オブジェクトの設定
+        $this->controller =& $controller;
+        $this->ctl =& $this->controller;
+
+        $this->config =& $controller->getConfig();
+        $this->logger =& $this->controller->getLogger();
+    }
+
+    /**
+     *  実行前フィルタ
+     *
+     *  @access public
+     *  @return Ethna_Error:実行中止 any:正常終了
+     */
+    function preFilter()
+    {
+    }
+
+    /**
+     *  アクション実行前フィルタ
+     *
+     *  @access public
+     *  @param  string  $action_name    実行されるアクション名
+     *  @return string  null:正常終了 (string):実行するアクション名を変更
+     */
+    function preActionFilter($action_name)
+    {
+        return null;
+    }
+
+    /**
+     *  アクション実行後フィルタ
+     *
+     *  @access public
+     *  @param  string  $action_name    実行されたアクション名
+     *  @param  string  $forward_name   実行されたアクションからの戻り値
+     *  @return string  null:正常終了 (string):遷移名を変更
+     */
+    function postActionFilter($action_name, $forward_name)
+    {
+        return null;
+    }
+
+    /**
+     *  実行後フィルタ
+     *
+     *  @access public
+     *  @return Ethna_Error:実行中止 any:正常終了
+     */
+    function postFilter()
+    {
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Generator.php b/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Generator.php
new file mode 100644 (file)
index 0000000..766ada9
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator
+/**
+ *  スケルトン生成プラグイン
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator
+{
+    /** @var    object  Ethna_Controller    スケルトン生成に使うコントローラ */
+    var $ctl;
+
+    /**
+     *  コンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_Plugin_Generator(&$controller, $type, $name)
+    {
+        // Ethna_Generatorでpluginを取得するときに使ったコントローラ
+        // ex, add-projectではEthna_Controller, app-actionではApp_Controller
+        $this->ctl =& $controller;
+    }
+
+    /**
+     *  スケルトンファイルの絶対パスを解決する
+     *
+     *  @access private
+     *  @param  string  $skel   スケルトンファイル
+     */
+    function _resolveSkelfile($skel)
+    {
+        $file = realpath($skel);
+        if (file_exists($file)) {
+            return $file;
+        }
+
+        // アプリの skel ディレクトリ
+        $base = $this->ctl->getBasedir();
+        $file = "$base/skel/$skel";
+        if (file_exists($file)) {
+            return $file;
+        }
+
+        // Ethna本体の skel ディレクトリ
+        $base = dirname(dirname(dirname(__FILE__)));
+        $file = "$base/skel/$skel";
+        if (file_exists($file)) {
+            return $file;
+        }
+
+        return false;
+    }
+
+    /**
+     *  スケルトンファイルにマクロを適用してファイルを生成する
+     *
+     *  @access private
+     *  @param  string  $skel       スケルトンファイル
+     *  @param  string  $entity     生成ファイル名
+     *  @param  array   $macro      置換マクロ
+     *  @param  bool    $overwrite  上書きフラグ
+     *  @return bool    true:正常終了 false:エラー
+     */
+    function _generateFile($skel, $entity, $macro, $overwrite = false)
+    {
+        if (file_exists($entity)) {
+            if ($overwrite === false) {
+                printf("file [%s] already exists -> skip\n", $entity);
+                return true;
+            } else {
+                printf("file [%s] already exists, to be overwriten.\n", $entity);
+            }
+        }
+
+        $resolved = $this->_resolveSkelfile($skel);
+        if ($resolved === false) {
+            printf("skelton file [%s] not found.\n", $skel);
+            return false;
+        } else {
+            $skel = $resolved;
+        }
+
+        $rfp = fopen($skel, "r");
+        if ($rfp == null) {
+            return false;
+        }
+        $wfp = fopen($entity, "w");
+        if ($wfp == null) {
+            fclose($rfp);
+            return false;
+        }
+
+        for (;;) {
+            $s = fread($rfp, 4096);
+            if (strlen($s) == 0) {
+                break;
+            }
+
+            foreach ($macro as $k => $v) {
+                $s = preg_replace("/{\\\$$k}/", $v, $s);
+            }
+            fwrite($wfp, $s);
+        }
+
+        fclose($wfp);
+        fclose($rfp);
+
+        $st = stat($skel);
+        if (chmod($entity, $st[2]) == false) {
+            return false;
+        }
+
+        printf("file generated [%s -> %s]\n", $skel, $entity);
+
+        return true;
+    }
+
+    /**
+     *  ユーザ定義のマクロを設定する(~/.ethna)
+     *
+     *  @access private
+     */
+    function _getUserMacro()
+    {
+        if (isset($_SERVER['USERPROFILE']) && is_dir($_SERVER['USERPROFILE'])) {
+            $home = $_SERVER['USERPROFILE'];
+        } else {
+            $home = $_SERVER['HOME'];
+        }
+
+        if (is_file("$home/.ethna") == false) {
+            return array();
+        }
+
+        $user_macro = parse_ini_file("$home/.ethna");
+        return $user_macro;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Handle.php b/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Handle.php
new file mode 100644 (file)
index 0000000..7595710
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_Getopt.php';
+
+// {{{ Ethna_Plugin_Handle
+/**
+ *  コマンドラインハンドラプラグインの基底クラス
+ *  
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle
+{
+    /** @var    handler's id */
+    var $id;
+
+    /** @var    command line arguments */
+    var $arg_list;
+
+    /**
+     *  Ethna_Handle constructor (stub for php4)
+     *
+     *  @access public
+     */
+    function Ethna_Plugin_Handle(&$controller, $type, $name)
+    {
+        $id = $name;
+        $id = preg_replace('/^([A-Z])/e', "strtolower('\$1')", $id);
+        $id = preg_replace('/([A-Z])/e', "'-' . strtolower('\$1')", $id);
+        $this->id = $id;
+    }
+
+    /**
+     *  get handler-id
+     *
+     *  @access public
+     */
+    function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return "description of " . $this->id;
+    }
+
+    /**
+     *  get handler's usage
+     *
+     *  @access public
+     */
+    function getUsage()
+    {
+        return "usage of " . $this->id;
+    }
+
+    /**
+     *  set arguments
+     *
+     *  @access public
+     */
+    function setArgList($arg_list)
+    {
+        $this->arg_list = $arg_list;
+    }
+
+    /**
+     * easy getopt :)
+     *
+     * @param   array   $lopts  long options
+     * @return  array   list($opts, $args)
+     * @access  protected
+     */
+    function &_getopt($lopts = array())
+    {
+        // create opts
+        // ex: $lopts = array('foo', 'bar=');
+        $lopts = to_array($lopts);
+        $sopts = '';
+        $opt_def = array();
+        foreach ($lopts as $lopt) {
+            if ($lopt{strlen($lopt) - 2} === '=') {
+                $opt_def[$lopt{0}] = substr($lopt, 0, strlen($lopt) - 2);
+                $sopts .= $lopt{0} . '::';
+            } else if ($lopt{strlen($lopt) - 1} === '=') {
+                $opt_def[$lopt{0}] = substr($lopt, 0, strlen($lopt) - 1);
+                $sopts .= $lopt{0} . ':';
+            } else {
+                $opt_def[$lopt{0}] = $lopt;
+                $sopts .= $lopt{0};
+            }
+        }
+
+        // do getopt
+        // ex: $sopts = 'fb:';
+        $opt = new Ethna_Getopt();
+        $opts_args = $opt->getopt($this->arg_list, $sopts, $lopts);
+        if (Ethna::isError($opts_args)) {
+            return $opts_args;
+        }
+
+        // parse opts
+        // ex: "-ff --bar=baz" gets
+        //      $opts = array('foo' => array(true, true),
+        //                    'bar' => array('baz'));
+        $opts = array();
+        foreach ($opts_args[0] as $opt) {
+            $opt[0] = $opt[0]{0} === '-' ? $opt_def[$opt[0]{2}] : $opt_def[$opt[0]{0}];
+            $opt[1] = $opt[1] === null ? true : $opt[1];
+            if (isset($opts[$opt[0]]) === false) {
+                $opts[$opt[0]] = array($opt[1]);
+            } else {
+                $opts[$opt[0]][] = $opt[1];
+            }
+        }
+        $opts_args[0] = $opts;
+
+        return $opts_args;
+    }
+
+    /**
+     *  just perform
+     *
+     *  @access public
+     */
+    function perform()
+    {
+    }
+
+    /**
+     *  show usage
+     *
+     *  @access public
+     */
+    function usage()
+    {
+        echo "usage:\n";
+        echo $this->getUsage() . "\n\n";
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Logwriter.php b/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Logwriter.php
new file mode 100644 (file)
index 0000000..8ebb9d3
--- /dev/null
@@ -0,0 +1,207 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Logwriter.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Logwriter
+/**
+ *  ログ出力基底クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Logwriter
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    string  ログアイデンティティ文字列 */
+    var $ident;
+
+    /** @var    int     ログファシリティ */
+    var $facility;
+
+    /** @var    int     ログオプション */
+    var $option;
+
+    /** @var    bool    バックトレースが取得可能かどうか */
+    var $have_backtrace;
+
+    /** @var    array   ログレベル名テーブル */
+    var $level_name_table = array(
+        LOG_EMERG   => 'EMERG',
+        LOG_ALERT   => 'ALERT',
+        LOG_CRIT    => 'CRIT',
+        LOG_ERR     => 'ERR',
+        LOG_WARNING => 'WARNING',
+        LOG_NOTICE  => 'NOTICE',
+        LOG_INFO    => 'INFO',
+        LOG_DEBUG   => 'DEBUG',
+    );
+
+    /**#@-*/
+
+    /**
+     *  Ethna_Plugin_Logwriterクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  string  $log_ident      ログアイデンティティ文字列(プロセス名等)
+     *  @param  int     $log_facility   ログファシリティ
+     *  @param  string  $log_file       ログ出力先ファイル名(LOG_FILEオプションが指定されている場合のみ)
+     *  @param  int     $log_option     ログオプション(LOG_FILE,LOG_FUNCTION...)
+     */
+    function Ethna_Plugin_Logwriter()
+    {
+    }
+
+    /**
+     *  ログオプションを設定する
+     *
+     *  @access public
+     *  @param  int     $option     ログオプション(LOG_FILE,LOG_FUNCTION...)
+     */
+    function setOption($option)
+    {
+        $this->ident = $option['ident'];
+        $this->facility = $option['facility'];
+        $this->option = $option;
+        $this->have_backtrace = function_exists('debug_backtrace');
+    }
+
+    /**
+     *  ログ出力を開始する
+     *
+     *  @access public
+     */
+    function begin()
+    {
+    }
+
+    /**
+     *  ログを出力する
+     *
+     *  @access public
+     *  @param  int     $level      ログレベル(LOG_DEBUG, LOG_NOTICE...)
+     *  @param  string  $message    ログメッセージ(+引数)
+     */
+    function log($level, $message)
+    {
+    }
+
+    /**
+     *  ログ出力を終了する
+     *
+     *  @access public
+     */
+    function end()
+    {
+    }
+
+    /**
+     *  ログアイデンティティ文字列を取得する
+     *
+     *  @access public
+     *  @return string  ログアイデンティティ文字列
+     */
+    function getIdent()
+    {
+        return $this->ident;
+    }
+
+    /**
+     *  ログレベルを表示文字列に変換する
+     *
+     *  @access private
+     *  @param  int     $level  ログレベル(LOG_DEBUG,LOG_NOTICE...)
+     *  @return string  ログレベル表示文字列(LOG_DEBUG→"DEBUG")
+     */
+    function _getLogLevelName($level)
+    {
+        if (isset($this->level_name_table[$level]) == false) {
+            return null;
+        }
+        return $this->level_name_table[$level];
+    }
+
+    /**
+     *  ログ出力箇所の情報(関数名/ファイル名等)を取得する
+     *
+     *  @access private
+     *  @return array   ログ出力箇所の情報
+     */
+    function _getBacktrace()
+    {
+        $skip_method_list = array(
+            array('ethna', 'raise'),
+            array(null, 'raiseerror'),
+            array(null, 'handleerror'),
+            array('ethna_logger', null),
+            array('ethna_plugin_logwriter', null),
+            array('ethna_error', null),
+            array('ethna_apperror', null),
+            array('ethna_actionerror', null),
+            array('ethna_backend', 'log'),
+            array(null, 'ethna_error_handler'),
+            array(null, 'trigger_error'),
+        );
+
+        if ($this->have_backtrace == false) {
+            return null;
+        }
+
+        $bt = debug_backtrace();
+        $i = 0;
+        while ($i < count($bt)) {
+            if (isset($bt[$i]['class']) == false) {
+                $bt[$i]['class'] = null;
+            }
+            $skip = false;
+
+            // メソッドスキップ処理
+            foreach ($skip_method_list as $method) {
+                $class = $function = true;
+                if ($method[0] != null) {
+                    $class = preg_match("/^$method[0]/i", $bt[$i]['class']);
+                }
+                if ($method[1] != null) {
+                    $function = preg_match("/^$method[1]/i", $bt[$i]['function']);
+                }
+                if ($class && $function) {
+                    $skip = true;
+                    break;
+                }
+            }
+
+            if ($skip) {
+                $i++;
+            } else {
+                break;
+            }
+        }
+
+        $c =& Ethna_Controller::getInstance();
+        $basedir = $c->getBasedir();
+
+        $function = sprintf("%s.%s", isset($bt[$i]['class']) ? $bt[$i]['class'] : 'global', $bt[$i]['function']);
+
+        $file = $bt[$i]['file'];
+        if (strncmp($file, $basedir, strlen($basedir)) == 0) {
+            $file = substr($file, strlen($basedir));
+        }
+        if (strncmp($file, ETHNA_BASE, strlen(ETHNA_BASE)) == 0) {
+            $file = preg_replace('#^/+#', '', substr($file, strlen(ETHNA_BASE)));
+        }
+        $line = $bt[$i]['line'];
+        return array('function' => $function, 'pos' => sprintf('%s:%s', $file, $line));
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Urlhandler.php b/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Urlhandler.php
new file mode 100644 (file)
index 0000000..b46396e
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+// vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
+/**
+ *  Ethna_Plugin_Urlhandler.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Urlhandler
+/**
+ *  Urlhandlerプラグインの基底クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Urlhandler
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Backend   backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Logger    ログオブジェクト */
+    var $logger;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_Plugin_Urlhandlerクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    $controller コントローラオブジェクト
+     */
+    function Ethna_Plugin_Urlhandler(&$controller)
+    {
+        $this->backend =& $controller->getBackend();
+        $this->logger =& $controller->getLogger();
+    }
+
+    /**
+     *  アクションをユーザリクエストに変換する
+     *
+     *  @param string $action
+     *  @param array $param
+     *  @access public
+     *  @return array array($path_string, $path_key)
+     */
+    function actionToRequest($action, $param)
+    {
+        die('override!');
+    }
+
+    /**
+     *  ユーザリクエストをアクションに変換する
+     *
+     *  @param array $http_vars
+     *  @access public
+     *  @return array $http_vars with 'action_foobar' => 'true' element.
+     */
+    function requestToAction($http_vars)
+    {
+        die('override!');
+    }
+
+    /**
+     *  アクションをリクエストパラメータに変換する
+     *
+     *  @access public
+     *  @param array $http_vars
+     *  @param string $action
+     *  @return $http_vars with 'action_foobar' element.
+     */
+    function buildActionParameter($http_vars, $action)
+    {
+        if ($action == "") {
+            return $http_vars;
+        }
+        $key = sprintf('action_%s', $action);
+        $http_vars[$key] = 'true';
+        return $http_vars;
+    }
+
+    /**
+     *  パラメータをURLに変換する
+     *
+     *  @access public
+     *  @param array $query query list 
+     *  @return string query string
+     */
+    function buildQueryParameter($query)
+    {
+        $param = '';
+
+        foreach ($query as $key => $value) {
+            if (is_array($value)) {
+                foreach ($value as $k => $v) {
+                    if (is_numeric($k)) {
+                        $k = '';
+                    }
+                    $param .= sprintf('%s=%s&',
+                                      urlencode(sprintf('%s[%s]', $key, $k)),
+                                      urlencode($v));
+                }
+            } else if (is_null($value) == false) {
+                $param .= sprintf('%s=%s&', urlencode($key), urlencode($value));
+            }
+        }
+
+        return substr($param, 0, -1);
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Validator.php b/Idea_Plugin_Extlib/class/Plugin/Ethna_Plugin_Validator.php
new file mode 100644 (file)
index 0000000..34d43da
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// UPLOAD_ERR_* が未定義の場合 (PHP 4.3.0 以前)
+if (defined('UPLOAD_ERR_OK') == false) {
+    define('UPLOAD_ERR_OK', 0);
+}
+
+// {{{ Ethna_Plugin_Validator
+/**
+ *  バリデータプラグインの基底クラス
+ *  
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Backend   backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Logger    ログオブジェクト */
+    var $logger;
+
+    /** @var    object  Ethna_ActionForm    フォームオブジェクト */
+    var $action_form;
+
+    /** @var    object  Ethna_ActionForm    フォームオブジェクト */
+    var $af;
+
+    /** @var    bool    配列を受け取るバリデータかどうかのフラグ */
+    var $accept_array = false;
+
+    /**#@-*/
+
+    /**
+     *  コンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_Controller    $controller コントローラオブジェクト
+     */
+    function Ethna_Plugin_Validator(&$controller)
+    {
+        $this->backend =& $controller->getBackend();
+        $this->logger =& $controller->getLogger();
+        $this->action_form =& $controller->getActionForm();
+        $this->af =& $this->action_form;
+    }
+
+    /**
+     *  フォーム値検証のためにActionFormから呼び出されるメソッド
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        die('override!');
+    }
+
+    /**
+     *  フォーム定義を取得する
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     */
+    function getFormDef($name)
+    {
+        return $this->af->getDef($name);
+    }
+
+    /**
+     *  フォームのtypeを取得する(配列の場合は値のみ)
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     */
+    function getFormType($name)
+    {
+        $def = $this->af->getDef($name);
+        if (isset($def['type'])) {
+            if (is_array($def['type'])) {
+                return $def['type'][0];
+            } else {
+                return $def['type'];
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     *  フォーム値が空かどうかを判定 (配列フォームの場合は各要素に対して呼び出す)
+     *
+     *  @access protected
+     *  @param  mixed   $var       フォームの値 (配列フォームの場合は各要素)
+     *  @param  int     $type      フォームのtype
+     */
+    function isEmpty($var, $type)
+    {
+        if ($type == VAR_TYPE_FILE) {
+            if (isset($var['error']) == false || $var['error'] != UPLOAD_ERR_OK) {
+                return true;
+            }
+            if (isset($var['tmp_name']) == false || is_uploaded_file($var['tmp_name']) == false) {
+                return true;
+            }
+            if (isset($var['size']) == false || $var['size'] == 0) {
+                return true;
+            }
+        } else {
+            if (is_scalar($var) == false || strlen($var) == 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     *  true を参照で返す
+     *
+     *  @access protected
+     */
+    function &ok()
+    {
+        $true = true;
+        return $true;
+    }
+
+    /**
+     *  エラーを返す
+     *
+     *  @access protected
+     *  @param  string  $msg        エラーメッセージ
+     *  @param  int     $code       エラーコード
+     *  @param  mixed   $info       エラーメッセージにsprintfで渡すパラメータ
+     */
+    function &error($msg, $code, $info = null)
+    {
+        if ($info != null) {
+            if (is_array($info)) {
+                return Ethna::raiseNotice($msg, $code, $info);
+            } else {
+                return Ethna::raiseNotice($msg, $code, array($info));
+            }
+        } else {
+            return Ethna::raiseNotice($msg, $code);
+        }
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Action.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Action.php
new file mode 100644 (file)
index 0000000..568c2a7
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_Action.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_Action
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_Action extends Ethna_Plugin_Generator
+{
+    /**
+     *  アクションのスケルトンを生成する
+     *
+     *  @access public
+     *  @param  string  $action_name    アクション名
+     *  @param  string  $skelton        スケルトンファイル名
+     *  @param  int     $gateway        ゲートウェイ
+     *  @return true|Ethna_Error        true:成功 Ethna_Error:失敗
+     */
+    function &generate($action_name, $skelton = null, $gateway = GATEWAY_WWW)
+    {
+        $action_dir = $this->ctl->getActiondir($gateway);
+        $action_class = $this->ctl->getDefaultActionClass($action_name, $gateway);
+        $action_form = $this->ctl->getDefaultFormClass($action_name, $gateway);
+        $action_path = $this->ctl->getDefaultActionPath($action_name);
+
+        // entity
+        $entity = $action_dir . $action_path;
+        Ethna_Util::mkdir(dirname($entity), 0755);
+
+        // skelton
+        if ($skelton === null) {
+            switch ($gateway) {
+            case GATEWAY_WWW:
+                $skelton = "skel.action.php";
+                break;
+            case GATEWAY_CLI:
+                $skelton = "skel.action_cli.php";
+                break;
+            case GATEWAY_XMLRPC:
+                $skelton = "skel.action_xmlrpc.php";
+                break;
+            default:
+                $err = Ethna::raiseError('unknown gateway.');
+                return $err;
+            }
+        }
+
+        // macro
+        $macro = array();
+        $macro['project_id'] = $this->ctl->getAppId();
+        $macro['action_name'] = $action_name;
+        $macro['action_class'] = $action_class;
+        $macro['action_form'] = $action_form;
+        $macro['action_path'] = $action_path;
+
+        // user macro
+        $user_macro = $this->_getUserMacro();
+        $macro = array_merge($macro, $user_macro);
+
+
+        // generate
+        if (file_exists($entity)) {
+            printf("file [%s] already exists -> skip\n", $entity);
+        } else if ($this->_generateFile($skelton, $entity, $macro) == false) {
+            printf("[warning] file creation failed [%s]\n", $entity);
+        } else {
+            printf("action script(s) successfully created [%s]\n", $entity);
+        }
+
+        $true = true;
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_ActionTest.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_ActionTest.php
new file mode 100644 (file)
index 0000000..4813c45
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_ActionTest.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_ActionTest
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_ActionTest extends Ethna_Plugin_Generator
+{
+    /**
+     *  アクション用テストのスケルトンを生成する
+     *  (現在のところ GATEWAY_WWW のみ対応)
+     *
+     *  @access public
+     *  @param  string  $action_name    アクション名
+     *  @param  string  $skelton        スケルトンファイル名
+     *  @param  int     $gateway        ゲートウェイ
+     *  @return true|Ethna_Error        true:成功 Ethna_Error:失敗
+     */
+    function &generate($action_name, $skelton = null, $gateway = GATEWAY_WWW)
+    {
+        $action_dir = $this->ctl->getActiondir($gateway);
+        $action_class = $this->ctl->getDefaultActionClass($action_name, $gateway);
+        $action_form = $this->ctl->getDefaultFormClass($action_name, $gateway);
+        $action_path = $this->ctl->getDefaultActionPath($action_name . 'Test');
+
+        // entity
+        $entity = $action_dir . $action_path;
+        Ethna_Util::mkdir(dirname($entity), 0755);
+
+        // skelton
+        if ($skelton === null) {
+            $skelton = 'skel.action_test.php';
+        }
+
+        // macro
+        $macro = array();
+        $macro['project_id'] = $this->ctl->getAppId();
+        $macro['action_name'] = $action_name;
+        $macro['action_class'] = $action_class;
+        $macro['action_form'] = $action_form;
+        $macro['action_path'] = $action_path;
+
+        // user macro
+        $user_macro = $this->_getUserMacro();
+        $macro = array_merge($macro, $user_macro);
+
+        // original action script existence check.
+        $original_action_path = $this->ctl->getDefaultActionPath($action_name);
+        $original_action_entity = $action_dir . $original_action_path;
+        if (!file_exists($original_action_entity)) {
+            printf("\n");
+            printf("[!!!!warning!!!!] original action script was not found.\n");
+            printf("[!!!!warning!!!!] You must generate it by the following command :\n");
+            printf("[!!!!warning!!!!] ethna add-action %s\n\n", $action_name);
+        } 
+
+        // generate
+        if (file_exists($entity)) {
+            printf("file [%s] already exists -> skip\n", $entity);
+        } else if ($this->_generateFile($skelton, $entity, $macro) == false) {
+            printf("[warning] file creation failed [%s]\n", $entity);
+        } else {
+            printf("action test(s) successfully created [%s]\n", $entity);
+        }
+
+        $true = true;
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_AppManager.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_AppManager.php
new file mode 100644 (file)
index 0000000..bb5ac85
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_AppManager.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_AppManager
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_AppManager extends Ethna_Plugin_Generator
+{
+    /**
+     *  アプリケーションマネージャのスケルトンを生成する
+     *
+     *  @access public
+     *  @param  string  $manager_name    アプリケーションマネージ名
+     *  @return bool    true:成功 false:失敗
+     */
+    function generate($manager_name)
+    {
+        $class_name = $this->ctl->getManagerClassName($manager_name);
+        $app_dir = $this->ctl->getDirectory('app');
+        $app_path = "${class_name}.php";
+
+        $macro = array();
+        $macro['project_id'] = $this->ctl->getAppId();
+        $macro['app_path'] = $app_path;
+        $macro['app_manager'] = $class_name;
+
+        $user_macro = $this->_getUserMacro();
+        $macro = array_merge($macro, $user_macro);
+
+        $path = "$app_dir/$app_path";
+        Ethna_Util::mkdir(dirname($path), 0755);
+        if (file_exists($path)) {
+            printf("file [%s] already exists -> skip\n", $path);
+        } else if ($this->_generateFile("skel.app_manager.php", $path, $macro) == false) {
+            printf("[warning] file creation failed [%s]\n", $path);
+        } else {
+            printf("app-manager script(s) successfully created [%s]\n", $path);
+        }
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_AppObject.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_AppObject.php
new file mode 100644 (file)
index 0000000..3bb15d1
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_AppObject.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_AppObject
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_AppObject extends Ethna_Plugin_Generator
+{
+    /**
+     *  アプリケーションオブジェクトのスケルトンを生成する
+     *
+     *  @access public
+     *  @param  string  $table_name     テーブル名
+     *  @return bool    true:成功 false:失敗
+     */
+    function generate($table_name)
+    {
+        $table_id = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($table_name));
+
+        $app_dir = $this->ctl->getDirectory('app');
+        $app_path = ucfirst($this->ctl->getAppId()) . '_' . $table_id .'.php';
+
+        $macro = array();
+        $macro['project_id'] = $this->ctl->getAppId();
+        $macro['app_path'] = $app_path;
+        $macro['app_object'] = ucfirst($this->ctl->getAppId()) . '_' . $table_id;
+
+        $user_macro = $this->_getUserMacro();
+        $macro = array_merge($macro, $user_macro);
+
+        $path = "$app_dir/$app_path";
+        Ethna_Util::mkdir(dirname($path), 0755);
+        if (file_exists($path)) {
+            printf("file [%s] already exists -> skip\n", $path);
+        } else if ($this->_generateFile("skel.app_object.php", $path, $macro) == false) {
+            printf("[warning] file creation failed [%s]\n", $path);
+        } else {
+            printf("app-object script(s) successfully created [%s]\n", $path);
+        }
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_EntryPoint.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_EntryPoint.php
new file mode 100644 (file)
index 0000000..a9be4ff
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_EntryPoint.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_EntryPoint
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_EntryPoint extends Ethna_Plugin_Generator
+{
+    /**
+     *  エントリポイントのスケルトンを生成する
+     *
+     *  @access public
+     *  @param  string  $skelton    スケルトンファイル名
+     *  @param  int     $gateway    ゲートウェイ
+     *  @return true|Ethna_Error    true:成功 Ethna_Error:失敗
+     */
+    function &generate($action_name, $skelton = null, $gateway = GATEWAY_WWW)
+    {
+        $true = true;
+
+        // entity
+        switch ($gateway) {
+        case GATEWAY_WWW:
+            $entity = sprintf("%s/%s.%s", $this->ctl->getDirectory('www'),
+                              $action_name, $this->ctl->getExt('php'));
+            break;
+        case GATEWAY_CLI:
+            $entity = sprintf("%s/%s.%s", $this->ctl->getDirectory('bin'),
+                              $action_name, $this->ctl->getExt('php'));
+            break;
+        default:
+            $ret =& Ethna::raiseError(
+                'add-entry-point accepts only GATEWAY_WWW or GATEWAY_CLI.');
+            return $ret;
+        }
+
+        // skelton
+        if ($skelton === null) {
+            switch ($gateway) {
+            case GATEWAY_WWW:
+                $skelton = 'skel.entry_www.php';
+                break;
+            case GATEWAY_CLI:
+                $skelton = 'skel.entry_cli.php';
+                break;
+            }
+        }
+        if (file_exists($entity)) {
+            printf("file [%s] already exists -> skip\n", $entity);
+            return $true;
+        }
+
+        // macro
+        $macro = array();
+        $macro['project_id'] = $this->ctl->getAppId();
+        $macro['action_name'] = $action_name;
+        $macro['dir_app'] = $this->ctl->getDirectory('app');
+
+        // user macro
+        $user_macro = $this->_getUserMacro();
+        $macro = array_merge($macro, $user_macro);
+
+        // generate
+        $ret = $this->_generateFile($skelton, $entity, $macro);
+        if ($ret) {
+            printf("action script(s) successfully created [%s]\n", $entity);
+        } else {
+            printf("[warning] file creation failed [%s]\n", $entity);
+            return $true; // XXX: error handling
+        }
+
+        // chmod
+        if ($gateway === GATEWAY_CLI) {
+            // is needed?
+            //$ret = Ethna_Util::chmod($entity, 0777);
+        }
+            
+
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_I18n.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_I18n.php
new file mode 100644 (file)
index 0000000..4316955
--- /dev/null
@@ -0,0 +1,554 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_I18n.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com> 
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_I18n
+/**
+ *  i18n 向け、メッセージカタログ生成クラスのスーパークラス
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com> 
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_I18n extends Ethna_Plugin_Generator
+{
+    /**#@+
+     *  @access protected 
+     */
+
+    /** @var    array  解析済みトークン  */ 
+    var $tokens = array();
+
+    /** @var    string   ロケール名  */ 
+    var $locale;
+
+    /** @var    boolean  gettext利用フラグ  */ 
+    var $use_gettext;
+   
+    /** @var    boolean  既存ファイルが存在した場合にtrue */ 
+    var $file_exists;
+
+    /** @var    string   実行時のUnix Time(ファイル名生成用) */ 
+    var $time;
+
+    /**
+     *  プロジェクトのメッセージカタログを生成する
+     *
+     *  @access public
+     *  @param  string  $locale         生成するカタログのロケール
+     *  @param  int     $use_gettext    gettext 使用フラグ
+     *                                  true ならgettext のカタログ生成
+     *                                  false ならEthna組み込みのカタログ生成
+     *  @param  array   $ext_dirs       走査する追加のディレクトリの配列
+     *  @return true|Ethna_Error        true:成功 Ethna_Error:失敗
+     */
+    function &generate($locale, $use_gettext, $ext_dirs = array())
+    {
+        $this->time = time();
+        $this->locale = $locale;
+        $this->use_gettext = $use_gettext;
+
+        $outfile_path = $this->_get_output_file();
+
+        //
+        //  既存ファイルが存在した場合は、以下の動きをする
+        //
+        //  1. Ethna 組み込みのカタログの場合、既存のiniファイル
+        //  の中身を抽出し、既存の翻訳を可能な限りマージする
+        //  2. gettext 利用の場合は、新たにファイルを作らせ、
+        //  既存翻訳とのマージは msgmergeプログラムを使わせる 
+        //
+        if ($this->file_exists) {
+            $msg = ($this->use_gettext)
+                 ? ("[NOTICE]: Message catalog file already exists! "
+                  . "CREATING NEW FILE ...\n"
+                  . "You can run msgmerge program to merge translation.\n"
+                   )
+                 : ("[NOTICE]: Message catalog file already exists!\n"
+                  . "This is overwritten and existing translation is merged automatically.\n");
+             print "\n-------------------------------\n"
+                 . $msg
+                 . "-------------------------------\n\n"; 
+        }
+
+        // app ディレクトリとテンプレートディレクトリを
+        // 再帰的に走査する。ユーザから指定があればそれも走査
+        $app_dir = $this->ctl->getDirectory('app');
+        $template_dir = $this->ctl->getDirectory('template');
+        $scan_dir = array(
+            $app_dir, "${template_dir}/${locale}",
+        );
+        $scan_dir = array_merge($scan_dir, $ext_dirs);
+
+        //  ディレクトリを走査
+        foreach ($scan_dir as $dir) {
+            if (is_dir($dir) === false) {
+                Ethna::raiseNotice("$dir is not Directory.", E_GENERAL);
+                continue;
+            }
+            $r = $this->_analyzeDirectory($dir);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+        } 
+
+        //  解析済みトークンを元に、カタログファイルを生成
+        $r = $this->_generateFile();
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        $true = true;
+        return $true;
+    }
+
+    /**
+     *  出力ファイル名を取得します。 
+     *
+     *  @access private
+     *  @return string  出力ファイル名
+     */
+    function _get_output_file()
+    {
+        $locale_dir = $this->ctl->getDirectory('locale');
+        $ext = ($this->use_gettext) ? 'po' : 'ini';
+        $filename = $this->locale . ".${ext}";
+        $new_filename = NULL;
+
+        $outfile_path = "${locale_dir}/"
+                      . $this->locale
+                      . "/LC_MESSAGES/$filename";
+
+        $this->file_exists = (file_exists($outfile_path));
+        if ($this->file_exists && $this->use_gettext) {
+            $new_filename = $this->locale . '_' . $this->time . ".${ext}";
+            $outfile_path = "${locale_dir}/"
+                          . $this->locale
+                          . "/LC_MESSAGES/$new_filename";
+        }
+
+        return $outfile_path;
+    }
+    /**
+     *  指定されたディレクトリを再帰的に走査します。
+     *
+     *  @access protected
+     *  @param  string  $dir     走査対象ディレクトリ 
+     *  @return true|Ethna_Error true:成功 Ethna_Error:失敗
+     */
+    function _analyzeDirectory($dir)
+    {
+        $dh = opendir($dir);
+        if ($dh == false) {
+            return Ethna::raiseWarning(
+                       "unable to open Directory: $dir", E_GENERAL
+                   );
+        }
+
+        //  走査対象はテンプレートとPHPスクリプト 
+        $php_ext = $this->ctl->getExt('php');
+        $tpl_ext = $this->ctl->getExt('tpl');
+        $r = NULL;
+    
+        //  ディレクトリなら再帰的に走査
+        //  ファイルならトークンを解析する
+        while(($file = readdir($dh)) !== false) {
+            if (is_dir("$dir/$file")) {
+                if (strpos($file, '.') !== 0) {  // 隠しファイルは対象外
+                   $r = $this->_analyzeDirectory("$dir/$file");
+                }
+            } else {
+                if (preg_match("#\.${php_ext}\$#i", $file) > 0) {
+                    $r = $this->_analyzeFile("$dir/$file");
+                }
+                if (preg_match("#\.${tpl_ext}\$#i", $file) > 0) {
+                    $r = $this->_analyzeTemplate("$dir/$file");
+                }
+            }
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+        }
+
+        closedir($dh);
+        return true;
+    }
+
+    /**
+     *  指定されたPHPスクリプトを調べ、メッセージ処理関数の呼び出し
+     *  箇所を取得します。
+     *
+     *  NOTICE: このメソッドは、指定ファイルがPHPスクリプト
+     *          (テンプレートファイル)として正しいものかどう
+     *          かはチェックしません。 
+     *
+     *  @access protected
+     *  @param  string  $file     走査対象ファイル
+     *  @return true|Ethna_Error true:成功 Ethna_Error:失敗
+     */
+    function _analyzeFile($file)
+    {
+        $file_path = realpath($file);
+        printf("Analyzing file ... %s\n", $file);
+
+        //  ファイルを開けないならエラー
+        $fp = @fopen($file_path, 'r');
+        if ($fp === false) {
+            return Ethna::raiseWarning(
+                       "unable to open file: $file", E_GENERAL
+                   );
+        }
+        fclose($fp);
+
+        //  トークンを全て取得。
+        $file_tokens = token_get_all(
+                           file_get_contents($file_path)
+                       );
+        $token_num = count($file_tokens);
+        $in_et_function = false;   
+
+        //  アクションディレクトリは特別扱いするため、それ
+        //  を取得
+        $action_dir = $this->ctl->getActionDir(GATEWAY_WWW);
+
+        //  トークンを走査し、関数呼び出しを解析する
+        for ($i = 0; $i < $token_num; $i++) {
+
+            $token = $file_tokens[$i];
+            $token_idx = false;
+            $token_str = NULL;
+            $token_linenum = false;
+
+            //   面倒を見るのは、トークンの場合のみ
+            //   単純な文字列は読み飛ばす
+            if (is_array($token)) {
+                $token_idx = array_shift($token);
+                $token_str = array_shift($token);
+
+                //  PHP 5.2.2 以降のみ行番号を取得可能
+                //  @see http://www.php.net/token_get_all
+                if (version_compare(PHP_VERSION, '5.2.2') >= 0) {
+                    $token_linenum = array_shift($token);
+                } 
+                //  i18n 呼び出し関数の場合、フラグを立てる
+                if ($token_idx == T_STRING && $token_str == '_et') {
+                    $in_et_function = true;
+                    continue; 
+                }
+                //  i18n 呼び出しの後、定数文字列が来たら、
+                //  それを引数と看做す。PHPの文法的にvalid
+                //  か否かはこのルーチンでは面倒を見ない
+                if ($in_et_function == true
+                 && $token_idx == T_CONSTANT_ENCAPSED_STRING) {
+                    $token_str = substr($token_str, 1);     // 最初のクォートを除く
+                    $token_str = substr($token_str, 0, -1); // 最後のクォートを除く
+                    $this->tokens[$file_path][] = array(
+                                                      'token_str' => $token_str,
+                                                      'linenum' => $token_linenum,
+                                                      'translation' => ''
+                                                  );
+                    $in_et_function = false;
+                    continue;
+                }
+            }
+        }
+
+        //  アクションスクリプト の場合は、
+        //  ActionForm の $form メンバ解析
+        $php_ext = $this->ctl->getExt('php');
+        $action_dir_regex = $action_dir;
+        if (ETHNA_OS_WINDOWS) {
+            $action_dir_regex = str_replace('\\', '\\\\', $action_dir);
+            $action_dir_regex = str_replace('/', '\\\\', $action_dir_regex);
+        }
+        if (preg_match("#$action_dir_regex#", $file_path)
+        && !preg_match("#.*Test\.${php_ext}$#", $file_path)) {
+            $this->_analyzeActionForm($file_path); 
+        }
+
+        //  Ethna組み込みのメッセージカタログであれば翻訳をマージする
+        $this->_mergeEthnaMessageCatalog();
+
+        return true; 
+    }
+
+    /**
+     *  指定されたPHPスクリプトを調べ、メッセージ処理関数の呼び出し
+     *  箇所を取得します。
+     *
+     *  NOTICE: このメソッドは、指定ファイルがPHPスクリプトとして
+     *          正しいものかどうかはチェックしません。 
+     *
+     *  @access protected
+     *  @param  string  $file_path  走査対象ファイル
+     *  @return true|Ethna_Error true:成功 Ethna_Error:失敗
+     */
+    function _analyzeActionForm($file_path)
+    {
+        //   アクションスクリプトのトークンを取得 
+        $tokens = token_get_all(
+                      file_get_contents($file_path)
+                  );
+
+        //   クラスのトークンのみを取り出す
+        $class_names = array();
+        $class_started = false;
+        for ($i = 0; $i < count($tokens); $i++) { 
+            $token = $tokens[$i];
+            if (is_array($token)) {
+                $token_name = array_shift($token);
+                $token_str = array_shift($token);
+                
+                if ($token_name == T_CLASS) {  //  クラス定義開始
+                    $class_started = true;
+                    continue;
+                }
+                //    T_CLASS の直後に来た T_STRING をクラス名と見做す
+                if ($class_started === true && $token_name == T_STRING) {
+                    $class_started = false;
+                    $class_names[] = $token_str;
+                } 
+            }
+        }
+
+        //  アクションフォームのクラス名を特定
+        $af_classname = NULL;
+        foreach ($class_names as $name) {
+            $action_name = $this->ctl->actionFormToName($name);
+            if (!empty($action_name)) {
+                $af_classname = $name;
+                break;
+            }
+        }
+
+        //  特定したクラスをインスタンス化し、フォーム定義を解析する
+        printf("    Analyzing ActionForm class ... %s\n", $af_classname);
+        require_once $file_path;
+        $af = new $af_classname($this->ctl);
+        $form_def = $af->getDef();
+        $translatable_code = array('name', 'required_error', 'type_error', 'min_error',
+                                   'max_error', 'regexp_error'
+                             );
+        foreach ($form_def as $key => $def) {
+            //    対象となるのは name, *_error
+            //    但し、定義されていた場合のみ対象にする
+            //    @see http://ethna.jp/ethna-document-dev_guide-form-message.html 
+            foreach ($translatable_code as $code) {
+                if (array_key_exists($code, $def)) {
+                    $token_str = $def[$code]; 
+                    $this->tokens[$file_path][] = array(
+                                                      'token_str' => $token_str,
+                                                      'linenum' => false, // 行番号は取得しない
+                                                      'translation' => ''
+                                                  );
+                }
+            }
+        } 
+    }
+
+    /**
+     *  指定されたテンプレートファイルを調べ、メッセージ処理関数
+     *  の呼び出し箇所を取得します。
+     *
+     *  @access protected
+     *  @param  string  $file    走査対象ファイル 
+     *  @return true|Ethna_Error true:成功 Ethna_Error:失敗
+     */
+    function _analyzeTemplate($file)
+    {
+        //  デフォルトはSmartyのテンプレートと看做す
+        $renderer =& $this->ctl->getRenderer();
+        $engine =& $renderer->getEngine();
+        $engine_name = get_class($engine);
+        if (strncasecmp('Smarty', $engine_name, 6) !== 0) {
+            return Ethna::raiseError(
+                       "You seems to use template engine other than Smarty ... : $engine_name"
+                   ); 
+        }
+
+        printf("Analyzing Template file ... %s\n", $file);
+
+        //  use smarty internal function :)
+        $compile_path = $engine->_get_compile_path($file);        
+        $compile_result = NULL;
+        if ($engine->_is_compiled($file, $compile_path)
+         || $engine->_compile_resource($file, $compile_path)) {
+            $compile_result = file_get_contents($compile_path);
+        }
+
+        if (empty($compile_result)) {
+            return Ethna::raiseError(
+                       "could not compile template file : $file"
+                   ); 
+        }
+
+        //  コンパイル済みのテンプレートを解析する
+        $tokens = token_get_all($compile_result);
+
+        for ($i = 0; $i < count($tokens); $i++) { 
+            $token = $tokens[$i];
+            if (is_array($token)) {
+                $token_name = array_shift($token);
+                $token_str = array_shift($token);
+                
+                if ($token_name == T_STRING
+                 && strcmp($token_str, 'smarty_modifier_i18n') === 0) {
+                    $i18n_str = $this->_find_template_i18n($tokens, $i);
+                    if (!empty($i18n_str)) {
+                        $i18n_str = substr($i18n_str, 1);     // 最初のクォートを除く
+                        $i18n_str = substr($i18n_str, 0, -1); // 最後のクォートを除く
+                        $this->tokens[$file][] = array(
+                                                      'token_str' => $i18n_str,
+                                                      'linenum' => false,
+                                                      'translation' => '',
+                                                 );
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     *  テンプレートのトークンを逆順に走査し、
+     *  翻訳トークンを取得します。
+     *
+     *  @param $tokens 解析対象トークン
+     *  @param $index  インデックス
+     *  @access private 
+     */
+    function _find_template_i18n($tokens, $index)
+    {
+        for ($j = $index; $j > 0; $j--) {
+            $tmp_token = $tokens[$j];
+
+            if (is_array($tmp_token)) {
+                $tmp_token_name = array_shift($tmp_token);
+                $tmp_token_str = array_shift($tmp_token);
+                if ($tmp_token_name == T_CONSTANT_ENCAPSED_STRING 
+                 && !preg_match('#^["\']i18n["\']$#', $tmp_token_str)) {
+                    $prev_token = $tokens[$j - 1];
+                    if (!is_array($prev_token) && $prev_token == '=') {
+                        return $tmp_token_str;
+                    } 
+                }
+            }
+        }
+        return NULL;
+    }
+
+    /**
+     *  Ethna組み込みのメッセージカタログファイルを、上書き
+     *  する場合にマージします。
+     *
+     *  @access private
+     */
+    function _mergeEthnaMessageCatalog()
+    {
+        if (!($this->file_exists && !$this->use_gettext)) {
+            return;
+        }
+        $outfile_path = $this->_get_output_file();
+
+        $i18n = $this->ctl->getI18N();
+        $existing_catalog = $i18n->parseEthnaMsgCatalog($outfile_path);
+
+        foreach ($this->tokens as $file_path => $tokens) {
+            for ($i = 0; $i < count($tokens); $i++) {
+                $token = $tokens[$i];
+                $token_str = $token['token_str'];
+                if (array_key_exists($token_str, $existing_catalog)) {
+                    $this->tokens[$file_path][$i]['translation'] = $existing_catalog[$token_str];
+                }
+            }
+        }
+    }
+
+    /**
+     *  解析済みのメッセージ処理関数の情報を元に、カタログファイ
+     *  ルを生成します。 生成先は以下のパスになる。
+     *  [appid]/[locale_dir]/[locale_name]/LC_MESSAGES/[locale_name].[ini|po]
+     *
+     *  @access protected 
+     *  @param  string  $locale         生成するカタログのロケール
+     *  @param  int     $use_gettext    gettext 使用フラグ
+     *                                  true ならgettext のカタログ生成
+     *                                  false ならEthna組み込みのカタログ生成
+     *  @return true|Ethna_Error true:成功 Ethna_Error:失敗
+     */
+    function _generateFile()
+    {
+        $outfile_path = $this->_get_output_file();
+
+        $skel = ($this->use_gettext)
+              ? 'locale/skel.msg.po'
+              : 'locale/skel.msg.ini';
+        $resolved = $this->_resolveSkelfile($skel);
+        if ($resolved === false) {
+            return Ethna::raiseError("skelton file [%s] not found.\n", $skel);
+        } else {
+            $skel = $resolved;
+        }
+
+        $contents = file_get_contents($skel);
+        $macro['project_id'] = $this->ctl->getAppId();
+        $macro['locale_name'] = $this->locale;
+        $macro['now_date'] = strftime('%Y-%m-%d %H:%M%z');
+        foreach ($macro as $k => $v) {
+            $contents = preg_replace("/{\\\$$k}/", $v, $contents);
+        }
+
+        //  generate file contents
+        foreach ($this->tokens as $file_path => $tokens) {
+            $is_first_loop = false;
+            foreach ($tokens as $token) {
+                $token_str = $token['token_str'];
+                $token_line = $token['linenum'];
+                $token_line = ($token_line !== false) ? ":${token_line}" : '';
+                $translation = $token['translation'];
+
+                if ($this->use_gettext) {
+                    $contents .= (
+                        "#: ${file_path}${token_line}\n"
+                      . "msgid \"${token_str}\"\n"
+                      . "msgstr \"${translation}\"\n\n"
+                    ); 
+                } else {
+                    if ($is_first_loop === false) {
+                        $contents .= "\n; ${file_path}\n";
+                        $is_first_loop = true;
+                    }
+                    $contents .= "\"${token_str}\" = \"${translation}\"\n";
+                }
+            }
+        } 
+
+        //  finally write.
+        $outfile_dir = dirname($outfile_path);
+        if (!is_dir($outfile_dir)) {
+            Ethna_Util::mkdir($outfile_dir, 0755);
+        }
+        $wfp = @fopen($outfile_path, "w");
+        if ($wfp == null) {
+            return Ethna::raiseError("unable to open file: $outfile_path");
+        }
+        if (fwrite($wfp, $contents) === false) {
+            fclose($wfp);
+            return Ethna::raiseError("unable to write contents to $outfile_path");
+        }
+        fclose($wfp);
+        printf("Message catalog template successfully created [%s]\n", $outfile_path);
+
+        return true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Plugin.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Plugin.php
new file mode 100644 (file)
index 0000000..2ef8962
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_Plugin.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_Plugin
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_Plugin extends Ethna_Plugin_Generator
+{
+    /**
+     *  プラグインを生成する
+     *
+     *  @access public
+     *  @param  string  $type       プラグインの$type
+     *  @param  string  $name       プラグインの$name
+     *  @param  bool    $overwrite  上書きオプション
+     *  @return bool    true:成功 false:失敗
+     */
+    function generate($type, $name, $overwrite = false)
+    {
+        $appid = $this->ctl->getAppId();
+        $plugin =& $this->ctl->getPlugin();
+
+        list($class, $plugin_dir, $plugin_path) = $plugin->getPluginNaming($type, $name, $appid);
+
+        $macro = array();
+        $macro['project_id'] = $appid;
+        $macro['application_id'] = $appid;
+        $user_macro = $this->_getUserMacro();
+        $macro = array_merge($macro, $user_macro);
+
+        Ethna_Util::mkdir(dirname("$plugin_dir/$plugin_path"), 0755);
+
+        if ($this->_generateFile("skel.plugin.{$type}_{$name}.php", "$plugin_dir/$plugin_path", $macro, $overwrite) == false) {
+            printf("[warning] file creation failed [%s]\n", "$plugin_dir/$plugin_path");
+        } else {
+            printf("plugin script(s) successfully created [%s]\n", "$plugin_dir/$plugin_path");
+        }
+    }
+
+    /**
+     *  プラグインを消す
+     *
+     *  @access public
+     *  @param  string  $type       プラグインの$type
+     *  @param  string  $name       プラグインの$name
+     *  @return bool    true:成功 false:失敗
+     */
+    function remove($type, $name)
+    {
+        $appid = $this->ctl->getAppId();
+        $plugin =& $this->ctl->getPlugin();
+
+        list($class, $plugin_dir, $plugin_path) = $plugin->getPluginNaming($type, $name, $appid);
+
+        $macro = array();
+        $macro['project_id'] = $appid;
+        $user_macro = $this->_getUserMacro();
+        $macro = array_merge($macro, $user_macro);
+
+        if (file_exists("$plugin_dir/$plugin_path")) {
+            unlink("$plugin_dir/$plugin_path");
+            printf("file [%s] successfully unlinked\n", "$plugin_dir/$plugin_path");
+        } else {
+            printf("file [%s] not found\n", "$plugin_dir/$plugin_path");
+        }
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Project.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Project.php
new file mode 100644 (file)
index 0000000..f0f02b8
--- /dev/null
@@ -0,0 +1,226 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_Project.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_Project
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_Project extends Ethna_Plugin_Generator
+{
+    /**
+     *  プロジェクトスケルトンを生成する
+     *
+     *  @access public
+     *  @param  string  $id         プロジェクトID
+     *  @param  string  $basedir    プロジェクトベースディレクトリ
+     *  @param  string  $skeldir    スケルトンディレクトリ。これが指定されると、そこにある
+     *                              ファイルが優先される。また、ETHNA_HOME/skel にないもの
+     *                              も追加してコピーする 
+     *  @param  string  $locale     ロケール名
+     *                              (ロケール名は、ll_cc の形式。ll = 言語コード cc = 国コード)
+     *  @param  string  $encoding   プロジェクトで使用するエンコーディング 
+     *  @return bool    true:成功   Ethna_Error:失敗
+     */
+    function generate($id, $basedir, $skeldir, $locale, $encoding)
+    {
+        $dir_list = array(
+            array("app", 0755),
+            array("app/action", 0755),
+            array("app/action_cli", 0755),
+            array("app/action_xmlrpc", 0755),
+            array("app/filter", 0755),
+            array("app/plugin", 0755),
+            array("app/plugin/Filter", 0755),
+            array("app/plugin/Validator", 0755),
+            array("app/plugin/Smarty", 0755),
+            array("app/view", 0755),
+            array("app/test", 0755),
+            array("bin", 0755),
+            array("etc", 0755),
+            array("lib", 0755),
+            array("locale", 0755),
+            array("locale/$locale", 0755),
+            array("locale/$locale/LC_MESSAGES", 0755),
+            array("log", 0777),
+            array("schema", 0755),
+            array("skel", 0755),
+            array("template", 0755),
+            array("template/$locale", 0755),
+            array("tmp", 0777),
+            array("www", 0755),
+            array("www/css", 0755),
+            array("www/js", 0755),
+        );
+
+        // double check.
+        $id = strtolower($id);
+        $r = Ethna_Controller::checkAppId($id);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        $basedir = sprintf("%s/%s", $basedir, $id);
+
+        // ディレクトリ作成
+        if (is_dir($basedir) == false) {
+            // confirm
+            printf("creating directory ($basedir) [y/n]: ");
+            flush();
+            $fp = fopen("php://stdin", "r");
+            $r = trim(fgets($fp, 128));
+            fclose($fp);
+            if (strtolower($r) != 'y') {
+                return Ethna::raiseError('aborted by user');
+            }
+
+            if (mkdir($basedir, 0775) == false) {
+                return Ethna::raiseError('directory creation failed');
+            }
+        }
+        foreach ($dir_list as $dir) {
+            $mode = $dir[1];
+            $dir = $dir[0];
+            $target = "$basedir/$dir";
+            if (is_dir($target)) {
+                printf("%s already exists -> skipping...\n", $target);
+                continue;
+            }
+            if (mkdir($target, $mode) == false) {
+                return Ethna::raiseError('directory creation failed');
+            } else {
+                printf("project sub directory created [%s]\n", $target);
+            }
+            if (chmod($target, $mode) == false) {
+                return Ethna::raiseError('chmod failed');
+            }
+        }
+
+        // スケルトンファイル作成
+        $macro['ethna_version'] = ETHNA_VERSION;
+        $macro['application_id'] = strtoupper($id);
+        $macro['project_id'] = ucfirst($id);
+        $macro['project_prefix'] = $id;
+        $macro['basedir'] = realpath($basedir);
+        $macro['locale'] = $locale;
+        $macro['client_enc'] = $encoding;
+
+        $macro['action_class'] = '{$action_class}';
+        $macro['action_form'] = '{$action_form}';
+        $macro['action_name'] = '{$action_name}';
+        $macro['action_path'] = '{$action_path}';
+        $macro['forward_name'] = '{$forward_name}';
+        $macro['view_name'] = '{$view_name}';
+        $macro['view_path'] = '{$view_path}';
+
+        $user_macro = $this->_getUserMacro();
+        $default_macro = $macro;
+        $macro = array_merge($macro, $user_macro);
+
+        //  select locale file.
+        $locale_file = (file_exists(ETHNA_BASE . "/skel/locale/$locale/ethna_sysmsg.ini"))
+                     ? "locale/$locale/ethna_sysmsg.ini"
+                     : 'locale/ethna_sysmsg.default.ini';
+
+        $realfile_maps = array(
+            $locale_file    => "$basedir/locale/$locale/LC_MESSAGES/ethna_sysmsg.ini",
+            "www.index.php" => "$basedir/www/index.php",
+            "www.info.php"  => "$basedir/www/info.php",
+            "www.unittest.php" => "$basedir/www/unittest.php",
+            "www.xmlrpc.php" => "$basedir/www/xmlrpc.php",
+            "www.css.ethna.css" => "$basedir/www/css/ethna.css",
+            "dot.ethna" => "$basedir/.ethna",
+            "app.controller.php" => sprintf("$basedir/app/%s_Controller.php", $macro['project_id']),
+            "app.error.php" => sprintf("$basedir/app/%s_Error.php", $macro['project_id']),
+            "app.actionclass.php" => sprintf("$basedir/app/%s_ActionClass.php", $macro['project_id']),
+            "app.actionform.php" => sprintf("$basedir/app/%s_ActionForm.php", $macro['project_id']),
+            "app.viewclass.php" => sprintf("$basedir/app/%s_ViewClass.php", $macro['project_id']),
+            "app.action.default.php" => "$basedir/app/action/Index.php",
+            "app.plugin.filter.default.php" => sprintf("$basedir/app/plugin/Filter/%s_Plugin_Filter_ExecutionTime.php", $macro['project_id']),
+            "app.view.default.php" => "$basedir/app/view/Index.php",
+            "app.unittest.php" => sprintf("$basedir/app/%s_UnitTestManager.php", $macro['project_id']),
+            "app.url_handler.php" => sprintf("$basedir/app/%s_UrlHandler.php", $macro['project_id']),
+            "etc.ini.php" => sprintf("$basedir/etc/%s-ini.php", $macro['project_prefix']),
+            "template.index.tpl" => sprintf("$basedir/template/$locale/index.tpl"),
+        );
+
+        $skelfile_maps = array(
+            "skel.action.php" => sprintf("$basedir/skel/skel.action.php"),
+            "skel.action_cli.php" => sprintf("$basedir/skel/skel.action_cli.php"),
+            "skel.action_test.php" => sprintf("$basedir/skel/skel.action_test.php"),
+            "skel.app_object.php" => sprintf("$basedir/skel/skel.app_object.php"),
+            "skel.entry_www.php" => sprintf("$basedir/skel/skel.entry_www.php"),
+            "skel.entry_cli.php" => sprintf("$basedir/skel/skel.entry_cli.php"),
+            "skel.view.php" => sprintf("$basedir/skel/skel.view.php"),
+            "skel.template.tpl" => sprintf("$basedir/skel/skel.template.tpl"),
+            "skel.view_test.php" => sprintf("$basedir/skel/skel.view_test.php"),
+        );
+
+        //    also copy user defined skel file.
+        if (!empty($skeldir)) {
+            $handle = opendir($skeldir);
+            while (($file = readdir($handle)) !== false) {
+                if (is_dir(realpath("$skeldir/$file"))) {
+                    continue;
+                }
+                if (array_key_exists($file, $skelfile_maps) == false) {
+                    $skelfile_maps[$file] = sprintf("$basedir/skel/$file");
+                }
+            }
+        }
+
+        $real_r = $this->_generate($realfile_maps, $macro, $skeldir);
+        if (Ethna::isError($real_r)) {
+            return $real_r;
+        }
+
+        //  skelファイルにはエンコーディングマクロは適用しない
+        //  skel.template.tpl は、add-[view|template]時に適用させるため。
+        unset($default_macro['client_enc']);
+        $skel_r = $this->_generate($skelfile_maps, $default_macro, $skeldir);
+        if (Ethna::isError($skel_r)) {
+            return $skel_r;
+        }
+
+        return true;
+    }
+
+    /**
+     *  実際のプロジェクトスケルトンを生成処理を行う
+     *
+     *  @access private 
+     *  @param  string  $maps       スケルトン名と生成されるファイルの配列 
+     *  @param  string  $macro      適用マクロ 
+     *  @param  string  $skeldir    スケルトンディレクトリ。これが指定されると、そこにある
+     *                              ファイルが優先される。また、ETHNA_HOME/skel にないもの
+     *                              も追加してコピーする 
+     *  @return bool     true:成功  Ethna_Error:失敗
+     */
+    function _generate($maps, $macro, $skeldir)
+    {
+        foreach ($maps as $skel => $realfile) {
+            if (!empty($skeldir) && file_exists("$skeldir/$skel")) {
+                $skel = "$skeldir/$skel";
+            }
+            if ($this->_generateFile($skel, $realfile, $macro) == false) {
+                return Ethna::raiseError("generating files failed");
+            }
+        }
+        return true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Template.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Template.php
new file mode 100644 (file)
index 0000000..0e80cf0
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_Template.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_Template
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_Template extends Ethna_Plugin_Generator
+{
+    /**
+     *  テンプレートのスケルトンを生成する
+     *
+     *  @access public
+     *  @param  string  $forward_name   テンプレート名
+     *  @param  string  $skelton        スケルトンファイル名
+     *  @param  string  $locale         ロケール名
+     *  @param  string  $encoding       エンコーディング
+     *  @return true|Ethna_Error        true:成功 Ethna_Error:失敗
+     */
+    function &generate($forward_name, $skelton = null, $locale, $encoding)
+    {
+        //  ロケールが指定された場合は、それを優先する 
+        if (!empty($locale)) {
+            $this->ctl->setLocale($locale);
+        }
+
+        //  ロケール名がディレクトリに含まれていない場合は、
+        //  ディレクトリがないためなのでそれを補正 
+        $tpl_dir = $this->ctl->getTemplatedir();
+        $tpl_path = $this->ctl->getDefaultForwardPath($forward_name);
+
+        // entity
+        $entity = $tpl_dir . '/' . $tpl_path;
+        Ethna_Util::mkdir(dirname($entity), 0755);
+
+        // skelton
+        if ($skelton === null) {
+            $skelton = 'skel.template.tpl';
+        }
+
+        // macro
+        $macro = array();
+        // add '_' for tpl and no user macro for tpl
+        $macro['_project_id'] = $this->ctl->getAppId();
+        $macro['client_enc'] = $encoding;
+
+        // generate
+        if (file_exists($entity)) {
+            printf("file [%s] already exists -> skip\n", $entity);
+        } else if ($this->_generateFile($skelton, $entity, $macro) == false) {
+            printf("[warning] file creation failed [%s]\n", $entity);
+        } else {
+            printf("template file(s) successfully created [%s]\n", $entity);
+        }
+
+        $true = true;
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Test.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_Test.php
new file mode 100644 (file)
index 0000000..4a06d0d
--- /dev/null
@@ -0,0 +1,75 @@
+<?php\r
+// vim: foldmethod=marker\r
+/**\r
+ * Ethna_Plugin_Generator_Test.php\r
+ * \r
+ * @author BoBpp <bobpp@users.sourceforge.jp>\r
+ * @license http://www.opensource.org/licenses/bsd-license.php The BSD License\r
+ * @package Ethna\r
+ * @version $Id$\r
+ */\r
\r
+// {{{ Ethna_Plugin_Generator_Test\r
+/**\r
+ * Normal Test Case Generator.\r
+ * \r
+ * @author BoBpp <bobpp@users.sourceforge.jp>\r
+ * @package Ethna\r
+ */\r
+class Ethna_Plugin_Generator_Test extends Ethna_Plugin_Generator\r
+{\r
+    /**\r
+     * ファイル生成を行う\r
+     * \r
+     * @access public\r
+     * @param string $skelfile スケルトンファイル名\r
+     * @param string $name     テストケース名\r
+     * @return mixed TRUE; OK\r
+     *               Ethna_Error: エラー発生\r
+     */\r
+    function &generate($skelfile, $name)\r
+    {\r
+        // Controllerを取得\r
+        $ctl =& $this->ctl;\r
+        \r
+        // テストを生成するディレクトリがあるか?\r
+        // なければ app/test がデフォルト。\r
+        $dir = $ctl->getDirectory('test');\r
+        if ($dir === null) {\r
+            $dir = $ctl->getDirectory('app') . "/" . "test";\r
+        }\r
+        \r
+        // ファイル名生成\r
+        $file = preg_replace('/_(.)/e', "'/' . strtoupper('\$1')", ucfirst($name)) . "Test.php";\r
+        $generatePath = "$dir/$file";\r
+        \r
+        // スケルトン決定\r
+        $skelton = (!empty($skelfile))\r
+                 ? $skelfile\r
+                 : "skel.test.php";\r
+        \r
+        // マクロ生成\r
+        $macro = array();\r
+        $macro['project_id'] = ucfirst($ctl->getAppId());\r
+        $macro['file_path'] = $file;\r
+        $macro['name'] = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($name));\r
+        \r
+        $userMacro = $this->_getUserMacro();\r
+        $macro = array_merge($macro, $userMacro);\r
+        \r
+        // 生成\r
+        Ethna_Util::mkdir(dirname($generatePath), 0755);\r
+        if (file_exists($generatePath)) {\r
+            printf("file [%s] already exists -> skip\n", $generatePath);\r
+        } else if ($this->_generateFile($skelton, $generatePath, $macro) == false) {\r
+            printf("[warning] file creation failed [%s]\n", $generatePath);\r
+        } else {\r
+            printf("test script(s) successfully created [%s]\n", $generatePath);\r
+        }\r
+\r
+        $true = true;\r
+        return $true;\r
+    }\r
+}\r
+// }}}\r
+?>\r
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_View.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_View.php
new file mode 100644 (file)
index 0000000..c29e6d7
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_View.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_View
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_View extends Ethna_Plugin_Generator
+{
+    /**
+     *  ビューのスケルトンを生成する
+     *
+     *  @access public
+     *  @param  string  $forward_name   ビュー名
+     *  @param  string  $skelton        スケルトンファイル名
+     *  @return true|Ethna_Error        true:成功 Ethna_Error:失敗
+     */
+    function &generate($forward_name, $skelton = null, $gateway = GATEWAY_WWW)
+    {
+        $view_dir = $this->ctl->getViewdir();
+        $view_class = $this->ctl->getDefaultViewClass($forward_name, $gateway);
+        $view_path = $this->ctl->getDefaultViewPath($forward_name);
+
+        // entity
+        $entity = $view_dir . $view_path;
+        Ethna_Util::mkdir(dirname($entity), 0755);
+
+        // skelton
+        if ($skelton === null) {
+            $skelton = 'skel.view.php';
+        }
+
+        // macro
+        $macro = array();
+        $macro['project_id'] = $this->ctl->getAppId();
+        $macro['forward_name'] = $forward_name;
+        $macro['view_class'] = $view_class;
+        $macro['view_path'] = $view_path;
+
+        // user macro
+        $user_macro = $this->_getUserMacro();
+        $macro = array_merge($macro, $user_macro);
+
+
+        // generate
+        if (file_exists($entity)) {
+            printf("file [%s] already exists -> skip\n", $entity);
+        } else if ($this->_generateFile($skelton, $entity, $macro) == false) {
+            printf("[warning] file creation failed [%s]\n", $entity);
+        } else {
+            printf("view script(s) successfully created [%s]\n", $entity);
+        }
+
+        $true = true;
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_ViewTest.php b/Idea_Plugin_Extlib/class/Plugin/Generator/Ethna_Plugin_Generator_ViewTest.php
new file mode 100644 (file)
index 0000000..a306069
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Generator_ViewTest.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Generator_ViewTest
+/**
+ *  スケルトン生成クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Generator_ViewTest extends Ethna_Plugin_Generator
+{
+    /**
+     *  ビュー用テストのスケルトンを生成する
+     *
+     *  @access public
+     *  @param  string  $forward_name   ビュー名
+     *  @param  string  $skelton        スケルトンファイル名
+     *  @return true|Ethna_Error        true:成功 Ethna_Error:失敗
+     */
+    function &generate($forward_name, $skelton = null, $gateway = GATEWAY_WWW)
+    {
+        $view_dir = $this->ctl->getViewdir();
+        $view_class = $this->ctl->getDefaultViewClass($forward_name, $gateway);
+        $view_path = $this->ctl->getDefaultViewPath($forward_name . 'Test');
+
+        // entity
+        $entity = $view_dir . $view_path;
+        Ethna_Util::mkdir(dirname($entity), 0755);
+
+        // skelton
+        if ($skelton === null) {
+            $skelton = 'skel.view_test.php';
+        }
+
+        // macro
+        $macro = array();
+        $macro['project_id'] = $this->ctl->getAppId();
+        $macro['forward_name'] = $forward_name;
+        $macro['view_class'] = $view_class;
+        $macro['view_path'] = $view_path;
+
+        // original view script existence check.
+        $original_view_path = $this->ctl->getDefaultViewPath($forward_name);
+        $original_view_entity = $view_dir . $original_view_path;
+        if (!file_exists($original_view_entity)) {
+            printf("\n");
+            printf("[!!!!warning!!!!] original view script was not found.\n");
+            printf("[!!!!warning!!!!] You must generate it by the following command :\n");
+            printf("[!!!!warning!!!!] ethna add-view %s\n\n", $forward_name);
+        } 
+
+        // user macro
+        $user_macro = $this->_getUserMacro();
+        $macro = array_merge($macro, $user_macro);
+
+
+        // generate
+        if (file_exists($entity)) {
+            printf("file [%s] already exists -> skip\n", $entity);
+        } else if ($this->_generateFile($skelton, $entity, $macro) == false) {
+            printf("[warning] file creation failed [%s]\n", $entity);
+        } else {
+            printf("view test(s) successfully created [%s]\n", $entity);
+        }
+
+        $true = true;
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddAction.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddAction.php
new file mode 100644 (file)
index 0000000..497b922
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_AddAction.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Handle_AddAction
+/**
+ *  add-action handler
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_AddAction extends Ethna_Plugin_Handle
+{
+    /**
+     *  add action
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        //
+        //  '-w[with-unittest]' and '-u[unittestskel]' option
+        //  are not intuisive, but I dare to define them because
+        //  -t and -s option are reserved by add-[action|view] handle
+        //  and Ethna_Getopt cannot interpret two-character option.
+        //
+        $r = $this->_getopt(
+                  array('basedir=',
+                        'skelfile=',
+                        'gateway=',
+                        'with-unittest',
+                        'unittestskel=',
+                  )
+             );
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // action_name
+        $action_name = array_shift($arg_list);
+        if ($action_name == null) {
+            return Ethna::raiseError('action name isn\'t set.', 'usage');
+        }
+        $r =& Ethna_Controller::checkActionName($action_name);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        $ret =& $this->_perform('Action', $action_name, $opt_list);
+        return $ret;
+    }
+
+    /**
+     *  @access protected
+     */
+    function &_perform($target, $target_name, $opt_list)
+    {
+        // basedir
+        if (isset($opt_list['basedir'])) {
+            $basedir = realpath(end($opt_list['basedir']));
+        } else {
+            $basedir = getcwd();
+        }
+
+        // skelfile
+        if (isset($opt_list['skelfile'])) {
+            $skelfile = end($opt_list['skelfile']);
+        } else {
+            $skelfile = null;
+        }
+        
+        // gateway
+        if (isset($opt_list['gateway'])) {
+            $gateway = 'GATEWAY_' . strtoupper(end($opt_list['gateway']));
+            if (defined($gateway)) {
+                $gateway = constant($gateway);
+            } else {
+                return Ethna::raiseError('unknown gateway', 'usage');
+            }
+        } else {
+            $gateway = GATEWAY_WWW;
+        }
+        
+        //  possible target is Action, View.
+        $r =& Ethna_Generator::generate($target, $basedir,
+                                        $target_name, $skelfile, $gateway);
+        if (Ethna::isError($r)) {
+            printf("error occurred while generating skelton. please see also following error message(s)\n\n");
+            return $r;
+        }
+
+        //
+        //  if specified, generate corresponding testcase,
+        //  except for template.
+        //
+        if ($target != 'Template' && isset($opt_list['with-unittest'])) {
+            $testskel = (isset($opt_list['unittestskel']))
+                      ? end($opt_list['unittestskel'])
+                      : null;
+            $r =& Ethna_Generator::generate("{$target}Test", $basedir, $target_name, $testskel, $gateway);
+            if (Ethna::isError($r)) {
+                printf("error occurred while generating action test skelton. please see also following error message(s)\n\n");
+                return $r;
+            }
+        }  
+
+        $true = true;
+        return $true;
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+add new action to project:
+    {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [-g|--gateway=www|cli|xmlrpc] [-w|--with-unittest] [-u|--unittestskel=file] [action]
+
+EOS;
+    }
+
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [-g|--gateway=www|cli|xmlrpc] [-w|--with-unittest] [-u|--unittestskel=file] [action]
+
+EOS;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddActionTest.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddActionTest.php
new file mode 100644 (file)
index 0000000..b27f1e6
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/**
+ *  Ethna_Plugin_Handle_AddActionTest.php
+ *
+ *  @author     halt feits <halt.feits@gmail.com>
+ *  @package    Ethna
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Handle/Ethna_Plugin_Handle_AddAction.php';
+
+// {{{ Ethna_Plugin_Handle_AddActionTest
+/**
+ *  add-action-test handler
+ *
+ *  @author     halt feits <halt.feits@gmail.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_AddActionTest extends Ethna_Plugin_Handle_AddAction
+{
+    /**
+     *  add action test
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        $r =& $this->_getopt(array('basedir=', 'skelfile='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // action_name
+        $action_name = array_shift($arg_list);
+        if ($action_name == null) {
+            return Ethna::raiseError('action name isn\'t set.', 'usage');
+        }
+        $r =& Ethna_Controller::checkActionName($action_name);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        $ret =& $this->_perform('ActionTest', $action_name, $opt_list);
+        return $ret;
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+add new action test to project:
+    {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [action]
+
+EOS;
+    }
+
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [action]
+EOS;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddAppManager.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddAppManager.php
new file mode 100644 (file)
index 0000000..c94a343
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/**
+ *  Ethna_Plugin_Handle_AddAppManager.php
+ *
+ *  @author     nozzzzz <nozzzzz@gmail.com>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Handle/Ethna_Plugin_Handle_AddAppObject.php';
+
+// {{{ Ethna_Plugin_Handle_AddAppManager
+/**
+ *  add-app-manager handler
+ *
+ *  @author     nozzzzz <nozzzzz@gmail.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_AddAppManager extends Ethna_Plugin_Handle_AddAppObject
+{
+    /**
+     *  add app-manager
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        return $this->_perform('AppManager');
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+add new app-manager to project:
+    {$this->id} [-b|--basedir=dir] [app-manager name]
+
+EOS;
+    }
+
+    /**
+     *  get usage
+     *
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [app-manager name]
+EOS;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddAppObject.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddAppObject.php
new file mode 100644 (file)
index 0000000..2236de9
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_AddAppObject.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Handle_AddAppObject
+/**
+ *  add-app-object handler
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_AddAppObject extends Ethna_Plugin_Handle
+{
+    /**
+     *  add app-object
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        return $this->_perform('AppObject');
+    }
+
+    /**
+     *  @access protected
+     */
+    function _perform($target)
+    {
+        $r =& $this->_getopt(array('basedir='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // table_name
+        $table_name = array_shift($arg_list);
+        if ($table_name == null) {
+            return Ethna::raiseError('table name isn\'t set.', 'usage');
+        }
+
+        // basedir
+        if (isset($opt_list['basedir'])) {
+            $basedir = realpath(end($opt_list['basedir']));
+        } else {
+            $basedir = getcwd();
+        }
+
+        $r =& Ethna_Generator::generate($target, $basedir, $table_name);
+        if (Ethna::isError($r)) {
+            printf("error occurred while generating skelton. please see also following error message(s)\n\n");
+            return $r;
+        }
+
+        return true;
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+add new app-object to project:
+    {$this->id} [-b|--basedir=dir] [table name]
+
+EOS;
+    }
+
+    /**
+     *  get usage
+     *
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [table name]
+EOS;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddEntryPoint.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddEntryPoint.php
new file mode 100644 (file)
index 0000000..298e212
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_AddEntryPoint.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Handle/Ethna_Plugin_Handle_AddAction.php';
+
+// {{{ Ethna_Plugin_Handle_AddEntryPoint
+/**
+ *  add-action handler
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_AddEntryPoint extends Ethna_Plugin_Handle_AddAction
+{
+    /**
+     *  add action entry point
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        $r =& $this->_getopt(array('basedir=', 'skelfile=', 'gateway='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // action_name
+        $action_name = array_shift($arg_list);
+        if ($action_name == null) {
+            return Ethna::raiseError('action name isn\'t set.', 'usage');
+        }
+        $r =& Ethna_Controller::checkActionName($action_name);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        // add entry point
+        $ret =& $this->_perform('EntryPoint', $action_name, $opt_list);
+        if (Ethna::isError($ret) || $ret === false) { 
+            return $ret;
+        }
+
+        // add action (no effects if already exists.)
+        $ret =& $this->_perform('Action', $action_name, $opt_list);
+        if (Ethna::isError($ret) || $ret === false) { 
+            return $ret;
+        }
+
+        return true;
+
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+add new action and its entry point to project:
+    {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [-g|--gateway=www|cli] [action]
+
+EOS;
+    }
+
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [-g|--gateway=www|cli] [action]
+EOS;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddProject.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddProject.php
new file mode 100644 (file)
index 0000000..c895f0b
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_AddProject.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Handle_AddProject
+/**
+ *  add-project handler
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_AddProject extends Ethna_Plugin_Handle
+{
+    /**
+     *  add project:)
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        $r = $this->_getopt(array('basedir=', 'skeldir=', 'locale=', 'encoding='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // app_id
+        $app_id = array_shift($arg_list);
+        if ($app_id == null) {
+            return Ethna::raiseError('Application id isn\'t set.', 'usage');
+        }
+        $r = Ethna_Controller::checkAppId($app_id);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        // basedir
+        if (isset($opt_list['basedir'])) {
+            $basedir = realpath(end($opt_list['basedir']));
+        } else {
+            $basedir = getcwd();
+        }
+
+        // skeldir
+        if (isset($opt_list['skeldir'])) {
+            $selected_dir = end($opt_list['skeldir']);
+            $skeldir = realpath($selected_dir);
+            if ($skeldir == false || is_dir($skeldir) == false || file_exists($skeldir) == false) {
+                return Ethna::raiseError("You specified skeldir, but invalid : $selected_dir", 'usage');
+            }
+        } else {
+            $skeldir = null;
+        }
+
+        // locale
+        if (isset($opt_list['locale'])) {
+            $locale = end($opt_list['locale']);
+            if (!preg_match('/^[A-Za-z_]+$/', $locale)) {
+                return Ethna::raiseError("You specified locale, but invalid : $locale", 'usage');
+            }
+        } else {
+            $locale = 'ja_JP';  //  default locale. 
+        }
+
+        // encoding
+        if (isset($opt_list['encoding'])) {
+            $encoding = end($opt_list['encoding']);
+            if (function_exists('mb_list_encodings')) {
+                $supported_enc = mb_list_encodings();
+                if (!in_array($encoding, $supported_enc)) {
+                    return Ethna::raiseError("Unknown Encoding : $encoding", 'usage');
+                }
+            }
+        } else {
+            $encoding = 'UTF-8';  //  default encoding. 
+        }
+
+        $r = Ethna_Generator::generate('Project', null, $app_id, $basedir, $skeldir, $locale, $encoding);
+        if (Ethna::isError($r)) {
+            printf("error occurred while generating skelton. please see also error messages given above\n\n");
+            return $r;
+        }
+
+        printf("\nproject skelton for [%s] is successfully generated at [%s]\n\n", $app_id, $basedir);
+        return true;
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+add new project:
+    {$this->id} [-b|--basedir=dir] [-s|--skeldir] [-l|--locale] [-e|--encoding] [Application id]
+
+EOS;
+    }
+
+    /**
+     *  get usage
+     *
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [-s|--skeldir] [-l|--locale] [-e|--encoding] [Application id]
+EOS;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddTemplate.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddTemplate.php
new file mode 100644 (file)
index 0000000..b8715f1
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_AddTemplate.php
+ *
+ *  @author     nnno <nnno@nnno.jp> 
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Handle/Ethna_Plugin_Handle_AddView.php';
+
+// {{{ Ethna_Plugin_Handle_AddTemplate
+/**
+ *  add-template handler
+ *
+ *  @author     nnno <nnno@nnno.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_AddTemplate extends Ethna_Plugin_Handle_AddView
+{
+    /**
+     *  add template 
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        $r =& $this->_getopt(
+                  array('basedir=',
+                        'skelfile=',
+                        'locale=',
+                        'encoding=',
+                  )
+              ); 
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // template
+        $template = array_shift($arg_list);
+        if ($template == null) {
+            return Ethna::raiseError('template name isn\'t set.', 'usage');
+        }
+        $r =& Ethna_Controller::checkViewName($template); // XXX: use checkViewName().
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        // add template
+        $ret =& $this->_performTemplate($template, $opt_list);
+        return $ret;
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+add new template to project:
+    {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [-l|--locale=locale] [-e|--encoding] [template]
+
+EOS;
+    }
+
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [-l|--locale=locale] [-e|--encoding] [template]
+EOS;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddTest.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddTest.php
new file mode 100644 (file)
index 0000000..cbc4a4e
--- /dev/null
@@ -0,0 +1,95 @@
+<?php\r
+// vim: foldmethod=marker\r
+/**\r
+ * Ethna_Plugin_Handle_AddTest.php\r
+ * \r
+ * @author  BoBpp <bobpp@users.sourceforge.jp>\r
+ * @license http://www.opensource.org/licenses/bsd-license.php The BSD License\r
+ * @package Ethna\r
+ * @version $Id$\r
+ */\r
+\r
+// {{{ Ethna_Plugin_Handle_AddTest\r
+/**\r
+ * Ethna_Handle which generates Normal Test Case\r
+ * \r
+ * @author BoBpp <bobpp@users.sourceforge.jp>\r
+ * @package Ethna\r
+ */\r
+class Ethna_Plugin_Handle_AddTest extends Ethna_Plugin_Handle\r
+{\r
+    /**\r
+     * コマンドの概要を返す\r
+     * \r
+     * @access protected\r
+     * @return string コマンド概要\r
+     */\r
+    function getDescription()\r
+    {\r
+         return <<<EOS\r
+Create Normal UnitTestCase\r
+    (If you want action(view) test, use add-[action|view]-test):\r
+    {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [name]\r
+\r
+EOS;\r
+    }\r
+     \r
+     /**\r
+      * コマンドの使用法を返す\r
+      * \r
+      * @access protected\r
+      * @return string コマンドの使用方法\r
+      */\r
+    function getUsage()\r
+    {\r
+        return <<<EOS\r
+ethna {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [name]\r
+\r
+EOS;\r
+    }\r
+     \r
+    /**\r
+     * コマンドの実装部分\r
+     * \r
+     * テストケースファイル生成を行う\r
+     * \r
+     * @access protected\r
+     * @return mixed 実行結果: TRUE: 成功\r
+     *                         Ethna_Error: エラー\r
+     */\r
+    function &perform()\r
+    {\r
+        // get args. \r
+        $r = $this->_getopt(array('basedir=','skelfile='));\r
+        if (Ethna::isError($r)) {\r
+            return $r;\r
+        }\r
+        list($optlist, $arglist) = $r;\r
+        \r
+        $num = count($arglist);\r
+        if ($num < 1 || $num > 3) {\r
+            return Ethna::raiseError("Invalid Arguments.");\r
+        }\r
+        \r
+        if (isset($optlist['skelfile'])) {\r
+            $skelfile = end($optlist['skelfile']);\r
+        } else {\r
+            $skelfile = null;\r
+        }\r
\r
+        $baseDir = isset($optlist['basedir']) ? $optlist['basedir'] : getcwd();\r
+        $name = $arglist[0];\r
+        \r
+        $r =& Ethna_Generator::generate(\r
+            'Test', $baseDir, $skelfile, $name\r
+        );\r
+        if (Ethna::isError($r)) {\r
+            return $r;\r
+        }\r
+        \r
+        $true = true;\r
+        return $true;\r
+    }\r
+}\r
+// }}}\r
+?>\r
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddView.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddView.php
new file mode 100644 (file)
index 0000000..e072d14
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_AddView.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Handle/Ethna_Plugin_Handle_AddAction.php';
+
+// {{{ Ethna_Plugin_Handle_AddView
+/**
+ *  add-view handler
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_AddView extends Ethna_Plugin_Handle_AddAction
+{
+    /**
+     *  add view
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        //
+        //  '-w[with-unittest]' and '-u[unittestskel]' option
+        //  are not intuisive, but I dare to define them because
+        //  -t and -s option are reserved by add-[action|view] handle
+        //  and Ethna_Getopt cannot interpret two-character option.
+        //
+        $r =& $this->_getopt(
+                  array('basedir=',
+                        'skelfile=',
+                        'with-unittest',
+                        'unittestskel=',
+                        'template',
+                        'locale=',
+                        'encoding=',
+                  )
+              );
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // view_name
+        $view_name = array_shift($arg_list);
+        if ($view_name == null) {
+            return Ethna::raiseError('view name isn\'t set.', 'usage');
+        }
+        $r =& Ethna_Controller::checkViewName($view_name);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        // add view(invoke parent class method)
+        $ret =& $this->_perform('View', $view_name, $opt_list);
+        if (Ethna::isError($ret) || $ret === false) { 
+            return $ret;
+        }
+
+        // add template
+        if (isset($opt_list['template'])) {
+            $ret =& $this->_performTemplate($view_name, $opt_list);
+            if (Ethna::isError($ret) || $ret === false) { 
+                return $ret;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     *  Special Function for generating template.
+     *
+     *  @param  string $target_name Template Name
+     *  @param  array  $opt_list    Option List.
+     *  @access protected
+     */
+    function &_performTemplate($target_name, $opt_list)
+    {
+        // basedir
+        if (isset($opt_list['basedir'])) {
+            $basedir = realpath(end($opt_list['basedir']));
+        } else {
+            $basedir = getcwd();
+        }
+
+        // skelfile
+        if (isset($opt_list['skelfile'])) {
+            $skelfile = end($opt_list['skelfile']);
+        } else {
+            $skelfile = null;
+        }
+
+        // locale
+        $ctl =& Ethna_Handle::getAppController(getcwd());
+        if (isset($opt_list['locale'])) {
+            $locale = end($opt_list['locale']);
+            if (!preg_match('/^[A-Za-z_]+$/', $locale)) {
+                return Ethna::raiseError("You specified locale, but invalid : $locale", 'usage');
+            }
+        } else {
+            if (Ethna::isError($ctl)) {
+                $locale = 'ja_JP';
+            } else {
+                $locale = $ctl->getLocale();
+            }
+        }
+
+        // encoding
+        if (isset($opt_list['encoding'])) {
+            $encoding = end($opt_list['encoding']);
+            if (function_exists('mb_list_encodings')) {
+                $supported_enc = mb_list_encodings();
+                if (!in_array($encoding, $supported_enc)) {
+                    return Ethna::raiseError("Unknown Encoding : $encoding", 'usage');
+                }
+            }
+        } else {
+            if (Ethna::isError($ctl)) {
+                $encoding = 'UTF-8';
+            } else {
+                $encoding = $ctl->getClientEncoding();
+            }
+        }
+
+        $r =& Ethna_Generator::generate('Template', $basedir,
+                                        $target_name, $skelfile, $locale, $encoding);
+        if (Ethna::isError($r)) {
+            printf("error occurred while generating skelton. please see also following error message(s)\n\n");
+            return $r;
+        }
+
+        $true = true;
+        return $true;
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+add new view to project:
+    {$this->id} [options... ] [view name]
+    [options ...] are as follows.
+        [-b|--basedir=dir] [-s|--skelfile=file]
+        [-w|--with-unittest] [-u|--unittestskel=file]
+        [-t|--template] [-l|--locale] [-e|--encoding]
+    NOTICE: "-w" and "-u" options are ignored when you specify -t option.
+            "-l" and "-e" options are enabled when you specify -t option.
+
+EOS;
+    }
+
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [options... ] [view name]
+    [options ...] are as follows.
+        [-b|--basedir=dir] [-s|--skelfile=file]
+        [-w|--with-unittest] [-u|--unittestskel=file]
+        [-t|--template] [-l|--locale] [-e|--encoding]
+    NOTICE: "-w" and "-u" options are ignored when you specify -t option.
+            "-l" and "-e" options are enabled when you specify -t option.
+EOS;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddViewTest.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_AddViewTest.php
new file mode 100644 (file)
index 0000000..a7cc597
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/**
+ *  Ethna_Plugin_Handle_AddViewTest.php
+ *
+ *  @author     halt feits <halt.feits@gmail.com>
+ *  @package    Ethna
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Handle/Ethna_Plugin_Handle_AddView.php';
+
+// {{{ Ethna_Plugin_Handle_AddViewTest
+/**
+ *  add-view-test handler
+ *
+ *  @author     halt feits <halt.feits@gmail.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_AddViewTest extends Ethna_Plugin_Handle_AddView
+{
+    /**
+     *  add view test
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        $r =& $this->_getopt(array('basedir=', 'skelfile='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // view_name
+        $view_name = array_shift($arg_list);
+        if ($view_name == null) {
+            return Ethna::raiseError('view name isn\'t set.', 'usage');
+        }
+        $r =& Ethna_Controller::checkViewName($view_name);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        $ret =& $this->_perform('ViewTest', $view_name, $opt_list);
+        return $ret;
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+add new view test to project:
+    {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [view]
+
+EOS;
+    }
+
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [-s|--skelfile=file] [view]
+EOS;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_ChannelUpdate.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_ChannelUpdate.php
new file mode 100644 (file)
index 0000000..a0dc044
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_ChannelUpdate.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_PearWrapper.php';
+
+// {{{ Ethna_Plugin_Handle_ChannelUpdate
+/**
+ *  info-plugin handler
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_ChannelUpdate extends Ethna_Plugin_Handle
+{
+    // {{{ _parseArgList()
+    /**
+     * @access private
+     */
+    function &_parseArgList()
+    {
+        $r =& $this->_getopt(array('local', 'master', 'basedir=',
+                                   'channel=', 'pearopt='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+        $ret = array();
+
+        // options
+        $ret['target'] = isset($opt_list['master']) ? 'master' : 'local';
+        if (isset($opt_list['basedir'])) {
+            $ret['basedir'] = end($opt_list['basedir']);
+        }
+        if (isset($opt_list['channel'])) {
+            $ret['channel'] = end($opt_list['channel']);
+        }
+
+        return $ret;
+    }
+    // }}}
+
+    // {{{ perform()
+    /**
+     *  @access public
+     */
+    function perform()
+    {
+        $args =& $this->_parseArgList();
+        if (Ethna::isError($args)) {
+            return $args;
+        }
+
+        $pear =& new Ethna_PearWrapper();
+        if (isset($args['pearopt'])) {
+            $pear->setPearOpt($args['pearopt']);
+        }
+
+        $target = isset($args['target']) ? $args['target'] : null;
+        $channel = isset($args['channel']) ? $args['channel'] : null;
+        $basedir = isset($args['basedir']) ? realpath($args['basedir']) : getcwd();
+
+        $r =& $pear->init($target, $basedir, $channel);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        $r =& $pear->doClearCache();
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        $r =& $pear->doChannelUpdate();
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ getDescription()
+    /**
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+update package repositry channel:
+    {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master]
+
+EOS;
+    }
+    // }}}
+
+    // {{{ getUsage()
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [type name]
+EOS;
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_ClearCache.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_ClearCache.php
new file mode 100644 (file)
index 0000000..f79caa8
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_ClearCache.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_PearWrapper.php';
+
+// {{{ Ethna_Plugin_Handle_ClearCache
+/**
+ *  clear-cache handler
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_ClearCache extends Ethna_Plugin_Handle
+{
+    /**
+     *  clear cache files.
+     *
+     *  @access public
+     *  @todo   implement Ethna_Renderer::clear_cache();
+     *  @todo   implement Ethna_Plugin_Cachemanager::clear_cache();
+     *  @todo   avoid echo, printf
+     */
+    function perform()
+    {
+        $r =& $this->_getopt(array('basedir=', 
+                                   'any-tmp-files', 'smarty', 'pear', 'cachemanager'));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($args,) = $r;
+
+        $basedir = isset($args['basedir']) ? realpath(end($args['basedir'])) : getcwd();
+        $controller =& Ethna_Handle::getAppController($basedir);
+        if (Ethna::isError($controller)) {
+            return $controller;
+        }
+        $tmp_dir = $controller->getDirectory('tmp');
+
+        if (isset($args['smarty']) || isset($args['any-tmp-files'])) {
+            echo "cleaning smarty caches, compiled templates...";
+            $renderer =& $controller->getRenderer();
+            if (strtolower(get_class($renderer)) == "ethna_renderer_smarty") {
+                $renderer->engine->clear_all_cache();
+                $renderer->engine->clear_compiled_tpl();
+            }
+            echo " done\n";
+        }
+
+        if (isset($args['cachemanager']) || isset($args['any-tmp-files'])) {
+            echo "cleaning Ethna_Plugin_Cachemanager caches...";
+            $cache_dir = sprintf("%s/cache", $tmp_dir);
+            Ethna_Util::purgeDir($cache_dir);
+            echo " done\n";
+        }
+
+        if (isset($args['pear']) || isset($args['any-tmp-files'])) {
+            echo "cleaning pear caches...";
+            ob_start();
+            $pear =& new Ethna_PearWrapper();
+            $r =& $pear->init('local', $basedir); 
+            if (Ethna::isError($r)) {
+                echo ob_get_clean();
+                return $r;
+            }
+            $r =& $pear->doClearCache();
+            if (Ethna::isError($r)) {
+                echo ob_get_clean();
+                return $r;
+            }
+            ob_get_clean();
+            echo " done\n";
+        }
+
+        if (isset($args['any-tmp-files'])) {
+            echo "cleaning tmp dirs...";
+            // purge only entries in tmp.
+            if ($dh = opendir($tmp_dir)) {
+                while (($entry = readdir($dh)) !== false) {
+                    if ($entry === '.' || $entry === '..') {
+                        continue;
+                    }
+                    Ethna_Util::purgeDir("{$tmp_dir}/{$entry}");
+                }
+                closedir($dh);
+            }
+            echo " done\n";
+        }
+
+        return true;
+    }
+
+    // {{{ getDescription()
+    /**
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+clear project's cache files:
+    {$this->id} [-b|--basedir=dir] [-a|--any-tmp-files] [-s|--smarty] [-p|--pear] [-c|--cachemanager]
+
+EOS;
+    }
+    // }}}
+
+    // {{{ getUsage()
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [-a|--any-tmp-files] [-s|--smarty] [-p|--pear] [-c|--cachemanager]
+EOS;
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_I18n.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_I18n.php
new file mode 100644 (file)
index 0000000..317356a
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_I18n.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com> 
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Handle_I18n
+/**
+ *  i18n handler
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_I18n extends Ethna_Plugin_Handle
+{
+    /**
+     *  generate message catalog.
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        $r = $this->_getopt(
+                  array('basedir=',
+                        'locale=',
+                        'gettext',
+                  )
+             );
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // basedir
+        if (isset($opt_list['basedir'])) {
+            $basedir = realpath(end($opt_list['basedir']));
+        } else {
+            $basedir = getcwd();
+        }
+
+        // locale
+        if (isset($opt_list['locale'])) {
+            $locale = end($opt_list['locale']);
+            if (!preg_match('/^[A-Za-z_]+$/', $locale)) {
+                return Ethna::raiseError("You specified locale, but invalid : $locale", 'usage');
+            }
+        } else {
+            $locale = 'ja_JP';  //  default locale. 
+        }
+
+        //  use gettext ?
+        $use_gettext = (isset($opt_list['gettext'])) ? true : false;
+
+        //  generate message catalog.
+        $ret =& Ethna_Generator::generate('I18n', $basedir, $locale, $use_gettext, $arg_list);
+        if (Ethna::isError($ret)) {
+            printf("error occurred while generating skelton. please see also following error message(s)\n\n");
+            return $ret;
+        }
+
+        return $ret;
+    }
+
+    /**
+     *  get handler's description
+     *
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+generate message catalog of project:
+    {$this->id} [-b|--basedir=dir] [-l|--locale=locale] [-g|--gettext] [extdir1] [extdir2] ...
+
+EOS;
+    }
+
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-b|--basedir=dir] [-l|--locale=locale] [-g|--gettext] [extdir1] [extdir2] ...
+
+EOS;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_InfoPlugin.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_InfoPlugin.php
new file mode 100644 (file)
index 0000000..59be104
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_InfoPlugin.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_PearWrapper.php';
+
+// {{{ Ethna_Plugin_Handle_InfoPlugin
+/**
+ *  info-plugin handler
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_InfoPlugin extends Ethna_Plugin_Handle
+{
+    // {{{ _parseArgList()
+    /**
+     * @access private
+     */
+    function &_parseArgList()
+    {
+        $r =& $this->_getopt(array('local', 'master',
+                                   'basedir=', 'channel=', 'pearopt='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+        $ret = array();
+
+        // options
+        $ret['target'] = isset($opt_list['master']) ? 'master' : 'local';
+        if (isset($opt_list['basedir'])) {
+            $ret['basedir'] = end($opt_list['basedir']);
+        }
+        if (isset($opt_list['channel'])) {
+            $ret['channel'] = end($opt_list['channel']);
+        }
+
+        // arguments
+        if (count($arg_list) == 2) {
+            $ret['type'] = $arg_list[0];
+            $ret['name'] = $arg_list[1];
+        }
+
+        return $ret;
+    }
+    // }}}
+
+    // {{{ perform()
+    /**
+     *  @access public
+     */
+    function perform()
+    {
+        $args =& $this->_parseArgList();
+        if (Ethna::isError($args)) {
+            return $args;
+        }
+
+        $pear =& new Ethna_PearWrapper();
+        if (isset($args['pearopt'])) {
+            $pear->setPearOpt($args['pearopt']);
+        }
+
+        if (isset($args['type']) && isset($args['name'])) {
+            $target = isset($args['target']) ? $args['target'] : null;
+            $channel = isset($args['channel']) ? $args['channel'] : null;
+            $basedir = isset($args['basedir']) ? realpath($args['basedir']) : getcwd();
+            if ($target == 'master') {
+                $pkg_name = sprintf('Ethna_Plugin_%s_%s', $args['type'], $args['name']);
+            } else {
+                $pkg_name = sprintf('App_Plugin_%s_%s', $args['type'], $args['name']);
+            }
+
+            $r =& $pear->init($target, $basedir, $channel);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+            $r =& $pear->doInfo($pkg_name);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+
+        } else {
+            return Ethna::raiseError('invalid arguments', 'usage');
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ getDescription()
+    /**
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+show plugin information:
+    {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [type name]
+
+EOS;
+    }
+    // }}}
+
+    // {{{ getUsage()
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [type name]
+EOS;
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_InstallPlugin.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_InstallPlugin.php
new file mode 100644 (file)
index 0000000..6d2cb6f
--- /dev/null
@@ -0,0 +1,181 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_InstallPlugin.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_PearWrapper.php';
+
+// {{{ Ethna_Plugin_Handle_InstallPlugin
+/**
+ *  install-plugin handler
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_InstallPlugin extends Ethna_Plugin_Handle
+{
+    // {{{ _parseArgList()
+    /**
+     * @access private
+     */
+    function &_parseArgList()
+    {
+        $r =& $this->_getopt(array('local', 'master', 'state=',
+                                   'basedir=', 'channel=', 'pearopt='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+        $ret = array();
+
+        // options
+        $ret['target'] = isset($opt_list['master']) ? 'master' : 'local';
+        if (isset($opt_list['basedir'])) {
+            $ret['basedir'] = end($opt_list['basedir']);
+        }
+        if (isset($opt_list['channel'])) {
+            $ret['channel'] = end($opt_list['channel']);
+        }
+        if (isset($opt_list['state'])) {
+            $ret['state'] = end($opt_list['state']);
+        }
+        if (isset($opt_list['pearopt'])) {
+            $ret['pearopt'] = $opt_list['pearopt'];
+        }
+
+        // arguments
+        if (count($arg_list) == 2) {
+            $ret['type'] = $arg_list[0];
+            $ret['name'] = $arg_list[1];
+        } else if (count($arg_list) == 1) {
+            $ret['pkg_file_or_url'] = $arg_list[0];
+        }
+
+        return $ret;
+    }
+    // }}}
+
+    // {{{ perform()
+    /**
+     *  @access public
+     *  @todo   deal with the package including some plugins.
+     */
+    function perform()
+    {
+        $args =& $this->_parseArgList();
+        if (Ethna::isError($args)) {
+            return $args;
+        }
+
+        $pear =& new Ethna_PearWrapper();
+        if (isset($args['pearopt'])) {
+            $pear->setPearOpt($args['pearopt']);
+        }
+
+        if (isset($args['pkg_file_or_url'])) {
+            // install from local tgz.
+            $pkg_file_or_url = $args['pkg_file_or_url'];
+            $pkg_name =& Ethna_PearWrapper::getPackageNameFromTgz($pkg_file_or_url);
+            if (Ethna::isError($pkg_name)) {
+                return $pkg_name;
+            }
+            list($appid,, $ctype, $cname) = explode('_', $pkg_name, 4);
+            $target = isset($args['target']) ? $args['target'] : null;
+            if ($target == 'master') {
+                if ($appid != 'Ethna') {
+                    return Ethna::raiseError("this package is not for master.");
+                }
+            } else {
+                if ($appid == 'Ethna') {
+                    return Ethna::raiseError("this package is not for local.");
+                }
+            }
+            $channel = isset($args['channel']) ? $args['channel'] : null;
+            $basedir = isset($args['basedir']) ? realpath($args['basedir']) : getcwd();
+
+            $r =& $pear->init($target, $basedir, $channel);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+            $r =& $pear->doInstallFromTgz($pkg_file_or_url, $pkg_name);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+
+        } else if (isset($args['type']) && isset($args['name'])) {
+            // install from repository.
+            $target = isset($args['target']) ? $args['target'] : null;
+            $channel = isset($args['channel']) ? $args['channel'] : null;
+            $basedir = isset($args['basedir']) ? realpath($args['basedir']) : getcwd();
+            $state = isset($args['state']) ? $args['state'] : null;
+            if ($target == 'master') {
+                $pkg_name = sprintf('Ethna_Plugin_%s_%s', $args['type'], $args['name']);
+            } else {
+                $pkg_name = sprintf('App_Plugin_%s_%s', $args['type'], $args['name']);
+            }
+
+            $r =& $pear->init($target, $basedir, $channel);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+            $r =& $pear->doInstall($pkg_name, $state);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+
+            $pkg_name = $pear->getCanonicalPackageName($pkg_name);
+            if (Ethna::isError($pkg_name)) {
+                return $pkg_name;
+            }
+
+        } else {
+            return Ethna::raiseError('invalid number of arguments', 'usage');
+        }
+
+        if ($target != 'master') {
+            list(,, $ctype, $cname) = explode('_', $pkg_name, 4);
+            $r = Ethna_Generator::generate('Plugin', $basedir, $ctype, $cname, true);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ getDescription()
+    /**
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+install plugin:
+    {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [type name|packagefile|packageurl]
+
+EOS;
+    }
+    // }}}
+
+    // {{{
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [type name|packagefile|packageurl]
+EOS;
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_ListPlugin.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_ListPlugin.php
new file mode 100644 (file)
index 0000000..8e44244
--- /dev/null
@@ -0,0 +1,243 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_ListPlugin.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_PearWrapper.php';
+
+// {{{ Ethna_Plugin_Handle_ListPlugin
+/**
+ *  list-plugin handler
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_ListPlugin extends Ethna_Plugin_Handle
+{
+    // {{{ perform()
+    /**
+     *  @access public
+     */
+    function perform()
+    {
+        $r =& $this->_getopt(array('local', 'master',  'type=',
+                                   'basedir=', 'channel=', 'verbose'));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($args,) = $r;
+
+        $target = isset($args['master']) ? 'master' : 'local';
+        $channel = isset($args['channel']) ? end($args['channel']) : null;
+        $basedir = isset($args['basedir']) ? realpath(end($args['basedir'])) : getcwd();
+        $verbose = isset($args['verbose']);
+        $type = isset($args['type']) ? end($args['type']) : null;
+
+        // prepare PearWrapper object.
+        $pear =& new Ethna_PearWrapper();
+        $r =& $pear->init($target, $basedir, $channel);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        // get plugin list.
+        $plugins_found = $this->_getFoundPluginList($pear, $type);
+        if (Ethna::isError($plugins_found)) {
+            return $plugins_found;
+        }
+        $plugins_installed = $this->_getInstalledPluginList($pear, $type);
+        if (Ethna::isError($plugins_installed)) {
+            return $plugins_installed;
+        }
+
+        // create table data.
+        $data = array();
+        $class_list = array_merge(array_keys($plugins_found), array_keys($plugins_installed));
+        sort($class_list);
+        $class_list = array_unique($class_list);
+        foreach ($class_list as $class_name) {
+            $tmp = array();
+
+            // check found plugin.
+            if (isset($plugins_found[$class_name])) {
+                list($type, $name) = $plugins_found[$class_name];
+                $tmp[0] = $type;
+                $tmp[1] = $name;
+                $tmp[2] = $class_name;
+                $tmp[3] = '-';
+                if ($verbose) {
+                    $tmp[4] = '-';
+                    $tmp[5] = '-';
+                }
+            }
+
+            // check installed plugin.
+            if (isset($plugins_installed[$class_name])) {
+                list($type, $name, $pkg_name, $pkg_version, $pkg_state)
+                    = $plugins_installed[$class_name];
+                if (isset($tmp[0])) {
+                    $tmp[3] = $pkg_name;
+                    if ($verbose) {
+                        $tmp[4] = $pkg_version;
+                        $tmp[5] = $pkg_state;
+                    }
+                } else {
+                    // this plugin is only in skelton
+                    $tmp[0] = $type;
+                    $tmp[1] = $name;
+                    $tmp[2] = '-';
+                    $tmp[3] = $pkg_name;
+                    if ($verbose) {
+                        $tmp[4] = $pkg_version;
+                        $tmp[5] = $pkg_state;
+                    }
+                }
+            }
+
+            if (isset($tmp[0])) {
+                $data[] = $tmp;
+            }
+        }
+
+        usort($data, array(&$this, '_sort'));
+        if ($verbose) {
+            $pear->displayTable('installed plugins',
+                array('type', 'name', 'class', 'package', 'version', 'state'),
+                $data);
+        } else {
+            $pear->displayTable('installed plugins',
+                array('type', 'name', 'class', 'package'),
+                $data);
+        }
+        return true;
+    }
+    // }}}
+
+    // {{{ _getInstalledPluginList()
+    /**
+     *  get a list of plugins under pear installation management.
+     *
+     *  @param  object  $pear   Ethna_PearWrapper object.
+     *  @param  string  $_type   plugin type
+     *  @return array   package list
+     *  @access private
+     *  @todo   deal with the package including some plugins.
+     */
+    function &_getInstalledPluginList(&$pear, $_type = null)
+    {
+        $pkg_list =& $pear->getInstalledPackageList();
+        if (Ethna::isError($pkg_list)) {
+            return $pkg_list;
+        }
+
+        $ret = array();
+
+        $plugin =& $pear->target_ctl->getPlugin();
+        $appid = $pear->target_ctl->getAppId();
+        $test_prefix = $pear->target == 'master' ? 'Ethna' : 'App';
+
+        foreach ($pkg_list as $pkg_name) {
+            list($prefix,, $type, $name) = explode('_', $pkg_name, 4);
+            if (($_type === null || $_type == $type) && $prefix == $test_prefix) {
+                list($class_name,,) = $plugin->getPluginNaming($type, $name, $appid);
+                $pkg_version = $pear->getVersion($pkg_name);
+                $pkg_state = $pear->getState($pkg_name);
+                $ret[$class_name] = array($type, $name, $pkg_name,
+                                          $pkg_version, $pkg_state);
+            }
+        }
+        return $ret;
+    }
+    // }}}
+
+    // {{{ _getFoundPluginList()
+    /**
+     *  get a list of plugins found from controller.
+     *  (a local plugin might be installed but still in only skelton.)
+     *
+     *  @param  object  $pear   Ethna_PearWrapper object.
+     *  @param  string  $_type   plugin type
+     *  @return array   package list
+     *  @access private
+     */
+    function &_getFoundPluginList(&$pear, $_type = null)
+    {
+        $ret = array();
+
+        $plugin =& $pear->target_ctl->getPlugin();
+        $type_list = $_type === null ? $plugin->searchAllPluginType() : array($_type);
+
+        foreach ($type_list as $type) {
+            $plugin->searchAllPluginSrc($type);
+            if (isset($plugin->src_registry[$type]) === false) {
+                continue;
+            }
+            foreach ($plugin->src_registry[$type] as $name => $src) {
+                if (empty($src)) {
+                    continue;
+                }
+                list($appid,, $type, $name) = explode('_', $src[0], 4);
+                if (($pear->target == 'master' && $appid == 'Ethna') || $appid != 'Ethna') {
+                    // XXX: src is private! ([0] is class name)
+                    $ret[$src[0]] = array($type, $name);
+                }
+            }
+        }
+        return $ret;
+    }
+    // }}}
+
+    // {{{ _sort
+    /**
+     *  sort callback method
+     */
+    function _sort($a, $b)
+    {
+        $cmp_type = strcmp($a[0], $b[0]);
+        if ($cmp_type !== 0) {
+            return $cmp_type;
+        }
+        $cmp_name = strcmp($a[1], $b[1]);
+        if ($cmp_name !== 0) {
+            return $cmp_name;
+        }
+        return 0;
+    }
+    // }}}
+
+    // {{{ getDescription()
+    /**
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+list local or master plugins. if type (case sensitive) not specified, list all plugins:
+    {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [-t|--type=type] [-v|--verbose]
+
+EOS;
+    }
+    // }}}
+
+    // {{{ getUsage()
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [-t|--type=type] [-v|--verbose]
+EOS;
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_MakePluginPackage.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_MakePluginPackage.php
new file mode 100644 (file)
index 0000000..8c4eb97
--- /dev/null
@@ -0,0 +1,299 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_MakePluginPackage.php
+ *
+ *  please go to http://ethna.jp/ethna-document-dev_guide-pearchannel.html
+ *  for more info.
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_PearWrapper.php';
+
+// {{{ Ethna_Plugin_Handle_MakePluginPackage
+/**
+ *  make-plugin-package handler.
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_MakePluginPackage extends Ethna_Plugin_Handle
+{
+    // {{{ perform()
+    /**
+     * @access public
+     */
+    function perform()
+    {
+        require_once 'PEAR/PackageFileManager.php';
+        require_once 'PEAR/PackageFileManager2.php';
+        require_once 'PEAR/PackageFileManager/File.php';
+
+        // 引数の評価
+        $args =& $this->_parseArgList();
+        if (Ethna::isError($args)) {
+            return $args;
+        }
+        list($ini, $skelfile, $workdir) = $args;
+
+        // パッケージを作るプラグインの target
+        $targets = array();
+        if ($ini['plugin']['master'] == true) {
+            $targets[] = 'master';
+        }
+        if ($ini['plugin']['local'] == true) {
+            $targets[] = 'local';
+        }
+
+        // 設定用の配列を用意
+        $setting = array();
+
+        // {{{ master と local で共通の設定
+        // プラグイン名
+        $ptype = $ini['plugin']['type'];
+        $pname = $ini['plugin']['name'];
+
+        // パッケージの説明
+        $setting['channel']     = $ini['package']['channel'];
+        $setting['summary']     = $ini['package']['summary'];
+        $setting['description'] = $ini['package']['description'];
+
+        // リリースの説明
+        $setting['version']     = $ini['release']['version'];
+        $setting['state']       = $ini['release']['state'];
+        $setting['notes']       = $ini['release']['notes'];
+
+        // メンテナ
+        $mnts = $ini['maintainers'];
+        for ($i = 1; ; $i++) {
+            if (isset($mnts["name$i"]) == false
+                || isset($mnts["user$i"]) == false
+                || isset($mnts["email$i"]) == false) {
+                break;
+            }
+            $setting['maintainers'][] = array(
+                'user'   => $mnts["user$i"],
+                'name'   => $mnts["name$i"],
+                'email'  => $mnts["email$i"],
+                'role'   => isset($mnts["role$i"]) ? $mnts["role$i"] : 'lead',
+                'active' => isset($mnts["active$i"]) ? $mnts["active$i"] == true: 'yes',
+                );
+        }
+
+        // ライセンス
+        $setting['license']['name'] = $ini['license']['name'];
+        if (isset($ini['license']['uri'])) {
+            $setting['license']['uri'] = $ini['license']['uri'];
+        }
+        // }}}
+
+        // まるまるコピー :-p
+        $setting = array('master' => $setting, 'local' => $setting);
+
+        // {{{ master と local で異なる部分
+        // パッケージ名
+        $setting['master']['pkgname'] = "Ethna_Plugin_{$ptype}_{$pname}";
+        $setting['local'] ['pkgname'] = "App_Plugin_{$ptype}_{$pname}";
+
+        // プラグインのファイル名
+        $setting['master']['filename'] = "Ethna_Plugin_{$ptype}_{$pname}.php";
+        $setting['local'] ['filename'] = "skel.plugin.{$ptype}_{$pname}.php";
+
+        // 入力ファイルの置換マクロ
+        $setting['master']['macro'] = array(
+            // package 時に置換
+            'application_id'    => 'Ethna',
+            'project_id'        => 'Ethna',
+            );
+        $setting['local']['macro'] = array(
+            // install 時に置換
+            );
+
+        // setOptins($config) 時に merge する設定
+        $setting['master']['config'] = array(
+            'baseinstalldir' => "Ethna/class/Plugin/{$ptype}",
+            );
+        $setting['local']['config'] = array(
+            'baseinstalldir' => '.',
+            );
+
+        // 任意に $packagexml->doSomething() するための callback
+        $setting['master']['callback'] = array(
+            'addPackageDepWithChannel'
+                => array('optional', 'ethna', 'pear.ethna.jp', '2.3.0'),
+            );
+        $setting['local']['callback'] = array(
+            // local 用のパッケージを master にインストールさせないための conflict
+            'addConflictingPackageDepWithChannel'
+                => array('pear', 'pear.php.net'),
+            );
+        // }}}
+
+
+        // パッケージ作成
+        $this->pear =& new Ethna_PearWrapper();
+        $this->pear->init('master');
+        foreach ($targets as $target) {
+            $this->_makePackage($skelfile, $setting[$target], "$workdir/$target");
+        }
+    }
+    // }}}
+
+    // {{{ _makePackage()
+    /**
+     * @access private
+     */
+    function &_makePackage($skelfile, $setting, $workdir)
+    {
+        if (Ethna_Util::mkdir($workdir, 0755) === false) {
+            return Ethna::raiseError("failed making working dir: $workdir.");
+        }
+
+        // プラグインの元ファイルを作成
+        $rfp = fopen($skelfile, "r");
+        if ($rfp == false) {
+            return Ethna::raiseError("failed open skelton file: $skelfile.");
+        }
+        $outputfile = $setting['filename'];
+        $wfp = fopen("$workdir/$outputfile", "w");
+        if ($rfp == false) {
+            fclose($rfp);
+            return Ethna::raiseError("failed creating working file: $outputfile.");
+        }
+        for (;;) {
+            $s = fread($rfp, 4096);
+            if (strlen($s) == 0) {
+                break;
+            }
+            foreach ($setting['macro'] as $k => $v) {
+                $s = preg_replace("/{\\\$$k}/", $v, $s);
+            }
+            fwrite($wfp, $s);
+        }
+        fclose($wfp);
+        fclose($rfp);
+
+        // package.xml を作る
+        $pkgconfig = array(
+            'packagedirectory' => $workdir,
+            'outputdirectory' => $workdir,
+            'ignore' => array('CVS/', '.cvsignore', '.svn/',
+                              'package.xml', 'package.ini', $setting['pkgname'].'-*.tgz'),
+            'filelistgenerator' => 'file',
+            'changelogoldtonew' => false,
+            );
+
+        $packagexml =& new PEAR_PackageFileManager2();
+
+        $pkgconfig = array_merge($pkgconfig, $setting['config']);
+        $packagexml->setOptions($pkgconfig);
+
+        $packagexml->setPackage($setting['pkgname']);
+        $packagexml->setSummary($setting['summary']);
+        $packagexml->setNotes($setting['notes']);
+        $packagexml->setDescription($setting['description']);
+        $packagexml->setChannel($setting['channel']);
+        $packagexml->setAPIVersion($setting['version']);
+        $packagexml->setReleaseVersion($setting['version']);
+        $packagexml->setReleaseStability($setting['state']);
+        $packagexml->setAPIStability($setting['state']);
+        $packagexml->setPackageType('php');
+        foreach ($setting['maintainers'] as $m) {
+            $packagexml->addMaintainer($m['role'], $m['user'], $m['name'],
+                                       $m['email'], $m['active']);
+        }
+        $packagexml->setLicense($setting['license']['name'],
+                                $setting['license']['uri']);
+
+        $packagexml->addRole('css', 'php');
+        $packagexml->addRole('tpl', 'php');
+        $packagexml->addRole('ethna', 'php');
+        $packagexml->addRole('sh', 'script');
+        $packagexml->addRole('bat', 'script');
+
+        $packagexml->setPhpDep('4.1.0');
+        $packagexml->setPearinstallerDep('1.3.5');
+
+        $packagexml->generateContents();
+
+        foreach ($setting['callback'] as $method => $params) {
+            $r = call_user_func_array(array(&$packagexml, $method), $params);
+        }
+
+        $r = $packagexml->writePackageFile();
+        if (PEAR::isError($r)) {
+            return Ethna::raiseError($r->getMessage, $r->getCode());
+        }
+
+        // package を作る
+        $r = $this->pear->_run('package', array(), array("$workdir/package.xml"));
+        if (PEAR::isError($r)) {
+            return Ethna::raiseError($r->getMessage, $r->getCode());
+        }
+
+        if (Ethna_Util::purgeDir($workdir) === false) {
+            return Ethna::raiseError("failed cleaning up working dir: $workdir.");
+        }
+    }
+    // }}}
+
+    // {{{ _parseArgList()
+    /**
+     * @access private
+     */
+    function &_parseArgList()
+    {
+        $r =& $this->_getopt(array('inifile=', 'skelfile=', 'workdir='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+
+        // inifile
+        if (isset($opt_list['inifile'])
+            && is_readable(end($opt_list['inifile']))) {
+            $ini = parse_ini_file(end($opt_list['inifile']), true);
+        } else {
+            return Ethna::raiseError('give a valid inifile.');
+        }
+
+        // skelfile
+        if (isset($opt_list['skelfile'])
+            && is_readable(end($opt_list['skelfile']))) {
+            $skelfile = end($opt_list['skelfile']);
+        } else {
+            return Ethna::raiseError('give a valid filename of plugin skelton file.');
+        }
+
+        // workdir
+        if (isset($opt_list['workdir'])) {
+            $workdir = end($opt_list['workdir']);
+        } else {
+            $workdir = getcwd();
+        }
+
+        return array($ini, $skelfile, $workdir);
+    }
+    // }}}
+
+    // {{{ getDescription()
+    /**
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+make plugin package:
+    {$this->id} [-i|--inifile=file] [-s|--skelfile=file] [-w|--workdir=dir]
+
+EOS;
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_PearLocal.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_PearLocal.php
new file mode 100644 (file)
index 0000000..25cfc3a
--- /dev/null
@@ -0,0 +1,257 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_PearLocal.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once 'PEAR/Config.php';
+require_once ETHNA_BASE . '/class/Ethna_PearWrapper.php';
+
+// {{{ Ethna_PearConfig_Local
+/**
+ *  Special Class for Pear Install Handler.
+ *  This class should be instantiated by ONLY Ethna_Plugin_Handle_PearLocal.
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     private
+ *  @package    Ethna
+ */
+class Ethna_PearConfig_Local extends Ethna_PearWrapper 
+{
+
+    // {{{ _setLocalConfig 
+    /**
+     *  config for local.
+     *
+     *  @return true|Ethna_Error
+     *  @access private 
+     */
+    function &_setLocalConfig()
+    {
+        $true = true;
+
+        // determine dirs
+        $base = $this->target_ctl->getBaseDir();
+        $bin  = $this->target_ctl->getDirectory('bin');
+        $tmp  = $this->target_ctl->getDirectory('tmp');
+        $lib  = "{$base}/lib";
+        $dirs = array(
+                'php_dir'       => "$lib",
+                'bin_dir'       => "{$base}/bin",
+                'cache_dir'     => "{$tmp}/.pear/cache",
+                'download_dir'  => "{$lib}/.pear/download",
+                'temp_dir'      => "{$lib}/.pear/temp",
+                'doc_dir'       => "{$lib}/.pear/doc",
+                'ext_dir'       => "{$lib}/.pear/ext",
+                'data_dir'      => "{$lib}/.pear/data",
+                'test_dir'      => "{$lib}/.pear/test",
+                );
+
+        $default_pearrc = "{$base}"
+                        . DIRECTORY_SEPARATOR
+                        . "lib"
+                        . DIRECTORY_SEPARATOR
+                        . "pear.conf";
+        $app_config = $this->target_ctl->getConfig();
+        $app_pearrc = $app_config->get('app_pear_local_config');
+        $pearrc = (empty($app_pearrc))
+                ? $default_pearrc
+                : "{$base}/$app_pearrc";
+        $this->conf_file = $pearrc;
+        $this->config =& PEAR_Config::singleton($pearrc);
+
+        // read local .pearrc if exists.
+        if (is_file($pearrc) && is_readable($pearrc)) {
+            $this->config->readConfigFile($pearrc);
+        }
+
+        // set dirs to config
+        foreach ($dirs as $key => $dir) {
+            $_dir = $this->config->get($key, 'user');
+            if (!isset($_dir)) {
+                if (is_dir($dir) == false) {
+                    Ethna_Util::mkdir($dir, 0755);
+                }
+                $this->config->set($key, $dir);
+            }
+        }
+
+        if ($this->channel == 'dummy') {
+            $default_channel = $this->config->get('default_channel', 'user');
+            $this->channel = (empty($default_channel))
+                           ? 'pear.php.net'
+                           : $default_channel;
+        }
+
+        // setup channel
+        $reg =& $this->config->getRegistry();
+        if ($reg->channelExists($this->channel) == false) {
+            $ret =& $this->doChannelDiscover();
+            if (Ethna::isError($ret)) {
+                return $ret;
+            }
+        }
+        $this->config->set('default_channel', $this->channel);
+
+        // write local .pearrc
+        $this->config->writeConfigFile($pearrc);
+
+        return $true;
+    }
+    // }}}
+
+    // {{{ getConfFile 
+    /**
+     *    return local config filename.
+     */
+     function getConfFile()
+     {
+         return $this->conf_file;
+
+     }
+     // }}}
+}
+// }}}
+
+// {{{ Ethna_Plugin_Handle_PearLocal
+/**
+ *  pear package install handler
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_PearLocal extends Ethna_Plugin_Handle
+{
+    // {{{ _parseArgList() 
+    /**
+     * @access private
+     */
+    function &_parseArgList()
+    {
+        $r =& $this->_getopt(array('basedir=', 'channel='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        list($opt_list, $arg_list) = $r;
+        $ret = array();
+
+        // options
+        if (isset($opt_list['basedir'])) {
+            $ret['basedir'] = end($opt_list['basedir']);
+        }
+        if (isset($opt_list['channel'])) {
+            $ret['channel'] = end($opt_list['channel']);
+        }
+
+        // arguments
+        $ret['pear_args'] = $arg_list;
+
+        return $ret;
+    }
+    // }}}
+
+    // {{{ perform()
+    /**
+     *  @access public
+     *  @todo   deal with the package including some plugins.
+     */
+    function perform()
+    {
+        $true = true;
+
+        //   check arguments.
+        $args =& $this->_parseArgList();
+        if (Ethna::isError($args)) {
+            return Ethna::raiseError(
+                $args->getMessage(),
+                'usage'
+            );
+        }
+
+        $basedir = isset($args['basedir']) ? realpath($args['basedir']) : getcwd();
+        $channel = isset($args['channel']) ? $args['channel'] : 'dummy';
+
+        $pear_local =& new Ethna_PearConfig_Local();
+        $r =& $pear_local->init('local', $basedir, $channel);
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        //    build command string.
+        $pear_cmds = $args['pear_args'];
+        $pear_bin = (ETHNA_OS_WINDOWS)
+                  ? getenv('PHP_PEAR_BIN_DIR') . DIRECTORY_SEPARATOR . 'pear.bat'
+                  : (PHP_BINDIR . DIRECTORY_SEPARATOR . 'pear');
+        $local_conf_file = $pear_local->getConfFile();
+        array_unshift(
+            $pear_cmds,
+            $pear_bin,
+            '-c',
+            $local_conf_file
+        );
+        if (ETHNA_OS_WINDOWS) {
+            foreach($pear_cmds as $key => $value) {
+                $pear_cmds[$key] = (strpos($value, ' ') !== false)
+                                 ? ('"' . $value . '"')
+                                 : $value;
+            }
+        }
+        $command_str = implode(' ', $pear_cmds);
+
+        //   finally exec pear command.
+        if (ETHNA_OS_WINDOWS) {
+            $tmp_dir_name ="ethna_tmp_dir";
+            Ethna_Util::mkdir($tmp_dir_name, 0777);
+            $tmpnam = tempnam($tmp_dir_name, "temp") .'.bat';
+            $fp = fopen($tmpnam, 'w');
+            fwrite($fp, "@echo off\r\n");
+            fwrite($fp, $command_str . " 2>&1");
+            fclose ($fp);
+            system($tmpnam);
+            Ethna_Util::purgeDir($tmp_dir_name);
+        } else {
+            system($command_str);
+        }
+
+        return $true;
+    }
+    // }}}
+
+    // {{{ getDescription()
+    /**
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+install pear package to {base_dir}/lib, {base_dir}/bin ... :
+    {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [pear command ...]
+    for more pear command information, see "pear help"
+
+EOS;
+    }
+    // }}}
+
+    // {{{ getUsage()
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [pear command ...]
+    for more pear command information, see "pear help"
+
+EOS;
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_UninstallPlugin.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_UninstallPlugin.php
new file mode 100644 (file)
index 0000000..4a392ac
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_UninstallPlugin.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_PearWrapper.php';
+
+// {{{ Ethna_Plugin_Handle_UninstallPlugin
+/**
+ *  install-plugin handler
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_UninstallPlugin extends Ethna_Plugin_Handle
+{
+    // {{{ _parseArgList()
+    /**
+     * @access private
+     */
+    function &_parseArgList()
+    {
+        $r =& $this->_getopt(array('local', 'master', 'basedir=',
+                                   'channel=', 'pearopt='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+        $ret = array();
+
+        // options
+        $ret['target'] = isset($opt_list['master']) ? 'master' : 'local';
+        if (isset($opt_list['basedir'])) {
+            $ret['basedir'] = end($opt_list['basedir']);
+        }
+        if (isset($opt_list['channel'])) {
+            $ret['channel'] = end($opt_list['channel']);
+        }
+
+        // arguments
+        if (count($arg_list) == 2) {
+            $ret['type'] = $arg_list[0];
+            $ret['name'] = $arg_list[1];
+        }
+
+        return $ret;
+    }
+    // }}}
+
+    // {{{ perform()
+    /**
+     *  @access public
+     *  @todo   deal with the package including some plugins.
+     */
+    function perform()
+    {
+        $args =& $this->_parseArgList();
+        if (Ethna::isError($args)) {
+            return $args;
+        }
+
+        $pear =& new Ethna_PearWrapper();
+        if (isset($args['pearopt'])) {
+            $pear->setPearOpt($args['pearopt']);
+        }
+
+        if (isset($args['type']) && isset($args['name'])) {
+            // install from repository.
+            $target = isset($args['target']) ? $args['target'] : null;
+            $channel = isset($args['channel']) ? $args['channel'] : null;
+            $basedir = isset($args['basedir']) ? realpath($args['basedir']) : getcwd();
+            if ($target == 'master') {
+                $pkg_name = sprintf('Ethna_Plugin_%s_%s', $args['type'], $args['name']);
+            } else {
+                $pkg_name = sprintf('App_Plugin_%s_%s', $args['type'], $args['name']);
+            }
+
+            $r =& $pear->init($target, $basedir, $channel);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+            $pkg_name = $pear->getCanonicalPackageName($pkg_name);
+            if (Ethna::isError($pkg_name)) {
+                return $pkg_name;
+            }
+            $r =& $pear->doUninstall($pkg_name);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+
+        } else {
+            return Ethna::raiseError('invalid number of arguments', 'usage');
+        }
+
+        if ($target != 'master') {
+            list(,, $ctype, $cname) = explode('_', $pkg_name, 4);
+            $r = Ethna_Generator::remove('Plugin', $basedir, $ctype, $cname);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ getDescription()
+    /**
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+uninstall plugin:
+    {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [type name]
+
+EOS;
+    }
+    // }}}
+
+    // {{{
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [type name]
+EOS;
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_UpgradePlugin.php b/Idea_Plugin_Extlib/class/Plugin/Handle/Ethna_Plugin_Handle_UpgradePlugin.php
new file mode 100644 (file)
index 0000000..ab1ad73
--- /dev/null
@@ -0,0 +1,169 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_UpgradePlugin.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_PearWrapper.php';
+
+// {{{ Ethna_Plugin_Handle_UpgradePlugin
+/**
+ *  install-plugin handler
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Handle_UpgradePlugin extends Ethna_Plugin_Handle
+{
+    // {{{ _parseArgList()
+    /**
+     * @access private
+     */
+    function &_parseArgList()
+    {
+        $r =& $this->_getopt(array('local', 'master', 'state=',
+                                   'basedir=', 'channel=', 'pearopt='));
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+        list($opt_list, $arg_list) = $r;
+        $ret = array();
+
+        // options
+        $ret['target'] = isset($opt_list['master']) ? 'master' : 'local';
+        if (isset($opt_list['basedir'])) {
+            $ret['basedir'] = end($opt_list['basedir']);
+        }
+        if (isset($opt_list['channel'])) {
+            $ret['channel'] = end($opt_list['channel']);
+        }
+        if (isset($opt_list['state'])) {
+            $ret['state'] = end($opt_list['state']);
+        }
+
+        // arguments
+        if (count($arg_list) == 2) {
+            $ret['type'] = $arg_list[0];
+            $ret['name'] = $arg_list[1];
+        } else if (count($arg_list) == 1) {
+            $ret['pkg_file_or_url'] = $arg_list[0];
+        }
+
+        return $ret;
+    }
+    // }}}
+
+    // {{{ perform()
+    /**
+     *  @access public
+     *  @todo   deal with the package including some plugins.
+     */
+    function perform()
+    {
+        $args =& $this->_parseArgList();
+        if (Ethna::isError($args)) {
+            return $args;
+        }
+
+        $pear =& new Ethna_PearWrapper();
+        if (isset($args['pearopt'])) {
+            $pear->setPearOpt($args['pearopt']);
+        }
+
+        if (isset($args['pkg_file_or_url'])) {
+            // install from local tgz.
+            $pkg_file_or_url = $args['pkg_file_or_url'];
+            $pkg_name =& Ethna_PearWrapper::getPackageNameFromTgz($pkg_file_or_url);
+            if (Ethna::isError($pkg_name)) {
+                return $pkg_name;
+            }
+            list($appid,, $ctype, $cname) = explode('_', $pkg_name, 4);
+            $target = $appid == 'Ethna' ? 'master' : 'local';
+            $channel = isset($args['channel']) ? $args['channel'] : null;
+            $basedir = isset($args['basedir']) ? realpath($args['basedir']) : getcwd();
+
+            $r =& $pear->init($target, $basedir, $channel);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+            $r =& $pear->doUpgradeFromTgz($pkg_file_or_url, $pkg_name);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+
+        } else if (isset($args['type']) && isset($args['name'])) {
+            // install from repository.
+            $target = isset($args['target']) ? $args['target'] : null;
+            $channel = isset($args['channel']) ? $args['channel'] : null;
+            $basedir = isset($args['basedir']) ? realpath($args['basedir']) : getcwd();
+            $state = isset($args['state']) ? $args['state'] : null;
+            if ($target == 'master') {
+                $pkg_name = sprintf('Ethna_Plugin_%s_%s', $args['type'], $args['name']);
+            } else {
+                $pkg_name = sprintf('App_Plugin_%s_%s', $args['type'], $args['name']);
+            }
+
+            $r =& $pear->init($target, $basedir, $channel);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+            $r =& $pear->doUpgrade($pkg_name, $state);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+
+            $pkg_name = $pear->getCanonicalPackageName($pkg_name);
+            if (Ethna::isError($pkg_name)) {
+                return $pkg_name;
+            }
+
+        } else {
+            return Ethna::raiseError('invalid number of arguments', 'usage');
+        }
+
+        if ($target != 'master') {
+            list(,, $ctype, $cname) = explode('_', $pkg_name, 4);
+            $r = Ethna_Generator::generate('Plugin', $basedir, $ctype, $cname, true);
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ getDescription()
+    /**
+     *  @access public
+     */
+    function getDescription()
+    {
+        return <<<EOS
+upgrade plugin:
+    {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [type name|packagefile|packageurl]
+
+EOS;
+    }
+    // }}}
+
+    // {{{
+    /**
+     *  @access public
+     */
+    function getUsage()
+    {
+        return <<<EOS
+ethna {$this->id} [-c|--channel=channel] [-b|--basedir=dir] [-l|--local] [-m|--master] [type name|packagefile|packageurl]
+EOS;
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Alertmail.php b/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Alertmail.php
new file mode 100644 (file)
index 0000000..d8be237
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Logwriter_Alertmail.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Logwriter_Alertmail
+/**
+ *  ログ出力クラス(アラートメール)
+ *  Ethna_Logger にある _alert() をプラグインにしただけです。
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Logwriter_Alertmail extends Ethna_Plugin_Logwriter
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    array   アラート送信先メールアドレス */
+    var $mailaddress = array();
+
+    /**#@-*/
+
+    /**
+     *  ログオプションを設定する
+     *
+     *  @access public
+     *  @param  int     $option     ログオプション(LOG_FILE,LOG_FUNCTION...)
+     */
+    function setOption($option)
+    {
+        parent::setOption($option);
+        
+        if (isset($option['mailaddress'])) {
+            $this->mailaddress = preg_split('/\s*,\s*/',
+                                            $option['mailaddress'],
+                                            -1, PREG_SPLIT_NO_EMPTY);
+        }
+    }
+
+    /**
+     *  ログを出力する
+     *
+     *  @access public
+     *  @param  int     $level      ログレベル(LOG_DEBUG, LOG_NOTICE...)
+     *  @param  string  $message    ログメッセージ(+引数)
+     */
+    function log($level, $message)
+    {
+        if (count($this->mailaddress) == 0) {
+            return;
+        }
+
+        $prefix = $this->ident;
+        if (array_key_exists("pid", $this->option)) {
+            $prefix .= sprintf('[%d]', getmypid());
+        }
+        $prefix .= sprintf('(%s): ', $this->_getLogLevelName($level));
+        if (array_key_exists("function", $this->option) ||
+            array_key_exists("pos", $this->option)) {
+            $tmp = "";
+            $bt = $this->_getBacktrace();
+            if ($bt && $bt['function']
+                && array_key_exists("function", $this->option) ) {
+                $tmp .= $bt['function'];
+            }
+            if ($bt && array_key_exists("pos", $this->option) && $bt['pos']) {
+                $tmp .= $tmp ? sprintf('(%s)', $bt['pos']) : $bt['pos'];
+            }
+            if ($tmp) {
+                $prefix .= $tmp . ": ";
+            }
+        }
+
+        $this->_alert($prefix . $message . "\n");
+
+        return $prefix . $message;
+    }
+
+    /**
+     *  メールを送信する
+     *
+     *  @access protected
+     *  @param  string  $message    ログメッセージ
+     */
+    function _alert($message)
+    {
+        restore_error_handler();
+
+        $c =& Ethna_Controller::getInstance();
+        $appid = $c->getAppId();
+
+        $header = "Mime-Version: 1.0\n";
+        $header .= "Content-Type: text/plain; charset=ISO-2022-JP\n";
+        $header .= "X-Alert: " . $appid;
+        $subject = sprintf("[%s] alert (%s%s)\n", $appid,
+                           substr($message, 0, 12),
+                           strlen($message) > 12 ? "..." : "");
+
+        $message = sprintf("--- [log message] ---\n%s\n\n", $message);
+        if (function_exists("debug_backtrace")) {
+            $bt = debug_backtrace();
+            $message .= sprintf("--- [backtrace] ---\n%s\n", Ethna_Util::FormatBacktrace($bt));
+        }
+        
+        foreach ($this->mailaddress as $address) {
+            mail($address,
+                 $subject,
+                 mb_convert_encoding($message, "ISO-2022-JP"),
+                 $header);
+        }
+
+        set_error_handler("ethna_error_handler");
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Default.php b/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Default.php
new file mode 100644 (file)
index 0000000..e80348b
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Logwriter_Default.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Logwriter_Default
+/**
+ *  ログ出力基底クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Logwriter_Default extends Ethna_Plugin_Logwriter
+{
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Echo.php b/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Echo.php
new file mode 100644 (file)
index 0000000..17af742
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Logwriter_Echo.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Logwriter_Echo
+/**
+ *  ログ出力基底クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Logwriter_Echo extends Ethna_Plugin_Logwriter
+{
+    /**#@+
+     *  @access private
+     */
+
+    /**#@-*/
+
+    /**
+     *  ログを出力する
+     *
+     *  @access public
+     *  @param  int     $level      ログレベル(LOG_DEBUG, LOG_NOTICE...)
+     *  @param  string  $message    ログメッセージ(+引数)
+     */
+    function log($level, $message)
+    {
+        $c =& Ethna_Controller::getInstance();
+
+        $prefix = $this->ident;
+        if (array_key_exists("pid", $this->option)) {
+            $prefix .= sprintf('[%d]', getmypid());
+        }
+        $prefix .= sprintf($c->getGateway() != GATEWAY_WWW ? '(%s): ' : '(<b>%s</b>): ',
+            $this->_getLogLevelName($level)
+        );
+        if (array_key_exists("function", $this->option) ||
+            array_key_exists("pos", $this->option)) {
+            $tmp = "";
+            $bt = $this->_getBacktrace();
+            if ($bt && array_key_exists("function", $this->option) && $bt['function']) {
+                $tmp .= $bt['function'];
+            }
+            if ($bt && array_key_exists("pos", $this->option) && $bt['pos']) {
+                $tmp .= $tmp ? sprintf('(%s)', $bt['pos']) : $bt['pos'];
+            }
+            if ($tmp) {
+                $prefix .= $tmp . ": ";
+            }
+        }
+
+        $br = $c->getGateway() != GATEWAY_WWW ? "" : "<br />";
+        echo($prefix . $message . $br . "\n");
+
+        return $prefix . $message;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_File.php b/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_File.php
new file mode 100644 (file)
index 0000000..97af332
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Logwriter_File.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Logwriter_File
+/**
+ *  ログ出力クラス(File)
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Logwriter_File extends Ethna_Plugin_Logwriter
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    int     ログファイルハンドル */
+    var $fp;
+
+    /** @var    int     ログファイルパーミッション */
+    var $mode = 0666;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_Plugin_Logwriter_Fileクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_Plugin_Logwriter_File()
+    {
+        $this->fp = null;
+    }
+
+    /**
+     *  ログオプションを設定する
+     *
+     *  @access public
+     *  @param  int     $option     ログオプション(LOG_FILE,LOG_FUNCTION...)
+     */
+    function setOption($option)
+    {
+        parent::setOption($option);
+        
+        if (isset($option['file'])) {
+            $this->file = $option['file'];
+        } else {
+            $this->file = $this->_getLogFile();
+        }
+
+        if (isset($option['mode'])) {
+            $this->mode = $option['mode'];
+        }
+    }
+
+    /**
+     *  ログ出力を開始する
+     *
+     *  @access public
+     */
+    function begin()
+    {
+        $this->fp = fopen($this->file, 'a');
+        $st = fstat($this->fp);
+        if (function_exists("posix_getuid") && posix_getuid() == $st[4]) {
+            chmod($this->file, intval($this->mode, 8));
+        }
+    }
+
+    /**
+     *  ログを出力する
+     *
+     *  @access public
+     *  @param  int     $level      ログレベル(LOG_DEBUG, LOG_NOTICE...)
+     *  @param  string  $message    ログメッセージ(+引数)
+     */
+    function log($level, $message)
+    {
+        if ($this->fp == null) {
+            return;
+        }
+
+        $prefix = strftime('%Y/%m/%d %H:%M:%S ') . $this->ident;
+        if (array_key_exists("pid", $this->option)) {
+            $prefix .= sprintf('[%d]', getmypid());
+        }
+        $prefix .= sprintf('(%s): ', $this->_getLogLevelName($level));
+        if (array_key_exists("function", $this->option) ||
+            array_key_exists("pos", $this->option)) {
+            $tmp = "";
+            $bt = $this->_getBacktrace();
+            if ($bt && array_key_exists("function", $this->option) && $bt['function']) {
+                $tmp .= $bt['function'];
+            }
+            if ($bt && array_key_exists("pos", $this->option) && $bt['pos']) {
+                $tmp .= $tmp ? sprintf('(%s)', $bt['pos']) : $bt['pos'];
+            }
+            if ($tmp) {
+                $prefix .= $tmp . ": ";
+            }
+        }
+        fwrite($this->fp, $prefix . $message . "\n");
+
+        return $prefix . $message;
+    }
+
+    /**
+     *  ログ出力を終了する
+     *
+     *  @access public
+     */
+    function end()
+    {
+        if ($this->fp) {
+            fclose($this->fp);
+            $this->fp = null;
+        }
+    }
+
+    /**
+     *  ログファイルの書き出し先を取得する(ログファシリティに
+     *  LOG_FILEが指定されている場合のみ有効)
+     *
+     *  ログファイルの書き出し先を変更したい場合はこのメソッドを
+     *  オーバーライドします
+     *
+     *  @access protected
+     *  @return string  ログファイルの書き出し先
+     */
+    function _getLogFile()
+    {
+        $controller =& Ethna_Controller::getInstance();
+
+        if (array_key_exists("dir", $this->option)) {
+            $dir = $this->option['dir'];
+        } else {
+            $dir = $controller->getDirectory('log');
+        }
+
+        return sprintf('%s/%s.log',
+            $dir,
+            strtolower($controller->getAppid())
+        );
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Syslog.php b/Idea_Plugin_Extlib/class/Plugin/Logwriter/Ethna_Plugin_Logwriter_Syslog.php
new file mode 100644 (file)
index 0000000..f7aa921
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Logwriter_Syslog.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Logwriter_Syslog
+/**
+ *  ログ出力クラス(Syslog)
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Logwriter_Syslog extends Ethna_Plugin_Logwriter
+{
+    /**
+     *  ログ出力を開始する
+     *
+     *  @access public
+     */
+    function begin()
+    {
+        // syslog用オプションのみを指定
+        if (array_key_exists("pid", $this->option)) {
+            $option = $this->option & (LOG_PID);
+        }
+        openlog($this->ident, $option, $this->facility);
+    }
+
+    /**
+     *  ログを出力する
+     *
+     *  @access public
+     *  @param  int     $level      ログレベル(LOG_DEBUG, LOG_NOTICE...)
+     *  @param  string  $message    ログメッセージ(+引数)
+     */
+    function log($level, $message)
+    {
+        $prefix = sprintf('%s: ', $this->_getLogLevelName($level));
+        if (array_key_exists("function", $this->option) ||
+            array_key_exists("pos", $this->option)) {
+            $tmp = "";
+            $bt = $this->_getBacktrace();
+            if ($bt && array_key_exists("function", $this->option) && $bt['function']) {
+                $tmp .= $bt['function'];
+            }
+            if ($bt && array_key_exists("pos", $this->option) && $bt['pos']) {
+                $tmp .= $tmp ? sprintf('(%s)', $bt['pos']) : $bt['pos'];
+            }
+            if ($tmp) {
+                $prefix .= $tmp . ": ";
+            }
+        }
+        syslog($level, $prefix . $message);
+
+        return $prefix . $message;
+    }
+
+    /**
+     *  ログ出力を終了する
+     *
+     *  @access public
+     */
+    function end()
+    {
+        closelog();
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/block.form.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/block.form.php
new file mode 100644 (file)
index 0000000..b3ae1b4
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+/**
+ *  smarty block:フォームタグ出力プラグイン
+ */
+function smarty_block_form($params, $content, &$smarty, &$repeat)
+{
+    if ($repeat) {
+        // {form}: ブロック内部に進む前の処理
+
+        // 配列指定のフォームヘルパ用カウンタをリセットする
+        $c =& Ethna_Controller::getInstance();
+        $view =& $c->getView();
+        $view->resetFormCounter();
+
+        // {form default=... }
+        if (isset($params['default']) === false) {
+            // 指定なしのときは $form を使う
+            // 1テンプレートに複数 {form} を指定する場合は、
+            // default を指定することが必要
+            $af =& $c->getActionForm();
+
+            // c.f. http://smarty.net/manual/en/plugins.block.functions.php
+            $smarty->_tag_stack[count($smarty->_tag_stack)-1][1]['default']
+                =& $af->getArray(false);
+        }
+
+        // {form name=... }
+        // 複数 {form} が置かれた場合に、それぞれを識別する役割を果たす
+        if (isset($params['name']) === false) {
+            // c.f. http://smarty.php.net/manual/en/plugins.block.functions.php
+            $smarty->_tag_stack[count($smarty->_tag_stack)-1][1]['name']
+                = 'default';
+        }
+
+        // 動的フォームヘルパを呼ぶ
+        if (isset($params['ethna_action'])) {
+            $ethna_action = $params['ethna_action'];
+            $view =& $c->getView();
+            $view->addActionFormHelper($ethna_action, true);
+        }  
+
+        // ここで返す値は出力されない
+        return '';
+
+    } else {
+        // {/form}: ブロック全体を出力
+
+        $c =& Ethna_Controller::getInstance();
+        $view =& $c->getView();
+        if ($view === null) {
+            return null;
+        }
+
+        // {form ethna_action=... }
+        if (isset($params['ethna_action'])) {
+            $ethna_action = $params['ethna_action'];
+            unset($params['ethna_action']);
+
+            $view->addActionFormHelper($ethna_action);
+            $hidden = $c->getActionRequest($ethna_action, 'hidden');
+            $content = $hidden . $content;
+        }
+
+        //  {form name=... }
+        //  指定された場合は、submitされた {form}を識別する
+        //  id をhiddenタグで指定する
+        $name = $params['name'];
+        unset($params['name']);
+        if ($name != 'default') {
+            $name_hidden = sprintf('<input type="hidden" name="ethna_fid" value="%s" />',
+                                   htmlspecialchars($name, ENT_QUOTES)
+                           );
+            $content = $name_hidden . $content;
+        }
+
+        // enctype の略称対応
+        if (isset($params['enctype'])) {
+            if ($params['enctype'] == 'file'
+                || $params['enctype'] == 'multipart') {
+                $params['enctype'] = 'multipart/form-data';
+            } else if ($params['enctype'] == 'url') {
+                $params['enctype'] = 'application/x-www-form-urlencoded';
+            }
+        }
+
+        // defaultはもう不要
+        if (isset($params['default'])) {
+            unset($params['default']);
+        }
+
+        // $contentを囲む<form>ブロック全体を出力
+        return $view->getFormBlock($content, $params);
+    }
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.checkbox_list.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.checkbox_list.php
new file mode 100644 (file)
index 0000000..909f782
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/**
+ *  smarty function:チェックボックスフィルタ関数(配列対応)
+ *
+ *  @param  string  $form   チェックボックスに渡されたフォーム値
+ *  @param  string  $key    評価対象の配列インデックス
+ *  @param  string  $value  評価値
+ *  @deprecated
+ */
+function smarty_function_checkbox_list($params, &$smarty)
+{
+    extract($params);
+
+    if (isset($key) == false) {
+        $key = null;
+    }
+    if (isset($value) == false) {
+        $value = null;
+    }
+    if (isset($checked) == false) {
+        $checked = "checked";
+    }
+
+    if (is_null($key) == false) {
+        if (isset($form[$key])) {
+            if (is_null($value)) {
+                print $checked;
+            } else {
+                if (strcmp($form[$key], $value) == 0) {
+                    print $checked;
+                }
+            }
+        }
+    } else if (is_null($value) == false) {
+        if (is_array($form)) {
+            if (in_array($value, $form)) {
+                print $checked;
+            }
+        } else {
+            if (strcmp($value, $form) == 0) {
+                print $checked;
+            }
+        }
+    }
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.csrfid.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.csrfid.php
new file mode 100644 (file)
index 0000000..3c94dff
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ *  smarty function: 正当なポストであることを保証するIDを出力する
+ *
+ *  sample:
+ *  <code>
+ *  {csrfid}
+ *  </code>
+ *  <code>
+ *  <input type="hidden" name="csrfid" value="a0f24f75e...e48864d3e">
+ *  </code>
+ *
+ *  @param  string  $type   表示タイプ("get" or "post"−デフォルト="post")
+ *  @see    isRequestValid
+ */
+function smarty_function_csrfid($params, &$smarty)
+{
+    $c =& Ethna_Controller::getInstance();
+    $name = $c->config->get('csrf');
+    if (is_null($name)) {
+        $name = 'Session';
+    }
+    $plugin =& $c->getPlugin();
+    $csrf = $plugin->getPlugin('Csrf', $name);
+    $csrfid = $csrf->get();
+    $token_name = $csrf->getName();
+    if (isset($params['type']) && $params['type'] == 'get') {
+        return sprintf("%s=%s", $token_name, $csrfid);
+    } else {
+        return sprintf("<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n", $token_name, $csrfid);
+    }
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.form_input.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.form_input.php
new file mode 100644 (file)
index 0000000..6d62936
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+/**
+ *  smarty function:フォームタグ生成
+ *
+ *  @param  string  $name   フォーム項目名
+ */
+function smarty_function_form_input($params, &$smarty)
+{
+    // name
+    if (isset($params['name'])) {
+        $name = $params['name'];
+        unset($params['name']);
+    } else {
+        return null;
+    }
+
+    // view object
+    $c =& Ethna_Controller::getInstance();
+    $view =& $c->getView();
+    if ($view === null) {
+        return null;
+    }
+
+    // 現在の{form_input}を囲むform blockがあればパラメータを取得しておく
+    $block_params = null;
+    for ($i = count($smarty->_tag_stack); $i >= 0; --$i) {
+        if ($smarty->_tag_stack[$i][0] === 'form') {
+            $block_params = $smarty->_tag_stack[$i][1];
+            break;
+        }
+    }
+
+    // action
+    $action = null;
+    if (isset($params['action'])) {
+        $action = $params['action'];
+        unset($params['action']);
+    } else if (isset($block_params['ethna_action'])) {
+        $action = $block_params['ethna_action'];
+    }
+    if ($action !== null) {
+        $view->addActionFormHelper($action, true);
+    }
+
+    // default
+    if (isset($params['default'])) {
+        // {form_input default=...}が指定されていればそのまま
+
+    } else if (isset($block_params['default'])) {
+        // 外側の {form default=...} ブロック
+        if (isset($block_params['default'][$name])) {
+            $params['default'] = $block_params['default'][$name];
+        }
+    }
+
+    // 現在のアクションで受け取ったフォーム値を補正する
+    // 補正できるのは、以下の場合のみ
+    //
+    // 1. {form name=...} の値が設定されていないか、submitされていないとき
+    // 2. {form name=...} の値と、submitされたそれが等しいとき
+    $af =& $c->getActionForm();
+    $val = $af->get($name);
+    $form_id = $block_params['name'];     // {form name=... }
+    $cur_form_id = $af->get('ethna_fid'); // submitされたフォームID
+    $can_fill = ($cur_form_id == null
+              || $form_id == null
+              || $form_id == $cur_form_id);
+    if ($can_fill && $val !== null) {
+        $params['default'] = $val;
+    }
+
+    return $view->getFormInput($name, $action, $params);
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.form_name.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.form_name.php
new file mode 100644 (file)
index 0000000..019acad
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/**
+ *  smarty function:フォーム表示名生成
+ *
+ *  @param  string  $name   フォーム項目名
+ */
+function smarty_function_form_name($params, &$smarty)
+{
+    // name
+    if (isset($params['name'])) {
+        $name = $params['name'];
+        unset($params['name']);
+    } else {
+        return null;
+    }
+
+    // view object
+    $c =& Ethna_Controller::getInstance();
+    $view =& $c->getView();
+    if ($view === null) {
+        return null;
+    }
+
+    // action
+    $action = null;
+    if (isset($params['action'])) {
+        $action = $params['action'];
+        unset($params['action']);
+    } else {
+        for ($i = count($smarty->_tag_stack); $i >= 0; --$i) {
+            if ($smarty->_tag_stack[$i][0] === 'form') {
+                if (isset($smarty->_tag_stack[$i][1]['ethna_action'])) {
+                    $action = $smarty->_tag_stack[$i][1]['ethna_action'];
+                }
+                break;
+            }
+        }
+    }
+    if ($action !== null) {
+        $view->addActionFormHelper($action);
+    }
+
+    return $view->getFormName($name, $action, $params);
+}
+
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.form_submit.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.form_submit.php
new file mode 100644 (file)
index 0000000..fc38c77
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+/**
+ *  smarty function:フォームのsubmitボタン生成
+ *
+ *  @param  string  $submit   フォーム項目名
+ */
+function smarty_function_form_submit($params, &$smarty)
+{
+    $c =& Ethna_Controller::getInstance();
+    $view =& $c->getView();
+    if ($view === null) {
+        return null;
+    }
+    return $view->getFormSubmit($params);
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.is_error.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.is_error.php
new file mode 100644 (file)
index 0000000..b4137f3
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+/**
+ *  smarty function:指定されたフォーム項目でエラーが発生しているかどうかを返す
+ *  NOTE: {if is_error('name')} は Ethna_Util.php の is_error() であって、
+ *        smarty_function_is_error() ではないことに注意
+ *
+ *  @param  string  $name   フォーム項目名
+ */
+function smarty_function_is_error($params, &$smarty)
+{
+    $name = isset($params['name']) ? $params['name'] : null;
+    return is_error($name);
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.message.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.message.php
new file mode 100644 (file)
index 0000000..d8cb70c
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+/**
+ *  smarty function:指定されたフォーム項目に対応するエラーメッセージを出力する
+ *
+ *  sample:
+ *  <code>
+ *  <input type="text" name="foo">{message name="foo"}
+ *  </code>
+ *  <code>
+ *  <input type="text" name="foo">fooを入力してください
+ *  </code>
+ *
+ *  @param  string  $name   フォーム項目名
+ */
+function smarty_function_message($params, &$smarty)
+{
+    if (isset($params['name']) === false) {
+        return '';
+    }
+
+    $c =& Ethna_Controller::getInstance();
+    $action_error =& $c->getActionError();
+
+    $message = $action_error->getMessage($params['name']);
+    if ($message === null) {
+        return '';
+    }
+
+    $id = isset($params['id']) ? $params['id']
+        : str_replace("_", "-", "ethna-error-" . $params['name']);
+    $class = isset($params['class']) ? $params['class'] : "ethna-error";
+    return sprintf('<span class="%s" id="%s">%s</span>',
+        $class, $id, htmlspecialchars($message));
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.select.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.select.php
new file mode 100644 (file)
index 0000000..69e971e
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+/**
+ *  smarty function:セレクトフィールド生成
+ *
+ *  sample:
+ *  <code>
+ *  $smarty->assign('hoge',
+ *                   array(
+ *                       '1' => array('name' => 'foo'),
+ *                       '2' => array('name' => 'bar')
+ *                   )
+ *  );
+ *  {select list=$hoge name="hoge" value="1" empty="-- please select --"}
+ *  </code>
+ *  <code>
+ *  <select name="hoge">
+ *    <option value="">-- please select --</option>
+ *    <option value="1" selected="selected">foo</option>
+ *    <option value="2">bar</option>
+ *  </select>
+ *  </code>
+ *
+ *  @param  array   $list   選択肢一覧
+ *  @param  string  $name   フォーム項目名
+ *  @param  string  $value  セレクトボックスに渡されたフォーム値
+ *  @param  string  $empty  空エントリ(「---選択して下さい---」等)
+ *  @deprecated
+ */
+function smarty_function_select($params, &$smarty)
+{
+    extract($params);
+
+    //  empty="...." を加えると、無条件に追加される
+    //  ない場合は追加されない
+    print "<select name=\"$name\">\n";
+    if ($empty) {
+        printf("<option value=\"\">%s</option>\n", $empty);
+    }
+    foreach ($list as $id => $elt) {
+        //    標準に合わせる
+        //    @see http://www.w3.org/TR/html401/interact/forms.html#adef-selected
+        //    @see http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd
+        //    @see http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-strict.dtd
+        //    @see http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-frameset.dtd
+        //    @see http://www.w3.org/TR/xhtml-modularization/abstract_modules.html#s_sformsmodule
+        printf("<option value=\"%s\" %s>%s</option>\n",
+               $id, $id == $value ? 'selected="selected"' : '', $elt['name']);
+    }
+    print "</select>\n";
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.uniqid.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.uniqid.php
new file mode 100644 (file)
index 0000000..a4985ce
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+/**
+ *  smarty function:ユニークIDを生成する(double postチェック用)
+ *
+ *  sample:
+ *  <code>
+ *  {uniqid}
+ *  </code>
+ *  <code>
+ *  <input type="hidden" name="uniqid" value="a0f24f75e...e48864d3e">
+ *  </code>
+ *
+ *  @param  string  $type   表示タイプ("get" or "post"−デフォルト="post")
+ *  @see    isDuplicatePost
+ */
+function smarty_function_uniqid($params, &$smarty)
+{
+    $uniqid = Ethna_Util::getRandom();
+    if (isset($params['type']) && $params['type'] == 'get') {
+        return "uniqid=$uniqid";
+    } else {
+        return "<input type=\"hidden\" name=\"uniqid\" value=\"$uniqid\" />\n";
+    }
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/function.url.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/function.url.php
new file mode 100644 (file)
index 0000000..c1d2799
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+/**
+ *  smarty function:url生成
+ */
+function smarty_function_url($params, &$smarty)
+{
+    $action = $path = $path_key = null;
+    $query = $params;
+
+    foreach (array('action', 'anchor', 'scheme') as $key) {
+        if (isset($params[$key])) {
+            ${$key} = $params[$key];
+        } else {
+            ${$key} = null;
+        }
+        unset($query[$key]);
+    }
+
+    $c =& Ethna_Controller::getInstance();
+    $config =& $c->getConfig();
+    $url_handler =& $c->getUrlHandler();
+    list($path, $path_key) = $url_handler->actionToRequest($action, $query);
+
+    if ($path != "") {
+        if (is_array($path_key)) {
+            foreach ($path_key as $key) {
+                unset($query[$key]);
+            }
+        }
+    } else {
+        $query = $url_handler->buildActionParameter($query, $action);
+    }
+    $query = $url_handler->buildQueryParameter($query);
+
+    $url = sprintf('%s%s', $config->get('url'), $path);
+
+    if (preg_match('|^(\w+)://(.*)$|', $url, $match)) {
+        if ($scheme) {
+            $match[1] = $scheme;
+        }
+        $match[2] = preg_replace('|/+|', '/', $match[2]);
+        $url = $match[1] . '://' . $match[2];
+    }
+
+    $url .= $query ? "?$query" : "";
+    $url .= $anchor ? "#$anchor" : "";
+
+    return $url;
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.checkbox.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.checkbox.php
new file mode 100644 (file)
index 0000000..96691ab
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ *  smarty modifier:チェックボックス用フィルタ
+ *
+ *  sample:
+ *  <code>
+ *  <input type="checkbox" name="test" {""|checkbox}>
+ *  <input type="checkbox" name="test" {"1"|checkbox}>
+ *  </code>
+ *  <code>
+ *  <input type="checkbox" name="test">
+ *  <input type="checkbox" name="test" checked="checked">
+ *  </code>
+ *
+ *  @param  string  $string チェックボックスに渡されたフォーム値(スカラーのみ)
+ *  @return string  $stringが空文字列あるいは0, null, false 以外の場合は"checked"
+ */
+function smarty_modifier_checkbox($string)
+{
+    if (is_scalar($string) && $string != "" && $string != "0") {
+        return 'checked="checked"';
+    }
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.count.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.count.php
new file mode 100644 (file)
index 0000000..6a7ad31
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+/**
+ *  smarty modifier:count()
+ *
+ *  count()関数のwrapper
+ *
+ *  sample:
+ *  <code>
+ *  $smarty->assign("array", array(1, 2, 3));
+ *
+ *  {$array|@count}
+ *  </code>
+ *  <code>
+ *  3
+ *  </code>
+ *
+ *  @param  array   $array  対象となる配列
+ *  @return int     配列の要素数
+ */
+function smarty_modifier_count($array)
+{
+    return count($array);
+}
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.filter.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.filter.php
new file mode 100644 (file)
index 0000000..544fa2b
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ *  smarty modifier:filter()
+ *
+ *  指定された連想配列のうち$keyで指定された要素のみを配列に再構成する
+ *
+ *  sample:
+ *  <code>
+ *  $smarty->assign("array", array(
+ *      array("foo" => 1, "bar" => 4),
+ *      array("foo" => 2, "bar" => 5),
+ *      array("foo" => 3, "bar" => 6),
+ *  ));
+ *
+ *  {$array|@filter:"foo"|@join:","}
+ *  </code>
+ *  <code>
+ *  1,2,3
+ *  </code>
+ *  
+ *  @param  array   $array  filter対象となる配列
+ *  @param  string  $key    抜き出して配列を構成する連想配列のキー
+ *  @return array   再構成された配列
+ */
+function smarty_modifier_filter($array, $key)
+{
+    if (is_array($array) == false) {
+        return $array;
+    }
+    $tmp = array();
+    foreach ($array as $v) {
+        if (isset($v[$key]) == false) {
+            continue;
+        }
+        $tmp[] = $v[$key];
+    }
+    return $tmp;
+}
+
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.form_value.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.form_value.php
new file mode 100644 (file)
index 0000000..e3439db
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ *  smarty modifier:フォーム値出力フィルタ
+ *
+ *  フォーム名を変数で指定してフォーム値を取得したい場合に使用する
+ *
+ *  sample:
+ *  <code>
+ *  $this->af->set('foo', 'bar');
+ *  $smarty->assign('key', 'foo');
+ *  {$key|form_value}
+ *  </code>
+ *  <code>
+ *  bar
+ *  </code>
+ *
+ *  @param  string  $string フォーム項目名
+ *  @return string  フォーム値
+ */
+function smarty_modifier_form_value($string)
+{
+    $c =& Ethna_Controller::getInstance();
+    $af =& $c->getActionForm();
+
+    $elts = explode(".", $string);
+    $r = $af->get($elts[0]);
+    for ($i = 1; $i < count($elts); $i++) {
+        $r = $r[$elts[$i]];
+    }
+
+    return htmlspecialchars($r, ENT_QUOTES);
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.i18n.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.i18n.php
new file mode 100644 (file)
index 0000000..20f8b2c
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ *  smarty modifier:i18nフィルタ
+ *
+ *  sample:
+ *  <code>
+ *  {"english"|i18n}
+ *  </code>
+ *  <code>
+ *  英語
+ *  </code>
+ *
+ *  @param  string  $string i18n処理対象の文字列
+ *  @return string  ロケールに対応したメッセージ
+ */
+function smarty_modifier_i18n($string)
+{
+    $c =& Ethna_Controller::getInstance();
+
+    $i18n =& $c->getI18N();
+
+    return $i18n->get($string);
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.join.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.join.php
new file mode 100644 (file)
index 0000000..c1217d4
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ *  smarty modifier:join()
+ *
+ *  join()関数のwrapper
+ *
+ *  sample:
+ *  <code>
+ *  $smarty->assign("array", array(1, 2, 3));
+ *
+ *  {$array|@join:":"}
+ *  </code>
+ *  <code>
+ *  1:2:3
+ *  </code>
+ *
+ *  @param  array   $array  join対象の配列
+ *  @param  string  $glue   連結文字列
+ *  @return string  連結後の文字列
+ */
+function smarty_modifier_join($array, $glue)
+{
+    if (is_array($array) == false) {
+        return $array;
+    }
+    return implode($glue, $array);
+}
+
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.number_format.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.number_format.php
new file mode 100644 (file)
index 0000000..9072be6
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+/**
+ *  smarty modifier:number_format()
+ *
+ *  number_format()関数のwrapper
+ *
+ *  sample:
+ *  <code>
+ *  {"12345"|number_format}
+ *  </code>
+ *  <code>
+ *  12,345
+ *  </code>
+ *
+ *  @param  string  $string フォーマット対象文字列
+ *  @return string  フォーマット済み文字列
+ */
+function smarty_modifier_number_format($string)
+{
+    if ($string === "" || $string == null) {
+        return "";
+    }
+    return number_format($string);
+}
+
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.select.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.select.php
new file mode 100644 (file)
index 0000000..bd1f6ff
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ *  smarty modifier:セレクトボックス用フィルタ
+ *
+ *  単純なセレクトボックスの場合はsmarty関数"select"を利用することで
+ *  タグを省略可能
+ *
+ *  sample:
+ *  <code>
+ *  $smarty->assign("form", 1);
+ *
+ *  <option value="1" {$form|select:"1"}>foo</option>
+ *  <option value="2" {$form|select:"2"}>bar</option>
+ *  </code>
+ *  <code>
+ *  <option value="1" selected="selected">foo</option>
+ *  <option value="2" >bar</option>
+ *  </code>
+ *
+ *  @param  string  $string セレクトボックスに渡されたフォーム値
+ *  @param  string  $value  <option>タグに指定されている値
+ *  @return string  $stringが$valueにマッチする場合は"selected"
+ */
+function smarty_modifier_select($string, $value)
+{
+    //    標準に合わせる
+    //    @see http://www.w3.org/TR/html401/interact/forms.html#adef-selected
+    //    @see http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd
+    //    @see http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-strict.dtd
+    //    @see http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-frameset.dtd
+    //    @see http://www.w3.org/TR/xhtml-modularization/abstract_modules.html#s_sformsmodule
+    if ($string == $value) {
+        return 'selected="selected"';
+    }
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.strftime.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.strftime.php
new file mode 100644 (file)
index 0000000..f242c9c
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ *  smarty modifier:strftime()
+ *
+ *  strftime()関数のwrapper
+ *
+ *  sample:
+ *  <code>
+ *  {"2004/01/01 01:01:01"|strftime:"%Y年%m月%d日"}
+ *  </code>
+ *  <code>
+ *  2004年01月01日
+ *  </code>
+ *
+ *  @param  string  $string フォーマット対象文字列
+ *  @param  string  $format 書式指定文字列(strftime()関数参照)
+ *  @return string  フォーマット済み文字列
+ */
+function smarty_modifier_strftime($string, $format)
+{
+    if ($string === "" || $string == null) {
+        return "";
+    }
+    return strftime($format, strtotime($string));
+}
+
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.truncate_i18n.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.truncate_i18n.php
new file mode 100644 (file)
index 0000000..9dd8a42
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ *  smarty modifier:文字列切り詰め処理(i18n対応)
+ *
+ *  sample:
+ *  <code>
+ *  {"日本語です"|truncate_i18n:7:"..."}
+ *  </code>
+ *  <code>
+ *  日本...
+ *  </code>
+ *
+ *  @param  int     $len        最大文字幅
+ *  @param  string  $postfix    末尾に付加する文字列
+ */
+function smarty_modifier_truncate_i18n($string, $len = 80, $postfix = "...")
+{
+    $ctl =& Ethna_Controller::getInstance();
+    $client_enc = $ctl->getClientEncoding();
+
+    //    いわゆる半角を単位にしてwrapする位置を測るため、いったん
+    //    EUC_JP に変換する
+    $euc_string = mb_convert_encoding($string, 'EUC_JP', $client_enc);
+
+    $r = mb_strimwidth($euc_string, 0, $len, $postfix, 'EUC_JP');
+
+    //    最後に、クライアントエンコーディングに変換
+    $r = mb_convert_encoding($r, $client_enc, 'EUC_JP');
+
+    return $r;
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.unique.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.unique.php
new file mode 100644 (file)
index 0000000..bf3fa47
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ *  smarty modifier:unique()
+ *
+ *  unique()関数のwrapper
+ *
+ *  sample:
+ *  <code>
+ *  $smarty->assign("array1", array("a", "a", "b", "a", "b", "c"));
+ *  $smarty->assign("array2", array(
+ *      array("foo" => 1, "bar" => 4),
+ *      array("foo" => 1, "bar" => 4),
+ *      array("foo" => 1, "bar" => 4),
+ *      array("foo" => 2, "bar" => 5),
+ *      array("foo" => 3, "bar" => 6),
+ *      array("foo" => 2, "bar" => 5),
+ *  ));
+ *
+ *  {$array1|@unique|@join:''}
+ *  {$array2|@unique:"foo"|@join:''}
+ *  </code>
+ *  <code>
+ *  abc
+ *  123
+ *  </code>
+ *
+ *  @param  array   $array  処理対象となる配列
+ *  @param  key     $key    処理対象となるキー(nullなら配列要素)
+ *  @return array   再構成された配列
+ */
+function smarty_modifier_unique($array, $key = null)
+{
+    if (is_array($array) == false) {
+        return $array;
+    }
+    if ($key != null) {
+        $tmp = array();
+        foreach ($array as $v) {
+            if (isset($v[$key]) == false) {
+                continue;
+            }
+            $tmp[] = $v[$key];
+        }
+        return array_unique($tmp);
+    } else {
+        return array_unique($array);
+    }
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.wordwrap_i18n.php b/Idea_Plugin_Extlib/class/Plugin/Smarty/modifier.wordwrap_i18n.php
new file mode 100644 (file)
index 0000000..fb69522
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ *  smarty modifier:文字列のwordwrap処理
+ *
+ *  sample:
+ *  <code>
+ *  {"あいうaえaおaかきaaaくけこ"|wordwrap_i18n:8}
+ *  </code>
+ *  <code>
+ *  あいうa
+ *  えaおaか
+ *  きaaaく
+ *  けこ
+ *  </code>
+ *
+ *  @param  string  $string wordwrapする文字列
+ *  @param  string  $break  改行文字
+ *  @param  int     $width  wordwrap幅(半角$width文字でwordwrapする)
+ *  @param  int     $indent インデント幅(半角$indent文字)
+ *                          数値を指定するが、はじめの行はインデントされない
+ *  @return string  wordwrap処理された文字列
+ */
+function smarty_modifier_wordwrap_i18n($string, $width, $break = "\n", $indent = 0)
+{
+    $ctl =& Ethna_Controller::getInstance();
+    $client_enc = $ctl->getClientEncoding();
+
+    //    いわゆる半角を単位にしてwrapする位置を測るため、いったん
+    //    EUC_JP に変換する
+    $euc_string = mb_convert_encoding($string, 'EUC_JP', $client_enc);
+
+    $r = "";
+    $i = "$break" . str_repeat(" ", $indent);
+    $tmp = $euc_string;
+    do {
+        $n = strpos($tmp, $break);
+        if ($n !== false && $n < $width) {
+            $s = substr($tmp, 0, $n);
+            $r .= $s . $i;
+            $tmp = substr($tmp, strlen($s) + strlen($break));
+            continue;
+        }
+
+        $s = mb_strimwidth($tmp, 0, $width, "", 'EUC_JP');
+
+        $n = strlen($s);
+        if ($n >= $width && $tmp{$n} != "" && $tmp{$n} != " ") {
+            while ((ord($s{$n-1}) & 0x80) == 0) {
+                if ($s{$n-1} == " " || $n == 0) {
+                    break;
+                }
+                $n--;
+            }
+        }
+        $s = substr($s, 0, $n);
+
+        $r .= $s . $i;
+        $tmp = substr($tmp, strlen($s));
+    } while (strlen($s) > 0);
+
+    $r = preg_replace('/\s+$/', '', $r);
+
+    //    最後に、クライアントエンコーディングに変換
+    $r = mb_convert_encoding($r, $client_enc, 'EUC_JP');
+
+    return $r;
+}
+
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Custom.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Custom.php
new file mode 100644 (file)
index 0000000..696c17f
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Custom.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Custom
+/**
+ *  customバリデータのラッパープラグイン
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Custom extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = true;
+
+    /**
+     *  customバリデータのラッパー
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $false = false;
+
+        $method_list = preg_split('/\s*,\s*/', $params['custom'], -1, PREG_SPLIT_NO_EMPTY);
+        if (is_array($method_list) == false) {
+            return $true;
+        }
+
+        foreach ($method_list as $method) {
+            if (method_exists($this->af, $method)) {
+                $ret =& $this->af->$method($name);
+                if (Ethna::isError($ret)) {
+                    // このエラーはすでに af::checkSomething() で ae::add()
+                    // してある
+                    return $false;
+                }
+            }
+        }
+
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_File.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_File.php
new file mode 100644 (file)
index 0000000..e09cb36
--- /dev/null
@@ -0,0 +1,196 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_File.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// UPLOAD_ERR_* が未定義の場合
+if (defined('UPLOAD_ERR_OK') == false) { // PHP 4.3.0
+    define('UPLOAD_ERR_OK', 0);
+}
+if (defined('UPLOAD_ERR_INI_SIZE') == false) { // PHP 4.3.0
+    define('UPLOAD_ERR_INI_SIZE', 1);
+}
+if (defined('UPLOAD_ERR_FORM_SIZE') == false) { // PHP 4.3.0
+    define('UPLOAD_ERR_FORM_SIZE', 2);
+}
+if (defined('UPLOAD_ERR_PARTIAL') == false) { // PHP 4.3.0
+    define('UPLOAD_ERR_PARTIAL', 3);
+}
+if (defined('UPLOAD_ERR_NO_FILE') == false) { // PHP 4.3.0
+    define('UPLOAD_ERR_NO_FILE', 4);
+}
+if (defined('UPLOAD_ERR_NO_TMP_DIR') == false) { // PHP 4.3.10, 5.0.3
+    define('UPLOAD_ERR_NO_TMP_DIR', 6);
+}
+if (defined('UPLOAD_ERR_CANT_WRITE') == false) { // PHP 5.1.0
+    define('UPLOAD_ERR_CANT_WRITE', 7);
+}
+
+// {{{ Ethna_Plugin_Validator_File
+/**
+ *  ファイルチェックプラグイン
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_File extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  アップロードされたファイルのチェックを行う
+     *  XXX: プラグインのエラーコードを修正する
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        if ($this->getFormType($name) != VAR_TYPE_FILE) {
+            return $true;
+        }
+
+        // そもそもアップロードされていない場合はスキップ
+        if ($var['error'] == UPLOAD_ERR_NO_FILE) {
+            return $true;
+        }
+
+
+        // エラーコードの検査
+        $msg = '';
+        switch ($var['error']) {
+        case UPLOAD_ERR_INI_SIZE: 
+            $msg = _et("Uploaded file size exceeds php.ini's upload_max_filesize directive.");
+            break;
+        case UPLOAD_ERR_FORM_SIZE:
+            $msg = _et('Uploaded File size exceeds MAX_FILE_SIZE specified in HTML Form.');
+            break;
+        case UPLOAD_ERR_PARTIAL:
+            $msg= _et('File was only uploaded patially.');
+            break;
+        case UPLOAD_ERR_NO_FILE:
+            $msg = _et('File was not uploaded.');
+            break;
+        case UPLOAD_ERR_NO_TMP_DIR:
+            $msg = _et('Temporary folder was not found.');
+            break;
+        case UPLOAD_ERR_CANT_WRITE:
+            $msg= _et('Could not write uploaded file to disk.');
+            break;
+        }
+        if ($msg != '') {
+            if (isset($params['error'])) {
+                $msg = $params['error'];
+            }
+            return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_FILE);
+        }
+
+
+        // tmp_name の検査
+        if (isset($var['tmp_name']) == false || is_uploaded_file($var['tmp_name']) == false) {
+            if (isset($params['error'])) {
+                $msg = $params['error'];
+            } else {
+                $msg = _et('invalid tmp_name.');
+            }
+            return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_FILE);
+        }
+
+        // size の検査
+        if (isset($params['size_max'])) {
+            $st = stat($var['tmp_name']);
+            if ($st[7] > $this->_getSizeAsBytes($params['size_max'])) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Uploaded file size must be less than %s.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_FILE, array($params['size_max']));
+            }
+        }
+        if (isset($params['size_min'])) {
+            $st = stat($var['tmp_name']);
+            if ($st[7] < $this->_getSizeAsBytes($params['size_min'])) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Uploaded file size must be more than %s.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_FILE, array($params['size_min']));
+            }
+        }
+
+
+        // type の検査
+        if (isset($params['type'])) {
+            $type_list = to_array($params['type']);
+            $posted_mime = explode('/', $var['type'], 2);
+            foreach ($type_list as $type) {
+                $wanted_mime = explode('/', $type, 2);
+                $test = (count($wanted_mime) == 1)
+                        ? (strcasecmp($wanted_mime[0], $posted_mime[0]) == 0)
+                : (strcasecmp($type, $var['type']) == 0);  
+                if ($test == true) {
+                    break;
+                }
+            }
+            if ($test == false) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Invalid file type.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_FILE);
+            }
+        }
+
+        // name(ファイル名)の検査
+        if (isset($params['name'])) {
+            $test = ($params['name']{0} == '/')
+                ? preg_match($params['name'], $var['name'])
+                : (strcmp($params['name'], $var['name']) == 0);
+            if ($test == false) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Invalid file name.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_FILE);
+            }
+        }
+
+        return $true;
+    }
+
+
+    function _getSizeAsBytes($size)
+    {
+        $unit = 1;
+        if (preg_match('/^([0-9]+)([mk])?(b(ytes?)?)?$/i', trim($size), $matches)) {
+            if (isset($matches[1])) {
+                $size = $matches[1];
+            }
+            if (isset($matches[2])) {
+                if (strtolower($matches[2]) === 'm') {
+                    $unit = 1048576;
+                } else if (strtolower($matches[2]) === 'k') {
+                    $unit = 1024;
+                }
+            }
+        }
+        return intval($matches[1]) * $unit;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Max.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Max.php
new file mode 100644 (file)
index 0000000..b69e513
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Max.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Max
+/**
+ *  最大値チェックプラグイン
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Max extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  最大値のチェックを行う
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['max']) == false || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        switch ($type) {
+            case VAR_TYPE_INT:
+                if ($var > $params['max']) {
+                    if (isset($params['error'])) {
+                        $msg = $params['error'];
+                    } else {
+                        $msg = _et('Please input less than %d(int) to {form}.');
+                    }
+                    return Ethna::raiseNotice($msg, E_FORM_MAX_INT, array($params['max']));
+                }
+                break;
+
+            case VAR_TYPE_FLOAT:
+                if ($var > $params['max']) {
+                    if (isset($params['error'])) {
+                        $msg = $params['error'];
+                    } else {
+                        $msg = _et('Please input less than %f(float) to {form}.');
+                    }
+                    return Ethna::raiseNotice($msg, E_FORM_MAX_FLOAT, array($params['max']));
+                }
+                break;
+
+            case VAR_TYPE_DATETIME:
+                $t_max = strtotime($params['max']);
+                $t_var = strtotime($var);
+                if ($t_var > $t_max) {
+                    if (isset($params['error'])) {
+                        $msg = $params['error'];
+                    } else {
+                        $msg = _et('Please input datetime value before %s to {form}.');
+                    }
+                    return Ethna::raiseNotice($msg, E_FORM_MAX_DATETIME, array($params['max']));
+                }
+                break;
+
+            case VAR_TYPE_FILE:
+                $st = stat($var['tmp_name']);
+                if ($st[7] > $params['max'] * 1024) {
+                    if (isset($params['error'])) {
+                        $msg = $params['error'];
+                    } else {
+                        $msg = _et('Please specify file whose size is less than %d KB to {form}.');
+                    }
+                    return Ethna::raiseNotice($msg, E_FORM_MAX_FILE, array($params['max']));
+                }
+                break;
+
+            case VAR_TYPE_STRING:
+
+                //
+                //  マルチバイトエンコーディングと、そうでない場合で
+                //  異なるプラグインを呼ぶ。
+                //
+                //  これは Ethna_Controller#client_encoding の値によ
+                //  って動きが決まる
+                //
+
+                $ctl = Ethna_Controller::getInstance();
+                $client_enc = $ctl->getClientEncoding();
+                $plugin = $this->backend->getPlugin();
+
+                //  select Plugin.
+                if (mb_enabled() && strcasecmp('UTF-8', $client_enc) == 0) {
+                    $plugin_name = 'Mbstrmax';
+                    $params['mbstrmax'] = $params['max'];
+                } elseif (strcasecmp('EUC-JP', $client_enc == 0)
+                       || strcasecmp('eucJP-win', $client_enc == 0)) {
+                    //  2.3.x compatibility
+                    $plugin_name = 'Strmaxcompat';
+                    $params['strmaxcompat'] = $params['max'];
+                } else {
+                    $plugin_name = 'Strmax';
+                    $params['strmax'] = $params['max'];
+                }
+                unset($params['max']);
+
+                $vld = $plugin->getPlugin('Validator', $plugin_name);
+                return $vld->validate($name, $var, $params);
+
+                break;
+        }
+
+        return $true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Mbregexp.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Mbregexp.php
new file mode 100644 (file)
index 0000000..7fc2c27
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+// {{{ Ethna_Plugin_Validator_Mbegexp
+/**
+ *  マルチバイト対応正規表現によるバリデータプラグイン
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Mbregexp extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  正規表現によるフォーム値のチェックを行う(マルチバイト対応)
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['mbregexp']) == false
+            || $type == VAR_TYPE_FILE || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        $ctl =& $this->backend->getController();
+        $cli_enc = $ctl->getClientEncoding();
+        $encoding = (isset($params['encoding']))
+                  ? $params['encoding']
+                  : $cli_enc;
+        mb_regex_encoding($encoding);
+
+        if (mb_ereg($params['mbregexp'], $var) !== 1) {
+            if (isset($params['error'])) {
+                $msg = $params['error'];
+            } else {
+                $msg = _et('Please input {form} properly.');
+            }
+            return Ethna::raiseNotice($msg, E_FORM_REGEXP);
+        }
+
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmax.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmax.php
new file mode 100644 (file)
index 0000000..f584ea5
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Mbstrmax.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Mbstrmax
+/**
+ *  最大値チェックプラグイン (マルチバイト文字列用)
+ *
+ *  NOTE:
+ *      - mbstring を有効にしておく必要があります。
+ *      - エラーメッセージは、全角半角を区別しません。
+ * 
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Mbstrmax extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  最大値のチェックを行う
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     *  @return true: 成功  Ethna_Error: エラー
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['mbstrmax']) == false || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        if ($type == VAR_TYPE_STRING) {
+            $max_param = $params['mbstrmax'];
+            if (mb_strlen($var) > $max_param) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Please input less than %d characters to {form}.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_MAX_STRING,
+                        array($max_param));
+            }
+        }
+
+        return $true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmin.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmin.php
new file mode 100644 (file)
index 0000000..b5bb703
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Mbstrmin.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Mbstrmin
+/**
+ *  最小値チェックプラグイン (マルチバイト文字列用)
+ *
+ *  NOTE:
+ *      - mbstring を有効にしておく必要があります。
+ *      - エラーメッセージは、全角半角を区別しません。
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Mbstrmin extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  最小値のチェックを行う
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     *  @return true: 成功  Ethna_Error: エラー
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['mbstrmin']) == false || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        if ($type == VAR_TYPE_STRING) {
+            $min_param = $params['mbstrmin'];
+            if (mb_strlen($var) < $min_param) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Please input more than %d characters to {form}.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_MIN_STRING,
+                        array($min_param));
+            }
+        }
+
+        return $true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Min.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Min.php
new file mode 100644 (file)
index 0000000..2cf94db
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Min.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Min
+/**
+ *  最小値チェックプラグイン
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Min extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  最小値のチェックを行う
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['min']) == false || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        switch ($type) {
+            case VAR_TYPE_INT:
+                if ($var < $params['min']) {
+                    if (isset($params['error'])) {
+                        $msg = $params['error'];
+                    } else {
+                        $msg = _et('Please input more than %d(int) to {form}.');
+                    }
+                    return Ethna::raiseNotice($msg, E_FORM_MIN_INT, array($params['min']));
+                }
+                break;
+
+            case VAR_TYPE_FLOAT:
+                if ($var < $params['min']) {
+                    if (isset($params['error'])) {
+                        $msg = $params['error'];
+                    } else {
+                        $msg = _et('Please input more than %f(float) to {form}.');
+                    }
+                    return Ethna::raiseNotice($msg, E_FORM_MIN_FLOAT, array($params['min']));
+                }
+                break;
+
+            case VAR_TYPE_DATETIME:
+                $t_min = strtotime($params['min']);
+                $t_var = strtotime($var);
+                if ($t_var < $t_min) {
+                    if (isset($params['error'])) {
+                        $msg = $params['error'];
+                    } else {
+                        $msg = _et('Please input datetime value %s or later to {form}.');
+                    }
+                    return Ethna::raiseNotice($msg, E_FORM_MIN_DATETIME, array($params['min']));
+                }
+                break;
+
+            case VAR_TYPE_FILE:
+                $st = stat($var['tmp_name']);
+                if ($st[7] < $params['min'] * 1024) {
+                    if (isset($params['error'])) {
+                        $msg = $params['error'];
+                    } else {
+                        $msg = _et('Please specify file whose size is more than %d KB.');
+                    }
+                    return Ethna::raiseNotice($msg, E_FORM_MIN_FILE, array($params['min']));
+                }
+                break;
+
+            case VAR_TYPE_STRING:
+
+                //
+                //  マルチバイトエンコーディングと、そうでない場合で
+                //  異なるプラグインを呼ぶ。
+                //
+                //  これは Ethna_Controller#client_encoding の値によ
+                //  って動きが決まる
+                //
+
+                $ctl = Ethna_Controller::getInstance();
+                $client_enc = $ctl->getClientEncoding();
+                $plugin = $this->backend->getPlugin();
+
+                //  select Plugin.
+                if (mb_enabled() && strcasecmp('UTF-8', $client_enc) == 0) {
+                    $plugin_name = 'Mbstrmin';
+                    $params['mbstrmin'] = $params['min'];
+                } elseif (strcasecmp('EUC-JP', $client_enc == 0)
+                       || strcasecmp('eucJP-win', $client_enc == 0)) {
+                    $plugin_name = 'Strmincompat';
+                    $params['strmincompat'] = $params['min'];
+                } else { 
+                    $plugin_name = 'Strmin';
+                    $params['strmin'] = $params['min'];
+                }
+                unset($params['min']);
+
+                $vld = $plugin->getPlugin('Validator', $plugin_name);
+                return $vld->validate($name, $var, $params);
+
+                break;
+        }
+
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Regexp.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Regexp.php
new file mode 100644 (file)
index 0000000..6409d94
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Regexp.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Regexp
+/**
+ *  正規表現によるバリデータプラグイン
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Regexp extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  正規表現によるフォーム値のチェックを行う
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['regexp']) == false
+            || $type == VAR_TYPE_FILE || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        if (preg_match($params['regexp'], $var) == 0) {
+            if (isset($params['error'])) {
+                $msg = $params['error'];
+            } else {
+                $msg = _et('Please input {form} properly.');
+            }
+            return Ethna::raiseNotice($msg, E_FORM_REGEXP);
+        }
+
+        return $true;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Required.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Required.php
new file mode 100644 (file)
index 0000000..f6ab5eb
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Required.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Required
+/**
+ *  必須フォームの検証プラグイン
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Required extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = true;
+
+    /**
+     *  フォームに値が入力されているかを検証する
+     *
+     *  配列の場合は、入力されるべき key のリスト、
+     *  あるいは key の数を指定できます
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        if (isset($params['required']) && $params['required'] == false) {
+            return $true;
+        }
+        $form_def = $this->getFormDef($name);
+
+        // 選択型のフォームかどうか
+        switch ($form_def['form_type']) {
+        case FORM_TYPE_SELECT:
+        case FORM_TYPE_RADIO:
+        case FORM_TYPE_CHECKBOX:
+        case FORM_TYPE_FILE:
+            $choice = true;
+            break;
+        default:
+            $choice = false;
+        }
+
+        // スカラーの場合
+        if (is_array($form_def['type']) == false) {
+            if ($this->isEmpty($var, $this->getFormType($name))) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else if ($choice) {
+                    $msg = _et('{form} was not selected.');
+                } else {
+                    $msg = _et('no input to {form}.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_REQUIRED);
+            } else {
+                return $true;
+            }
+        }
+                
+        // 配列の場合
+        $valid_keys = array();
+        if ($var != null) {
+            foreach (array_keys($var) as $key) {
+                if ($this->isEmpty($var[$key], $this->getFormType($name)) == false) {
+                    $valid_keys[] = $key;
+                }
+            }
+        }
+
+        // 配列の required_key のチェック
+        // 'required_key' => array(xx) に設定された配列の要素値がなければエラー。
+        if (isset($params['key'])) {
+            $invalid_keys = array_diff(to_array($params['key']), $valid_keys);
+            if (count($invalid_keys) > 0) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else if ($choice) {
+                    $msg = _et('Required item of {form} was not selected.');
+                } else {
+                    $msg = _et('Required item of {form} was not submitted.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_REQUIRED);
+            }
+        }
+
+        // 配列の required_num のチェック
+        // 'required_num' => xx に設定された数より、validな値の数が少なければエラー。
+        if (isset($params['num'])) {
+            if (count($valid_keys) < intval($params['num'])) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else if ($choice) {
+                    $msg = _et('Required numbers of {form} was not selected.');
+                } else {
+                    $msg = _et('Required numbers of {form} was not submitted.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_REQUIRED);
+            }
+        }
+
+        // とくに指定がないとき: フォームに与えられた全要素に
+        // valid な値が入っていなければならない
+        if (isset($params['key']) == false && isset($params['num']) == false) {
+            if (count($valid_keys) == 0 || count($valid_keys) != count($var)) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else if ($choice) {
+                    $msg = _et('Please select {form}.');
+                } else {
+                    $msg = _et('Please input {form}.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_REQUIRED);
+            }
+        }
+
+        return $true;
+    }
+
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmax.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmax.php
new file mode 100644 (file)
index 0000000..f1fad55
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Strmax.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Strmax
+/**
+ *  最大値チェックプラグイン (シングルバイト文字列用)
+ *
+ *  NOTE: 
+ *    - mbstring 不要
+ *    - エラーメッセージは、全角半角を区別しません。
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Strmax extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  最大値のチェックを行う (シングルバイト文字列用)
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     *  @return true: 成功  Ethna_Error: エラー
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['strmax']) == false || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        if ($type == VAR_TYPE_STRING) {
+            $max_param = $params['strmax'];
+            if (strlen($var) > $max_param) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Please input less than %d characters to {form}.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_MAX_STRING,
+                        array($params['strmax']));
+            }
+        }
+
+        return $true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmaxcompat.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmaxcompat.php
new file mode 100644 (file)
index 0000000..4de0096
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Strmaxcompat.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Strmaxcompat
+/**
+ *  最大値チェックプラグイン
+ *  (マルチバイト文字列(EUC_JP)用. Ethna 2.3.x までの互換性保持用)
+ *
+ *  NOTE: 
+ *    - EUC_JP 専用のプラグインです。
+ *    - クライアントエンコーディングがEUC_JP以外の場合は、入力を無条件でEUC_JPに変換します 
+ *      (但し mbstringが入っていない場合は除く) 
+ *    - エラーメッセージは、全角半角を区別したものが出力されます。
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Strmaxcompat extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  最大値のチェックを行う
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     *  @return true: 成功  Ethna_Error: エラー
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['strmaxcompat']) == false || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        $ctl = $this->backend->getController();
+        $client_enc = $ctl->getClientEncoding();
+        if (mb_enabled()
+        && (strcasecmp('EUC-JP', $client_enc) != 0
+         && strcasecmp('eucJP-win', $client_enc) != 0)) {
+            $var = mb_convert_encoding($var, 'EUC-JP', $client_enc);
+        }
+
+        if ($type == VAR_TYPE_STRING) {
+            $max_param = $params['strmaxcompat'];
+            if (strlen($var) > $max_param) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Please input less than %d full-size (%d half-size) characters to {form}.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_MAX_STRING,
+                            array(intval($max_param/2), $max_param));
+            }
+        }
+
+        return $true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmin.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmin.php
new file mode 100644 (file)
index 0000000..132c4da
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Strmin.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Strmin
+/**
+ *  最小値チェックプラグイン (シングルバイト文字列用)
+ *
+ *  NOTE: 
+ *    - mbstring 不要
+ *    - エラーメッセージは、全角半角を区別しません。
+ * 
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Strmin extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  最小値のチェックを行う (シングルバイト文字列用)
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['strmin']) == false || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        if ($type == VAR_TYPE_STRING) {
+            $min_param = $params['strmin'];
+            if (strlen($var) < $min_param) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Please input more than %d characters to {form}.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_MIN_STRING,
+                        array($min_param));
+            }
+        }
+
+        return $true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmincompat.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Strmincompat.php
new file mode 100644 (file)
index 0000000..49c4122
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Strmincompat.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Strmincompat
+/**
+ *  最小値チェックプラグイン
+ *  (マルチバイト文字列(EUC_JP)用. Ethna 2.3.x までの互換性保持用)
+ *
+ *  NOTE: 
+ *    - EUC_JP 専用のプラグインです。
+ *    - クライアントエンコーディングがEUC_JP以外の場合は、入力を無条件でEUC_JPに変換します 
+ *      (但し mbstringが入っていない場合は除く) 
+ *    - エラーメッセージは、全角半角を区別したものが出力されます。
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Strmincompat extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  最小値のチェックを行う
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $this->getFormType($name);
+        if (isset($params['strmincompat']) == false || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        $ctl = $this->backend->getController();
+        $client_enc = $ctl->getClientEncoding();
+        if (mb_enabled()
+        && (strcasecmp('EUC-JP', $client_enc) != 0
+         && strcasecmp('eucJP-win', $client_enc) != 0)) {
+            $var = mb_convert_encoding($var, 'EUC-JP', $client_enc);
+        }
+
+        if ($type == VAR_TYPE_STRING) {
+            $min_param = $params['strmincompat'];
+            if (strlen($var) < $min_param) {
+                if (isset($params['error'])) {
+                    $msg = $params['error'];
+                } else {
+                    $msg = _et('Please input more than %d full-size (%d half-size) characters to {form}.');
+                }
+                return Ethna::raiseNotice($msg, E_FORM_MIN_STRING,
+                            array(intval($min_param/2), $min_param));
+            }
+        }
+
+        return $true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Type.php b/Idea_Plugin_Extlib/class/Plugin/Validator/Ethna_Plugin_Validator_Type.php
new file mode 100644 (file)
index 0000000..349d881
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Type.php
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_Plugin_Validator_Type
+/**
+ *  タイプチェックプラグイン
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Plugin_Validator_Type extends Ethna_Plugin_Validator
+{
+    /** @var    bool    配列を受け取るかフラグ */
+    var $accept_array = false;
+
+    /**
+     *  フォーム値の型チェックを行う
+     *
+     *  @access public
+     *  @param  string  $name       フォームの名前
+     *  @param  mixed   $var        フォームの値
+     *  @param  array   $params     プラグインのパラメータ
+     */
+    function &validate($name, $var, $params)
+    {
+        $true = true;
+        $type = $params['type'];
+        if ($type == VAR_TYPE_FILE || $this->isEmpty($var, $type)) {
+            return $true;
+        }
+
+        foreach (array_keys(to_array($var)) as $key) {
+            switch ($type) {
+                case VAR_TYPE_INT:
+                    if (!preg_match('/^-?\d+$/', $var)) {
+                        if (isset($params['error'])) {
+                            $msg = $params['error'];
+                        } else {
+                            $msg = _et('Please input integer value to {form}.');
+                        }
+                        return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_INT);
+                    }
+                    break;
+
+                case VAR_TYPE_FLOAT:
+                    if (!preg_match('/^-?\d+$/', $var) && !preg_match('/^-?\d+\.\d+$/', $var)) {
+                        if (isset($params['error'])) {
+                            $msg = $params['error'];
+                        } else {
+                            $msg = _et('Please input float value to {form}.');
+                        }
+                        return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_FLOAT);
+                    }
+                    break;
+
+                case VAR_TYPE_BOOLEAN:
+                    if ($var != "1" && $var != "0") {
+                        if (isset($params['error'])) {
+                            $msg = $params['error'];
+                        } else {
+                            $msg = _et('You can input 0 or 1 to {form}.');
+                        }
+                        return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_BOOLEAN);
+                    }
+                    break;
+
+                case VAR_TYPE_DATETIME:
+                    $r = strtotime($var);
+                    if ($r == -1 || $r === false) {
+                        if (isset($params['error'])) {
+                            $msg = $params['error'];
+                        } else {
+                            $msg = _et('Please input valid datetime to {form}.');
+                        }
+                        return Ethna::raiseNotice($msg, E_FORM_WRONGTYPE_DATETIME);
+                    }
+                    break;
+            }
+        }
+
+        return $true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Renderer/Ethna_Renderer_Rhaco.php b/Idea_Plugin_Extlib/class/Renderer/Ethna_Renderer_Rhaco.php
new file mode 100644 (file)
index 0000000..f40e9c2
--- /dev/null
@@ -0,0 +1,366 @@
+<?php
+/**
+ *  Ethna_Renderer_Rhaco.php (experimental)
+ *
+ *  @author     TSURUOKA Naoya <tsuruoka@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id: Ethna_Renderer_Rhaco.php,v 1.16 2007/01/17 18:38:51 ichii386 Exp $
+ */
+
+set_include_path(get_include_path() . PATH_SEPARATOR . BASE . '/lib/rhaco');
+
+require_once 'rhaco/Rhaco.php';
+require_once 'rhaco/tag/TemplateParser.php';
+require_once ETHNA_BASE . '/class/Ethna_SmartyPlugin.php';
+
+/**
+ *  Rhacoレンダラクラス
+ *
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Renderer_Rhaco extends Ethna_Renderer
+{
+    /** @var    string compile directory  */
+    var $compile_dir = '';
+
+    var $template_dir = '';
+
+    /**
+     * plugin list
+     * @var     array
+     * @access  protected
+     */
+    var $smarty_plugin_list = array();
+
+    /**
+     * Rhaco TemplateParser
+     * @var     TemplateParser_Ethna $engine
+     * @access  protected
+     */
+    var $engine;
+    
+    /**
+     *  Ethna_Renderer_Rhacoクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_Renderer_Rhaco(&$controller)
+    {
+        parent::Ethna_Renderer($controller);
+        
+        $this->template_dir = $controller->getTemplatedir() . '/';
+        $this->compile_dir = $controller->getDirectory('template_c');
+
+        Rhaco::constant('TEMPLATE_PATH', $this->template_dir);
+        
+        $this->engine =& new TemplateParser_Ethna();
+
+        /*
+        $this->setTemplateDir($template_dir);
+        $this->compile_dir = $compile_dir;
+         */
+        $this->engine->template_dir = $this->template_dir;
+        $this->engine->compile_dir = $this->compile_dir;
+        $this->engine->compile_id = md5($this->template_dir);
+
+        // 一応がんばってみる
+        if (is_dir($this->engine->compile_dir) === false) {
+            Ethna_Util::mkdir($this->engine->compile_dir, 0755);
+        }
+
+        $this->_setDefaultPlugin();
+    }
+    
+    /**
+     *  ビューを出力する
+     *
+     *  @access public
+     *  @param  string  $template   テンプレート名
+     *  @param  bool    $capture    true ならば出力を表示せずに返す
+     */
+    function perform($template = null, $capture = false)
+    {
+        if ($template === null && $this->template === null) {
+            return Ethna::raiseWarning('template is not defined');
+        }
+
+        if ($template !== null) {
+            $this->template = $template;
+        }
+
+        if ((is_absolute_path($this->template) && is_readable($this->template))
+            || is_readable($this->template_dir . $this->template)) {
+                if ($capture === true) {
+                    $captured = $this->engine->read($this->template);
+                    return $captured;
+                } else {
+                    $captured = $this->engine->read($this->template);
+                    print($captured);
+                }
+        } else {
+            return Ethna::raiseWarning('template not found ' . $this->template);
+        }
+    }
+    
+    /**
+     * テンプレート変数を取得する
+     * 
+     *  @todo fixme
+     *  @access public
+     *  @param string $name  変数名
+     *  @return mixed 変数
+     */
+    function &getProp($name = null)
+    {
+        $property =& $this->engine->variables[$name];
+
+        if ($property !== null) {
+            return $property;
+        }
+
+        return null;
+    }
+
+    /**
+     *  テンプレート変数を削除する
+     * 
+     *  @param name    変数名
+     *  @todo
+     *  @access public
+     */
+    function removeProp()
+    {
+        $this->engine->clearVariable(func_num_args());
+    }
+
+    /**
+     *  テンプレート変数に配列を割り当てる
+     * 
+     *  @param array $array
+     *  @access public
+     */
+    function setPropArray($array)
+    {
+        $this->engine->setVariable($array);
+    }
+
+    /**
+     *  テンプレート変数に配列を参照として割り当てる
+     * 
+     *  @param array $array
+     *  @todo no implement
+     *  @access public
+     */
+    function setPropArrayByRef(&$array)
+    {
+        //$this->engine->assign_by_ref($array);
+    }
+
+    /**
+     *  テンプレート変数を割り当てる
+     * 
+     *  @param string $name 変数名
+     *  @param mixed $value 値
+     * 
+     *  @access public
+     */
+    function setProp($name, $value)
+    {
+        $this->engine->setVariable($name, $value);
+    }
+
+    /**
+     *  テンプレート変数に参照を割り当てる
+     * 
+     *  @access public
+     *  @todo fixme
+     *  @param string $name 変数名
+     *  @param mixed $value 値
+     */
+    function setPropByRef($name, &$value)
+    {
+        $this->engine->setVariable($name, $value);
+        //$this->engine->assign_by_ref($name, $value);
+    }
+
+    /**
+     * setPlugin
+     *
+     * @access public
+     */
+    function setPlugin($name, $type, $plugin)
+    {
+        //Smartyプラグイン関数の有無をチェック
+        if (is_callable($plugin) === false) {
+            return Ethna::raiseWarning('Does not exists.');
+        }
+
+        $this->smarty_plugin_list[$name] = array(
+            'plugin' => $plugin,
+            'type' => $type
+        );
+    }
+
+    /**
+     * getPluginList
+     *
+     * @access public
+     */
+    function getPluginList()
+    {
+        return $this->smarty_plugin_list;
+    }
+
+    /**
+     *  デフォルトの設定.
+     *
+     *  @access public
+     */
+    function _setDefaultPlugin()
+    {
+        /*
+        // default modifiers
+        $this->setPlugin('number_format','modifier','smarty_modifier_number_format');
+        $this->setPlugin('strftime','modifier','smarty_modifier_strftime');
+        $this->setPlugin('count','modifier','smarty_modifier_count');
+        $this->setPlugin('join','modifier','smarty_modifier_join');
+        $this->setPlugin('filter','modifier', 'smarty_modifier_filter');
+        $this->setPlugin('unique','modifier','smarty_modifier_unique');
+        $this->setPlugin('wordwrap_i18n','modifier','smarty_modifier_wordwrap_i18n');
+        $this->setPlugin('truncate_i18n','modifier','smarty_modifier_truncate_i18n');
+        $this->setPlugin('i18n','modifier','smarty_modifier_i18n');
+        $this->setPlugin('checkbox','modifier','smarty_modifier_checkbox');
+        $this->setPlugin('select','modifier','smarty_modifier_select');
+        $this->setPlugin('form_value','modifier','smarty_modifier_form_value');
+         */
+
+        // default functions
+        $this->setPlugin('is_error','function','smarty_function_is_error');
+        $this->setPlugin('message','function','smarty_function_message');
+        $this->setPlugin('uniqid','function','smarty_function_uniqid');
+        $this->setPlugin('select','function','smarty_function_select');
+        $this->setPlugin('checkbox_list','function','smarty_function_checkbox_list');
+        $this->setPlugin('form_name','function','smarty_function_form_name');
+        $this->setPlugin('form_input','function','smarty_function_form_input');
+        $this->setPlugin('form_submit','function','smarty_function_form_submit');
+        $this->setPlugin('url','function','smarty_function_url');
+        $this->setPlugin('csrfid','function','smarty_function_csrfid');
+
+        // default blocks
+        $this->setPlugin('form','block','smarty_block_form');       
+
+        $this->engine->setSmartyPluginList($this->getPluginList());
+    }
+
+
+}
+
+/**
+ * TemplateParser_Ethna
+ */
+class TemplateParser_Ethna extends TemplateParser
+{
+    /**
+     * smarty_function list
+     * @var     array
+     * @access  protected
+     */
+    var $smarty_plugin_list = array();
+    
+    /**
+     * fake property for Smaty
+     *
+     * @access public
+     */
+    var $_tag_stack = array();
+
+    /**
+     * setSmartyPluginList
+     *
+     */
+    function setSmartyPluginList($plugin_list)
+    {
+        if (!is_array($plugin_list)) {
+            return false;
+        }
+        $this->smarty_plugin_list = $plugin_list;
+    }
+
+    /**
+     * getSmartyPluginList
+     *
+     * @access public
+     */
+    function getSmartyPluginList()
+    {
+        return $this->smarty_plugin_list;
+    }
+
+    /**
+     * smarty_function dispatcher
+     *
+     * @access protected
+     * @param string $src
+     */
+    function _exec9002_smartyfunctions($src)
+    {
+        $tag = new SimpleTag();
+        $smarty_plugin_list = $this->getSmartyPluginList();
+
+        foreach($smarty_plugin_list as $name => $plugin_config) {
+            
+            while ($tag->set($src, $this->_getTagName($name))) {
+
+                if ($plugin_config['type'] == 'function') {
+
+                    $param = $tag->toHash();
+                    $src = str_replace(
+                        $tag->getPlain(),
+                        $plugin_config['plugin']($param, $this),
+                        $src
+                    );
+
+                } else if ($plugin_config['type'] == 'block') {
+                    
+                    $repeat_before = true;
+                    $repeat_after = false;
+                    $param_list = $tag->getParameter();
+                    foreach ($param_list as $param_tag) {
+                        $param[$param_tag->getName()] = $param_tag->getValue();
+                    }
+                    $content = $tag->getValue();
+
+                    //before(not return value)
+                    $result = $plugin_config['plugin']($param, $content, $this, $repeat_before);
+
+                    //after
+                    $result = $plugin_config['plugin']($param, $content, $this, $repeat_after);
+                    $src = str_replace(
+                        $tag->getPlain(),
+                        $result,
+                        $src
+                    );
+
+                }
+            }
+
+        }
+
+        return $src;
+    }
+
+    /**
+     * TemplateParser _getTagName
+     *
+     * @access protected
+     * @param string $value
+     */
+    function _getTagName($value)
+    {
+        return sprintf("rt:%s",$value);
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/class/Renderer/Ethna_Renderer_Smarty.php b/Idea_Plugin_Extlib/class/Renderer/Ethna_Renderer_Smarty.php
new file mode 100644 (file)
index 0000000..75979a4
--- /dev/null
@@ -0,0 +1,209 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Renderer_Smarty.php
+ *
+ *  @author     Kazuhiro Hosoi <hosoi@gree.co.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+require_once 'Smarty/Smarty.class.php';
+
+// {{{ Ethna_Renderer_Smarty
+/**
+ *  Smartyレンダラクラス(Mojaviのまね)
+ *
+ *  @author     Kazuhiro Hosoi <hosoi@gree.co.jp>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Renderer_Smarty extends Ethna_Renderer
+{
+    /** @var    string compile directory  */
+    var $compile_dir;
+    
+    /**
+     *  Ethna_Renderer_Smartyクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_Renderer_Smarty(&$controller)
+    {
+        parent::Ethna_Renderer($controller);
+        
+        $this->engine =& new Smarty;
+        
+        $template_dir = $controller->getTemplatedir();
+        $compile_dir = $controller->getDirectory('template_c');
+
+        $this->setTemplateDir($template_dir);
+        $this->compile_dir = $compile_dir;
+        $this->engine->template_dir = $this->template_dir;
+        $this->engine->compile_dir = $this->compile_dir;
+        $this->engine->compile_id = md5($this->template_dir);
+
+        // 一応がんばってみる
+        if (is_dir($this->engine->compile_dir) === false) {
+            Ethna_Util::mkdir($this->engine->compile_dir, 0755);
+        }
+
+        $this->engine->plugins_dir = array_merge(
+            $controller->getDirectory('plugins'),
+            array(ETHNA_BASE . '/class/Plugin/Smarty', SMARTY_DIR . 'plugins')
+        );
+    }
+    
+    /**
+     *  ビューを出力する
+     *
+     *  @param  string  $template   テンプレート名
+     *  @param  bool    $capture    true ならば出力を表示せずに返す
+     *
+     *  @access public
+     */
+    function perform($template = null, $capture = false)
+    {
+        if ($template === null && $this->template === null) {
+            return Ethna::raiseWarning('template is not defined');
+        }
+
+        if ($template !== null) {
+            $this->template = $template;
+        }
+
+        if ((is_absolute_path($this->template) && is_readable($this->template))
+            || is_readable($this->template_dir . $this->template)) {
+                if ($capture === true) {
+                    $captured = $this->engine->fetch($this->template);
+                    return $captured;
+                } else {
+                    $this->engine->display($this->template);
+                }
+        } else {
+            return Ethna::raiseWarning('template not found ' . $this->template);
+        }
+    }
+    
+    /**
+     * テンプレート変数を取得する
+     * 
+     *  @param string $name  変数名
+     *
+     *  @return mixed 変数
+     *
+     *  @access public
+     */
+    function &getProp($name = null)
+    {
+        $property =& $this->engine->get_template_vars($name);
+
+        if ($property !== null) {
+            return $property;
+        }
+        return null;
+    }
+
+    /**
+     *  テンプレート変数を削除する
+     * 
+     *  @param name    変数名
+     * 
+     *  @access public
+     */
+    function removeProp($name)
+    {
+        $this->engine->clear_assign($name);
+    }
+
+    /**
+     *  テンプレート変数に配列を割り当てる
+     * 
+     *  @param array $array
+     * 
+     *  @access public
+     */
+    function setPropArray($array)
+    {
+        $this->engine->assign($array);
+    }
+
+    /**
+     *  テンプレート変数に配列を参照として割り当てる
+     * 
+     *  @param array $array
+     * 
+     *  @access public
+     */
+    function setPropArrayByRef(&$array)
+    {
+        $this->engine->assign_by_ref($array);
+    }
+
+    /**
+     *  テンプレート変数を割り当てる
+     * 
+     *  @param string $name 変数名
+     *  @param mixed $value 値
+     * 
+     *  @access public
+     */
+    function setProp($name, $value)
+    {
+        $this->engine->assign($name, $value);
+    }
+
+    /**
+     *  テンプレート変数に参照を割り当てる
+     * 
+     *  @param string $name 変数名
+     *  @param mixed $value 値
+     * 
+     *  @access public
+     */
+    function setPropByRef($name, &$value)
+    {
+        $this->engine->assign_by_ref($name, $value);
+    }
+
+    /**
+     *  プラグインをセットする
+     * 
+     *  @param string $name プラグイン名
+     *  @param string $type プラグインタイプ
+     *  @param mixed $plugin プラグイン本体
+     * 
+     *  @access public
+     */
+    function setPlugin($name, $type, $plugin) 
+    {
+        //プラグイン関数の有無をチェック
+        if (is_callable($plugin) === false) {
+            return Ethna::raiseWarning('Does not exists.');
+        }
+
+        //プラグインの種類をチェック
+        $register_method = 'register_' . $type;
+        if (method_exists($this->engine, $register_method) === false) {
+            return Ethna::raiseWarning('This plugin type does not exist');
+        }
+
+        // フィルタは名前なしで登録
+        if ($type === 'prefilter' || $type === 'postfilter' || $type === 'outputfilter') {
+            parent::setPlugin($name, $type, $plugin);
+            $this->engine->$register_method($plugin);
+            return;
+        }
+        
+        // プラグインの名前をチェック
+        if ($name === '') {
+            return Ethna::raiseWarning('Please set plugin name');
+        }
+       
+        // プラグインを登録する
+        parent::setPlugin($name, $type, $plugin);
+        $this->engine->$register_method($name, $plugin);
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_ActionForm.php b/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_ActionForm.php
new file mode 100644 (file)
index 0000000..4243c88
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_SOAP_ActionForm.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_SOAP_ActionForm
+/**
+ *  SOAPフォームクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_SOAP_ActionForm extends Ethna_ActionForm
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    array   引数定義 */
+    var $arg = array();
+
+    /** @var    array   戻り値定義 */
+    var $retval = array();
+
+    /**#@-*/
+
+    /**
+     *  Ethna_SOAP_ActionFormクラスのコンストラクタ
+     *
+     *  @access public
+     *  @param  object  Ethna_ActionError   $action_error   アクションエラーオブジェクト
+     */
+    function Ethna_SOAP_ActionForm(&$action_error)
+    {
+        $this->form =& $this->arg;
+
+        parent::Ethna_ActionForm($action_error);
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_Gateway.php b/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_Gateway.php
new file mode 100644 (file)
index 0000000..8cb2c4f
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_SOAP_Gateway.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_SOAP_Gateway
+/**
+ *  SOAPゲートウェイの基底クラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_SOAP_Gateway
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_SOAP_Gatewayクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_SOAP_Gateway()
+    {
+        $this->controller =& Ethna_Controller::getInstance();
+    }
+
+    /**
+     *  SOAPアクションを実行する
+     *
+     *  @access public
+     */
+    function dispatch()
+    {
+        $this->controller->trigger();
+    }
+
+    /**
+     *  アプリケーション設定値一覧を取得する
+     *
+     *  @access public
+     *  @return array   アプリケーション設定値一覧
+     */
+    function &getApp()
+    {
+        $action_form =& $this->controller->getActionForm();
+        return $action_form->app_vars;
+    }
+
+    /**
+     *  エラーコードを取得する
+     *
+     *  @access public
+     *  @return int     エラーコード(nullならエラー無し)
+     */
+    function getErrorCode()
+    {
+        $action_error =& $this->controller->getActionError();
+        if ($action_error->count() == 0) {
+            return null;
+        }
+        
+        // 最初の1つを返す
+        $error_list = $action_error->getErrorList();
+        $error =& $error_list[0];
+
+        return $error->getCode();
+    }
+
+    /**
+     *  エラーメッセージを取得する
+     *
+     *  @access public
+     *  @return string  エラーメッセージ(nullならエラー無し)
+     */
+    function getErrorMessage()
+    {
+        $action_error =& $this->controller->getActionError();
+        if ($action_error->count() == 0) {
+            return null;
+        }
+
+        // 最初の1つを返す
+        $message_list = $action_error->getMessageList();
+        $message = $message_list[0];
+
+        return $message;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_GatewayGenerator.php b/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_GatewayGenerator.php
new file mode 100644 (file)
index 0000000..d3c457e
--- /dev/null
@@ -0,0 +1,181 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_SOAP_GatewayGenerator.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_SOAP_GatewayGenerator
+/**
+ *  指定されたコントローラに対応するゲートウェイクラスコードを生成するクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna   
+ */
+class Ethna_SOAP_GatewayGenerator
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Config        設定オブジェクト */
+    var $config;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト */
+    var $action_error;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト(省略形) */
+    var $ae;
+
+    /** @var    string      ゲートウェイクラスコード */
+    var $gateway;
+
+    /** @var    string      ゲートウェイクラス識別名 */
+    var $name;
+
+    /** @var    string      ゲートウェイクラスネームスペース */
+    var $namespace;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_SOAP_GatewayGeneratorクラスのコンストラクタ
+     *
+     *  @access public
+     */
+    function Ethna_SOAP_GatewayGenerator()
+    {
+        $this->controller =& Ethna_Controller::getInstance();
+        $this->config =& $this->controller->getConfig();
+        $this->action_error = null;
+        $this->ae =& $this->action_error;
+        $this->gateway = "";
+        $this->name = $this->controller->getAppId();
+        $this->namespace = $this->_getNameSpace();
+    }
+
+    /**
+     *  ゲートウェイクラスコードを生成する
+     *
+     *  @access public
+     *  @return string  ゲートウェクラスコード
+     */
+    function generate()
+    {
+        $prev_type = $this->controller->getClientType();
+        $this->controller->setClientType(CLIENT_TYPE_SOAP);
+
+        $this->gateway .= $this->_getHeader();
+        $this->gateway .= $this->_getEntry();
+        $this->gateway .= $this->_getFooter();
+
+        $this->controller->setClientType($prev_type);
+
+        return $this->gateway;
+    }
+
+    /**
+     *  ゲートウェイクラスのクラス名を取得する
+     *
+     *  @access public
+     *  @return string  ゲートウェイクラスのクラス名
+     */
+    function getClassName()
+    {
+        return sprintf("Ethna_SOAP_%sGateway", $this->name);
+    }
+
+    /**
+     *  ゲートウェイクラスコード(ヘッダ部分)を取得する
+     *
+     *  @access private
+     *  @return string  ゲートウェイクラスコード(ヘッダ部分)
+     */
+    function _getHeader()
+    {
+        $header = sprintf("class Ethna_SOAP_%sGateway extends Ethna_SOAP_Gateway {\n", $this->name);
+
+        return $header;
+    }
+
+    /**
+     *  ゲートウェイクラスコード(メソッドエントリ部分)を取得する
+     *
+     *  @access private
+     *  @return string  ゲートウェイクラスコード(メソッドエントリ部分)
+     */
+    function _getEntry()
+    {
+        $entry = "";
+        foreach ($this->controller->soap_action as $k => $v) {
+            $action_form_name = $this->controller->getActionFormName($k);
+            $form =& new $action_form_name($this->controller);
+            $arg_list = array_keys($form->form);
+
+            $entry .= "  function $k(";
+            for ($i = 0; $i < count($arg_list); $i++) {
+                if ($i > 0) {
+                    $entry .= ", ";
+                }
+                $entry .= "\$" . $arg_list[$i];
+            }
+            $entry .= ") {\n";
+
+            $entry .= "    \$_SERVER['REQUEST_METHOD'] = 'post';\n";
+            $entry .= "    \$_POST['action_$k'] = 'dummy';\n";
+            foreach ($arg_list as $arg) {
+                $entry .= "    \$_POST['$arg'] = \$$arg;\n";
+            }
+            
+            $entry .= "    \$this->dispatch();\n";
+
+            $entry .= "    \$app =& \$this->getApp();\n";
+            $entry .= "    \$errorcode = \$this->getErrorCode();\n";
+            $entry .= "    \$errormessage = \$this->getErrorMessage();\n";
+            $entry .= "    \$retval = array();\n";
+            foreach ($form->retval as $k => $v) {
+                $entry .= "    \$retval['$k'] = \$app['$k'];\n";
+            }
+            $entry .= "    \$retval['errorcode'] = \$errorcode;\n";
+            $entry .= "    \$retval['errormessage'] = \$errormessage;\n";
+
+            $entry .= "    return \$retval;\n";
+            $entry .= "  }\n";
+        }
+        return $entry;
+    }
+
+    /**
+     *  ゲートウェイクラスコード(フッタ部分)を取得する
+     *
+     *  @access private
+     *  @return string  ゲートウェイクラスコード(フッタ部分)
+     */
+    function _getFooter()
+    {
+        $footer = "}\n";
+
+        return $footer;
+    }
+
+    /**
+     *  ネームスペースを取得する
+     *
+     *  @access private
+     *  @return string  ネームスペース
+     */
+    function _getNameSpace()
+    {
+        return sprintf("%s/%s", $this->config->get('url'), $this->name);
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_Util.php b/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_Util.php
new file mode 100644 (file)
index 0000000..1b9f9fa
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_SOAP_Util.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_SOAP_Util
+/**
+ *  SOAPユーティリティクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_SOAP_Util
+{
+    /**
+     *  型定義がオブジェクト型の配列かどうかを返す
+     *
+     *  @access public
+     *  @param  array   $def    型定義
+     *  @return bool    true:オブジェクト型配列 false:それ以外の型
+     *  @static
+     */
+    function isArrayOfObject($def)
+    {
+        if (is_array($def) == false) {
+            return false;
+        }
+        $keys = array_keys($def);
+        if (count($keys) == 1 && is_array($def[$keys[0]])) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     *  型定義がスカラー値の配列かどうかを返す
+     *
+     *  @access public
+     *  @param  array   $def    型定義
+     *  @return bool    true:スカラー型配列 false:それ以外の型
+     *  @static
+     */
+    function isArrayOfScalar($def)
+    {
+        if (is_array($def) == false) {
+            return false;
+        }
+        $keys = array_keys($def);
+        if (count($keys) == 1 && is_array($def[$keys[0]]) == false) {
+            return true;
+        }
+    }
+
+    /**
+     *  スカラー値の型名を返す
+     *
+     *  @access public
+     *  @param  array   $def    型定義
+     *  @return string  型名
+     *  @static
+     */
+    function getScalarTypeName($def)
+    {
+        $name = null;
+        switch ($def) {
+        case VAR_TYPE_STRING:
+            $name = "string";
+            break;
+        case VAR_TYPE_INT:
+            $name = "int";
+            break;
+        case VAR_TYPE_FLOAT:
+            $name = "float";
+            break;
+        case VAR_TYPE_DATETIME:
+            $name = "datetime";
+            break;
+        case VAR_TYPE_BOOLEAN:
+            $name = "boolean";
+            break;
+        }
+        return $name;
+    }
+
+    /**
+     *  配列の型名を返す
+     *
+     *  @access public
+     *  @param  array   $def    型定義
+     *  @return string  型名
+     *  @static
+     */
+    function getArrayTypeName($def)
+    {
+        $name = null;
+        switch ($def) {
+        case VAR_TYPE_STRING:
+            $name = "ArrayOfString";
+            break;
+        case VAR_TYPE_INT:
+            $name = "ArrayOfInt";
+            break;
+        case VAR_TYPE_FLOAT:
+            $name = "ArrayOfFloat";
+            break;
+        case VAR_TYPE_DATETIME:
+            $name = "ArrayOfDatetime";
+            break;
+        case VAR_TYPE_BOOLEAN:
+            $name = "ArrayOfBoolean";
+            break;
+        }
+        return $name;
+    }
+
+    /**
+     *  戻り値型定義を正規化する
+     *
+     *  @access public
+     *  @param  array   $retval 戻り値型定義
+     *  @static
+     */
+    function fixRetval(&$retval)
+    {
+        $retval['errorcode'] = VAR_TYPE_INT;
+        $retval['errormessage'] = VAR_TYPE_STRING;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_WsdlGenerator.php b/Idea_Plugin_Extlib/class/SOAP/Ethna_SOAP_WsdlGenerator.php
new file mode 100644 (file)
index 0000000..a710eb1
--- /dev/null
@@ -0,0 +1,426 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_SOAP_WsdlGenerator.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_SOAP_WsdlGenerator
+/**
+ *  指定されたコントローラに対応するWSDLを生成するクラス
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_SOAP_WsdlGenerator
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    object  Ethna_Controller    controllerオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Config        設定オブジェクト */
+    var $config;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト */
+    var $action_error;
+
+    /** @var    object  Ethna_ActionError   アクションエラーオブジェクト(省略形) */
+    var $ae;
+
+    /** @var    string      WSDL */
+    var $wsdl;
+
+    /** @var    string      ゲートウェイクラスコード */
+    var $gateway;
+
+    /** @var    string      ゲートウェイクラス識別名 */
+    var $name;
+
+    /** @var    string      ゲートウェイクラスネームスペース */
+    var $namespace;
+
+    /**#@-*/
+
+    /**
+     *  Ethna_SOAP_WsdlGeneratorクラスのコンストラクタ
+     */
+    function Ethna_SOAP_WsdlGenerator($gateway)
+    {
+        $this->controller =& Ethna_Controller::getInstance();
+        $this->config =& $this->controller->getConfig();
+        $this->action_error = null;
+        $this->ae =& $this->action_error;
+        $this->wsdl = "";
+        $this->name = $this->controller->getAppId();
+        $this->namespace = $this->_getNameSpace();
+        $this->gateway = $gateway;
+    }
+
+    /**
+     *  WSDLを生成する
+     *
+     *  @access public
+     *  @return string  WSDL
+     */
+    function generate()
+    {
+        $current_type = $this->controller->getClientType();
+        $this->controller->setClientType(CLIENT_TYPE_SOAP);
+
+        $this->wsdl .= $this->_getHeader();
+        $this->wsdl .= $this->_getTypes();
+        $this->wsdl .= $this->_getMessage();
+        $this->wsdl .= $this->_getPortType();
+        $this->wsdl .= $this->_getBinding();
+        $this->wsdl .= $this->_getService();
+        $this->wsdl .= $this->_getFooter();
+
+        $this->controller->setClientType($current_type);
+
+        return $this->wsdl;
+    }
+
+    /**
+     *  WSDL(ヘッダ部分)を取得する
+     *
+     *  @access private
+     *  @return string  WSDL(ヘッダ部分)
+     */
+    function _getHeader()
+    {
+        $header =<<<EOD
+<?xml version="1.0" encoding="utf-8"?>
+<definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
+    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+    xmlns:s="http://www.w3.org/2001/XMLSchema"
+    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
+    xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
+    xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
+    xmlns:tns="%s"
+    targetNamespace="%s"
+    name="%s"
+    xmlns="http://schemas.xmlsoap.org/wsdl/">\n\n
+EOD;
+        return sprintf($header, $this->namespace, $this->namespace, $this->name);
+    }
+
+    /**
+     *  WSDL(型定義部分)を取得する
+     *
+     *  @access private
+     *  @return string  WSDL(型定義部分)
+     */
+    function _getTypes()
+    {
+        $types = sprintf(" <types>\n  <s:schema targetNamespace=\"%s\">\n", $this->namespace);
+
+        // 基本型
+        $types .=<<<EOD
+   <s:complexType name="ArrayOfInt">
+    <s:complexContent mixed="false">
+     <s:restriction base="soapenc:Array">
+      <s:attribute d7p1:arrayType="s:int[]" ref="soapenc:arrayType" xmlns:d7p1="http://schemas.xmlsoap.org/wsdl/" />
+     </s:restriction>
+    </s:complexContent>
+   </s:complexType>
+   <s:complexType name="ArrayOfString">
+    <s:complexContent mixed="false">
+     <s:restriction base="soapenc:Array">
+      <s:attribute d7p1:arrayType="s:string[]" ref="soapenc:arrayType" xmlns:d7p1="http://schemas.xmlsoap.org/wsdl/" />
+     </s:restriction>
+    </s:complexContent>
+   </s:complexType>
+   <s:complexType name="Result">
+    <s:sequence>
+     <s:element name="errormessage" type="s:string" />
+     <s:element name="errorcode" type="s:int" />
+    </s:sequence>
+   </s:complexType>\n
+EOD;
+        
+        // アクション固有
+        foreach ($this->controller->soap_action as $k => $v) {
+            $action_form_name = $this->controller->getActionFormName($k);
+            $form =& new $action_form_name($this->controller);
+            if ($form->retval == null) {
+                continue;
+            }
+
+            // デフォルトエントリを追加
+            Ethna_SOAP_Util::fixRetval($form->retval);
+
+            // シリアライズ
+            $retval_name = preg_replace('/_(.)/e', "strtoupper('\$1')", ucfirst($k)) . "Result";
+            $types .= $this->_serializeTypes($form->retval, $retval_name);
+        }
+
+        return $types . "  </s:schema>\n </types>\n\n";
+    }
+
+    /**
+     *  WSDL(Message部分)を取得する
+     *
+     *  @access private
+     *  @return string  WSDL(Message部分)
+     *  @todo   respect access controlls
+     */
+    function _getMessage()
+    {
+        $n = 1;
+        $message = "";
+        foreach ($this->controller->soap_action as $k => $v) {
+            $message .= $this->_serializeMessage($k, $n);
+            $n++;
+        }
+
+        return $message . "\n";
+    }
+
+    /**
+     *  WSDL(PortType部分)を取得する
+     *
+     *  @access private
+     *  @return string  WSDL(PortType部分)
+     */
+    function _getPortType()
+    {
+        $port_type = sprintf(" <portType name=\"%sSoap\">\n", $this->name);
+
+        $n = 1;
+        foreach ($this->controller->soap_action as $k => $v) {
+            $port_type .= $this->_serializePortType($k, $n);
+            $n++;
+        }
+
+        $port_type .= " </portType>\n\n";
+
+        return $port_type;
+    }
+
+    /**
+     *  WSDL(Binding部分)を取得する
+     *
+     *  @access private
+     *  @return string  WSDL(Binding部分)
+     */
+    function _getBinding()
+    {
+        $namespace = "urn:" . $this->name;
+        $binding = " <binding name=\"" . $this->name . "Soap\" type=\"tns:" . $this->name . "Soap\">\n";
+        $binding .= "  <soap:binding style=\"rpc\" transport=\"http://schemas.xmlsoap.org/soap/http\" />\n";
+
+        $n = 1;
+        foreach ($this->controller->soap_action as $k => $v) {
+            $binding .= "  <operation name=\"$k\">\n";
+            $binding .= "   <soap:operation soapAction=\"$k\" style=\"rpc\" />\n";
+            $binding .= "   <input name=\"${k}${n}SoapIn\">\n";
+            $binding .= "    <soap:body use=\"encoded\" namespace=\"$namespace\" encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" />\n";
+            $binding .= "   </input>\n";
+            $binding .= "   <output name=\"${k}${n}SoapOut\">\n";
+            $binding .= "    <soap:body use=\"encoded\" namespace=\"$namespace\" encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" />\n";
+            $binding .= "   </output>\n";
+            $binding .= "  </operation>\n";
+            $n++;
+        }
+        $binding .= " </binding>\n";
+
+        return $binding;
+    }
+
+    /**
+     *  WSDL(Service部分)を取得する
+     *
+     *  @access private
+     *  @return string  WSDL(Service部分)
+     */
+    function _getService()
+    {
+        $name = $this->name;
+        $gateway= $this->gateway;
+        $service = " <service name=\"$name\">\n";
+        $service .= "  <port name=\"${name}Soap\" binding=\"tns:${name}Soap\">\n";
+        $service .= "   <soap:address location=\"$gateway\" />\n";
+        $service .= "  </port>\n";
+        $service .= " </service>\n";
+
+        return $service;
+    }
+
+    /**
+     *  WSDL(フッタ部分)を取得する
+     *
+     *  @access private
+     *  @return string  WSDL(フッタ部分)
+     */
+    function _getFooter()
+    {
+        return "</definitions>\n";
+    }
+
+    /**
+     *  ネームスペースを取得する
+     *
+     *  @access private
+     *  @return string  ネームスペース
+     */
+    function _getNameSpace()
+    {
+        return sprintf("%s/%s", $this->config->get('url'), $this->name);
+    }
+
+    /**
+     *  型のシリアライズ
+     *
+     *  @access private
+     *  @param  array   $def    型定義
+     *  @param  string  $name   変数名
+     *  @return string  シリアライズされた型定義
+     */
+    function _serializeTypes($def, $name)
+    {
+        if (is_array($def) == false) {
+            // nothing to do
+            return;
+        }
+
+        $types = $this->__serializeTypes($def, $name);
+
+        foreach ($def as $k => $v) {
+            if (is_array($def[$k]) == false || Ethna_SOAP_Util::isArrayOfScalar($def[$k])) {
+                continue;
+            }
+            $types .= $this->_serializeTypes($def[$k], $k);
+        }
+
+        return $types;
+    }
+
+    /**
+     *  型のシリアライズ(エレメント対応)
+     *
+     *  @access private
+     *  @param  array   $def    型定義
+     *  @param  string  $name   変数名
+     *  @return string  シリアライズされた型定義(各要素)
+     */
+    function __serializeTypes($def, $name)
+    {
+        $keys = array_keys($def);
+
+        if (Ethna_SOAP_Util::isArrayOfObject($def)) {
+            $array_name = sprintf("ArrayOf%s", $keys[0]);
+            $name = $keys[0];
+            $types = "   <s:complexType name=\"$array_name\">\n";
+            $types .= "    <s:complexContent mixed=\"false\">\n";
+            $types .= "     <s:restriction base=\"soapenc:Array\">\n";
+            $types .= "      <s:attribute d7p1:arrayType=\"tns:$name" . "[]" . "\" " .
+                "ref=\"soapenc:arrayType\" xmlns:d7p1=\"http://schemas.xmlsoap.org/wsdl/\" />\n";
+            $types .= "     </s:restriction>\n";
+            $types .= "    </s:complexContent>\n";
+            $types .= "   </s:complexType>\n";
+            return $types;
+        }
+
+        $types = "   <s:complexType name=\"$name\">\n";
+        $types .= "    <s:sequence>\n";
+        foreach ($keys as $key) {
+            if (is_array($def[$key])) {
+                $inner_keys = array_keys($def[$key]);
+                if (is_array($def[$key][$inner_keys[0]])) {
+                    $inner_name = sprintf("ArrayOf%s", $inner_keys[0]);
+                    $types .= "     <s:element name=\"$key\" type=\"tns:$inner_name\" />\n";
+                } else {
+                    $type_name = "tns:" . Ethna_SOAP_Util::getArrayTypeName($def[$key][$inner_keys[0]]);
+                    $types .= "     <s:element name=\"$key\" type=\"$type_name\" />\n";
+                }
+            } else {
+                $type_name = Ethna_SOAP_Util::getScalarTypeName($def[$key]);
+                $types .= "     <s:element name=\"$key\" type=\"s:$type_name\" />\n";
+            }
+        }
+        $types .= "    </s:sequence>\n";
+        $types .= "   </s:complexType>\n";
+
+        return $types;
+    }
+
+    /**
+     *  Messageのシリアライズ
+     *
+     *  @access private
+     *  @param  string  $name   message名
+     *  @param  int     $serno  message連番
+     *  @return string  シリアライズされたmessage
+     */
+    function _serializeMessage($name, $serno)
+    {
+        $action_form_name = $this->controller->getActionFormName($name);
+        $form =& new $action_form_name($this->controller);
+
+        /* SoapIn */
+        $message = " <message name=\"${name}${serno}SoapIn\">\n";
+        $keys = array();
+        if (is_array($form->form)) {
+            $keys = array_keys($form->form);
+        }
+        foreach ($keys as $key) {
+            $type_id =& $form->form[$key]['type'];
+            if (is_array($type_id)) {
+                $type_keys = array_keys($type_id);
+                $type = "tns:" . Ethna_SOAP_Util::getArrayTypeName($type_id[$type_keys[0]]);
+            } else {
+                $type = "s:" . Ethna_SOAP_Util::getScalarTypeName($type_id);
+            }
+            $message .= "  <part name=\"$key\" type=\"$type\" />\n";
+        }
+        $message .= " </message>\n";
+
+        /* SoapOut */
+        $message .= " <message name=\"${name}${serno}SoapOut\">\n";
+        if ($form->retval == null) {
+            $type = "tns:Result";
+        } else {
+            $type = "tns:${name}Result";
+        }
+        $message .= "  <part name=\"result\" type=\"$type\" />\n";
+        $message .= " </message>\n";
+
+        return $message;
+    }
+
+    /**
+     *  PortTypeのシリアライズ
+     *
+     *  @access private
+     *  @param  string  $name   porttype名
+     *  @param  int     $serno  porttype連番
+     *  @return string  シリアライズされたporttype
+     */
+    function _serializePortType($name, $serno)
+    {
+        $action_form_name = $this->controller->getActionFormName($name);
+        $form =& new $action_form_name($this->controller);
+
+        $args = null;
+        if (is_array($form->form)) {
+            $args = implode(' ', array_keys($form->form));
+        }
+
+        $port_type = "  <operation name=\"$name\" parameterOrder=\"$args\">\n";
+        $port_type .= "   <input name=\"${name}${serno}SoapIn\" message=\"tns:${name}${serno}SoapIn\" />\n";
+        $port_type .= "   <output name=\"${name}${serno}SoapOut\" message=\"tns:${name}${serno}SoapOut\" />\n";
+        $port_type .= "  </operation>\n";
+
+        return $port_type;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/View/Ethna_View_Info.php b/Idea_Plugin_Extlib/class/View/Ethna_View_Info.php
new file mode 100644 (file)
index 0000000..8856eb7
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_View_Info.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_View_Info
+/**
+ *  __ethna_info__ビューの実装
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_View_Info extends Ethna_ViewClass
+{
+    /**#@+
+     *  @access private
+     */
+
+    /**#@-*/
+
+    /**
+     *  遷移前処理
+     *
+     *  @access public
+     */
+    function preforward()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $em =& new Ethna_InfoManager($this->backend);
+
+        // cores
+        $this->af->setApp('app_id', $ctl->getAppId());
+        $this->af->setApp('ethna_version', ETHNA_VERSION);
+
+        // actions
+        $this->af->setApp('action_list', $em->getActionList());
+
+        // views 
+        $this->af->setApp('forward_list', $em->getForwardList());
+
+        // configuration
+        $this->af->setApp('configuration', $em->getConfiguration());
+
+        // plugins
+        $this->af->setApp('plugin_list', $em->getPluginList());
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/View/Ethna_View_List.php b/Idea_Plugin_Extlib/class/View/Ethna_View_List.php
new file mode 100644 (file)
index 0000000..3ed1d57
--- /dev/null
@@ -0,0 +1,181 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_View_List.php
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+// {{{ Ethna_View_List
+/**
+ *  リストビュー基底クラスの実装
+ *
+ *  @author     Masaki Fujimoto <fujimoto@php.net>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_View_List extends Ethna_ViewClass
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    int     表示開始オフセット */
+    var $offset = 0;
+
+    /** @var    int     表示件数 */
+    var $count = 25;
+
+    /** @var    array   検索対象項目一覧 */
+    var $search_list = array();
+
+    /** @var    string  検索マネージャクラス名 */
+    var $manager_name = null;
+
+    /** @var    string  表示対象クラス名 */
+    var $class_name = null;
+
+    /**#@-*/
+
+    /**
+     *  遷移前処理
+     *
+     *  @access public
+     */
+    function preforward()
+    {
+        // 表示オフセット/件数設定
+        $this->offset = $this->af->get('offset');
+        if ($this->offset == "") {
+            $this->offset = 0;
+        }
+        if (intval($this->af->get('count')) > 0) {
+            $this->count = intval($this->af->get('count'));
+        }
+
+        // 検索条件
+        $filter = array();
+        $sort = array();
+        foreach ($this->search_list as $key) {
+            if ($this->af->get("s_$key") != "") {
+                $filter[$key] = $this->af->get("s_$key");
+            }
+            if ($this->af->get("sort") == $key) {
+                $order = $this->af->get("order") == "desc" ? OBJECT_SORT_DESC : OBJECT_SORT_ASC;
+                $sort = array(
+                    $key => $order,
+                );
+            }
+        }
+
+        // 表示項目一覧
+        $manager_name = $this->manager_name;
+        for ($i = 0; $i < 2; $i++) {
+            list($total, $obj_list) = $this->$manager_name->getObjectList($this->class_name, $filter, $sort, $this->offset, $this->count);
+            if (count($obj_list) == 0 && $this->offset >= $total) {
+                $this->offset = 0;
+                continue;
+            }
+            break;
+        }
+
+        $r = array();
+        foreach ($obj_list as $obj) {
+            $value = $obj->getNameObject();
+            $value = $this->_fixNameObject($value, $obj);
+            $r[] = $value;
+        }
+        $list_name = sprintf("%s_list", strtolower(preg_replace('/(.)([A-Z])/', '\\1_\\2', $this->class_name)));
+        $this->af->setApp($list_name, $r);
+
+        // ナビゲーション
+        $this->af->setApp('nav', $this->_getNavigation($total, $obj_list));
+        $this->af->setAppNE('query', $this->_getQueryParameter());
+
+        // 検索オプション
+        $this->_setQueryOption();
+    }
+
+    /**
+     *  表示項目を修正する
+     *
+     *  @access protected
+     */
+    function _fixNameObject($value, $obj)
+    {
+        return $value;
+    }
+    
+    /**
+     *  ナビゲーション情報を取得する
+     *
+     *  @access private
+     *  @param  int     $total      検索総件数
+     *  @param  array   $list       検索結果
+     *  @return array   ナビゲーション情報を格納した配列
+     */
+    function _getNavigation($total, &$list)
+    {
+        $nav = array();
+        $nav['offset'] = $this->offset;
+        $nav['from'] = $this->offset + 1;
+        if ($total == 0) {
+            $nav['from'] = 0;
+        }
+        $nav['to'] = $this->offset + count($list);
+        $nav['total'] = $total;
+        if ($this->offset > 0) {
+            $prev_offset = $this->offset - $this->count;
+            if ($prev_offset < 0) {
+                $prev_offset = 0;
+            }
+            $nav['prev_offset'] = $prev_offset;
+        }
+        if ($this->offset + $this->count < $total) {
+            $next_offset = $this->offset + count($list);
+            $nav['next_offset'] = $next_offset;
+        }
+        $nav['direct_link_list'] = Ethna_Util::getDirectLinkList($total, $this->offset, $this->count);
+
+        return $nav;
+    }
+
+    /**
+     *  検索項目を生成する
+     *
+     *  @access protected
+     */
+    function _setQueryOption()
+    {
+    }
+
+    /**
+     *  検索内容を格納したGET引数を生成する
+     *
+     *  @access private
+     *  @param  array   $search_list    検索対象一覧
+     *  @return string  検索内容を格納したGET引数
+     */
+    function _getQueryParameter()
+    {
+        $query = "";
+
+        foreach ($this->search_list as $key) {
+            $value = $this->af->get("s_$key");
+            if (is_array($value)) {
+                foreach ($value as $v) {
+                    $query .= "&s_$key" . "[]=" . urlencode($v);
+                }
+            } else {
+                $query .= "&s_$key=" . urlencode($value);
+            }
+        }
+
+        return $query;
+    }
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/class/View/Ethna_View_UnitTest.php b/Idea_Plugin_Extlib/class/View/Ethna_View_UnitTest.php
new file mode 100644 (file)
index 0000000..9962a1b
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/**
+ *  Ethna_View_UnitTest.php
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+/**
+ *  __ethna_unittest__ビューの実装
+ *
+ *  @author     Takuya Ookubo <sfio@sakura.ai.to>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_View_UnitTest extends Ethna_ViewClass
+{
+    /**
+     *  遷移前処理
+     *
+     *  @access public
+     */
+    function preforward()
+    {
+        // タイムアウトしないように変更
+        if (!ini_get('safe_mode')) {
+            $max_execution_time = ini_get('max_execution_time');
+            set_time_limit(0);
+        }
+
+        if (!headers_sent()) {
+            // キャッシュしない
+            header("Expires: Thu, 01 Jan 1970 00:00:00 GMT");
+            header("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T"));
+            header("Cache-Control: no-store, no-cache, must-revalidate");
+            header("Cache-Control: post-check=0, pre-check=0", false);
+            header("Pragma: no-cache");
+        }
+
+        $ctl =& Ethna_Controller::getInstance();
+
+        // cores
+        $this->af->setApp('app_id', $ctl->getAppId());
+        $this->af->setApp('ethna_version', ETHNA_VERSION);
+
+        // include
+        $file = sprintf("%s/%s_UnitTestManager.php",
+                        $ctl->getDirectory('app'),
+                        $ctl->getAppId()
+                );
+        include_once $file;
+
+        // run
+        $r = sprintf("%s_UnitTestManager", $ctl->getAppId());
+        $ut =& new $r($this->backend);
+        list($report, $result) = $ut->run();
+        
+        // result
+        $this->af->setApp('report', $report);
+        $this->af->setApp('result', $result);
+
+        // タイムアウトを元に戻す
+        if (!ini_get('safe_mode')) {
+            set_time_limit($max_execution_time);
+        }
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/misc/_ethna b/Idea_Plugin_Extlib/misc/_ethna
new file mode 100644 (file)
index 0000000..b8cbd71
--- /dev/null
@@ -0,0 +1,43 @@
+#compdef ethna
+
+# usage:
+#   1. copy this file to your favorite directory (ex, ~/.zshrc.d/completion)
+#   2. add below lines to your .zshrc:
+#      > fpath=(~/.zshrc.d/completion $fpath)
+#      > autoload -U compinit
+#      > compinit -u
+#   3. push [tab] when you typing ethna command's argument.
+#      % ethna add-[tab]
+#
+# note:
+#   - we tested this function only with Debian/GNU Linux.
+#   - $Id: _ethna 411 2006-11-17 02:32:32Z ichii386 $
+
+_ethna() {
+    local curcontext="$curcontext" state line expl ret=1
+
+    _arguments -C \
+        '(-v --version)'{-v,--version}'[show version]' \
+        '1:ethna command:->ethna-cmd' \
+        '*:ethna command args:->ethna-cmd-arg' \
+        && ret=0
+
+    if [[ -n "$state" ]]; then
+        case $state in
+            ethna-cmd)
+                handles=( $($service | grep '^ \+.* -> .*:$' | awk '{print $1}') )
+                _wanted ethna-cmd expl 'ethna cmd' compadd -a handles && ret=0
+            ;;
+            ethna-cmd-arg)
+                usage=$($service | grep -A 1 "^ \+$words[2] ->")
+                if [[ -n "$usage" ]]; then
+                    _wanted ethna-cmd-arg expl 'ethna cmd arg' compadd -x "$usage" && ret=0
+                else
+                    _wanted ethna-cmd-arg expl 'ethna cmd arg' compadd -x "unknown command" && ret=0
+                fi
+            ;;
+        esac    
+    fi
+
+    return ret
+}
diff --git a/Idea_Plugin_Extlib/misc/optional_package/OPTIONAL_PACKAGE_HOWTO b/Idea_Plugin_Extlib/misc/optional_package/OPTIONAL_PACKAGE_HOWTO
new file mode 100644 (file)
index 0000000..8c255e7
--- /dev/null
@@ -0,0 +1,44 @@
+ここでは、Ethnaに依存する simpletest 及び、Smartyのパッケージ作成方法について
+説明します。
+
+1. PEAR_PackageProjectorをインストール
+- http://servlet.sakura.ne.jp/wiki/index.php?PEAR_PackageProjector の手順に従ってインストールする
+-- Linux で実行している場合、PEAR/PackageProjector/Derictory.php を以下のように修正しないと失敗するかもしれません。
+-- 310行目を以下のように修正
+
+         if (self::isAbsolutePath($path)) {
+             return $path;
+         }
+-        $tmp = (OS_WINDOWS) ? getcwd() :$_ENV['PWD'];
++        $tmp = (!OS_WINDOWS) ? getcwd() :$_ENV['PWD'];
+         return $tmp .DIRECTORY_SEPARATOR.$path;
+     }
+
+
+2. PEAR_PackageProjector のコマンド pearproj のパスを設定
+- Smarty/build/build[.bat] を開いて、pearproj コマンドを、
+  インストールされた pearproj コマンドの絶対パスに置き換える。
+- simpletest/build/build[.bat] も同様にする
+
+3. Smarty, simpletest のパッケージを src ディレクトリに配置
+- Smarty/src ディレクトリに Smarty-x.x.x.tar.gz をダウンロードし、「展開しておく」
+-- 展開しないとパッケージ作成時にエラーになります!
+- simpletest/src ディレクトリに simpletest-x.x.x.tar.gz をダウンロードし、「展開しておく」
+-- 展開しないとパッケージ作成時にエラーになります!
+-- 展開後、simpletest/src/simpletest ディレクトリができるので、simpletest/src/simpletest-x.x.x にリネームする
+
+4. build.conf の編集
+- Smarty/build/build.conf のバージョン番号をダウンロードしたSmartyのバージョン番号に置き換える
+- simpletest/build/build.conf も同様にする
+
+5. パッケージを生成
+- 以下のコマンドを実行すれば、Smarty と simpletest の release ディレクトリに
+  PEARパッケージが作成されます。
+
+-- Smartyの場合
+--- $ cd Smarty/build
+--- $ ./build
+
+-- simpletest の場合
+--- $cd simpletest/build
+--- $ ./build
diff --git a/Idea_Plugin_Extlib/misc/optional_package/Smarty/build/build b/Idea_Plugin_Extlib/misc/optional_package/Smarty/build/build
new file mode 100644 (file)
index 0000000..c0d8d44
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+pearproj -t --configure ./build.conf --make -p ./
diff --git a/Idea_Plugin_Extlib/misc/optional_package/Smarty/build/build.bat b/Idea_Plugin_Extlib/misc/optional_package/Smarty/build/build.bat
new file mode 100644 (file)
index 0000000..cceeab8
--- /dev/null
@@ -0,0 +1 @@
+pearproj -t --configure ./build.conf --make -p ./
\ No newline at end of file
diff --git a/Idea_Plugin_Extlib/misc/optional_package/Smarty/build/build.conf b/Idea_Plugin_Extlib/misc/optional_package/Smarty/build/build.conf
new file mode 100644 (file)
index 0000000..bb0dd8f
--- /dev/null
@@ -0,0 +1,74 @@
+;VERSION [2.6.22]
+
+[project]
+src_dir = ../src/Smarty-2.6.22/libs
+release_dir = ../release
+
+[package]
+package_name = Smarty
+package_type = php
+baseinstalldir = /Smarty
+channel = pear.ethna.jp
+summary = PHP Template Engine
+;description = Most Popular Template Engine
+notes = PEAR Style Package
+;summary_file = <filepath>
+description_file = ../src/Smarty-2.6.22/README
+;notes_file = <filepath>
+
+[role]
+;; role value is <php|data|doc|test|script|src>
+;sh = script
+debug.tpl = php
+BUGS = doc
+COPYING.lib = doc
+ChangeLog = doc
+FAQ = doc
+NEWS = doc
+QUICK_START = doc
+README = doc
+RELEASE_NOTES = doc
+TODO = doc
+
+[version]
+release_ver = 2.6.22
+release_stab = stable
+api_ver = 2.6.22
+api_stab = stable
+php_min = 4.0.6
+pear_min = 1.4.0
+
+[license]
+name = LGPL
+uri = http://www.gnu.org/licenses/lgpl.html
+
+[maintainer://monte]
+name = Monte Ohrt
+email = monte@ohrt.com
+role = lead
+
+[maintainer://andrei]
+name = Andrei Zmievski
+email = andrei@php.net
+role = lead
+
+;[file://<filepath>]
+;commandscript = command
+;ignore = 1
+;platform = windows
+;install = renamefile
+;; role value is <php|data|doc|test|script|src>
+;role = script
+
+;[dep://<packagename>]
+;; type: <required|optional>
+;type = optional
+;; channel: pear.php.net or __uri or etc...
+;channel = pear.php.net
+;min = 0
+;max = 0
+;recommended = 0
+;exclude = 0
+;providesextension = 0
+;nodefault = 0
+
diff --git a/Idea_Plugin_Extlib/misc/optional_package/Smarty/src/Smarty-2.6.22.tar.gz b/Idea_Plugin_Extlib/misc/optional_package/Smarty/src/Smarty-2.6.22.tar.gz
new file mode 100644 (file)
index 0000000..9ed7587
Binary files /dev/null and b/Idea_Plugin_Extlib/misc/optional_package/Smarty/src/Smarty-2.6.22.tar.gz differ
diff --git a/Idea_Plugin_Extlib/misc/optional_package/simpletest/build/build b/Idea_Plugin_Extlib/misc/optional_package/simpletest/build/build
new file mode 100644 (file)
index 0000000..c0d8d44
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+pearproj -t --configure ./build.conf --make -p ./
diff --git a/Idea_Plugin_Extlib/misc/optional_package/simpletest/build/build.bat b/Idea_Plugin_Extlib/misc/optional_package/simpletest/build/build.bat
new file mode 100644 (file)
index 0000000..fe5af31
--- /dev/null
@@ -0,0 +1 @@
+pearproj -t --configure ./build.conf --make -p ./
diff --git a/Idea_Plugin_Extlib/misc/optional_package/simpletest/build/build.conf b/Idea_Plugin_Extlib/misc/optional_package/simpletest/build/build.conf
new file mode 100644 (file)
index 0000000..46131a9
--- /dev/null
@@ -0,0 +1,69 @@
+;VERSION [1.0.1]
+
+[project]
+src_dir = ../src/simpletest
+release_dir = ../release
+
+[package]
+package_name = simpletest
+package_type = php
+baseinstalldir = /simpletest
+channel = pear.ethna.jp
+summary = Unit testing, mock objects and web testing framework for PHP built around test cases. 
+;description = 
+notes = PEAR Style Package
+;summary_file = <filepath>
+description_file = ../src/simpletest/README
+;notes_file = <filepath>
+
+[role]
+;; role value is <php|data|doc|test|script|src>
+;sh = script
+debug.tpl = php
+BUGS = doc
+COPYING.lib = doc
+ChangeLog = doc
+FAQ = doc
+NEWS = doc
+QUICK_START = doc
+README = doc
+RELEASE_NOTES = doc
+TODO = doc
+
+[version]
+release_ver = 1.0.1
+release_stab = stable
+api_ver = 1.0.1
+api_stab = stable
+php_min = 4.0.6
+pear_min = 1.4.0
+
+[license]
+name = LGPL
+uri = http://www.gnu.org/licenses/lgpl.html
+
+[maintainer://lastcraft]
+name = lastcraft
+email = lastcraft at users.sourceforge.net
+role = lead
+
+;[file://<filepath>]
+;commandscript = command
+;ignore = 1
+;platform = windows
+;install = renamefile
+;; role value is <php|data|doc|test|script|src>
+;role = script
+
+;[dep://<packagename>]
+;; type: <required|optional>
+;type = optional
+;; channel: pear.php.net or __uri or etc...
+;channel = pear.php.net
+;min = 0
+;max = 0
+;recommended = 0
+;exclude = 0
+;providesextension = 0
+;nodefault = 0
+
diff --git a/Idea_Plugin_Extlib/misc/optional_package/simpletest/src/simpletest_1.0.1.tar.gz b/Idea_Plugin_Extlib/misc/optional_package/simpletest/src/simpletest_1.0.1.tar.gz
new file mode 100644 (file)
index 0000000..b97fc21
Binary files /dev/null and b/Idea_Plugin_Extlib/misc/optional_package/simpletest/src/simpletest_1.0.1.tar.gz differ
diff --git a/Idea_Plugin_Extlib/misc/sample_package.ini b/Idea_Plugin_Extlib/misc/sample_package.ini
new file mode 100644 (file)
index 0000000..d9fcd07
--- /dev/null
@@ -0,0 +1,42 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; a sample for packager ini file.
+;;;
+;;; command line to get package tgz files:
+;;; > ethna make-plugin-package --inifile=./sample_package.ini --skelfile=./sample_plugin.php
+;;;
+;;; $Id$
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+[plugin]
+type    = Sample
+name    = Phpinfo
+master  = true
+local   = true
+
+[package]
+channel = pear.ethna.jp
+summary = "sample package for plugin packager"
+description = "This is a sample package for plugin packager."
+
+[release]
+version = 1.0.0
+state   = stable
+notes   = "initial release."
+
+[maintainers]
+role1   = lead
+name1   = ICHII Takashi
+user1   = ichii386
+email1  = ichii386@schweetheart.jp
+active1 = yes
+
+role2   = helper
+name2   = Masaki Fujimoto
+user2   = fujimoto
+email2  = fujimoto@php.net
+active2 = no
+
+[license]
+name    = The BSD License
+uri     = http://www.opensource.org/licenses/bsd-license.php
+
diff --git a/Idea_Plugin_Extlib/misc/sample_plugin.php b/Idea_Plugin_Extlib/misc/sample_plugin.php
new file mode 100644 (file)
index 0000000..32048f6
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  plugin sample file.
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Sample
+ *  @version    $Id$
+ */
+
+// {{{ {$application_id}_Plugin_Sample_Phpinfo
+/**
+ *  plugin sample class.
+ *
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ *  @access     public
+ *  @package    Sample
+ */
+class {$application_id}_Plugin_Sample_Phpinfo
+{
+    // {{{ perform
+    /**
+     *  do phpinfo().
+     *
+     *  @access public
+     */
+    function perform()
+    {
+        phpinfo();
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.action.default.php b/Idea_Plugin_Extlib/skel/app.action.default.php
new file mode 100644 (file)
index 0000000..f5a0a53
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ *  Index.php
+ *
+ *  @author    {$author}
+ *  @package   {$project_id}
+ *  @version   $Id$
+ */
+
+/**
+ *  Index form implementation
+ *
+ *  @author    {$author}
+ *  @access    public
+ *  @package   {$project_id}
+ */
+
+class {$project_id}_Form_Index extends {$project_id}_ActionForm
+{
+    /**
+     *  @access   private
+     *  @var      array   form definition.
+     */
+    var $form = array(
+       /*
+        *  TODO: Write form definition which this action uses.
+        *  @see http://ethna.jp/ethna-document-dev_guide-form.html
+        *
+        *  Example(You can omit all elements except for "type" one) :
+        *
+        *  'sample' => array(
+        *      // Form definition
+        *      'type'        => VAR_TYPE_INT,    // Input type
+        *      'form_type'   => FORM_TYPE_TEXT,  // Form type
+        *      'name'        => 'Sample',        // Display name
+        *  
+        *      //  Validator (executes Validator by written order.)
+        *      'required'    => true,            // Required Option(true/false)
+        *      'min'         => null,            // Minimum value
+        *      'max'         => null,            // Maximum value
+        *      'regexp'      => null,            // String by Regexp
+        *      'mbregexp'    => null,            // Multibype string by Regexp
+        *      'mbregexp_encoding' => 'UTF-8',   // Matching encoding when using mbregexp 
+        *
+        *      //  Filter
+        *      'filter'      => 'sample',        // Optional Input filter to convert input
+        *      'custom'      => null,            // Optional method name which
+        *                                        // is defined in this(parent) class.
+        *  ),
+        */
+    );
+
+    /**
+     *  Form input value convert filter : sample
+     *
+     *  @access protected
+     *  @param  mixed   $value  Form Input Value
+     *  @return mixed           Converted result.
+     */
+    /*
+    function _filter_sample($value)
+    {
+        //  convert to upper case.
+        return strtoupper($value);
+    }
+    */
+}
+
+/**
+ *  Index action implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$project_id}_Action_Index extends {$project_id}_ActionClass
+{
+    /**
+     *  preprocess Index action.
+     *
+     *  @access    public
+     *  @return    string  Forward name (null if no errors.)
+     */
+    function prepare()
+    {
+        /**
+        if ($this->af->validate() > 0) {
+            return 'error';
+        }
+        $sample = $this->af->get('sample');
+        */
+        return null;
+    }
+
+    /**
+     *  Index action implementation.
+     *
+     *  @access    public
+     *  @return    string  Forward Name.
+     */
+    function perform()
+    {
+        return 'index';
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.actionclass.php b/Idea_Plugin_Extlib/skel/app.actionclass.php
new file mode 100644 (file)
index 0000000..3aa3490
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  {$project_id}_ActionClass.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+// {{{ {$project_id}_ActionClass
+/**
+ *  action execution class
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @access     public
+ */
+class {$project_id}_ActionClass extends Ethna_ActionClass
+{
+    /**
+     *  authenticate before executing action.
+     *
+     *  @access public
+     *  @return string  Forward name.
+     *                  (null if no errors. false if we have something wrong.)
+     */
+    function authenticate()
+    {
+        return parent::authenticate();
+    }
+
+    /**
+     *  Preparation for executing action. (Form input check, etc.)
+     *
+     *  @access public
+     *  @return string  Forward name.
+     *                  (null if no errors. false if we have something wrong.)
+     */
+    function prepare()
+    {
+        return parent::prepare();
+    }
+
+    /**
+     *  execute action.
+     *
+     *  @access public
+     *  @return string  Forward name.
+     *                  (we does not forward if returns null.)
+     */
+    function perform()
+    {
+        return parent::perform();
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.actionform.php b/Idea_Plugin_Extlib/skel/app.actionform.php
new file mode 100644 (file)
index 0000000..3d0708c
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  {$project_id}_ActionForm.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+// {{{ {$project_id}_ActionForm
+/**
+ *  ActionForm class.
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @access     public
+ */
+class {$project_id}_ActionForm extends Ethna_ActionForm
+{
+    /**#@+
+     *  @access private
+     */
+
+    /** @var    array   form definition (default) */
+    var $form_template = array();
+
+    /**#@-*/
+
+    /**
+     *  Error handling of form input validation.
+     *
+     *  @access public
+     *  @param  string      $name   form item name.
+     *  @param  int         $code   error code.
+     */
+    function handleError($name, $code)
+    {
+        return parent::handleError($name, $code);
+    }
+
+    /**
+     *  setter method for form template.
+     *
+     *  @access protected
+     *  @param  array   $form_template  form template
+     *  @return array   form template after setting.
+     */
+    function _setFormTemplate($form_template)
+    {
+        return parent::_setFormTemplate($form_template);
+    }
+
+    /**
+     *  setter method for form definition.
+     *
+     *  @access protected
+     */
+    function _setFormDef()
+    {
+        return parent::_setFormDef();
+    }
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.controller.php b/Idea_Plugin_Extlib/skel/app.controller.php
new file mode 100644 (file)
index 0000000..4727030
--- /dev/null
@@ -0,0 +1,288 @@
+<?php
+/**
+ *  {$project_id}_Controller.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/** Application base directory */
+define('BASE', dirname(dirname(__FILE__)));
+
+/** include_path setting (adding "/app" and "/lib" directory to include_path) */
+$app = BASE . "/app";
+$lib = BASE . "/lib";
+ini_set('include_path', implode(PATH_SEPARATOR, array($app, $lib)) . PATH_SEPARATOR . ini_get('include_path'));
+
+
+/** including application library. */
+require_once 'Ethna/Ethna.php';
+require_once '{$project_id}_Error.php';
+require_once '{$project_id}_ActionClass.php';
+require_once '{$project_id}_ActionForm.php';
+require_once '{$project_id}_ViewClass.php';
+
+/**
+ *  {$project_id} application Controller definition.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$project_id}_Controller extends Ethna_Controller
+{
+    /**#@+
+     *  @access private
+     */
+
+    /**
+     *  @var    string  Application ID(appid)
+     */
+    var $appid = '{$application_id}';
+
+    /**
+     *  @var    array   forward definition.
+     */
+    var $forward = array(
+        /*
+         *  TODO: write forward definition here.
+         *
+         *  Example:
+         *
+         *  'index'         => array(
+         *      'view_name' => '{$project_id}_View_Index',
+         *  ),
+         */
+    );
+
+    /**
+     *  @var    array   action definition.
+     */
+    var $action = array(
+        /*
+         *  TODO: write action definition here.
+         *
+         *  Example:
+         *
+         *  'index'     => array(),
+         */
+    );
+
+    /**
+     *  @var    array   SOAP action definition.
+     */
+    var $soap_action = array(
+        /*
+         *  TODO: write action definition for SOAP application here.
+         *  Example:
+         *
+         *  'sample'            => array(),
+         */
+    );
+
+    /**
+     *  @var    array       application directory.
+     */
+    var $directory = array(
+        'action'        => 'app/action',
+        'action_cli'    => 'app/action_cli',
+        'action_xmlrpc' => 'app/action_xmlrpc',
+        'app'           => 'app',
+        'plugin'        => 'app/plugin',
+        'bin'           => 'bin',
+        'etc'           => 'etc',
+        'filter'        => 'app/filter',
+        'locale'        => 'locale',
+        'log'           => 'log',
+        'plugins'       => array('app/plugin/Smarty',),
+        'template'      => 'template',
+        'template_c'    => 'tmp',
+        'tmp'           => 'tmp',
+        'view'          => 'app/view',
+        'www'           => 'www',
+        'test'          => 'app/test',
+    );
+
+    /**
+     *  @var    array       database access definition.
+     */
+    var $db = array(
+        ''              => DB_TYPE_RW,
+    );
+
+    /**
+     *  @var    array       extention(.php, etc) configuration.
+     */
+    var $ext = array(
+        'php'           => 'php',
+        'tpl'           => 'tpl',
+    );
+
+    /**
+     *  @var    array   class definition.
+     */
+    var $class = array(
+        /*
+         *  TODO: When you override Configuration class, Logger class,
+         *        SQL class, don't forget to change definition as follows!
+         */
+        'class'         => 'Ethna_ClassFactory',
+        'backend'       => 'Ethna_Backend',
+        'config'        => 'Ethna_Config',
+        'db'            => 'Ethna_DB_PEAR',
+        'error'         => 'Ethna_ActionError',
+        'form'          => '{$project_id}_ActionForm',
+        'i18n'          => 'Ethna_I18N',
+        'logger'        => 'Ethna_Logger',
+        'plugin'        => 'Ethna_Plugin',
+        'session'       => 'Ethna_Session',
+        'sql'           => 'Ethna_AppSQL',
+        'view'          => '{$project_id}_ViewClass',
+        'renderer'      => 'Ethna_Renderer_Smarty',
+        'url_handler'   => '{$project_id}_UrlHandler',
+    );
+
+    /**
+     *  @var    array       list of application id where Ethna searches plugin.
+     */
+    var $plugin_search_appids = array(
+        /*
+         *  write list of application id where Ethna searches plugin.
+         *
+         *  Example:
+         *  When there are plugins whose name are like "Common_Plugin_Foo_Bar" in
+         *  application plugin directory, Ethna searches them in the following order.
+         *
+         *  1. Common_Plugin_Foo_Bar,
+         *  2. {$project_id}_Plugin_Foo_Bar
+         *  3. Ethna_Plugin_Foo_Bar
+         *
+         *  'Common', '{$project_id}', 'Ethna',
+         */
+        '{$project_id}', 'Ethna',
+    );
+
+    /**
+     *  @var    array       filter definition.
+     */
+    var $filter = array(
+        /*
+         *  TODO: when you use filter, write filter plugin name here.
+         *  (If you specify class name, Ethna reads filter class in 
+         *   filter directory)
+         *
+         *  Example:
+         *
+         *  'ExecutionTime',
+         */
+    );
+
+    /**
+     *  @var    array   smarty modifier definition.
+     */
+    var $smarty_modifier_plugin = array(
+        /*
+         *  TODO: write user defined smarty modifier here.
+         *
+         *  Example:
+         *
+         *  'smarty_modifier_foo_bar',
+         */
+    );
+
+    /**
+     *  @var    array   smarty function definition.
+     */
+    var $smarty_function_plugin = array(
+        /*
+         *  TODO: write user defined smarty function here.
+         *
+         *  Example:
+         *
+         *  'smarty_function_foo_bar',
+         */
+    );
+
+    /**
+     *  @var    array   smarty block definition.
+     */
+    var $smarty_block_plugin = array(
+        /*
+         *  TODO: write user defined smarty block here.
+         *
+         *  Example:
+         * 
+         *  'smarty_block_foo_bar',
+         */
+    );
+
+    /**
+     *  @var    array   smarty prefilter definition.
+     */
+    var $smarty_prefilter_plugin = array(
+        /*
+         *  TODO: write user defined smarty prefilter here.
+         *
+         *  Example:
+         *
+         *  'smarty_prefilter_foo_bar',
+         */
+    );
+
+    /**
+     *  @var    array   smarty postfilter definition.
+     */
+    var $smarty_postfilter_plugin = array(
+        /*
+         *  TODO: write user defined smarty postfilter here.
+         *
+         *  Example:
+         *
+         *  'smarty_postfilter_foo_bar',
+         */
+    );
+
+    /**
+     *  @var    array   smarty outputfilter definition.
+     */
+    var $smarty_outputfilter_plugin = array(
+        /*
+         *  TODO: write user defined smarty outputfilter here.
+         *
+         *  Example:
+         *
+         *  'smarty_outputfilter_foo_bar',
+         */
+    );
+
+    /**#@-*/
+
+    /**
+     *  Get Default language and locale setting.
+     *  If you want to change Ethna's output encoding, override this method.
+     *
+     *  @access protected
+     *  @return array   locale name(e.x ja_JP, en_US .etc),
+     *                  system encoding name,
+     *                  client encoding name(= template encoding)
+     *                  (locale name is "ll_cc" format. ll = language code. cc = country code.)
+     */
+    function _getDefaultLanguage()
+    {
+        return array('{$locale}', 'UTF-8', '{$client_enc}');
+    }
+
+    /**
+     *  テンプレートエンジンのデフォルト状態を設定する
+     *
+     *  @access protected
+     *  @param  object  Ethna_Renderer  レンダラオブジェクト
+     *  @obsolete
+     */
+    function _setDefaultTemplateEngine(&$renderer)
+    {
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.error.php b/Idea_Plugin_Extlib/skel/app.error.php
new file mode 100644 (file)
index 0000000..2aa8d98
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+/**
+ *  {$project_id}_Error.php
+ *
+ *  @package   {$project_id}
+ *
+ *  $Id$
+ */
+
+/*--- Application Error Definition ---*/
+/*
+ *  TODO: Write application error definition here.
+ *        Error codes 255 and below are reserved 
+ *        by Ethna, so use over 256 value for error code.
+ *
+ *  Example:
+ *  define('E_LOGIN_INVALID', 256);
+ */
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.plugin.filter.default.php b/Idea_Plugin_Extlib/skel/app.plugin.filter.default.php
new file mode 100644 (file)
index 0000000..b6a0098
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+/**
+ *  {$project_id}_Plugin_Filter_ExecutionTime.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  filter plugin implementation for measuring execution time.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$project_id}_Plugin_Filter_ExecutionTime extends Ethna_Plugin_Filter
+{
+    /**#@+
+     *  @access private
+     */
+
+    /**
+     *  @var    int     Start time.
+     */
+    var $stime;
+
+    /**#@-*/
+
+
+    /**
+     *  filter before first processing.
+     *
+     *  @access public
+     */
+    function preFilter()
+    {
+        $stime = explode(' ', microtime());
+        $stime = $stime[1] + $stime[0];
+        $this->stime = $stime;
+    }
+
+    /**
+     *  filter BEFORE executing action.
+     *
+     *  @access public
+     *  @param  string  $action_name  Action name.
+     *  @return string  null: normal.
+     *                string: if you return string, it will be interpreted
+     *                        as Action name which will be executed immediately.
+     */
+    function preActionFilter($action_name)
+    {
+        return null;
+    }
+
+    /**
+     *  filter AFTER executing action.
+     *
+     *  @access public
+     *  @param  string  $action_name    executed Action name.
+     *  @param  string  $forward_name   return value from executed Action.
+     *  @return string  null: normal.
+     *                string: if you return string, it will be interpreted
+     *                        as Forward name.
+     */
+    function postActionFilter($action_name, $forward_name)
+    {
+        return null;
+    }
+
+    /**
+     *  filter which will be executed at the end.
+     *
+     *  @access public
+     */
+    function postFilter()
+    {
+        $etime = explode(' ', microtime());
+        $etime = $etime[1] + $etime[0];
+        $time   = round(($etime - $this->stime), 4);
+
+        print "\n<!-- page was processed in $time seconds -->\n";
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.unittest.php b/Idea_Plugin_Extlib/skel/app.unittest.php
new file mode 100644 (file)
index 0000000..d0dbed7
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ *  {$project_id}_UnitTestManager.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  {$project_id} Unit Test Manager Class.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$project_id}_UnitTestManager extends Ethna_UnitTestManager
+{
+    /**
+     *  @var    array   General test case definition.
+     */
+    var $testcase = array(
+        /*
+         *  TODO: Write general test case definition here.
+         *
+         *  Example:
+         *
+         *  'util' => 'app/UtilTest.php',
+         */
+    );
+}
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.url_handler.php b/Idea_Plugin_Extlib/skel/app.url_handler.php
new file mode 100644 (file)
index 0000000..d2812bc
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ *  {$project_id}_UrlHandler.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  URLHandler class.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$project_id}_UrlHandler extends Ethna_UrlHandler
+{
+    /** @var    array   Action Mapping */
+    var $action_map = array(
+        /*
+        'user'  => array(
+            'user_login' => array(
+                'path'          => 'login',
+                'path_regexp'   => false,
+                'path_ext'      => false,
+                'option'        => array(),
+            ),
+        ),
+         */
+    );
+
+    /**
+     *  get {$project_id}_UrlHandler class instance.
+     *
+     *  @access public
+     */
+    function &getInstance($class_name = null)
+    {
+        $instance =& parent::getInstance(__CLASS__);
+        return $instance;
+    }
+
+    // {{{ normalize gateway request method.
+    /**
+     *  normalize request(via user defined gateway)
+     *
+     *  @access private
+     */
+    /*
+    function _normalizeRequest_User($http_vars)
+    {
+        return $http_vars;
+    }
+     */
+    // }}}
+
+    // {{{ generate gateway path method.
+    /**
+     *  generate path(via user defined gateway)
+     *
+     *  @access private
+     */
+    /*
+    function _getPath_User($action, $param)
+    {
+        return array("/user", array());
+    }
+     */
+    // }}}
+
+    // {{{ filter 
+    // }}}
+}
+
+// vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.view.default.php b/Idea_Plugin_Extlib/skel/app.view.default.php
new file mode 100644 (file)
index 0000000..b097059
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ *  Index.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  Index view implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$project_id}_View_Index extends {$project_id}_ViewClass
+{
+    /**
+     *  preprocess before forwarding.
+     *
+     *  @access public
+     */
+    function preforward()
+    {
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/app.viewclass.php b/Idea_Plugin_Extlib/skel/app.viewclass.php
new file mode 100644 (file)
index 0000000..bd3550c
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  {$project_id}_ViewClass.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+// {{{ {$project_id}_ViewClass
+/**
+ *  View class.
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @access     public
+ */
+class {$project_id}_ViewClass extends Ethna_ViewClass
+{
+    /**
+     *  set common default value.
+     *
+     *  @access protected
+     *  @param  object  {$project_id}_Renderer  Renderer object.
+     */
+    function _setDefault(&$renderer)
+    {
+    }
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/dot.ethna b/Idea_Plugin_Extlib/skel/dot.ethna
new file mode 100644 (file)
index 0000000..6e182e3
--- /dev/null
@@ -0,0 +1,4 @@
+[project]
+ethna_version = {$ethna_version}
+controller_file = "app/{$project_id}_Controller.php"
+controller_class = "{$project_id}_Controller"
diff --git a/Idea_Plugin_Extlib/skel/etc.ini.php b/Idea_Plugin_Extlib/skel/etc.ini.php
new file mode 100644 (file)
index 0000000..984e2bd
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/*
+ * {$project_prefix}-ini.php
+ *
+ * update:
+ */
+$config = array(
+    // site
+    'url' => '',
+
+    // debug
+    // (to enable ethna_info and ethna_unittest, turn this true)
+    'debug' => false,
+
+    // db
+    // sample-1: single db
+    // 'dsn' => 'mysql://user:password@server/database',
+    //
+    // sample-2: single db w/ multiple users
+    // 'dsn'   => 'mysql://rw_user:password@server/database', // read-write
+    // 'dsn_r' => 'mysql://ro_user:password@server/database', // read-only
+    //
+    // sample-3: multiple db (slaves)
+    // 'dsn'   => 'mysql://rw_user:password@master/database', // read-write(master)
+    // 'dsn_r' => array(
+    //     'mysql://ro_user:password@slave1/database',         // read-only(slave)
+    //     'mysql://ro_user:password@slave2/database',         // read-only(slave)
+    // ),
+
+    // log
+    // sample-1: sigile facility
+    'log_facility'          => 'echo',
+    'log_level'             => 'warning',
+    'log_option'            => 'pid,function,pos',
+    'log_filter_do'         => '',
+    'log_filter_ignore'     => 'Undefined index.*%%.*tpl',
+
+    // sample-2: mulitple facility
+    //'log' => array(
+    //    'echo'  => array(
+    //        'level'         => 'warning',
+    //    ),
+    //    'file'  => array(
+    //        'level'         => 'notice',
+    //        'file'          => '/var/log/{$project_prefix}.log',
+    //        'mode'          => 0666,
+    //    ),
+    //    'alertmail'  => array(
+    //        'level'         => 'err',
+    //        'mailaddress'   => 'alert@ml.example.jp',
+    //    ),
+    //),
+    //'log_option'            => 'pid,function,pos',
+    //'log_filter_do'         => '',
+    //'log_filter_ignore'     => 'Undefined index.*%%.*tpl',
+
+    // memcache
+    // sample-1: single (or default) memcache
+    // 'memcache_host' => 'localhost',
+    // 'memcache_port' => 11211,
+    // 'memcache_use_pconnect' => false,
+    // 'memcache_retry' => 3,
+    // 'memcache_timeout' => 3,
+    //
+    // sample-2: multiple memcache servers (distributing w/ namespace and ids)
+    // 'memcache' => array(
+    //     'namespace1' => array(
+    //         0 => array(
+    //             'memcache_host' => 'cache1.example.com',
+    //             'memcache_port' => 11211,
+    //         ),
+    //         1 => array(
+    //             'memcache_host' => 'cache2.example.com',
+    //             'memcache_port' => 11211,
+    //         ),
+    //     ),
+    // ),
+
+    // i18n
+    //'use_gettext' => false,
+    
+    // csrf
+    // 'csrf' => 'Session',
+);
+?>
diff --git a/Idea_Plugin_Extlib/skel/locale/ethna_sysmsg.default.ini b/Idea_Plugin_Extlib/skel/locale/ethna_sysmsg.default.ini
new file mode 100644 (file)
index 0000000..680b3fc
--- /dev/null
@@ -0,0 +1,113 @@
+;
+;   ethna_sysmsg.ini
+;
+;   This file stores Ethna's system message and error message
+;   and its translation. This file's encoding is always UTF-8.
+;   
+;   This file is ini file format. For example, 
+;   
+;   "msgid" = "Translated string."
+;
+;   msgid  and Translated string must be always double quoted.
+;   When you want to include double quote in msgid or Translated
+;   string, you must escape it by backslash character.
+;   Comment line is started by semicolon character.
+;
+;   DO NOT CHANGE msgid string!!!!
+;
+
+;   class/Ethna_ActionForm.php, class/Plugin/Validator/*.php
+"{form} contains machine dependent code." = ""
+"Please input {form} properly." = ""
+"Please input {form}." = ""
+"Please select {form}." = ""
+"{form} was not selected." = ""
+"no input to {form}." = ""
+"Required item of {form} was not selected." = ""
+"Required item of {form} was not submitted." = ""
+"Please input scalar value to {form}." = ""
+"Required numbers of {form} was not selected." = ""
+"Required numbers of {form} was not submitted." = ""
+"Please input array value to {form}." = ""
+"Please input integer value to {form}." = ""
+"Please input float value to {form}." = ""
+"Please input valid datetime to {form}." = ""
+"You can input 0 or 1 to {form}." = ""
+"Please input more than %d(int) to {form}." = ""
+"Please input more than %f(float) to {form}." = ""
+"Please input datetime value %s or later to {form}." = ""
+"Please specify file whose size is more than %d KB." = ""
+"Please input less than %d(int) to {form}." = ""
+"Please input less than %f(float) to {form}." = ""
+"Please input datetime value before %s to {form}." = ""
+"Please specify file whose size is less than %d KB to {form}." = ""
+"Please input more than %d full-size (%d half-size) characters to {form}." = ""
+"Please input less than %d full-size (%d half-size) characters to {form}." = ""
+"Please input less than %d characters to {form}." = ""
+"Please input more than %d characters to {form}." = ""
+"Uploaded file size exceeds php.ini's upload_max_filesize directive." = ""
+"Uploaded File size exceeds MAX_FILE_SIZE specified in HTML Form." = ""
+"File was only uploaded patially." = ""
+"File was not uploaded." = ""
+"Temporary folder was not found." = ""
+"Could not write uploaded file to disk." = ""
+"invalid tmp_name." = ""
+"Invalid file name." = ""
+"Invalid file type." = ""
+"Uploaded file size must be less than %s." = ""
+"Uploaded file size must be more than %s." = ""
+
+;   Ethna_InfoManager.php(Constructor)
+"TextBox" = ""
+"Password" = ""
+"TextArea" = ""
+"SelectBox" = ""
+"RadioButton" = ""
+"CheckBox" = ""
+"SubmitButton" = ""
+"Integer" = ""
+"Float" = ""
+"String" = ""
+"Datetime" = ""
+"Boolean" = ""
+"File" = ""
+
+;   Ethna_InfoManager.php(getConfiguration function)
+"Application ID" = ""
+"Application URL" = ""
+"Ethna Version" = ""
+"Ethna Base Directory" = ""
+"Backend" = ""
+"ClassFactory" = ""
+"Config" = ""
+"Error" = ""
+"Form" = ""
+"Log" = ""
+"Plugin" = ""
+"Session" = "" 
+"View" = ""
+"Class" = ""
+"DB Type" = ""
+"Application" = ""
+"Action" = ""
+"View" = ""
+"Filter" = ""
+"Template" = ""
+"Template Cache" = ""
+"Smarty Plugin" = ""
+"Configuration File" = ""
+"Locale" = ""
+"Logging" = ""
+"Temporary File" = ""
+"Directory" = ""
+"PHP Script" = ""
+"File Extention" = ""
+"Filter(%d)" = ""
+"Application Manager" = ""
+
+;   Ethna_Util.php
+"Heisei" = ""
+"Showa" = ""
+
+;   Ethna_MailSender.php
+"%Y/%m/%d %H:%M:%S" = ""
diff --git a/Idea_Plugin_Extlib/skel/locale/ja_JP/ethna_sysmsg.ini b/Idea_Plugin_Extlib/skel/locale/ja_JP/ethna_sysmsg.ini
new file mode 100644 (file)
index 0000000..c8a806a
--- /dev/null
@@ -0,0 +1,112 @@
+;
+;   ethna_sysmsg.ini
+;
+;   Ethna が出力するシステムメッセージ及びエラーメッセー
+;   ジの翻訳を格納するファイルです。エンコーディングは常にUTF-8です。
+;   ini ファイルの形式になっており、以下の書式をとります。
+;   
+;   "msgid" = "翻訳された文字列"
+;
+;   msgid と翻訳された文字列は、かならずダブルクオートで
+;   囲まれていなければなりません。文字列中にダブルクオート
+;   を含めたい場合は、バックスラッシュ[\]でエスケープします。
+;   また、コメントは行頭をセミコロン[;]ではじめます。
+;
+;   msgid は絶対に変更しないで下さい!
+;
+
+;   class/Ethna_ActionForm.php, class/Plugin/Validator/*.php
+"{form} contains machine dependent code." = "{form}に機種依存文字が入力されています"
+"Please input {form} properly." = "{form}を正しく入力してください"
+"Please input {form}." = "{form}を入力して下さい"
+"Please select {form}." = "{form}を選択して下さい"
+"{form} was not selected." = "{form} が選択されていません"
+"no input to {form}." = "{form} が入力されていません"
+"Required item of {form} was not selected." = "{form}の必要な項目が選択されていません"
+"Required item of {form} was not submitted." = "{form}の必要な項目が入力されていません"
+"Please input scalar value to {form}." = "{form}にはスカラー値を入力して下さい"
+"Required numbers of {form} was not selected." = "{form}が必要な数まで選択されていません"
+"Required numbers of {form} was not submitted." = "{form}が必要な数まで入力されていません"
+"Please input array value to {form}." = "{form}には配列を入力して下さい"
+"Please input integer value to {form}." = "{form}には数字(整数)を入力して下さい"
+"Please input float value to {form}." = "{form}には数字(小数)を入力して下さい"
+"Please input valid datetime to {form}." = "{form}には日付を入力して下さい"
+"You can input 0 or 1 to {form}." = "{form}には1または0のみ入力できます"
+"Please input more than %d(int) to {form}." = "{form}には%d以上の数字(整数)を入力して下さい"
+"Please input more than %f(float) to {form}." = "{form}には%f以上の数字(小数)を入力して下さい"
+"Please input datetime value %s or later to {form}." = "{form}には%s以降の日付を入力して下さい"
+"Please specify file whose size is more than %d KB." = "{form}には%dKB以上のファイルを指定して下さい"
+"Please input less than %d(int) to {form}." = "{form}には%d以下の数字(整数)を入力して下さい"
+"Please input less than %f(float) to {form}." = "{form}には%f以下の数字(小数)を入力して下さい"
+"Please input datetime value before %s to {form}." = "{form}には%s以前の日付を入力して下さい"
+"Please specify file whose size is less than %d KB to {form}." = "{form}には%dKB以下のファイルを指定して下さい"
+"Please input more than %d full-size (%d half-size) characters to {form}." = "{form}には全角%d文字以上(半角%d文字以上)入力して下さい"
+"Please input less than %d full-size (%d half-size) characters to {form}." = "{form}は全角%d文字以下(半角%d文字以下)で入力して下さい"
+"Please input less than %d characters to {form}." = "{form}は%d文字以下で入力して下さい"
+"Please input more than %d characters to {form}." = "{form}は%d文字以上入力してください"
+"Uploaded file size exceeds php.ini's upload_max_filesize directive." = "アップロードされたファイルは、php.ini の upload_max_filesize ディレクティブの値を超えています。"
+"Uploaded File size exceeds MAX_FILE_SIZE specified in HTML Form." = "アップロードされたファイルは、HTML フォームで指定された MAX_FILE_SIZE を超えています。"
+"File was only uploaded patially." = "アップロードされたファイルは一部のみしかアップロードされていません。"
+"File was not uploaded." = "ファイルはアップロードされませんでした。"
+"Temporary folder was not found." = "テンポラリフォルダがありません。"
+"Could not write uploaded file to disk." = "ディスクへの書き込みに失敗しました。"
+"invalid tmp_name." = "tmp_name が不正です。"
+"Invalid file name." = "ファイル名が正しくありません"
+"Invalid file type." = "ファイルタイプが正しくありません。"
+"Uploaded file size must be less than %s." = "ファイルサイズは%s以下にしてください。"
+"Uploaded file size must be more than %s." = "ファイルサイズは%s以上にしてください。"
+
+;   Ethna_InfoManager.php(Constructor)
+"TextBox" = "テキストボックス"
+"Password" = "パスワード"
+"TextArea" = "テキストエリア"
+"SelectBox" = "セレクトボックス"
+"RadioButton" = "ラジオボタン"
+"CheckBox" = "チェックボックス"
+"SubmitButton" = "フォーム送信ボックス"
+"Integer" = "整数値"
+"Float" = "浮動小数点数"
+"String" = "文字列"
+"Datetime" = "日付"
+"Boolean" = "真偽値"
+"File" = "ファイル"
+
+;   Ethna_InfoManager.php(getConfiguration function)
+"Application ID" = "アプリケーションID"
+"Application URL" = "アプリケーションURL"
+"Ethna Version" = "Ethnaバージョン"
+"Ethna Base Directory" = "Ethnaベースディレクトリ"
+"Backend" = "バックエンド"
+"ClassFactory" = "クラスファクトリ"
+"Config" = "設定"
+"Error" = "エラー"
+"Form" = "フォーム"
+"Log" = "ログ"
+"Plugin" = "プラグイン"
+"Session" = "セッション" 
+"View" = "ビュー"
+"Class" = "クラス"
+"DB Type" = "DBタイプ"
+"Application" = "アプリケーション"
+"Action" = "アクション"
+"View" = "ビュー"
+"Filter" = "フィルタ"
+"Template" = "テンプレート"
+"Template Cache" = "テンプレートキャッシュ"
+"Smarty Plugin" = "Smartyプラグイン"
+"Configuration File" = "設定ファイル"
+"Locale" = "ロケール"
+"Logging" = "ログ"
+"Temporary File" = "一時ファイル"
+"Directory" = "ディレクトリ"
+"PHP Script" = "PHPスクリプト"
+"File Extention" = "拡張子"
+"Filter(%d)" = "フィルタ(%d)"
+"Application Manager" = "アプリケーションマネージャ"
+
+;   Ethna_Util.php
+"Heisei" = "平成"
+"Showa" = "昭和"
+
+;   Ethna_MailSender.php
+"%Y/%m/%d %H:%M:%S" = "%Y年%m月%d日 %H時%M分%S秒"
diff --git a/Idea_Plugin_Extlib/skel/locale/skel.msg.ini b/Idea_Plugin_Extlib/skel/locale/skel.msg.ini
new file mode 100644 (file)
index 0000000..a7a06c0
--- /dev/null
@@ -0,0 +1,18 @@
+;
+;   {$locale_name}.ini
+;
+;   This file stores Ethna project({$project_id}) system
+;   message and error message and its translation. This 
+;   file's encoding is always UTF-8.
+;   
+;   This file is ini file like format. For example, 
+;   
+;   "msgid" = "Translated string."
+;
+;   msgid and Translated string must be always double quoted.
+;   When you want to include double quote in msgid or Translated
+;   string, you must escape it by backslash character.
+;   Comment line is started by semicolon character.
+;
+;   DO NOT CHANGE msgid string!!!!
+;
diff --git a/Idea_Plugin_Extlib/skel/locale/skel.msg.po b/Idea_Plugin_Extlib/skel/locale/skel.msg.po
new file mode 100644 (file)
index 0000000..6770e08
--- /dev/null
@@ -0,0 +1,18 @@
+#
+# {$locale_name}.po
+# This is message catalog file for Ethna Project({$project_id}).
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: {$now_date}\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
diff --git a/Idea_Plugin_Extlib/skel/skel.action.php b/Idea_Plugin_Extlib/skel/skel.action.php
new file mode 100644 (file)
index 0000000..6dbb28c
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ *  {$action_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  {$action_name} Form implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_form} extends {$project_id}_ActionForm
+{
+    /**
+     *  @access private
+     *  @var    array   form definition.
+     */
+    var $form = array(
+       /*
+        *  TODO: Write form definition which this action uses.
+        *  @see http://ethna.jp/ethna-document-dev_guide-form.html
+        *
+        *  Example(You can omit all elements except for "type" one) :
+        *
+        *  'sample' => array(
+        *      // Form definition
+        *      'type'        => VAR_TYPE_INT,    // Input type
+        *      'form_type'   => FORM_TYPE_TEXT,  // Form type
+        *      'name'        => 'Sample',        // Display name
+        *  
+        *      //  Validator (executes Validator by written order.)
+        *      'required'    => true,            // Required Option(true/false)
+        *      'min'         => null,            // Minimum value
+        *      'max'         => null,            // Maximum value
+        *      'regexp'      => null,            // String by Regexp
+        *      'mbregexp'    => null,            // Multibype string by Regexp
+        *      'mbregexp_encoding' => 'UTF-8',   // Matching encoding when using mbregexp 
+        *
+        *      //  Filter
+        *      'filter'      => 'sample',        // Optional Input filter to convert input
+        *      'custom'      => null,            // Optional method name which
+        *                                        // is defined in this(parent) class.
+        *  ),
+        */
+    );
+
+    /**
+     *  Form input value convert filter : sample
+     *
+     *  @access protected
+     *  @param  mixed   $value  Form Input Value
+     *  @return mixed           Converted result.
+     */
+    /*
+    function _filter_sample($value)
+    {
+        //  convert to upper case.
+        return strtoupper($value);
+    }
+    */
+}
+
+/**
+ *  {$action_name} action implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_class} extends {$project_id}_ActionClass
+{
+    /**
+     *  preprocess of {$action_name} Action.
+     *
+     *  @access public
+     *  @return string    forward name(null: success.
+     *                                false: in case you want to exit.)
+     */
+    function prepare()
+    {
+        return null;
+    }
+
+    /**
+     *  {$action_name} action implementation.
+     *
+     *  @access public
+     *  @return string  forward name.
+     */
+    function perform()
+    {
+        return '{$action_name}';
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/skel.action_cli.php b/Idea_Plugin_Extlib/skel/skel.action_cli.php
new file mode 100644 (file)
index 0000000..a99a321
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ *  {$action_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  {$action_name} Form implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_form} extends {$project_id}_ActionForm
+{
+    /**
+     *  @access private
+     *  @var    array   form definition.
+     */
+    var $form = array(
+       /*
+        *  TODO: Write form definition which this action uses.
+        *  @see http://ethna.jp/ethna-document-dev_guide-form.html
+        *
+        *  Example(You can omit all elements except for "type" one) :
+        *
+        *  'sample' => array(
+        *      // Form definition
+        *      'type'        => VAR_TYPE_INT,    // Input type
+        *      'form_type'   => FORM_TYPE_TEXT,  // Form type
+        *      'name'        => 'Sample',        // Display name
+        *  
+        *      //  Validator (executes Validator by written order.)
+        *      'required'    => true,            // Required Option(true/false)
+        *      'min'         => null,            // Minimum value
+        *      'max'         => null,            // Maximum value
+        *      'regexp'      => null,            // String by Regexp
+        *      'mbregexp'    => null,            // Multibype string by Regexp
+        *      'mbregexp_encoding' => 'UTF-8',   // Matching encoding when using mbregexp 
+        *
+        *      //  Filter
+        *      'filter'      => 'sample',        // Optional Input filter to convert input
+        *      'custom'      => null,            // Optional method name which
+        *                                        // is defined in this(parent) class.
+        *  ),
+        */
+    );
+
+    /**
+     *  Form input value convert filter : sample
+     *
+     *  @access protected
+     *  @param  mixed   $value  Form Input Value
+     *  @return mixed           Converted result.
+     */
+    /*
+    function _filter_sample($value)
+    {
+        //  convert to upper case.
+        return strtoupper($value);
+    }
+    */
+}
+
+/**
+ *  {$action_name} action implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_class} extends {$project_id}_ActionClass
+{
+    /**
+     *  preprocess of {$action_name} Action.
+     *
+     *  @access public
+     *  @return string    forward name(null: success.
+     *                                false: in case you want to exit.)
+     */
+    function prepare()
+    {
+        return null;
+    }
+
+    /**
+     *  {$action_name} action implementation.
+     *
+     *  @access public
+     *  @return string  forward name.
+     */
+    function perform()
+    {
+        return null;
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/skel.action_test.php b/Idea_Plugin_Extlib/skel/skel.action_test.php
new file mode 100644 (file)
index 0000000..0cc3628
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+/**
+ *  {$action_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  {$action_name} Form testcase.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_form}_TestCase extends Ethna_UnitTestCase
+{
+    /**
+     *  @access private
+     *  @var    string  Action name.
+     */
+    var $action_name = '{$action_name}';
+
+    /**
+     *  initialize test.
+     *
+     *  @access public
+     */
+    function setUp()
+    {
+        $this->createActionForm();  // create ActionForm.
+    }
+
+    /**
+     *  clean up testcase.
+     *
+     *  @access public
+     */
+    function tearDown()
+    {
+    }
+
+    /**
+     *  {$action_name} ActionForm sample testcase.
+     *
+     *  @access public
+     */
+    function test_formSample()
+    {
+        /*
+        // setting form input.
+        $this->af->set('id', 1);
+
+        // {$action_name} ActionForm input validation.
+        $this->assertEqual($this->af->validate(), 0);
+        */
+
+        /**
+         *  TODO: write test case! :)
+         *  @see http://simpletest.org/en/first_test_tutorial.html
+         *  @see http://simpletest.org/en/unit_test_documentation.html
+         */
+        $this->fail('No Test! write Test!');
+    }
+}
+
+/**
+ *  {$action_name} Action testcase.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_class}_TestCase extends Ethna_UnitTestCase
+{
+    /**
+     *  @access private
+     *  @var    string  Action name.
+     */
+    var $action_name = '{$action_name}';
+
+    /**
+     * initialize test.
+     *
+     * @access public
+     */
+    function setUp()
+    {
+        $this->createActionForm();  // create ActionForm.
+        $this->createActionClass(); // create ActionClass.
+
+        $this->session->start();    // start session.
+    }
+
+    /**
+     *  clean up testcase.
+     *
+     *  @access public
+     */
+    function tearDown()
+    {
+        $this->session->destroy();   // destroy session.
+    }
+
+    /**
+     *  {$action_name} ActionClass sample testcase.
+     *
+     *  @access public
+     */
+    function test_actionSample()
+    {
+        /*
+        // setting form input.
+        $this->af->set('id', 1);
+
+        // Authentication before processing {$action_name} Action.
+        $forward_name = $this->ac->authenticate();
+        $this->assertNull($forward_name);
+
+        // {$action_name} Action preprocess.
+        $forward_name = $this->ac->prepare();
+        $this->assertNull($forward_name);
+
+        // {$action_name} Action implementation.
+        $forward_name = $this->ac->perform();
+        $this->assertEqual($forward_name, '{$action_name}');
+        */
+
+        /**
+         *  TODO: write test case! :)
+         *  @see http://simpletest.org/en/first_test_tutorial.html
+         *  @see http://simpletest.org/en/unit_test_documentation.html
+         */
+        $this->fail('No Test! write Test!');
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/skel.action_xmlrpc.php b/Idea_Plugin_Extlib/skel/skel.action_xmlrpc.php
new file mode 100644 (file)
index 0000000..ae471fe
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+/**
+ *  {$action_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  {$action_name} Form implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_form} extends {$project_id}_ActionForm
+{
+    /**
+     *  @access private
+     *  @var    array   form definition.
+     */
+    var $form = array(
+       /*
+        *  TODO: Write form definition which this action uses.
+        *  @see http://ethna.jp/ethna-document-dev_guide-form.html
+        *
+        *  Example(You can omit all elements except for "type" one) :
+        *
+        *  'sample' => array(
+        *      // Form definition
+        *      'type'        => VAR_TYPE_INT,    // Input type
+        *      'form_type'   => FORM_TYPE_TEXT,  // Form type
+        *      'name'        => 'Sample',        // Display name
+        *  
+        *      //  Validator (executes Validator by written order.)
+        *      'required'    => true,            // Required Option(true/false)
+        *      'min'         => null,            // Minimum value
+        *      'max'         => null,            // Maximum value
+        *      'regexp'      => null,            // String by Regexp
+        *      'mbregexp'    => null,            // Multibype string by Regexp
+        *      'mbregexp_encoding' => 'UTF-8',   // Matching encoding when using mbregexp 
+        *
+        *      //  Filter
+        *      'filter'      => 'sample',        // Optional Input filter to convert input
+        *      'custom'      => null,            // Optional method name which
+        *                                        // is defined in this(parent) class.
+        *  ),
+        */
+    );
+
+    /**
+     *  Form input value convert filter : sample
+     *
+     *  @access protected
+     *  @param  mixed   $value  Form Input Value
+     *  @return mixed           Converted result.
+     */
+    /*
+    function _filter_sample($value)
+    {
+        //  convert to upper case.
+        return strtoupper($value);
+    }
+    */
+}
+
+/**
+ *  {$action_name} action implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_class} extends {$project_id}_ActionClass
+{
+    /**
+     *  preprocess of {$action_name} Action.
+     *
+     *  @access public
+     *  @return string    forward name(null: success.
+     *                                false: in case you want to exit.)
+     */
+    function prepare()
+    {
+        return null;
+    }
+
+    /**
+     *  {$action_name} action implementation.
+     *
+     *  @access public
+     *  @return string  forward name.
+     */
+    function perform()
+    {
+        return '{$action_name}';
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/skel/skel.app_manager.php b/Idea_Plugin_Extlib/skel/skel.app_manager.php
new file mode 100644 (file)
index 0000000..d4af361
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+/**
+ *  {$app_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  {$app_manager}
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$app_manager} extends Ethna_AppManager
+{
+}
+?>
diff --git a/Idea_Plugin_Extlib/skel/skel.app_object.php b/Idea_Plugin_Extlib/skel/skel.app_object.php
new file mode 100644 (file)
index 0000000..82fb28d
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ *  {$app_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  {$app_object}Manager
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$app_object}Manager extends Ethna_AppManager
+{
+}
+
+/**
+ *  {$app_object}
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$app_object} extends Ethna_AppObject
+{
+    /**
+     *  property display name getter.
+     *
+     *  @access public
+     */
+    function getName($key)
+    {
+        return $this->get($key);
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/skel.entry_cli.php b/Idea_Plugin_Extlib/skel/skel.entry_cli.php
new file mode 100644 (file)
index 0000000..28a0f27
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ *  {$action_name}.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+chdir(dirname(__FILE__));
+require_once '{$dir_app}/{$project_id}_Controller.php';
+
+ini_set('max_execution_time', 0);
+
+{$project_id}_Controller::main_CLI('{$project_id}_Controller', '{$action_name}');
+?>
diff --git a/Idea_Plugin_Extlib/skel/skel.entry_www.php b/Idea_Plugin_Extlib/skel/skel.entry_www.php
new file mode 100644 (file)
index 0000000..3f09d72
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+/**
+ *  {$action_name}.php
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+require_once '{$dir_app}/{$project_id}_Controller.php';
+
+{$project_id}_Controller::main('{$project_id}_Controller', '{$action_name}');
+?>
diff --git a/Idea_Plugin_Extlib/skel/skel.template.tpl b/Idea_Plugin_Extlib/skel/skel.template.tpl
new file mode 100644 (file)
index 0000000..bcafec0
--- /dev/null
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset={$client_enc}">
+    </head>
+    <body>
+        <h1>{$project_id}</h1>
+    </body>
+</html>
diff --git a/Idea_Plugin_Extlib/skel/skel.test.php b/Idea_Plugin_Extlib/skel/skel.test.php
new file mode 100644 (file)
index 0000000..b952614
--- /dev/null
@@ -0,0 +1,56 @@
+<?php\r
+/**\r
+ * {$file_path}\r
+ * \r
+ * @author    {$author}\r
+ * @package   {$project_id}.Test\r
+ * @version   $Id$\r
+ */\r
+\r
+/**\r
+ * {$name} TestCase \r
+ * \r
+ * @author    {$author}\r
+ * @package   {$project_id}.Test\r
+ */\r
+class {$name}_TestCase extends Ethna_UnitTestCase\r
+{\r
+    /**\r
+     * initialize test.\r
+     * \r
+     * @access public\r
+     */\r
+    function setUp()\r
+    {\r
+        // TODO: write test initialization code.\r
+        // Example: read test data from database.\r
+    }\r
+    \r
+    /**\r
+     *  clean up testcase.\r
+     * \r
+     *  @access public\r
+     */\r
+    function tearDown()\r
+    {\r
+        // TODO: write testcase cleanup code.\r
+        // Example: restore database data for development.\r
+    }\r
+    \r
+    /**\r
+     * sample testcase.\r
+     * \r
+     * @access public\r
+     */\r
+    function test_{$name}()\r
+    {\r
+        /**\r
+         *  TODO: write test case! :)\r
+         *  @see http://simpletest.org/en/first_test_tutorial.html\r
+         *  @see http://simpletest.org/en/unit_test_documentation.html\r
+         */\r
+        $this->fail('No Test! write Test!');\r
+    }\r
+}\r
+\r
+?>\r
diff --git a/Idea_Plugin_Extlib/skel/skel.view.php b/Idea_Plugin_Extlib/skel/skel.view.php
new file mode 100644 (file)
index 0000000..47c2691
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ *  {$view_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  {$forward_name} view implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$view_class} extends {$project_id}_ViewClass
+{
+    /**
+     *  preprocess before forwarding.
+     *
+     *  @access public
+     */
+    function preforward()
+    {
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/skel/skel.view_test.php b/Idea_Plugin_Extlib/skel/skel.view_test.php
new file mode 100644 (file)
index 0000000..87cd004
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+/**
+ *  {$view_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id$
+ */
+
+/**
+ *  {$forward_name} view implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$view_class}_TestCase extends Ethna_UnitTestCase
+{
+    /**
+     *  @access private
+     *  @var    string  view name.
+     */
+    var $forward_name = '{$forward_name}';
+
+    /**
+     * initialize test.
+     *
+     * @access public
+     */
+    function setUp()
+    {
+        $this->createPlainActionForm(); // create ActionForm
+        $this->createViewClass();       // create View.
+    }
+
+    /**
+     *  clean up testcase.
+     *
+     *  @access public
+     */
+    function tearDown()
+    {
+    }
+
+    /**
+     *  {$forward_name} preprocess sample testcase.
+     *
+     *  @access public
+     */
+    function test_viewSample()
+    {
+        /*
+        // setting form input. 
+        $this->af->set('id', 1);
+
+        // {$forward_name} preprocess.
+        $this->vc->preforward();
+        $this->assertNull($this->af->get('data'));
+        */
+
+        /**
+         *  TODO: write test case! :)
+         *  @see http://simpletest.org/en/first_test_tutorial.html
+         *  @see http://simpletest.org/en/unit_test_documentation.html
+         */
+        $this->fail('No Test! write Test!');
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/skel/template.index.tpl b/Idea_Plugin_Extlib/skel/template.index.tpl
new file mode 100644 (file)
index 0000000..cece182
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset={$client_enc}" />
+<link rel="stylesheet" href="{$config.url}css/ethna.css" type="text/css" />
+</head>
+<body>
+
+<div id="header">
+    <h1>{$project_id}</h1>
+</div>
+
+<div id="main">
+    <h2>Index Page</h2>
+    <p>hello, world!</p>
+</div>
+
+<div id="footer">
+    Powered By <a href="http://ethna.jp">Ethna</a>-{$smarty.const.ETHNA_VERSION}.
+</div>
+
+</body>
+</html>
diff --git a/Idea_Plugin_Extlib/skel/www.css.ethna.css b/Idea_Plugin_Extlib/skel/www.css.ethna.css
new file mode 100644 (file)
index 0000000..b0694a1
--- /dev/null
@@ -0,0 +1,55 @@
+body {
+    background: url("http://ethna.jp/image/pagebg.gif");
+    font-family: Arial,Verdana,Helvetica,'MS UI Gothic',sans-serif;
+    margin-left: 40px;
+    margin-right: 40px;
+}
+
+div#header {
+    margin-top: 40px;
+    margin-bottom: 1em;
+    padding: 0px 20px;
+    height: 40px;
+    background: url("http://ethna.jp/image/navbg.gif") repeat-x;
+}
+
+div#header h1 {
+    font-weight: bold;
+    background: url("http://ethna.jp/image/navlogo.gif") right no-repeat;
+}
+
+div#footer {
+    margin-bottom: 1em;
+    padding: 5px 20px;
+    background-color: #cee6e6;
+    text-align: right;
+}
+
+div#footer a {
+    color: #f35a21;
+}
+
+div#main {
+    margin-bottom: 1em;
+    padding: 10px 20px;
+    border: 1px solid #cee6e6;
+    background-color: #ffffff;
+}
+
+div#main h2 {
+    color: #00cccc;
+    border: 1px solid #cee6e6;
+    border-width: 0 0 1px 0;
+}
+
+div#main a:link {
+    color: #f35a21;
+}
+
+div#main a:visited {
+    color: #f29578;
+}
+
+span.ethna-error {
+    color: #ff0000;
+}
diff --git a/Idea_Plugin_Extlib/skel/www.index.php b/Idea_Plugin_Extlib/skel/www.index.php
new file mode 100644 (file)
index 0000000..f5f69ab
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+require_once dirname(__FILE__) . '/../app/{$project_id}_Controller.php';
+
+{$project_id}_Controller::main('{$project_id}_Controller', 'index');
+?>
diff --git a/Idea_Plugin_Extlib/skel/www.info.php b/Idea_Plugin_Extlib/skel/www.info.php
new file mode 100644 (file)
index 0000000..dcc668f
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+require_once dirname(__FILE__) . '/../app/{$project_id}_Controller.php';
+
+{$project_id}_Controller::main('{$project_id}_Controller', array(
+    '__ethna_info__',
+    )
+);
+?>
diff --git a/Idea_Plugin_Extlib/skel/www.unittest.php b/Idea_Plugin_Extlib/skel/www.unittest.php
new file mode 100644 (file)
index 0000000..6a245bb
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+error_reporting(E_ALL);
+require_once dirname(__FILE__) . '/../app/{$project_id}_Controller.php';
+
+{$project_id}_Controller::main('{$project_id}_Controller', array(
+    '__ethna_unittest__',
+    )
+);
+?>
diff --git a/Idea_Plugin_Extlib/skel/www.xmlrpc.php b/Idea_Plugin_Extlib/skel/www.xmlrpc.php
new file mode 100644 (file)
index 0000000..9e431fb
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+require_once '{$basedir}/app/{$project_id}_Controller.php';
+
+{$project_id}_Controller::main_XMLRPC('{$project_id}_Controller');
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionError_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionError_Test.php
new file mode 100644 (file)
index 0000000..6bd87ed
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+/**
+ *  Ethna_ActionError_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+//{{{    Ethna_ActionError_Test
+/**
+ *  Test Case For Ethna_ActionError
+ *
+ *  @access public
+ */
+class Ethna_ActionError_Test extends Ethna_UnitTestBase
+{
+    var $ae;
+    var $error_obj;
+    var $error_form_name;
+    var $error_form_name1;
+    var $message;
+    var $message1;
+
+    function setUp()
+    {
+        $this->ae = new Ethna_ActionError();
+        $this->error_form_name = "hoge";
+        $this->message = "test error";    
+        $this->error_form_name1 = "tititi";
+        $this->message1 = "test error1";    
+
+        $this->error_obj = new Ethna_Error(
+                               $this->message1,
+                               E_NOTICE,
+                               E_GENERAL
+                           );
+
+        //    add dummy error object.
+        $this->ae->add($this->error_form_name,
+                       $this->message,
+                       E_GENERAL
+        );
+        $this->ae->addObject($this->error_form_name1,
+                             $this->error_obj
+        );
+    }
+
+    function test_count()
+    {
+        $this->assertEqual($this->ae->count(), 2);
+    }
+
+    function test_length()
+    {
+        $this->assertEqual($this->ae->length(), 2);
+    }
+
+    function test_iserror()
+    {
+        $this->assertTrue(
+            $this->ae->isError($this->error_form_name)
+        );
+        $this->assertTrue(
+            $this->ae->isError($this->error_form_name1)
+        );
+    }
+
+    function test_geterrorlist()
+    {
+        $this->assertTrue(
+            is_array($this->ae->getErrorList())
+        );
+    }
+
+    function test_getmessage()
+    {
+        $error_msg = $this->ae->getMessage(
+                         $this->error_form_name
+                     );
+        $error_msg1 = $this->ae->getMessage(
+                         $this->error_form_name1
+                     );
+
+        $this->assertEqual($this->message, $error_msg); 
+        $this->assertEqual($this->message1, $error_msg1); 
+    }
+
+    function test_clear()
+    {
+        $this->ae->clear();
+        $this->assertTrue(
+            $this->ae->count() == 0
+        );
+    }
+
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Filter_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Filter_Test.php
new file mode 100644 (file)
index 0000000..3c3664e
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Filter_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_FilterTest_ActionForm
+/**
+ *  Test ActionForm For Filter 
+ *
+ *  @access public
+ */
+class Ethna_FilterTest_ActionForm extends Ethna_ActionForm
+{
+    var $form = array(
+        'test' => array(
+            'type' => VAR_TYPE_STRING,
+            'form_type' => FORM_TYPE_TEXT,
+            'name' => 'test',
+        ),
+    );
+
+    //    user defined filter
+    function _filter_toupper($value)
+    {
+        return strtoupper($value); 
+    }
+
+    function _filter_tolower($value)
+    {
+        return strtolower($value); 
+    }
+}
+// }}}
+
+// {{{    Ethna_ActionForm_Filter_Test
+/**
+ *  Test Case For Ethna_ActionForm(Filter)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Filter_Test extends Ethna_UnitTestBase
+{
+    var $local_af;
+
+    function setUp()
+    {
+        $this->local_af = new Ethna_FilterTest_ActionForm($this->ctl); 
+        $this->local_af->clearFormVars();
+        $this->ae->clear();
+    }
+
+    // {{{ FILTER_FW Test
+    function test_filter_fw()
+    {
+        //   半角カナ -> 全角カナ + ntrim 
+        $this->local_af->form['test']['filter'] = FILTER_FW;
+        $this->local_af->set('test', "\x00アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロン\x00");
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロン', $filtered_value);
+        $this->ae->clear();
+    }
+    // }}}
+
+    // {{{ FILTER_HW Test
+    function test_filter_hw()
+    {
+        //   全角英数字 -> 半角英数字 + ntrim + rtrim + ltrim
+        $this->local_af->form['test']['filter'] = FILTER_HW;
+        $this->local_af->set('test', " \t\n\r\0\x0B AB\x00CDEFG0123\x00456789\t\n\r\0\x0B ");
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('ABCDEFG0123456789', $filtered_value);
+    }
+    // }}}
+
+    // {{{ FILTER alnum_zentohan 
+    function test_filter_alnum_zentohan()
+    {
+        //  全角英数字->半角英数字
+        $this->local_af->form['test']['filter'] = 'alnum_zentohan'; 
+        $this->local_af->set('test', "ABCDEFG0123456789");
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('ABCDEFG0123456789', $filtered_value);
+    }
+    // }}}
+
+    // {{{ FILTER numeric_zentohan 
+    function test_filter_numeric_zentohan()
+    {
+        //  全角数字->半角数字
+        $this->local_af->form['test']['filter'] = 'numeric_zentohan'; 
+        $this->local_af->set('test', "0123456789");
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('0123456789', $filtered_value);
+    }
+    // }}}
+
+    // {{{ FILTER alphabet_zentohan 
+    function test_filter_alphabet_zentohan()
+    {
+        //  全角英字->半角英字(大文字)
+        $this->local_af->form['test']['filter'] = 'alnum_zentohan'; 
+        $this->local_af->set('test', "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $filtered_value);
+        $this->ae->clear();
+
+        //  全角英字->半角英字(小文字)
+        $this->local_af->form['test']['filter'] = 'alnum_zentohan'; 
+        $this->local_af->set('test', "abcdefghijklmnopqrstuvwxyz");
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('abcdefghijklmnopqrstuvwxyz', $filtered_value);
+    }
+    // }}}
+
+    // {{{ FILTER ltrim
+    function test_filter_ltrim()
+    {
+        //    ltrim は全角スペースを除けないので注意!!!
+        //    Ethna はデフォルトの文字のみを除きます
+        //    @see http://jp.php.net/ltrim
+        $this->local_af->form['test']['filter'] = 'ltrim'; 
+        $this->local_af->set('test', " \t\n\r\0\x0Bhoge");
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('hoge', $filtered_value);
+    }
+    // }}}
+
+    // {{{ FILTER rtrim
+    function test_filter_rtrim()
+    {
+        //    rtrim は全角スペースを除けないので注意!!!
+        //    Ethna はデフォルトの文字のみを除きます
+        //    @see http://jp.php.net/rtrim
+        $this->local_af->form['test']['filter'] = 'rtrim'; 
+        $this->local_af->set('test', "hoge \t\n\r\0\x0B");
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('hoge', $filtered_value);
+    }
+    // }}}
+
+    // {{{ FILTER ntrim
+    function test_filter_ntrim()
+    {
+        $this->local_af->form['test']['filter'] = 'ntrim'; 
+        $this->local_af->set('test', "\x00hoge\x00\x00");
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('hoge', $filtered_value);
+    }
+    // }}}
+
+    // {{{ FILTER kana_hantozen 
+    function test_filter_kana_hantozen()
+    {
+        //  半角カナ->全角カナ
+        $this->local_af->form['test']['filter'] = 'kana_hantozen'; 
+        $this->local_af->set('test', 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロン');
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロン', $filtered_value);
+    }
+    // }}}
+
+    // {{{ one custom filter 
+    function test_filter_custom_toupper()
+    {
+        //  小文字を大文字へ
+        $this->local_af->form['test']['filter'] = 'toupper'; 
+        $this->local_af->set('test', 'abcdefghijklmnopqrstuvwxyz');
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $filtered_value);
+    }
+    // }}}
+
+    // {{{ multiple custom filter 
+    function test_filter_custom_multiple()
+    {
+        //  小文字を大文字へ、そして元に戻す
+        $this->local_af->form['test']['filter'] = 'toupper,tolower'; 
+        $this->local_af->set('test', 'abcdefghijklmnopqrstuvwxyz');
+        $this->local_af->validate();
+        $filtered_value = $this->local_af->get('test');
+        $this->assertEqual('abcdefghijklmnopqrstuvwxyz', $filtered_value);
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_FormTemplate_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_FormTemplate_Test.php
new file mode 100644 (file)
index 0000000..207e3cd
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_FormTemplate_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{  Ethna_FormTemplate_ActionForm
+/**
+ *  Test ActionForm (Form Template) 
+ *
+ *  @access public
+ */
+class Ethna_FormTemplate_ActionForm extends Ethna_ActionForm
+{
+    var $form_template = array(
+       'normal' => array(
+           'name'      => '通常のフォームテンプレート用定義',
+           'required'  => false,
+           'form_type' => FORM_TYPE_SELECT,
+           'type'      => VAR_TYPE_INT,
+       ),
+       'syntax_sugar' => array(
+           'name'      => 'シンタックスシュガー用定義',
+           'required'  => true,
+           'form_type' => FORM_TYPE_TEXT,
+           'type'      => VAR_TYPE_STRING,
+       ),
+    );
+}
+// }}}
+
+// {{{  Ethna_FormTemplateTest_ActionForm
+/**
+ *  Test ActionForm (Form Template) 
+ *
+ *  @access public
+ */
+class Ethna_FormTemplateTest_ActionForm extends Ethna_FormTemplate_ActionForm
+{
+    var $form = array(
+       'normal' => array(),
+       'syntax_sugar',  //  シンタックスシュガー
+    );
+}
+// }}}
+
+// {{{  Ethna_ActionForm_FormTemplate_Test
+/**
+ *  Test Case For Ethna_ActionForm(Form Template)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_FormTemplate_Test extends Ethna_UnitTestBase
+{
+    var $local_af;
+
+    function setUp()
+    {
+        //   REQUEST_METHOD を設定しないと
+        //   フォームテンプレートが初期化されない
+        $_SERVER['REQUEST_METHOD'] = 'POST';
+        $this->local_af =& new Ethna_FormTemplateTest_ActionForm($this->ctl); 
+    }
+
+    function tearDown()
+    {
+        $_SERVER['REQUEST_METHOD'] = NULL;
+        $this->local_af = NULL;
+    }
+
+    // {{{ normal form template
+    function test_formtemplate_normal()
+    {
+        $normal_def = $this->local_af->getDef('normal');
+        $this->assertEqual($normal_def['name'], '通常のフォームテンプレート用定義');
+        $this->assertEqual($normal_def['required'], false);
+        $this->assertEqual($normal_def['form_type'], FORM_TYPE_SELECT);
+        $this->assertEqual($normal_def['type'], VAR_TYPE_INT);
+    }
+    // }}}
+
+    // {{{ syntax sugar 
+    function test_formtemplate_syntaxsugar()
+    {
+        $syntax_sugar_def = $this->local_af->getDef('syntax_sugar');
+        $this->assertEqual($syntax_sugar_def['name'], 'シンタックスシュガー用定義');
+        $this->assertEqual($syntax_sugar_def['required'], true);
+        $this->assertEqual($syntax_sugar_def['form_type'], FORM_TYPE_TEXT);
+        $this->assertEqual($syntax_sugar_def['type'], VAR_TYPE_STRING);
+    }
+    // }}}
+}
+// }}}
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_MultiArray_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_MultiArray_Test.php
new file mode 100644 (file)
index 0000000..708dfa7
--- /dev/null
@@ -0,0 +1,307 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_MultiArray_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{  Ethna_MultiArrayTest_ActionForm
+/**
+ *  Test ActionForm (MultiDimentional Array) 
+ *
+ *  @access public
+ */
+class Ethna_MultiArrayTest_ActionForm extends Ethna_ActionForm
+{
+    var $form = array(
+        //  多次元配列(値はスカラー)
+        'User[name]' => array(
+            'name'          => '名前',
+            'type'          => VAR_TYPE_STRING,
+            'form_type'     => FORM_TYPE_TEXT,
+        ),
+        'User[phone][home]' => array(
+            'name'          => '自宅電話番号',
+            'type'          => VAR_TYPE_STRING,
+            'form_type'     => FORM_TYPE_TEXT,
+        ),
+        'User[phone][mobile]' => array(
+            'name'          => '携帯電話番号',
+            'type'          => VAR_TYPE_STRING,
+            'form_type'     => FORM_TYPE_TEXT,
+        ),
+
+        //  多次元配列(値は配列)
+        'Artist[name]' => array(
+            'name'          => '好きなキャラクター',
+            'type'          => array(VAR_TYPE_STRING),
+            'form_type'     => FORM_TYPE_TEXT,
+        ),
+
+        //  10階層(1番上の "a" も含む)
+        'a[b][c][d][e][f][g][h][i][j]' => array(
+            'name'          => '10階層の多次元配列',
+            'type'          => VAR_TYPE_STRING,
+            'form_type'     => FORM_TYPE_TEXT,
+        ),
+
+        //  11階層(1番上の "a" も含む)
+        'a[b][c][d][e][f][g][h][i][j][k]' => array(
+            'name'          => '11階層の多次元配列',
+            'type'          => VAR_TYPE_STRING,
+            'form_type'     => FORM_TYPE_TEXT,
+        ),
+
+        //  自動で番号を割り当てるフォームには
+        //  対応していない 
+        //
+        //  [] でフォーム定義をすると、数字としては解釈されず、
+        //  空のキーとして解釈される
+        //  数値を補正しようとすると、他に[]として定義されている
+        //  全フォーム定義を調べなければならなくなる上、順番も保証
+        //  できないため、対応していないのは仕様とする 
+        'invalid[][data1][data2]' => array(
+            'name'          => '対応していない多次元フォーム定義',
+            'required'      => true,
+            'type'          => VAR_TYPE_INT,
+            'form_type'     => FORM_TYPE_TEXT,
+        ),
+
+        //  重複したフォーム定義
+        //  この場合は、どちらの値も思ったように受け取れない可能性が
+        //  高い(フォーム定義、GET, POSTされる値の順によっては、片方が
+        //  正しく受け取れる場合もある)
+        //
+        //  なぜなら、$_POST($_GET)に、PHP内で後から解釈した値で
+        //  上書きされる上、Ethna_ActionForm#setFormVars 内で、
+        //  後で解釈されたフォーム定義の値で上書きされるからである。
+        //  どちらの値が後に来るかはブラウザ依存だし、PHP内でどちら
+        //  が後に解釈されるかも分からないので、このような定義は避けること
+        //
+        'duplicate' => array(
+            'name'          => '文字列(一次元)',
+            'required'      => true,
+            'type'          => VAR_TYPE_STRING,
+            'form_type'     => FORM_TYPE_TEXT,
+        ),
+        'duplicate[abc]' => array(
+            'name'          => '文字列(多次元)',
+            'required'      => true,
+            'type'          => VAR_TYPE_STRING,
+            'form_type'     => FORM_TYPE_TEXT,
+        ),
+    );
+}
+// }}} 
+
+// {{{  Ethna_ActionForm_MultiArray_Test
+class Ethna_ActionForm_MultiArray_Test extends Ethna_UnitTestBase
+{
+    var $local_af;
+
+    function setUp()
+    {
+        $_SERVER['REQUEST_METHOD'] = 'POST';
+        $this->local_af = new Ethna_MultiArrayTest_ActionForm($this->ctl); 
+        $this->local_af->clearFormVars();
+        $this->ae->clear();
+    }
+
+    function tearDown()
+    {
+        $_SERVER['REQUEST_METHOD'] = NULL;
+        $_POST = array();
+        $this->local_af->clearFormVars();
+    }
+
+    // {{{  get
+    function test_get()
+    {
+        //
+        //   $_POST, $_GET に設定されるデータの方式は
+        //   PHP の仕様に従う
+        //
+        //   http://www.php.net/manual/ja/language.variables.external.php
+        //
+
+        //  1. 最下層のキーまで指定して値を取り出す
+        $_POST['User']['name'] = '剛田たけし';
+        $_POST['User']['phone']['home'] = '01-2345-6789';
+        $_POST['User']['phone']['mobile'] = '090-1234-5678';
+        $this->local_af->setFormVars();
+
+        $name = $this->local_af->get('User[name]');
+        $phone_home = $this->local_af->get('User[phone][home]');
+        $phone_mobile = $this->local_af->get('User[phone][mobile]');
+
+        $this->assertEqual($name, '剛田たけし');
+        $this->assertEqual($phone_home, '01-2345-6789');
+        $this->assertEqual($phone_mobile, '090-1234-5678');
+
+        //  2. 階層の途中から値を取り出す
+        $var = $this->local_af->get('User[phone]');
+        $this->assertEqual($var['home'], '01-2345-6789');
+        $this->assertEqual($var['mobile'], '090-1234-5678');
+
+        //  3. 親を指定してデータをすべて取り出す
+        $var = $this->local_af->get('User');
+        $this->assertEqual($var['name'], '剛田たけし');
+        $this->assertEqual($var['phone']['home'], '01-2345-6789');
+        $this->assertEqual($var['phone']['mobile'], '090-1234-5678');
+    }
+    // }}}
+
+    // {{{  get(array)
+    function test_get_array()
+    {
+        $_POST['Artist']['name'][] = '宮崎あおい';
+        $_POST['Artist']['name'][] = 'PHPの貴公子';
+        $_POST['Artist']['name'][] = '北海道の若頭';
+        $this->local_af->setFormVars();
+
+        //  1. 最下層のキーまで指定して値を取り出す
+        $artist0 = $this->local_af->get('Artist[name][0]');
+        $artist1 = $this->local_af->get('Artist[name][1]');
+        $artist2 = $this->local_af->get('Artist[name][2]');
+
+        $this->assertEqual($artist0, '宮崎あおい');
+        $this->assertEqual($artist1, 'PHPの貴公子');
+        $this->assertEqual($artist2, '北海道の若頭');
+        
+        //  2. 階層の途中から値を取り出す
+        $artists = $this->local_af->get('Artist[name]');
+        $this->assertEqual($artists[0], '宮崎あおい');
+        $this->assertEqual($artists[1], 'PHPの貴公子');
+        $this->assertEqual($artists[2], '北海道の若頭');
+
+        //  3. 親を指定してデータをすべて取り出す
+        $allartist = $this->local_af->get('Artist');
+        $this->assertEqual($allartist['name'][0], '宮崎あおい');
+        $this->assertEqual($allartist['name'][1], 'PHPの貴公子');
+        $this->assertEqual($allartist['name'][2], '北海道の若頭');
+    }
+    // }}}
+
+    // {{{  get(duplicate)
+    function test_get_duplicate()
+    {
+        //  PHP 5.2.6, 4.4.9 では 「宮崎あおい」が優先された
+        //  つまり、$_POST の中身は array('duplicate' => '宮崎あおい')
+        $_POST['duplicate'] = '宮崎あおい';
+        $_POST['duplicate']['abc'] = 'PHPの貴公子';
+        $this->local_af->setFormVars();
+
+        //
+        //  setFormVars が実行された結果、[duplicate][abc] のフォーム定義が
+        //  後で解釈され、$POST array('dupliate'=>array('abc' => NULL));
+        //  として上書きされる。よって、どちらも値が取得できない
+        //
+        $this->assertNotEqual('宮崎あおい', $this->local_af->get('duplicate'));
+        $this->assertNULL($this->local_af->get('duplicate[xxx]'));
+    }
+    // }}}
+
+    // {{{  get(too deep)
+    function test_get_too_deep()
+    {
+        //   10階層目, 11階層目に値を設定する
+        $_POST['a']['b']['c']['d']['e']['f']['g']['h']['i']['j'] = '10階層';
+        $_POST['a']['b']['c']['d']['e']['f']['g']['h']['i']['j']['k'] = '11階層';
+        $this->local_af->setFormVars();
+
+        //   深過ぎる階層の配列は、
+        //   たとえ定義されていても、取得しようとしてもNULLになる
+        $depth_10_val = $this->local_af->get('a[b][c][d][e][f][g][h][i][j]');
+        $depth_11_val = $this->local_af->get('a[b][c][d][e][f][g][h][i][j][k]');
+
+        $this->assertEqual($depth_10_val, '10階層');
+        $this->assertNULL($depth_11_val);
+    }
+    // }}}
+
+    // {{{  set 
+    function test_set()
+    {
+        // 1. 最下層のキーまで指定して値をセットする
+        $this->local_af->set('User[phone][home]', '01-2345-6789');
+        $this->local_af->set('User[name]', '剛田武');
+
+        $User = $this->local_af->get('User');
+        $this->assertEqual($User['phone']['home'], '01-2345-6789');
+        $this->assertEqual($User['name'], '剛田武');
+
+        // 2. 階層の途中から値をセットする
+        $this->local_af->clearFormVars();
+        $phone = array(
+            'home'   => '01-2345-6789',
+            'mobile' => '090-1234-5678',
+        );
+        $this->local_af->set('User[phone]', $phone);
+        $User = $this->local_af->get('User');
+        $this->assertEqual($User['phone']['home'], '01-2345-6789');
+        $this->assertEqual($User['phone']['mobile'], '090-1234-5678');
+
+        //  3. 親を指定してまとめて値をセットする
+        $this->local_af->clearFormVars();
+        $user = array (
+            'name' => '剛田武',
+            'phone' => array (
+                'home' => '01-2345-6789',
+                'mobile' => '090-1234-5678',
+            ),
+        );
+        $this->local_af->set('User', $user);
+        $User = $this->local_af->get('User');
+        $this->assertEqual($User['name'], '剛田武');
+        $this->assertEqual($User['phone']['home'], '01-2345-6789');
+        $this->assertEqual($User['phone']['mobile'], '090-1234-5678');
+    }
+    // }}}
+
+    // {{{  set(array)
+    function test_set_array()
+    {
+        $this->local_af->set('Artist[name][0]', '宮崎あおい');
+        $this->local_af->set('Artist[name][1]', 'PHPの貴公子');
+        $this->local_af->set('Artist[name][2]', '北海道の若頭');
+
+        $artist0 = $this->local_af->get('Artist[name][0]');
+        $artist1 = $this->local_af->get('Artist[name][1]');
+        $artist2 = $this->local_af->get('Artist[name][2]');
+
+        $this->assertEqual($artist0, '宮崎あおい');
+        $this->assertEqual($artist1, 'PHPの貴公子');
+        $this->assertEqual($artist2, '北海道の若頭');
+    }
+    // }}}
+
+    // {{{  set(too deep)
+    function test_set_too_deep()
+    {
+        //   深過ぎる階層の配列は、
+        //   たとえ設定しようとしてもNULLになる
+        $this->local_af->set('a[b][c][d][e][f][g][h][i][j]', '10階層');
+        $this->local_af->set('a[b][c][d][e][f][g][h][i][j][k]', '11階層');
+
+        $depth_10_val = $this->local_af->get('a[b][c][d][e][f][g][h][i][j]');
+        $depth_11_val = $this->local_af->get('a[b][c][d][e][f][g][h][i][j][k]');
+
+        $this->assertEqual($depth_10_val, '10階層');
+        $this->assertNULL($depth_11_val);
+    }
+    // }}}
+
+    // {{{ invalid multidimention def
+    function test_invalid()
+    {
+        $_POST['invalid'][]['data1']['data2'] = '受け取れません';
+        $this->local_af->setFormVars();
+        $this->assertNULL($this->local_af->get('invalid[][data1][data2]'));
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Test.php
new file mode 100644 (file)
index 0000000..f13b53a
--- /dev/null
@@ -0,0 +1,189 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_Test_ActionForm
+/**
+ *  Test ActionForm 
+ *
+ *  @access public
+ */
+class Ethna_Test_ActionForm extends Ethna_ActionForm
+{
+    var $form = array(
+        'test' => array(
+            'type' => VAR_TYPE_STRING,
+            'form_type' => FORM_TYPE_TEXT,
+            'name' => 'test',
+        ),
+
+        'no_name' => array(
+            'type' => VAR_TYPE_STRING,
+            'form_type' => FORM_TYPE_TEXT,
+        ),
+
+        'test_array' => array(
+            'type' => array(VAR_TYPE_STRING),
+            'form_type' => FORM_TYPE_TEXT,
+            'name' => 'test array',
+        ),
+
+    );
+}
+// }}}
+
+// {{{    Ethna_ActionForm_Test
+/**
+ *  Test Case For Ethna_ActionForm(Mainly Validator)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Test extends Ethna_UnitTestBase
+{
+    var $local_af;
+
+    function setUp()
+    {
+        //   REQUEST_METHOD を設定しないと
+        //   フォームテンプレートが初期化されない
+        $_SERVER['REQUEST_METHOD'] = 'POST';
+        $this->local_af = new Ethna_Test_ActionForm($this->ctl); 
+        $this->local_af->clearFormVars();
+        $this->ae->clear();
+    }
+
+    function tearDown()
+    {
+        $_SERVER['REQUEST_METHOD'] = NULL;
+        $this->local_af = NULL;
+        $_POST = array();
+    }
+
+    // {{{ get
+    function test_get()
+    {
+        $this->local_af->set('test', 'test');
+        $this->assertEqual('test', $this->local_af->get('test'));
+    }
+    // }}}
+
+    // {{{ getDef
+    function test_getDef()
+    {
+        //   null param.
+        $def = $this->local_af->getDef();
+        $this->assertEqual(3, count($def));
+        $this->assertEqual(10, count($def['test']));
+        $this->assertEqual('test', $def['test']['name']);
+
+        //   non-exist key.
+        $this->assertNull($this->local_af->getDef('hoge'));
+
+        $def = $this->local_af->getDef('test');
+        $this->assertEqual(10, count($def));
+        $this->assertEqual('test', $def['name']);
+    }
+    // }}}
+
+    // {{{ getName
+    function test_getName()
+    {
+        $this->assertNull($this->local_af->getName('hoge'));
+        $this->assertEqual('test', $this->local_af->getName('test'));
+        
+        //   もしフォームのname属性がないと、keyそのものが返ってくる
+        $this->assertEqual('no_name', $this->local_af->getName('no_name'));
+    }
+    // }}}
+
+    // {{{ clearFormVars
+    function test_clearFormVars()
+    {
+        $this->local_af->set('test', 'hoge');
+        $this->local_af->set('no_name', 'fuga');
+
+        $this->local_af->clearFormVars();
+
+        $this->assertNull($this->local_af->get('test'));
+        $this->assertNull($this->local_af->get('no_name'));
+    }
+    // }}}
+
+    // {{{ set 
+    function test_set()
+    {
+        $this->local_af->set('test', 'test');
+        $this->assertEqual('test', $this->local_af->get('test'));
+    }
+    // }}}
+
+    // {{{ getHiddenVars 
+    function test_getHiddenVars()
+    {
+        //    フォーム定義が配列で、submit された値が非配列の場合
+        //    かつ、フォーム定義が配列なので、結局出力するhiddden
+        //    タグも配列用のものとなる. 警告も勿論でない
+        $this->local_af->set('test_array', 1);
+
+        $hidden = $this->local_af->getHiddenVars();
+        $expected = "<input type=\"hidden\" name=\"test_array[0]\" value=\"1\" />\n";
+        $this->assertEqual($hidden, $expected);
+        $this->local_af->clearFormVars();
+
+        //    配列出力のテスト
+        $this->local_af->set('test_array', array(1, 2));
+        $hidden = $this->local_af->getHiddenVars();
+        $expected = "<input type=\"hidden\" name=\"test_array[0]\" value=\"1\" />\n"
+                  . "<input type=\"hidden\" name=\"test_array[1]\" value=\"2\" />\n";
+        $this->assertEqual($hidden, $expected);
+        $this->local_af->clearFormVars();
+
+        //    スカラーのテスト
+        $this->local_af->set('test', 1);
+        $hidden = $this->local_af->getHiddenVars();
+        $expected = "<input type=\"hidden\" name=\"test\" value=\"1\" />\n";
+        $this->assertEqual($hidden, $expected);
+        $this->local_af->clearFormVars();
+
+        //    フォーム定義がスカラーで、submitされた値が配列の場合
+        //    この場合は明らかに使い方が間違っている上、2重に値が
+        //    出力されても意味がないので、警告(E_NOTICE)扱いにする
+        //    この場合、hiddenタグは出力されない
+        $this->local_af->set('test', array(1,2));
+        $hidden = $this->local_af->getHiddenVars();
+        $this->assertEqual($hidden, '');  //  値が入っていない扱いなので空文字が返る
+        $this->local_af->clearFormVars();
+
+        //    include_list テスト
+        $this->local_af->set('test', 1);
+        $this->local_af->set('no_name', 'name');
+        $this->local_af->set('test_array', array(1,2));
+        $include_list = array('test');
+        $hidden = $this->local_af->getHiddenVars($include_list);
+        $expected = "<input type=\"hidden\" name=\"test\" value=\"1\" />\n";
+        $this->assertEqual($hidden, $expected);
+       
+        //    exclude_list テスト
+        $exclude_list = array('test_array', 'no_name');
+        $hidden = $this->local_af->getHiddenVars(NULL, $exclude_list);
+        $expected = "<input type=\"hidden\" name=\"test\" value=\"1\" />\n";
+        $this->assertEqual($hidden, $expected);
+
+        //    include_list, exclude_list の組み合わせ
+        $include_list = array('test', 'no_name');
+        $exclude_list = array('no_name');
+        $hidden = $this->local_af->getHiddenVars($include_list, $exclude_list);
+        $expected = "<input type=\"hidden\" name=\"test\" value=\"1\" />\n";
+        $this->assertEqual($hidden, $expected);
+    }
+    // }}}
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Custom_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Custom_Test.php
new file mode 100644 (file)
index 0000000..298134b
--- /dev/null
@@ -0,0 +1,279 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Custom_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Custom_Test
+/**
+ *  Test Case For Ethna_ActionForm(Custom Validator)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Custom_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ checkMailAddress
+    function test_checkMailAddress()
+    {
+        //    'required' => true とすると
+        //    Ethna_Plugin_Validator_Required の時点で
+        //    エラーになる入力があるためここではfalseに
+        //    設定
+        $form_string = array(
+                           'type' => VAR_TYPE_STRING,
+                           'required' => false,
+                           'custom' => 'checkMailaddress',
+                       );
+        $this->af->setDef('input', $form_string);
+
+        $this->af->set('input', 'hoge@fuga.net');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', '-hoge@fuga.net');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', '.hoge@fuga.net');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', '+hoge@fuga.net');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        // @がない
+        $this->af->set('input', 'hogefuga.et');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        // @の前に文字がない
+        $this->af->set('input', '@hogefuga.et');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        // @の後に文字がない
+        $this->af->set('input', 'hogefuga.net@');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        // 先頭文字が許されていない
+        $this->af->set('input', '%hoge@fuga.net');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        // 末尾文字が許されていない
+        $this->af->set('input', 'hoge@fuga.net.');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ checkBoolean
+    function test_checkBoolean()
+    {
+        //    'required' => true とすると
+        //    Ethna_Plugin_Validator_Required の時点で
+        //    エラーになる入力があるためここではfalseに
+        //    設定
+        $form_boolean = array(
+                            'type' => VAR_TYPE_BOOLEAN,
+                            'required' => false,
+                            'custom' => 'checkBoolean',
+                        );
+        $this->af->setDef('input', $form_boolean);
+
+        //   HTML フォームから入ってくる値は
+        //   文字列型である。
+        //   @see http://www.php.net/manual/en/types.comparisons.php  
+        $this->af->set('input', '0');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', '1');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        //   空文字列は false と見做すのが仕様
+        $this->af->set('input', '');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        // 0,1, 空文字列以外の値は全てエラー
+        $this->af->set('input', 3);
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', "true");
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', "false");
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ checkURL
+    function test_checkURL()
+    {
+        //    'required' => true とすると
+        //    Ethna_Plugin_Validator_Required の時点で
+        //    エラーになる入力があるためここではfalseに
+        //    設定
+        $form_url = array(
+                        'type' => VAR_TYPE_STRING,
+                        'required' => false,
+                        'custom' => 'checkURL',
+                    );
+        $this->af->setDef('input', $form_url);
+
+        $this->af->set('input', 'http://uga.net');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', 'https://uga.net');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', 'ftp://uga.net');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', 'http://');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        //    空文字列はエラーにしないのが仕様
+        $this->af->set('input', '');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        // '/'が足りない
+        $this->af->set('input', 'http:/');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        // 接頭辞がない
+        $this->af->set('input', 'hoge@fuga.net');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ checkVendorChar
+    function test_checkVendorChar()
+    {
+        //    'required' => true とすると
+        //    Ethna_Plugin_Validator_Required の時点で
+        //    エラーになる入力があるためここではfalseに
+        //    設定
+        $form_string = array(
+                           'type' => VAR_TYPE_STRING,
+                           'required' => false,
+                           'custom' => 'checkVendorChar',
+                       );
+        $this->af->setDef('input', $form_string);
+
+        $this->af->set('input', 'http://uga.net');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0x00));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0x79));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0x80));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0x8e));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0x8f));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0xae));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0xf8));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0xfd));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        /* IBM拡張文字 / NEC選定IBM拡張文字 */
+        //$c == 0xad || ($c >= 0xf9 && $c <= 0xfc)
+        $this->af->set('input', chr(0xad));
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0xf9));
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0xfa));
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear(); 
+
+        $this->af->set('input', chr(0xfc));
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Max_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Max_Test.php
new file mode 100644 (file)
index 0000000..d9a9f39
--- /dev/null
@@ -0,0 +1,253 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Max_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Max_Test
+/**
+ *  Test Case For Ethna_ActionForm(Max Validator)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Max_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Max Integer. 
+    function test_Validate_Max_Integer()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_INT,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'max' => 5,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', 5);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 6);
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 4); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Max Float. 
+    function test_Validate_Max_Float()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_FLOAT,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'max' => 5,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', 4.999999); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 5.000001);
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 5.0);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 6.0);
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Max Datetime. 
+    function test_Validate_Max_DateTime()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_DATETIME,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'max' => '2000-01-01',
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', '1999-12-31'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', '2000-01-02');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', '2000-01-01');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+    }
+    // }}}
+
+    // {{{ Validator Max String. 
+    // {{{ Validator Max String(UTF-8)
+    function test_Validate_Max_String_UTF8()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'max' => 5,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abcd'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcdef');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcde');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        $this->af->set('input', 'あいうえお');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あいうえおか');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あいうえ');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Max String(EUC-JP)
+    function test_Validate_Max_String_EUCJP()
+    {
+        $this->ctl->setClientEncoding('EUC-JP');
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'max' => 4,  //  全角2文字、半角4文字
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abc'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcde');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        $this->af->set('input', mb_convert_encoding('あい', 'EUC-JP', 'UTF-8'));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', mb_convert_encoding('あいう', 'EUC-JP', 'UTF-8'));
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', mb_convert_encoding('あ', 'EUC-JP', 'UTF-8'));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+
+        //   reset client encoding
+        $this->ctl->setClientEncoding('UTF-8');
+    }
+    // }}}
+
+    // {{{ Validator Max String(ASCII)
+    function test_Validate_Max_String_ASCII()
+    {
+        $this->ctl->setClientEncoding('ASCII');
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'max' => 4,  //  ascii 4文字 
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abc'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcde');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   reset client encoding
+        $this->ctl->setClientEncoding('UTF-8');
+    }
+    // }}}
+    // }}}
+
+    // {{{ Validator Max File. 
+    function test_Validate_Max_File()
+    {
+        //  skipped because we can't bypass 
+        //  is_uploaded_file function.
+    }
+    // }}}
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Mbregexp_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Mbregexp_Test.php
new file mode 100644 (file)
index 0000000..6af83a4
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Mbregexp_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Mbregexp_Test
+/**
+ *  Test Case For Ethna_ActionForm(Mbregexp Validator)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Mbregexp_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Mbregexp. 
+    function test_Validate_Regexp()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'mbregexp' => '^[あ-ん]+$',
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', 'a5A4Pgw9');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あいうえおかきくけこ');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 1459); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //    encoding に指定された文字コード以外の文字列
+        $euc_input = mb_convert_encoding('あいうえお', 'EUC-JP', 'UTF-8');
+        $this->af->set('input', $euc_input);
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Mbstrmax_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Mbstrmax_Test.php
new file mode 100644 (file)
index 0000000..75e21a5
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Mbstrmax_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Mbstrmax_Test
+/**
+ *  Test Case For Ethna_ActionForm(Max Validator(Multibyte String))
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Mbstrmax_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Max Multibyte String. 
+    function test_Validate_MbMax_String()
+    {
+        $form_def = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'form_type'     => FORM_TYPE_TEXT,
+                          'required'      => true,
+                          'mbstrmax'      => '3',
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abc'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'ab');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        $this->af->set('input', 'あいう');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あいうえ');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あい');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+
+        //  TODO: Error Message Test.
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Mbstrmin_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Mbstrmin_Test.php
new file mode 100644 (file)
index 0000000..fa7924b
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Mbstrmin_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Mbstrmin_Test
+/**
+ *  Test Case For Ethna_ActionForm(Min Validator(Multibyte String))
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Mbstrmin_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Min Multibyte String. 
+    function test_Validate_MbMin_String()
+    {
+        $form_def = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'form_type'     => FORM_TYPE_TEXT,
+                          'required'      => true,
+                          'mbstrmin'      => '3',
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abc'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'ab');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        $this->af->set('input', 'あいu');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あi');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あいuえ');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+
+        //  TODO: Error Message Test.
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Min_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Min_Test.php
new file mode 100644 (file)
index 0000000..c8d2fd6
--- /dev/null
@@ -0,0 +1,243 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Min_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id: Ethna_ActionForm_Test.php 494 2008-04-05 19:04:17Z mumumu-org $
+ */
+
+// {{{    Ethna_ActionForm_Validator_Min_Test
+/**
+ *  Test Case For Ethna_ActionForm(Min Validator)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Min_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Min Integer. 
+    function test_Validate_Min_Integer()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_INT,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'min' => 5,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', 5);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 4); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+        $this->af->set('input', 6);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Min Float. 
+    function test_Validate_Min_Float()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_FLOAT,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'min' => 5,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', 4.999999); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 5.0);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 5);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 4);
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Min Datetime. 
+    function test_Validate_Min_DateTime()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_DATETIME,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'min' => '2000-01-01',
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', '1999-12-31'); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', '2000-01-01');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', '2000-01-02');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+    }
+    // }}}
+
+    // {{{ Validator Min String. 
+    // {{{ Validator Min String(UTF-8)
+    function test_Validate_Min_String_UTF8()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'min' => 5,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abcd'); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcde');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        $this->af->set('input', 'あいうえお');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あいうえ');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Min String(EUC-JP)
+    function test_Validate_Min_String_EUCJP()
+    {
+        $this->ctl->setClientEncoding('EUC-JP');
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'min' => 4,  //  全角2文字、半角4文字
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abcd'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abc');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcde');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        $this->af->set('input', mb_convert_encoding('あい', 'EUC-JP', 'UTF-8'));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', mb_convert_encoding('あ', 'EUC-JP', 'UTF-8'));
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', mb_convert_encoding('あいう', 'EUC-JP', 'UTF-8'));
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+
+        //   reset client encoding
+        $this->ctl->setClientEncoding('UTF-8');
+    }
+    // }}}
+
+    // {{{ Validator Min String(ASCII)
+    function test_Validate_Min_String_ASCII()
+    {
+        $this->ctl->setClientEncoding('ASCII');
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'min' => 4,  //  ascii 4文字 
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abcd'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abc');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcde');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   reset client encoding
+        $this->ctl->setClientEncoding('UTF-8');
+    }
+    // }}}
+    // }}}
+
+    // {{{ Validator Min File. 
+    function test_Validate_Min_File()
+    {
+        //  skipped because we can't bypass 
+        //  is_uploaded_file function.
+    }
+    // }}}
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Regexp_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Regexp_Test.php
new file mode 100644 (file)
index 0000000..6adc800
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Regexp_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Regexp_Test
+/**
+ *  Test Case For Ethna_ActionForm(Regexp Validator)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Regexp_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Regexp. 
+    function test_Validate_Regexp()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'regexp' => '/^[A-Za-z0-9]+$/',
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', 'a5A4Pgw9');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', '-80pz;+');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 1459); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   regexp はマルチバイトには対応していない
+        $this->af->set('input', 'あいうえお');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Required_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Required_Test.php
new file mode 100644 (file)
index 0000000..929d8a6
--- /dev/null
@@ -0,0 +1,531 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Required_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Required_Test
+/**
+ *  Test Case For Ethna_ActionForm(Required Validator)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Required_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Required Integer. 
+    function test_Validate_Required_Integer()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_INT,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+        $this->af->set('input', 5);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', '0');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', null); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', ''); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Required Float. 
+    function test_Validate_Required_Float()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_FLOAT,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+        $this->af->set('input', 4.999999); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', null); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', ''); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Required Datetime. 
+    function test_Validate_Required_DateTime()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_DATETIME,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+        $this->af->set('input', '1999-12-31'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', null); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', ''); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Required String. 
+    function test_Validate_Min_String()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+        $this->af->set('input', 'ああああ'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', null); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', ''); 
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Required File. 
+    function test_Validate_Required_File()
+    {
+        //  skipped because we can't bypass 
+        //  is_uploaded_file function.
+    }
+    // }}}
+
+    // {{{ Validator Required Integer ARRAY. 
+    function test_Validate_Required_Integer_Array()
+    {
+        $test_form_type = array(
+                              FORM_TYPE_TEXT,
+                              FORM_TYPE_PASSWORD,
+                              FORM_TYPE_TEXTAREA,
+                              FORM_TYPE_SELECT,
+                              FORM_TYPE_RADIO,
+                              FORM_TYPE_CHECKBOX,
+                              FORM_TYPE_BUTTON,
+                              FORM_TYPE_HIDDEN,
+                          );
+
+        //
+        //    FILE以外の全てのフォームタイプをテスト
+        //
+        foreach ($test_form_type as $form_type) {
+
+            $form_def = array(
+                            'type' => array(VAR_TYPE_INT),
+                            'form_type' => $form_type,
+                            'required' => true,
+                        );        
+            $this->af->setDef('input', $form_def);
+            
+            //   Formが全くsubmitすらされていない場合
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+     
+            //   配列の場合, 何も指定がない場合は全部値が入力されていなければならない
+            $this->af->set('input', array(5, null, null));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array(5, 6, 7));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   空配列は当然エラー
+            $this->af->set('input', array());
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   required_num が指定された場合
+            //   指定された数だけvalidな値が入力されなければならない
+            $form_def = array(
+                            'type' => array(VAR_TYPE_INT),
+                            'form_type' => $form_type,
+                            'required' => true,
+                            'required_num' => 2,
+                        );        
+            $this->af->setDef('input', $form_def);
+    
+            $this->af->set('input', array(5, 6));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array(5, null));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   required_key が指定された場合
+            //   指定されたキーの要素にはvalidな値が入力されなければならない
+            $form_def = array(
+                            'type' => array(VAR_TYPE_INT),
+                            'form_type' => $form_type,
+                            'required' => true,
+                            'required_key' => array(1),
+                        );        
+            $this->af->setDef('input', $form_def);
+    
+            $this->af->set('input', array(null, 6));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array(6, null));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+        }
+    }
+    // }}}
+
+    // {{{ Validator Required Float ARRAY. 
+    function test_Validate_Required_Float_Array()
+    {
+        $test_form_type = array(
+                              FORM_TYPE_TEXT,
+                              FORM_TYPE_PASSWORD,
+                              FORM_TYPE_TEXTAREA,
+                              FORM_TYPE_SELECT,
+                              FORM_TYPE_RADIO,
+                              FORM_TYPE_CHECKBOX,
+                              FORM_TYPE_BUTTON,
+                              FORM_TYPE_HIDDEN,
+                          );
+
+        //
+        //    FILE以外の全てのフォームタイプをテスト
+        //
+        foreach ($test_form_type as $form_type) {
+
+            $form_def = array(
+                            'type' => array(VAR_TYPE_FLOAT),
+                            'form_type' => $form_type,
+                            'required' => true,
+                        );        
+            $this->af->setDef('input', $form_def);
+            
+            //   Formが全くsubmitすらされていない場合
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+     
+            //   配列の場合, 何も指定がない場合は全部値が入力されていなければならない
+            $this->af->set('input', array(5.0, null, null));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array(5.1, 6.65, 91.099));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   空配列は当然エラー
+            $this->af->set('input', array());
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   required_num が指定された場合
+            //   指定された数だけvalidな値が入力されなければならない
+            $form_def = array(
+                            'type' => array(VAR_TYPE_FLOAT),
+                            'form_type' => $form_type,
+                            'required' => true,
+                            'required_num' => 2,
+                        );        
+            $this->af->setDef('input', $form_def);
+    
+            $this->af->set('input', array(5.12, 87.090));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array('abcd', 878.911));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   required_key が指定された場合
+            //   指定されたキーの要素にはvalidな値が入力されなければならない
+            $form_def = array(
+                            'type' => array(VAR_TYPE_FLOAT),
+                            'form_type' => $form_type,
+                            'required' => true,
+                            'required_key' => array(1),
+                        );        
+            $this->af->setDef('input', $form_def);
+    
+            $this->af->set('input', array(null, 6.13));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array(6.019, 'abcd'));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+        }
+    }
+    // }}}
+
+    // {{{ Validator Required Datetime ARRAY. 
+    function test_Validate_Required_Datetime_Array()
+    {
+        $test_form_type = array(
+                              FORM_TYPE_TEXT,
+                              FORM_TYPE_PASSWORD,
+                              FORM_TYPE_TEXTAREA,
+                              FORM_TYPE_SELECT,
+                              FORM_TYPE_RADIO,
+                              FORM_TYPE_CHECKBOX,
+                              FORM_TYPE_BUTTON,
+                              FORM_TYPE_HIDDEN,
+                          );
+
+        //
+        //    FILE以外の全てのフォームタイプをテスト
+        //
+        foreach ($test_form_type as $form_type) {
+
+            $form_def = array(
+                            'type' => array(VAR_TYPE_DATETIME),
+                            'form_type' => $form_type,
+                            'required' => true,
+                        );        
+            $this->af->setDef('input', $form_def);
+            
+            //   Formが全くsubmitすらされていない場合
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+     
+            //   配列の場合, 何も指定がない場合は全部値が入力されていなければならない
+            $this->af->set('input', array('2005-01-01', '2005-01-44', null));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array('2005-01-01', '2005-01-02', '2005-01-03'));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   空配列は当然エラー
+            $this->af->set('input', array());
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   required_num が指定された場合
+            //   指定された数だけvalidな値が入力されなければならない
+            $form_def = array(
+                            'type' => array(VAR_TYPE_DATETIME),
+                            'form_type' => $form_type,
+                            'required' => true,
+                            'required_num' => 2,
+                        );        
+            $this->af->setDef('input', $form_def);
+    
+            $this->af->set('input', array('2008-01-01', '2008-01-02'));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array('2008-01-02', 'abcd'));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   required_key が指定された場合
+            //   指定されたキーの要素にはvalidな値が入力されなければならない
+            $form_def = array(
+                            'type' => array(VAR_TYPE_DATETIME),
+                            'form_type' => $form_type,
+                            'required' => true,
+                            'required_key' => array(1),
+                        );        
+            $this->af->setDef('input', $form_def);
+    
+            $this->af->set('input', array(null, '2009-12-31'));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array('2008-12-11', 'abcd'));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+        }
+    }
+    // }}}
+
+    // {{{ Validator Required String ARRAY. 
+    function test_Validate_Required_String_Array()
+    {
+        $test_form_type = array(
+                              FORM_TYPE_TEXT,
+                              FORM_TYPE_PASSWORD,
+                              FORM_TYPE_TEXTAREA,
+                              FORM_TYPE_SELECT,
+                              FORM_TYPE_RADIO,
+                              FORM_TYPE_CHECKBOX,
+                              FORM_TYPE_BUTTON,
+                              FORM_TYPE_HIDDEN,
+                          );
+
+        //
+        //    FILE以外の全てのフォームタイプをテスト
+        //
+        foreach ($test_form_type as $form_type) {
+
+            $form_def = array(
+                            'type' => array(VAR_TYPE_STRING),
+                            'form_type' => $form_type,
+                            'required' => true,
+                        );        
+            $this->af->setDef('input', $form_def);
+            
+            //   Formが全くsubmitすらされていない場合
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+     
+            //   配列の場合, 何も指定がない場合は全部値が入力されていなければならない
+            $this->af->set('input', array("abcd", null, null));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array("abcd", "cdef", "hogehoge"));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   空配列は当然エラー
+            $this->af->set('input', array());
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   required_num が指定された場合
+            //   指定された数だけvalidな値が入力されなければならない
+            $form_def = array(
+                            'type' => array(VAR_TYPE_STRING),
+                            'form_type' => $form_type,
+                            'required' => true,
+                            'required_num' => 2,
+                        );        
+            $this->af->setDef('input', $form_def);
+    
+            $this->af->set('input', array("abcd", "cdef"));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array("abcd", null));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            //   required_key が指定された場合
+            //   指定されたキーの要素にはvalidな値が入力されなければならない
+            $form_def = array(
+                            'type' => array(VAR_TYPE_STRING),
+                            'form_type' => $form_type,
+                            'required' => true,
+                            'required_key' => array(1),
+                        );        
+            $this->af->setDef('input', $form_def);
+    
+            $this->af->set('input', array(null, "abcd"));
+            $this->af->validate();
+            $this->assertFalse($this->ae->isError('input'));
+            $this->ae->clear();
+    
+            $this->af->set('input', array("abcd", null));
+            $this->af->validate();
+            $this->assertTrue($this->ae->isError('input'));
+            $this->ae->clear();
+        }
+    }
+    // }}}
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmax_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmax_Test.php
new file mode 100644 (file)
index 0000000..0acb978
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Strmax_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Strmax_Test
+/**
+ *  Test Case For Ethna_ActionForm(Max Validator(Single byte String))
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Strmax_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Max Single byte String. 
+    function test_Validate_SingleByteMax_String()
+    {
+        $form_def = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'form_type'     => FORM_TYPE_TEXT,
+                          'required'      => true,
+                          'strmax'      => '3',
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abc'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'ab');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        $this->af->set('input', 'あいう');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //  TODO: Error Message Test.
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmaxcompat_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmaxcompat_Test.php
new file mode 100644 (file)
index 0000000..cc55778
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Strmaxcompat_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Strmaxcompat_Test
+/**
+ *  Test Case For Ethna_ActionForm(Max Validator(2.3.x compatible))
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Strmaxcompat_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Max string(2.3.x compatible). 
+    function test_Validate_Max_String_Compatible()
+    {
+        $form_def = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'form_type'     => FORM_TYPE_TEXT,
+                          'required'      => true,
+                          'strmaxcompat'  => '4',  // 半角4文字、全角2文字
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abcd'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcde');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abあ');  // 実質半角4文字
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        //   内部で強制的にEUC-JPに変換される
+        $this->af->set('input', 'あい');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あいう');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あ');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+
+        //  TODO: Error Message Test.
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmin_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmin_Test.php
new file mode 100644 (file)
index 0000000..c0b0994
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Strmin_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Strmin_Test
+/**
+ *  Test Case For Ethna_ActionForm(Min Validator(Single byte String))
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Strmin_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Min Single byte String. 
+    function test_Validate_SingleByteMin_String()
+    {
+        $form_def = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'form_type'     => FORM_TYPE_TEXT,
+                          'required'      => true,
+                          'strmin'      => '3',
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abc'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'ab');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        $this->af->set('input', 'あい');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //  TODO: Error Message Test.
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmincompat_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Strmincompat_Test.php
new file mode 100644 (file)
index 0000000..467c961
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Strmincompat_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Strmincompat_Test
+/**
+ *  Test Case For Ethna_ActionForm(Min Validator(2.3.x compatible))
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Strmincompat_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Min string(2.3.x compatible). 
+    function test_Validate_Min_String_Compatible()
+    {
+        $form_def = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'form_type'     => FORM_TYPE_TEXT,
+                          'required'      => true,
+                          'strmincompat'  => '4',  // 半角4文字、全角2文字
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abcd'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abc');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abあ');  // 実質半角4文字
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        //   内部で強制的にEUC-JPに変換される
+        $this->af->set('input', 'あい');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あ');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'あいa'); // 実質半角5文字
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+
+        //  TODO: Error Message Test.
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Type_Test.php b/Idea_Plugin_Extlib/test/Ethna_ActionForm_Validator_Type_Test.php
new file mode 100644 (file)
index 0000000..f5bec36
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ActionForm_Validator_Type_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_ActionForm_Validator_Type_Test
+/**
+ *  Test Case For Ethna_ActionForm(Type Validator)
+ *
+ *  @access public
+ */
+class Ethna_ActionForm_Validator_Type_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        $this->af->use_validator_plugin = false;
+        $this->af->clearFormVars();
+        $this->af->form = array();
+        $this->ae->clear();
+    }
+
+    // {{{ Validator Type Integer. 
+    function test_Validate_Type_Integer()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_INT,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', 5);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //    null の値はTypeではチェックしない
+        $this->af->set('input', null); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+        $this->af->set('input', 6.5);
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Type Float. 
+    function test_Validate_Type_Float()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_FLOAT,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', 4.999999); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //    null の値はTypeではチェックしない
+        $this->af->set('input', null);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 4);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+    }
+    // }}}
+
+    // {{{ Validator Type Datetime. 
+    function test_Validate_Type_DateTime()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_DATETIME,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        $this->af->set('input', '1999-12-31'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', 'abcd');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        $this->af->set('input', ';-!#');
+        $this->af->validate();
+        $this->assertTrue($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //    null の値はTypeではチェックしない
+        $this->af->set('input', null);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+    }
+    // }}}
+
+    // {{{ Validator Type String. 
+    function test_Validate_Min_String()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                    );        
+        $this->af->setDef('input', $form_def);
+        
+        //   in ascii.
+        $this->af->set('input', 'abcd'); 
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //   multibyte.
+        $this->af->set('input', 'あいうえお');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //    null の値はTypeではチェックしない
+        $this->af->set('input', null);
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+
+        //    空文字の値はTypeではチェックしない
+        $this->af->set('input', '');
+        $this->af->validate();
+        $this->assertFalse($this->ae->isError('input'));
+        $this->ae->clear();
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ClassFactory_Test.php b/Idea_Plugin_Extlib/test/Ethna_ClassFactory_Test.php
new file mode 100644 (file)
index 0000000..3344700
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_ClassFactory_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/test/Ethna_MocktestManager.php';
+
+//{{{    Ethna_ClassFactory_Test
+/**
+ *  Test Case For Ethna_ClassFactory_Test 
+ *
+ *  @access public
+ */
+class Ethna_ClassFactory_Test extends Ethna_UnitTestBase
+{
+    var $cf;
+
+    function setUp()
+    {
+        $ctl =& new Ethna_Controller();
+        $this->cf =& $ctl->getClassFactory();
+    }
+
+    //    Ethna_Controller と Ethna_ClassFactory は
+    //    循環参照している。PHP4では、循環参照しているオブジェクト同士を
+    //    比較しようとすると延々再帰的にプロパティと値を比較しようとする
+    //    ため Fatal Error を起こす。よって、PHP5以降でのみ以下はテストする
+    //    @see http://www.php.net/manual/en/language.oop.object-comparison.php
+    //    @see http://www.php.net/manual/en/language.oop5.object-comparison.php
+
+    function test_getManager()
+    {
+        //    大文字小文字を区別されても、
+        //    同じインスタンスを返さなければ
+        //    ならない
+        if (version_compare(phpversion(), '5', '>=')) {
+            $manager = $this->cf->getManager('mocktest');
+            $manager_alt = $this->cf->getManager('Mocktest');
+            $this->assertTrue($manager === $manager_alt);
+    
+            //    weakパラメータが指定された場合は 
+            //    強制的に違うオブジェクトを返さなければならない
+            $manager = $this->cf->getManager('mocktest');
+            $manager_alt = $this->cf->getManager('Mocktest', true);
+            $this->assertFalse($manager === $manager_alt);
+        }
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_Class_Test.php b/Idea_Plugin_Extlib/test/Ethna_Class_Test.php
new file mode 100644 (file)
index 0000000..e1b5cd7
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+/**
+ *  Ethna_Class_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+class Dummy_Ethna_Error extends Ethna_Error
+{
+    //  nothing defined.
+}
+
+function dummy_error_callback_global(&$error)
+{
+    $GLOBALS['_dummy_error_callback_global'] = $error->getMessage();
+}
+
+//{{{    Ethna_Test
+/**
+ *  Test Case For Ethna class
+ *
+ *  @access public
+ */
+class Ethna_Class_Test extends Ethna_UnitTestBase
+{
+    var $dummy_error_value_class;
+
+    function setUp()
+    {
+        $GLOBALS['_dummy_error_callback_global'] = NULL;
+        $this->dummy_error_value_class = NULL;
+        Ethna::clearErrorCallback();
+    }
+
+    function dummy_error_callback_obj(&$error)
+    {
+        $this->dummy_error_value_class = $error->getMessage();
+    }
+
+    //{{{  isError test
+    function test_isError()
+    {
+        $error = new Ethna_Error();
+        $this->assertTrue(Ethna::isError($error));
+
+        $error = 'this is not object, but string.';
+        $this->assertFalse(Ethna::isError($error));
+
+        $error = new Dummy_Ethna_Error('Error Message', E_CACHE_GENERAL,
+                                 ETHNA_ERROR_DUMMY, E_USER_ERROR,
+                                 NULL, 'Ethna_Error'
+                 );
+        $this->assertFalse(Ethna::isError($error, E_FORM_REQUIRED));
+        $this->assertTrue(Ethna::isError($error, E_CACHE_GENERAL));
+
+        $error = new stdClass();
+        $this->assertFalse(Ethna::isError($error));
+
+        $error = NULL;
+        $this->assertFalse(Ethna::isError($error));
+
+        //   Ethna はPEARに依存しないので、
+        //   PEAR_Error を渡してもfalse が返らなければならない
+        $fp = @fopen('PEAR.php', 'r', true);
+        if ($fp !== false) {
+            require_once 'PEAR.php';
+            $error = new PEAR_Error();
+            $this->assertFalse(Ethna::isError($error));
+        }
+        fclose($fp);
+    }
+    // }}}
+
+    //{{{  raiseError test
+    function test_raiseError()
+    {
+        $error = Ethna::raiseError('Error!!!!!');
+        $this->assertEqual('Error!!!!!', $error->getMessage());
+        $this->assertEqual(E_USER_ERROR, $error->getLevel());
+        $this->assertEqual(E_GENERAL, $error->getCode());     
+
+        $error = Ethna::raiseError('Error', E_CACHE_GENERAL);
+        $this->assertEqual(E_CACHE_GENERAL, $error->getCode());     
+    }
+    // }}}
+
+    //{{{  raiseWarning test
+    function test_raiseWarning()
+    {
+        $error = Ethna::raiseWarning('Error!!!!!');
+        $this->assertEqual('Error!!!!!', $error->getMessage());
+        $this->assertEqual(E_USER_WARNING, $error->getLevel());
+        $this->assertEqual(E_GENERAL, $error->getCode());     
+
+        $error = Ethna::raiseWarning('Error!!!!!', E_CACHE_GENERAL);
+        $this->assertEqual(E_CACHE_GENERAL, $error->getCode());     
+    }
+    // }}}
+
+    //{{{  raiseNotice test
+    function test_raiseNotice()
+    {
+        $error = Ethna::raiseNotice('Error!!!!!');
+        $this->assertEqual('Error!!!!!', $error->getMessage());
+        $this->assertEqual(E_USER_NOTICE, $error->getLevel());
+        $this->assertEqual(E_GENERAL, $error->getCode());     
+
+        $error = Ethna::raiseNotice('Error!!!!!', E_CACHE_GENERAL);
+        $this->assertEqual(E_CACHE_GENERAL, $error->getCode());     
+    }
+    // }}}
+
+    //{{{  callback test
+    function test_error_callback_obj()
+    {
+        $this->assertNULL($GLOBALS['_dummy_error_callback_global']);
+        $this->assertNULL($this->dummy_error_value_class);
+
+        //   array の場合は クラス名|オブジェクト + method
+        Ethna::setErrorCallback(array(&$this, 'dummy_error_callback_obj'));
+        Ethna::raiseError('dummy_error_obj!!!');
+        $this->assertEqual('dummy_error_obj!!!', $this->dummy_error_value_class);
+        $this->assertNULL($GLOBALS['_dummy_error_callback_global']);
+    }
+
+    function test_error_callback_global()
+    {
+        $this->assertNULL($GLOBALS['_dummy_error_callback_global']);
+        $this->assertNULL($this->dummy_error_value_class);
+
+        //   string の場合はグローバル関数 
+        Ethna::setErrorCallback('dummy_error_callback_global');
+        Ethna::raiseError('dummy_error_global!!!');
+        $this->assertEqual('dummy_error_global!!!', $GLOBALS['_dummy_error_callback_global']);
+    }
+
+    function test_error_callback_mixed()
+    {
+        //   string の場合はグローバル関数 
+        Ethna::setErrorCallback('dummy_error_callback_global');
+        Ethna::setErrorCallback(array(&$this, 'dummy_error_callback_obj'));
+        Ethna::raiseError('dummy_error_global!!!');
+        $this->assertEqual('dummy_error_global!!!', $GLOBALS['_dummy_error_callback_global']);
+        $this->assertEqual('dummy_error_global!!!', $this->dummy_error_value_class);
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_Config_Test.php b/Idea_Plugin_Extlib/test/Ethna_Config_Test.php
new file mode 100644 (file)
index 0000000..08af746
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/**
+ *  Ethna_Config_Test.php
+ */
+
+/**
+ *  Ethna_Configクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Config_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        // etcディレクトリを上書き
+        $this->ctl->directory['etc'] = dirname(__FILE__);
+        $this->config = $this->ctl->getConfig();
+        $this->filename = dirname(__FILE__) . '/ethna-ini.php';
+    }
+
+    function tearDown()
+    {
+        if (file_exists($this->filename)) {
+            unlink($this->filename);
+        }
+    }
+
+    function test_getConfigFile()
+    {
+        $result = $this->config->_getConfigFile(); 
+        $this->assertEqual($result, $this->filename);
+    }
+
+    function test_update()
+    {
+        // この時点ではまだ ethna-ini.php は存在しない
+        $result = $this->config->get('foo');
+        $this->assertEqual($result, null);
+
+        // Ethna_Configオブジェクト内の値
+        $this->config->set('foo', 'bar');
+        $result = $this->config->get('foo');
+        $this->assertEqual($result, 'bar');
+
+        // ethna-ini.php が自動生成される
+        $this->config->update();
+
+        // ethna-ini.php を読み込み直す
+        $this->config->_getConfig();
+        $result = $this->config->get('foo');
+        $this->assertEqual($result, 'bar');
+
+        // 値を上書き
+        $this->config->set('foo', 'baz');
+        $this->config->update();
+
+        // もう一度読み込み直す
+        $this->config->_getConfig();
+        $result = $this->config->get('foo');
+        $this->assertEqual($result, 'baz');
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_Controller_Test.php b/Idea_Plugin_Extlib/test/Ethna_Controller_Test.php
new file mode 100644 (file)
index 0000000..9da1037
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Controller_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+//{{{    Ethna_Controller_Test
+/**
+ *  Test Case For Ethna_Controller_Test 
+ *
+ *  @access public
+ */
+class Ethna_Controller_Test extends Ethna_UnitTestBase
+{
+    var $test_ctl;
+
+    function setUp()
+    {
+        $this->test_ctl =& new Ethna_Controller();
+    }
+
+    function tearDown()
+    {
+        unset($GLOBALS['_Ethna_controller']);
+    }
+
+    // {{{ checkAppId
+    function test_checkAppId()
+    {
+        //  予約語(app, ethna)は当然駄目
+        //  これについては大文字、小文字を区別しない
+        $r = $this->test_ctl->checkAppId('ethna');
+        $this->assertTrue(Ethna::isError($r));
+
+        $r = $this->test_ctl->checkAppId('EthNa');
+        $this->assertTrue(Ethna::isError($r));
+
+        $r = $this->test_ctl->checkAppId('ETHNA');
+        $this->assertTrue(Ethna::isError($r));
+
+        $r = $this->test_ctl->checkAppId('app');
+        $this->assertTrue(Ethna::isError($r));
+
+        $r = $this->test_ctl->checkAppId('ApP');
+        $this->assertTrue(Ethna::isError($r));
+
+        $r = $this->test_ctl->checkAppId('APP');
+        $this->assertTrue(Ethna::isError($r));
+
+        //  数字で始まっては駄目
+        $r = $this->test_ctl->checkAppId('1');
+        $this->assertTrue(Ethna::isError($r));
+
+        $r = $this->test_ctl->checkAppId('0abcd');
+        $this->assertTrue(Ethna::isError($r));
+
+        //  始めがアンダースコアも駄目
+        $r = $this->test_ctl->checkAppId('_');
+        $this->assertTrue(Ethna::isError($r));
+
+        $r = $this->test_ctl->checkAppId('_abcd');
+        $this->assertTrue(Ethna::isError($r));
+
+        //  一文字でも英数字以外が混じれば駄目
+        $r = $this->test_ctl->checkAppId('ab;@e');
+        $this->assertTrue(Ethna::isError($r));
+
+        $r = $this->test_ctl->checkAppId('@bcde');
+        $this->assertTrue(Ethna::isError($r));
+
+        $r = $this->test_ctl->checkAppId('abcd:');
+        $this->assertTrue(Ethna::isError($r));
+
+        //  全部英数字であればOK
+        $r = $this->test_ctl->checkAppId('abcd');
+        $this->assertFalse(Ethna::isError($r));
+    }
+    // }}}
+
+    // {{{ test_getClientEncoding
+    function test_getClientEncoding()
+    {
+        $this->assertEqual('UTF-8', $this->test_ctl->getClientEncoding());
+    }
+    // }}} 
+
+    // {{{ test_setClientEncoding
+    function test_setClientEncoding()
+    {
+        $this->test_ctl->setClientEncoding('Shift_JIS');
+        $this->assertEqual('Shift_JIS', $this->test_ctl->getClientEncoding());
+    }
+    // }}}
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_Error_Test.php b/Idea_Plugin_Extlib/test/Ethna_Error_Test.php
new file mode 100644 (file)
index 0000000..9cfc1b2
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+/**
+ *  Ethna_Error_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+//{{{    Ethna_Error_Test
+/**
+ *  Test Case For Ethna_Error
+ *
+ *  @access public
+ */
+class Ethna_Error_Test extends Ethna_UnitTestBase
+{
+    var $error;
+
+    function setUp()
+    {
+        $this->error = Ethna::raiseError('general error');
+    }
+
+    function tearDown()
+    {
+        $error = NULL;
+    }
+
+    //{{{ getCode
+    function test_getcode()
+    {
+        $this->assertEqual(E_GENERAL, $this->error->getCode());
+    }
+    //}}}
+
+    //{{{ getLevel
+    function test_getlevel()
+    {
+        $this->assertEqual(E_USER_ERROR, $this->error->getLevel());
+    }
+    //}}}
+
+    //{{{ getMessage
+    function test_getmessage()
+    {
+        $this->assertEqual('general error', $this->error->getMessage());
+    }
+    //}}}
+
+    //{{{ setUserInfo, getUserInfo
+    function test_userinfo()
+    {
+        $this->error->addUserInfo('foobarbaz');
+        $this->error->addUserInfo('hoge');
+        $this->assertEqual('foobarbaz', $this->error->getUserInfo(0));
+        $this->assertEqual('hoge', $this->error->getUserInfo(1));
+
+        $info = $this->error->getUserInfo();
+        $this->assertEqual('foobarbaz', $info[0]);
+        $this->assertEqual('hoge', $info[1]);
+    }
+    //}}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_Getopt_Test.php b/Idea_Plugin_Extlib/test/Ethna_Getopt_Test.php
new file mode 100644 (file)
index 0000000..0d345ac
--- /dev/null
@@ -0,0 +1,429 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Getopt_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_Getopt.php';
+
+/**
+ *  Test Case For Ethna_Getopt
+ *
+ *  @access public
+ */
+class Ethna_Getopt_Test extends Ethna_UnitTestBase
+{
+    var $opt;
+
+    function setUp()
+    {
+        $this->opt = new Ethna_Getopt();
+    }
+
+    // {{{ readPHPArgv
+    function test_readPHPArgv()
+    {
+        global $argv;
+        $argv = array('test.php', 'a', '-b=c', '--c=d', 'e');
+        
+        $r = $this->opt->readPHPArgv();
+        $this->assertEqual('test.php', $argv[0]);
+        $this->assertEqual('a', $argv[1]);
+        $this->assertEqual('-b=c', $argv[2]);
+        $this->assertEqual('--c=d', $argv[3]);
+        $this->assertEqual('e', $argv[4]);
+    }
+    // }}}
+
+    //{{{ short option test
+    function test_shortopt_required()
+    {
+        // no args
+        $args = array();
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        // option -a is defined, but no args.
+        $args = array('-a');
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option -a requires an argument', $r->getMessage());
+
+        // unknown option 
+        $args = array('-c'); // -c is unknown.
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option -c', $r->getMessage());
+
+        // unknown option part 2.
+        $args = array('--foo'); // -foo is unknown.
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option --foo', $r->getMessage());
+
+        // -a option value is b. c is nonparsed.
+        $args = array('-a', 'b', 'c');
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertEqual('b', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('c', $nonparsed_arg[0]);
+
+        // -a value is bcd, e is nonparsed.
+        $args = array('-abcd', 'e');
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertEqual('bcd', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('e', $nonparsed_arg[0]);
+    }
+
+    function test_shortopt_optional()
+    {
+        // no args
+        $args = array();
+        $shortopt = 'a::';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        // option -a is defined, but no args.
+        $args = array('-a');
+        $shortopt = 'a::';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        // -a value is bcd, e is nonparsed arg.
+        $args = array('-abcd', 'e');
+        $shortopt = 'a::';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertEqual('bcd', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('e', $nonparsed_arg[0]);
+        // -a option value is none. b, c is nonparsed.
+        $args = array('-a', 'b', 'c');
+        $shortopt = 'a::';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('b', $nonparsed_arg[0]);
+        $this->assertEqual('c', $nonparsed_arg[1]);
+    }
+
+    function test_shortopt_disabled()
+    {
+        // no args
+        $args = array();
+        $shortopt = 'a';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        // option -a is defined, but no args.
+        $args = array('-a');
+        $shortopt = 'a';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        // option -a is defined, but value is disabled.
+        // value will be NEVER interpreted.
+        $args = array('-a', 'b', 'c');
+        $shortopt = 'a';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('b', $nonparsed_arg[0]);
+        $this->assertEqual('c', $nonparsed_arg[1]);
+
+        // successive option definition, but unrecognized option. :)
+        $args = array('-ab');
+        $shortopt = 'a';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual("unrecognized option -b", $r->getMessage());
+
+        // option setting will be refrected even when after values. :)
+        $args = array('-a', 'b', '-c', 'd', '-e', 'f');
+        $shortopt = 'ac:e::';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('b', $nonparsed_arg[0]);
+        $this->assertEqual('-c', $nonparsed_arg[1]);
+        $this->assertEqual('d', $nonparsed_arg[2]);
+        $this->assertEqual('-e', $nonparsed_arg[3]);
+        $this->assertEqual('f', $nonparsed_arg[4]);
+    }
+
+    function test_shortopt_complex()
+    {
+        //  complex option part 1.
+        $args = array();
+        $shortopt = 'ab:c::';
+        $args = array('-abcd', '-cd');
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        $this->assertEqual('b', $parsed_arg[1][0]);
+        $this->assertEqual('cd', $parsed_arg[1][1]);
+
+        $this->assertEqual('c', $parsed_arg[2][0]);
+        $this->assertEqual('d', $parsed_arg[2][1]);
+
+        //  complex option part 2.
+        $args = array('-a', '-c', 'd', 'e');
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        $this->assertEqual('c', $parsed_arg[1][0]);
+        $this->assertNULL($parsed_arg[1][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('d', $nonparsed_arg[0]);
+        $this->assertEqual('e', $nonparsed_arg[1]);
+
+        $args = array('-cd', '-ad');
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual("unrecognized option -d", $r->getMessage());
+    }
+    // }}}
+
+    // {{{  long option test
+    function test_longopt_required()
+    {
+        // no args
+        $args = array();
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+    
+        // option -a is defined, but no args.
+        $args = array('--foo');
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option --foo requires an argument', $r->getMessage());
+
+        // unknown option.
+        $args = array('--bar'); // -bar is unknown.
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option --bar', $r->getMessage());
+
+        // unknown option part 1.
+        $args = array('--bar'); // -bar is unknown.
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option --bar', $r->getMessage());
+
+        // unknown option part 2.
+        $args = array('-a'); // -a is unknown.
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option -a', $r->getMessage());
+
+        // --foo option value is bar. hoge is nonparsed. 
+        $args = array('--foo=bar', 'hoge');
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('--foo', $parsed_arg[0][0]);
+        $this->assertEqual('bar', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+        // --foo option value is bar. hoge, -fuga is nonparsed.
+        $args = array('--foo', 'bar', 'hoge', '-fuga');
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('--foo', $parsed_arg[0][0]);
+        $this->assertEqual('bar', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+        $this->assertEqual('-fuga', $nonparsed_arg[1]);
+    }
+
+    function test_longopt_optional()
+    {
+        // no args
+        $args = array();
+        $shortopt = NULL;
+        $longopt = array("foo==");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+        // option --foo is defined, but no args.
+        $args = array('--foo');
+        $shortopt = NULL;
+        $longopt = array("foo==");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        // -foo value is bar, hoge is nonparsed arg.
+        $args = array('--foo', 'bar', 'hoge');
+        $shortopt = NULL;
+        $longopt = array("foo==");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('--foo', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('bar', $nonparsed_arg[0]);
+        $this->assertEqual('hoge', $nonparsed_arg[1]);
+
+        // -foo value is bar, hoge, moge is nonparsed arg.
+        $args = array('--foo=bar', 'hoge', 'moge');
+        $shortopt = NULL;
+        $longopt = array("foo==");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('--foo', $parsed_arg[0][0]);
+        $this->assertEqual('bar', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+        $this->assertEqual('moge', $nonparsed_arg[1]);
+    }
+
+    function test_longopt_disabled()
+    {
+        // no args
+        $args = array();
+        $shortopt = NULL;
+        $longopt = array("foo");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        // option -foo is defined, but no args.
+        $args = array('--foo');
+        $shortopt = null;
+        $longopt = array("foo");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertfalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertequal('--foo', $parsed_arg[0][0]);
+        $this->assertnull($parsed_arg[0][1]);
+
+        // option -foo is defined, but value is disabled.
+        $args = array('--foo=bar');
+        $shortopt = null;
+        $longopt = array("foo");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual("option --foo doesn't allow an argument", $r->getMessage());
+
+        $args = array('--foo', 'hoge', 'bar');
+        $shortopt = null;
+        $longopt = array("foo");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertequal('--foo', $parsed_arg[0][0]);
+        $this->assertNull($parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+        $this->assertEqual('bar', $nonparsed_arg[1]);
+    }
+    // }}}
+
+    // {{{  short option, long option mixed.
+    function test_mixed_option()
+    {
+        // no args
+        $shortopt = 'ab:c::';
+        $longopt = array('foo=', 'bar==', 'hoge');
+
+        $args = array();
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+        
+        $args = array('-a', '--foo', 'bar', '--bar=moge', 'hoge', '--hoge');
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertequal('a', $parsed_arg[0][0]);
+        $this->assertNull($parsed_arg[0][1]);
+        $this->assertequal('--foo', $parsed_arg[1][0]);
+        $this->assertEqual('bar', $parsed_arg[1][1]);
+        $this->assertequal('--bar', $parsed_arg[2][0]);
+        $this->assertEqual('moge', $parsed_arg[2][1]);
+
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+        $this->assertEqual('--hoge', $nonparsed_arg[1]);
+    }
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_I18N_Test.php b/Idea_Plugin_Extlib/test/Ethna_I18N_Test.php
new file mode 100644 (file)
index 0000000..6f93b93
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_I18N_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+//{{{    Ethna_I18N_Test
+/**
+ *  Test Case For Ethna_I18N class
+ *
+ *  @access public
+ */
+class Ethna_I18N_Test extends Ethna_UnitTestBase
+{
+    var $i18n;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $this->i18n =& $ctl->getI18N();
+    }
+
+    // {{{  test_get_ja_JP
+    function test_get_ja_JP()
+    {
+        //  デフォルトは日本語のメッセージが返ってくる
+        $this->assertEqual($this->i18n->get('Backend'), 'バックエンド');
+        $this->assertEqual($this->i18n->get('Could not write uploaded file to disk.'), 'ディスクへの書き込みに失敗しました。');
+        $this->assertEqual($this->i18n->get('Filter(%d)'), 'フィルタ(%d)');
+        $this->assertEqual($this->i18n->get('Heisei'), '平成');
+        $this->assertEqual($this->i18n->get('%Y/%m/%d %H:%M:%S'), '%Y年%m月%d日 %H時%M分%S秒');
+
+        //  カタログにないメッセージはそのまま返ってくる 
+        $this->assertEqual($this->i18n->get('foo'), 'foo');
+        $this->assertEqual($this->i18n->get('www.example.com'), 'www.example.com');
+    }
+    // }}}
+
+    // {{{  test_get_fallback_locale
+    function test_get_fallback_locale()
+    {
+        //  ロケール切り替え
+        $this->i18n->setLanguage('en_US', 'ASCII', 'ASCII');
+
+        //  メッセージカタログファイルがないロケールの場合は、
+        //  skel/locale/ethna_sysmsg.ini にあるメッセージが返ってくる
+        $this->assertEqual($this->i18n->get('Backend'), 'Backend');
+        $this->assertEqual($this->i18n->get('Could not write uploaded file to disk.'),
+                           'Could not write uploaded file to disk.'
+        );
+        $this->assertEqual($this->i18n->get('Filter(%d)'), 'Filter(%d)');
+        $this->assertEqual($this->i18n->get('Heisei'), 'Heisei');
+        $this->assertEqual($this->i18n->get('%Y/%m/%d %H:%M:%S'),
+                           '%Y/%m/%d %H:%M:%S'
+        );
+
+        //  カタログにないメッセージはそのまま返ってくる 
+        $this->assertEqual($this->i18n->get('foo'), 'foo');
+        $this->assertEqual($this->i18n->get('www.example.com'), 'www.example.com');
+
+        //    ロケールを再切り替え
+        $this->i18n->setLanguage('ja_JP', 'UTF-8', 'UTF-8');
+
+        $this->assertEqual($this->i18n->get('Heisei'), '平成');
+        $this->assertEqual($this->i18n->get('foo'), 'foo');
+
+        //  ロケール再再切り替え
+        $this->i18n->setLanguage('en_US', 'ASCII', 'ASCII');
+        $this->assertEqual($this->i18n->get('foo'), 'foo');
+        $this->assertEqual($this->i18n->get('Heisei'), 'Heisei');
+
+        //  他のテストもあるので元に戻しておく
+        $this->i18n->setLanguage('ja_JP', 'UTF-8', 'UTF-8');
+    }
+    // }}} 
+
+    // {{{ test_parseEthnaMsgCatalog
+    function test_parseEthnaMsgCatalog()
+    {
+        $file = ETHNA_BASE . '/test/test_message_catalog.ini';
+        $messages = $this->i18n->parseEthnaMsgCatalog($file);
+
+        //   正常な翻訳行 (1行) 
+        $expected = '{form}に機種依存文字が入力されています';
+        $actual = $messages['{form} contains machine dependent code.'];
+        $this->assertEqual($expected, $actual); 
+
+        //   parse_ini_file 関数でパースできない値
+        $expected = 'はい';
+        $actual = $messages['yes'];
+        $this->assertEqual($expected, $actual); 
+
+        $expected = 'いいえ';
+        $actual = $messages['no'];
+        $this->assertEqual($expected, $actual); 
+
+        $expected = '開き括弧左';
+        $actual = $messages['{'];
+        $this->assertEqual($expected, $actual); 
+
+        $expected = '開き括弧右';
+        $actual = $messages['}'];
+        $this->assertEqual($expected, $actual); 
+
+        $expected = 'アンパサンド';
+        $actual = $messages['&'];
+        $this->assertEqual($expected, $actual); 
+
+        $expected = 'チルダ';
+        $actual = $messages['~'];
+        $this->assertEqual($expected, $actual); 
+
+        $expected = 'ビックリマーク';
+        $actual = $messages['!'];
+        $this->assertequal($expected, $actual); 
+
+        //   別の記号類 
+        $expected = '%Y年%m月%d日 " %H時%M分%S秒';
+        $actual = $messages['%Y/%m/%d "%H:%M:%S'];
+        $this->assertequal($expected, $actual); 
+
+        //   複数行に跨がる翻訳行
+        $expected = "  \nfuga    ";
+        $actual = $messages["\nhoge"];
+        $this->assertequal($expected, $actual); 
+
+        $expected = "あいうえお \"\n かきくけこ \nさしすせそ";
+        $actual = $messages["abcd\"efg\n hijklmn"];
+        $this->assertequal($expected, $actual); 
+
+        //  ダブルクォート連続
+        $expected = " ab\n\n\n cdefg ";
+        $actual = $messages['""""""'];
+        $this->assertequal($expected, $actual); 
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_Logger_Test.php b/Idea_Plugin_Extlib/test/Ethna_Logger_Test.php
new file mode 100644 (file)
index 0000000..d98dcc7
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+/**
+ *  Ethna_Logger_Test.php
+ */
+
+/**
+ *  Ethna_Loggerクラスのテストケース
+ *  (Logwriterではなく、LogwriterのマネージャとしてのLoggerのテスト)
+ *
+ *  @access public
+ */
+class Ethna_Logger_Test extends Ethna_UnitTestBase
+{
+    function setUp()
+    {
+        // ConfigクラスをEthna_Logger_Test_Configに設定
+        $this->ctl->class['config'] = 'Ethna_Logger_Test_Config';
+        $this->ctl->getConfig();
+    }
+
+    function tearDown()
+    {
+        // do nothing.
+    }
+
+    function _resetLoggerSetting($config)
+    {
+        unset($this->ctl->class_factory->object['logger']);
+        $config_obj =& $this->ctl->class_factory->object['config'];
+        $config_obj->config = $config;
+    }
+
+    /**
+     *  old style log setting.
+     */
+    function test_parseSetting_Compatible()
+    {
+        $config = array(
+            'log_facility'      => 'echo',
+            'log_level'         => 'warning',
+            'log_option'        => 'pid,function,pos',
+        );
+        $this->_resetLoggerSetting($config);
+        $this->logger =& $this->ctl->getLogger();
+
+        // facility
+        $facility = $this->logger->getLogFacility();
+        $this->assertEqual($facility, 'echo'); // not array, but string (for B.C.)
+
+        // level
+        $level_echo = $this->logger->level['echo'];
+        $this->assertEqual($level_echo, LOG_WARNING);
+
+        // option
+        $option_echo = $this->logger->option['echo'];
+        $this->assertEqual($option_echo['pid'], true);
+        $this->assertEqual($option_echo['function'], true);
+        $this->assertEqual($option_echo['pos'], true);
+    }
+
+    /**
+     *  structured style log setting.
+     */
+    function test_parseSetting_Structured()
+    {
+        $config = array(
+            'log' => array(
+                'echo'  => array(
+                    'level'         => 'warning',
+                ),
+                'file'  => array(
+                    'level'         => 'notice',
+                    'file'          => '/var/log/Ethna.log',
+                    'mode'          => 0666,
+                ),
+                'alertmail'  => array(
+                    'level'         => 'err',
+                    'mailaddress'   => 'alert@ml.example.jp',
+                ),
+            ),
+            'log_option'            => 'pid,function,pos',
+        );
+        $this->_resetLoggerSetting($config);
+        $this->logger =& $this->ctl->getLogger();
+
+        // facility
+        $facility = $this->logger->getLogFacility();
+        $this->assertEqual($facility, array('echo', 'file', 'alertmail'));
+
+        // level
+        $level_echo = $this->logger->level['echo'];
+        $this->assertEqual($level_echo, LOG_WARNING);
+        $level_file = $this->logger->level['file'];
+        $this->assertEqual($level_file, LOG_NOTICE);
+        $level_alertmail = $this->logger->level['alertmail'];
+        $this->assertEqual($level_alertmail, LOG_ERR);
+
+        // option
+        $option_echo = $this->logger->option['echo'];
+        $this->assertEqual($option_echo['pid'], true);
+        $this->assertEqual($option_echo['function'], true);
+        $this->assertEqual($option_echo['pos'], true);
+
+        $option_file = $this->logger->option['file'];
+        $this->assertEqual($option_file['pid'], true);
+        $this->assertEqual($option_file['function'], true);
+        $this->assertEqual($option_file['pos'], true);
+        $this->assertEqual($option_file['file'], '/var/log/Ethna.log');
+        $this->assertEqual($option_file['mode'], 0666);
+
+        $option_alertmail = $this->logger->option['alertmail'];
+        $this->assertEqual($option_alertmail['pid'], true);
+        $this->assertEqual($option_alertmail['function'], true);
+        $this->assertEqual($option_alertmail['pos'], true);
+        $this->assertEqual($option_alertmail['mailaddress'], 'alert@ml.example.jp');
+    }
+
+    /**
+     *  @todo   log level filter, begin(), log(), end()
+     */
+    //function test_etcetc()
+    //{
+    //    // not implemented yet.
+    //}
+}
+
+class Ethna_Logger_Test_Config extends Ethna_Config
+{
+    function Ethna_Logger_Test_Config()
+    {
+        // do nothing.
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_MockProject.php b/Idea_Plugin_Extlib/test/Ethna_MockProject.php
new file mode 100644 (file)
index 0000000..7427d5a
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_MockProject.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+define('ETHNA_TEST_DIR', ETHNA_BASE . '/test');
+define('ETHNA_TEST_PROJECT', 'mockproject');
+define('ETHNA_TEST_SKELDIR', ETHNA_TEST_DIR . '/skel/');
+
+/**
+ *  ethna command Emulator Class. 
+ *  
+ *  @access public
+ */
+class Ethna_MockProject
+{
+    var $basedir;
+    var $skel_dir;
+    var $project_name;
+    var $proj_basedir;
+    var $is_created;
+
+    /*
+     *  コンストラクタ
+     * 
+     *  @param $basedir プロジェクトベースディレクトリ  
+     *  @param $project_name プロジェクト名
+     *  @param $skel_dir スケルトンディレクトリ
+     *  @access public
+     */
+    function Ethna_MockProject($basedir = ETHNA_TEST_DIR,
+                               $project_name = ETHNA_TEST_PROJECT,
+                               $skel_dir = ETHNA_TEST_SKELDIR)
+    {
+        $this->basedir = $basedir;
+        $this->skel_dir = $skel_dir;
+        $this->project_name = $project_name;
+        $this->proj_basedir = "${basedir}/${project_name}";
+        $this->is_created = false;
+    }
+
+    /*
+     *  プロジェクトを作成します。
+     *  ethna add-project コマンドをエミュレートします。
+     * 
+     *  @access public
+     *  @return 成功したらtrue, 失敗したらEthna_Error 
+     */
+    function create()
+    {
+        $this->is_created = true;
+
+        if (!is_dir($this->proj_basedir)) {
+            do {
+                sleep(0.1);
+                $r = Ethna_Util::mkdir($this->proj_basedir, 0775);
+            } while ($r == false || is_dir($this->proj_basedir) == false);
+        }
+
+        //  fire ethna add-project command
+        $id = 'add-project';
+        $options = array(
+                       '-b',
+                       $this->basedir,
+                       '-s',
+                       $this->skel_dir, 
+                       $this->project_name,
+                   );
+        $r = $this->runCmd($id, $options); 
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        return true;
+    } 
+
+    /*
+     *  作成したプロジェクトに対してコマンドを
+     *  実行することで、ethna コマンドをエミュレートします。
+     *  (プロジェクトがない場合は作成されます)
+     * 
+     *  @access public
+     *  @param string $id  コマンドID (e.x add-action)
+     *  @param array  $options コマンドラインオプション
+     *                e.x ethna add-action -b /tmp test の場合
+     *                    array('-b', '/tmp', 'test') を指定
+     *  @return 成功したらtrue, 失敗したらEthna_Error 
+     */
+    function runCmd($id, $options = array())
+    {
+        if (($r = $this->create_ifnot_exists()) !== true) {
+            return $r;
+        }
+
+        //   supplement basedir option.
+        $in_basedir_opt = false;
+        foreach ($options as $opt) {
+            if ($opt == '-b' || $opt == '--basedir') {
+                $in_basedir_opt = true;
+            }
+        }
+        if (!$in_basedir_opt) { 
+            $base_opt = array('-b', $this->proj_basedir);
+            $options = array_merge($base_opt, $options);
+        }
+
+        $eh =& new Ethna_Handle();
+        $handler =& $eh->getHandler($id);
+        if (Ethna::isError($handler)) {
+            return $r;
+        }
+
+        ob_start(); //  supress output.
+        $handler->setArgList($options);
+        $r = $handler->perform();
+        ob_end_clean();
+
+        if (Ethna::isError($r)) {
+            return $r;
+        }
+
+        return true;
+    }
+
+    /*
+     *  作成したプロジェクトのコントローラクラス
+     *  のインスタンスを取得します。
+     *  (プロジェクトがない場合は作成されます)
+     * 
+     *  @access public
+     *  @return Ethna_Controller コントローラクラスのインスタンス
+     *          失敗したらEthna_Error 
+     */
+    function &getController()
+    {
+        if (($r = $this->create_ifnot_exists()) !== true) {
+            return $r;
+        }
+        return Ethna_Handle::getAppController($this->proj_basedir);
+    }
+
+    /*
+     *  作成したプロジェクトのベースディレクトリを取得します。
+     *
+     *  @access public 
+     *  @return string  プロジェクトのベースディレクトリ
+     */
+    function getBaseDir()
+    {
+        return $this->proj_basedir;
+    }
+
+    /*
+     *  プロジェクトを削除します。
+     *
+     *  @access public 
+     */
+    function delete()
+    {
+        Ethna_Util::purgeDir($this->proj_basedir);
+    }
+
+    /*
+     *  プロジェクトが既に作成されているかをチェックし,
+     *  存在しない場合は作成します。
+     *
+     *  @access private
+     *  @return boolean  既に作成している場合はtrue.
+     *                   プロジェクトの作成に失敗したらEthna_Error 
+     */
+    function create_ifnot_exists()
+    {
+        if ($this->is_created === false) {
+            $r = $this->create();
+            if (Ethna::isError($r)) {
+                return $r;
+            }
+        }
+        return true;
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_MocktestManager.php b/Idea_Plugin_Extlib/test/Ethna_MocktestManager.php
new file mode 100644 (file)
index 0000000..23eff6c
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ *  Ethna_MocktestManager.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com> 
+ *  @package    Ethna 
+ *  @version    $Id$
+ */
+
+/**
+ *  Ethna_MocktestManager
+ *  アプリケーションマネージャーテスト用のダミークラス
+ */
+class Ethna_MocktestManager extends Ethna_AppManager
+{
+    //  何も定義しない 
+}
+
+/**
+ *  Ethna_Mocktest
+ *  アプリケーションオブジェクトテスト用のダミークラス
+ */
+class Ethna_Mocktest extends Ethna_AppObject
+{
+    /**
+     *  property display name getter.
+     *
+     *  @access public
+     */
+    function getName($key)
+    {
+        return $this->get($key);
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_UnitTestBase.php b/Idea_Plugin_Extlib/test/Ethna_UnitTestBase.php
new file mode 100644 (file)
index 0000000..c4161ee
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ *  Ethna_UnitTestBase.php
+ *
+ *  @package    Ethna
+ *  @author     ICHII Takashi <ichii386@schweetheart.jp>
+ */
+
+/**
+ *  Ethnaのテストケースの基底クラス
+ */
+class Ethna_UnitTestBase extends UnitTestCase
+{
+    /** @var    object  Ethna_Backend       backendオブジェクト */
+    var $backend;
+
+    /** @var    object  Ethna_Controller    コントローラオブジェクト */
+    var $controller;
+
+    /** @var    object  Ethna_Controller    コントローラオブジェクト($controllerの省略形) */
+    var $ctl;
+
+    /** @var    object  Ethna_ActionForm    アクションフォームオブジェクト($action_formの省略形) */
+    var $af;
+
+    /** @var    object  Ethna_ActionError    アクションエラーオブジェクト($action_errorの省略形) */
+    var $ae;
+
+    function Ethna_UnitTestBase($label = false)
+    {
+        parent::UnitTestCase($label);
+
+        // controller
+        $this->ctl =& Ethna_Controller::getInstance();
+        if ($this->ctl === null) {
+            $this->ctl =& new Ethna_Controller();
+        }
+        $this->controller =& $this->ctl;
+
+        // backend
+        $this->backend =& $this->ctl->getBackend();
+
+        // actionform, actionerror.
+        if ($this->ctl->action_form === null) {
+            $this->ctl->action_form =& new Ethna_ActionForm($this->ctl);
+            $this->backend->setActionForm($this->ctl->action_form);
+        }
+        $this->af =& $this->ctl->action_form;
+        $this->ae =& $this->ctl->getActionError();
+
+        // viewclass
+        if ($this->ctl->view === null) {
+            $this->ctl->view =& new Ethna_ViewClass($this->backend, '', '');
+        }
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_UrlHandler_Test.php b/Idea_Plugin_Extlib/test/Ethna_UrlHandler_Test.php
new file mode 100644 (file)
index 0000000..9f7e1fd
--- /dev/null
@@ -0,0 +1,337 @@
+<?php
+/**
+ *  Ethna_UrlHandler_Test.php
+ */
+
+/**
+ *  Ethna_UrlHandlerクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_UrlHandler_Test extends Ethna_UnitTestBase
+{
+    var $url_handler;
+
+    function setUp()
+    {
+        $this->url_handler =& new Ethna_UrlHandler_TestClass($this);
+    }
+
+    // {{{ $_simple_map
+    var $_simple_map = array(
+        'entrypoint' => array(
+            'test_foo_bar' => array(
+                'path'          => 'foo/bar',
+                'path_regexp'   => false,
+                'path_ext'      => false,
+            ),
+        ),
+    );
+    // }}}
+
+    // {{{ $_complex_map
+    var $_complex_map = array(
+        'entrypoint' => array(
+            'test_foo_bar' => array(
+                'path'          => 'foo/bar',
+                'path_regexp'   => array(
+                    'dummy_index1' => '|foo/bar/([^/]*)$|',
+                    'dummy_index2' => '|foo/bar/([^/]*)/([^/]*)$|',
+                ),
+                'path_ext'      => array(
+                    'dummy_index1' => array(
+                        'param1'   => array(),
+                    ),
+                    'dummy_index2' => array(
+                        'param1'   => array(),
+                        'param2'   => array(),
+                    ),
+                ),
+            ),
+        ),
+    );
+    // }}}
+
+    // {{{ $_prefix_suffix_map
+    var $_prefix_suffix_map = array(
+        'entrypoint' => array(
+            'test_foo_bar' => array(
+                'path'          => 'foo/bar',
+                'path_regexp'   => '|foo/bar/(.*)$|',
+                'path_ext'      => array(
+                    'param1' => array(
+                        'url_prefix'    => 'URL-',
+                        'url_suffix'    => '-URL',
+                        'form_prefix'   => 'FORM-',
+                        'form_suffix'   => '-FORM',
+                    ),
+                ),
+            ),
+        ),
+    );
+    // }}}
+
+    // {{{ test_requestToAction_simple
+    function test_requestToAction_simple()
+    {
+        // pathinfo から action 取得
+        $http_vars = array(
+            '__url_handler__'   => 'entrypoint',      // not empty
+            '__url_info__'      => '/foo/bar',  // null or not empty
+            'param3'            => 'value3',
+        );
+
+        $this->url_handler->action_map = $this->_simple_map;
+        $injected = $this->url_handler->requestToAction($http_vars);
+
+        // action を受け取る
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual(count($diff), 1);
+        $this->assertEqual($diff['action_test_foo_bar'], true);
+
+        // action を受け取る以外の変化がないことを確認
+        $diff = array_diff($http_vars, $injected);
+        $this->assertEqual(count($diff), 0);
+
+
+        // action を受け取る
+        $this->url_handler->action_map = $this->_complex_map;
+        $injected = $this->url_handler->requestToAction($http_vars);
+
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual(count($diff), 1);
+        $this->assertEqual($diff['action_test_foo_bar'], true);
+
+    }
+    // }}}
+
+    // {{{ test_requestToAction_nopathinfo
+    function test_requestToAction_nopathinfo()
+    {
+        // pathinfo なし
+        $http_vars = array(
+            '__url_handler__'   => 'entrypoint',
+            '__url_info__'      => null,
+        );
+
+        $this->url_handler->action_map = $this->_complex_map;
+        $injected = $this->url_handler->requestToAction($http_vars);
+
+        // 変化なし
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual(count($diff), 0);
+    }
+    // }}}
+
+    // {{{ test_requestToAction_withparams1
+    function test_requestToAction_withparams1()
+    {
+        // pathinfo から action とパラメータを受け取る
+        $http_vars = array(
+            '__url_handler__'   => 'entrypoint',
+            '__url_info__'      => '/foo/bar/aaa',
+        );
+
+        // 一致する action_map がない: エラーとして array() を返す
+        $this->url_handler->action_map = $this->_simple_map;
+        $injected = $this->url_handler->requestToAction($http_vars);
+        $this->assertEqual(count($injected), 0);
+
+
+        // action とパラメータ param1 を受け取る
+        $this->url_handler->action_map = $this->_complex_map;
+        $injected = $this->url_handler->requestToAction($http_vars);
+
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual(count($diff), 2);
+        $this->assertEqual($diff['action_test_foo_bar'], true);
+        $this->assertEqual($diff['param1'], 'aaa');
+    }
+    // }}}
+
+    // {{{ test_requestToAction_withparams2
+    function test_requestToAction_withparams2()
+    {
+        // pathinfo から action と複数のパラメータを受け取る
+        $http_vars = array(
+            '__url_handler__'   => 'entrypoint',
+            '__url_info__'      => '/foo/bar/aaa/bbb',
+        );
+
+        $this->url_handler->action_map = $this->_complex_map;
+        $injected = $this->url_handler->requestToAction($http_vars);
+
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual(count($diff), 3);
+        $this->assertEqual($diff['action_test_foo_bar'], true);
+        $this->assertEqual($diff['param1'], 'aaa');
+        $this->assertEqual($diff['param2'], 'bbb');
+    }
+    // }}}
+
+    // {{{ test_requestToAction_withparams3
+    function test_requestToAction_withparams3()
+    {
+        // 定義された以上のパラメータがある場合
+        $http_vars = array(
+            '__url_handler__'   => 'entrypoint',
+            '__url_info__'      => '/foo/bar/aaa/bbb/ccc',
+        );
+
+        $this->url_handler->action_map = $this->_complex_map;
+        $injected = $this->url_handler->requestToAction($http_vars);
+        $this->assertEqual(count($injected), 0);
+    }
+    // }}}
+
+    // {{{ test_requestToAction_misc
+    function test_requestToAction_misc()
+    {
+        // 微妙な pathinfo のチェック
+        $http_vars = array(
+            '__url_handler__'   => 'entrypoint',
+        );
+        $this->url_handler->action_map = $this->_complex_map;
+
+        // 余分な slash が前後についている
+        $http_vars['__url_info__'] = '///foo///bar///value1///';
+        $injected = $this->url_handler->requestToAction($http_vars);
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual($diff['action_test_foo_bar'], true);
+        $this->assertEqual($diff['param1'], 'value1');
+        $this->assertFalse(isset($diff['param2']));
+
+        // path が '/./' を含む
+        $http_vars['__url_info__'] = '/foo/bar/./value1';
+        $injected = $this->url_handler->requestToAction($http_vars);
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual($diff['action_test_foo_bar'], true);
+        $this->assertEqual($diff['param1'], '.');
+        $this->assertEqual($diff['param2'], 'value1');
+
+        // path が '/../' を含む
+        $http_vars['__url_info__'] = '/foo/bar/../baz';
+        $injected = $this->url_handler->requestToAction($http_vars);
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual($diff['action_test_foo_bar'], true);
+        $this->assertEqual($diff['param1'], '..');
+        $this->assertEqual($diff['param2'], 'baz');
+
+        // 長いリクエスト
+        $http_vars['__url_info__'] = '/foo/bar/' . str_repeat('a', 10000);
+        $injected = $this->url_handler->requestToAction($http_vars);
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual($diff['action_test_foo_bar'], true);
+        $this->assertTrue(isset($diff['param1']));
+        $this->assertFalse(isset($diff['param2']));
+    }
+    // }}}
+
+    // {{{ test_requestToAction_prefix_suffix
+    function test_requestToAction_prefix_suffix()
+    {
+        $http_vars = array(
+            '__url_handler__'   => 'entrypoint',
+            '__url_info__'      => '/foo/bar/URL-value1-URL',
+            'param3'            => 'value3',
+        );
+
+        $this->url_handler->action_map = $this->_prefix_suffix_map;
+        $injected = $this->url_handler->requestToAction($http_vars);
+        $diff = array_diff($injected, $http_vars);
+        $this->assertEqual($diff['action_test_foo_bar'], true);
+        $this->assertEqual($diff['param1'], 'FORM-value1-FORM');
+    }
+    // }}}
+
+    // {{{ test_actionToRequest_simple
+    function test_actionToRequest_simple()
+    {
+        $action = 'test_foo_bar';
+        $param = array(
+            'hoge' => 'fuga',
+        );
+
+        $this->url_handler->action_map = $this->_simple_map;
+        $ret = $this->url_handler->actionToRequest($action, $param);
+        $this->assertFalse(is_null($ret));
+        list($path, $path_key) = $ret;
+
+        // action "test_foo_bar" に対応するのは "entrypoint" の "/foo/bar"
+        $this->assertEqual($path, 'entrypoint/foo/bar');
+        $this->assertTrue($path_key == array());
+    }
+    // }}}
+
+    // {{{ test_actionToRequest_unknownaction
+    function test_actionToRequest_unknownaction()
+    {
+        $action = 'test_unknown_action';
+        $param = array(
+            'hoge' => 'fuga',
+        );
+
+        $this->url_handler->action_map = $this->_simple_map;
+        $ret = $this->url_handler->actionToRequest($action, $param);
+        $this->assertTrue(is_null($ret));
+    }
+    // }}}
+
+    // {{{ test_actionToRequest_param1
+    function test_actionToRequest_param1()
+    {
+        $action = 'test_foo_bar';
+        $param = array(
+            'hoge' => 'fuga',
+            'param1' => 'value1',
+        );
+
+        $this->url_handler->action_map = $this->_complex_map;
+        list($path, $path_key) = $this->url_handler->actionToRequest($action, $param);
+        $this->assertEqual($path, 'entrypoint/foo/bar/value1');
+        $this->assertTrue($path_key == array('param1'));
+    }
+    // }}}
+
+    // {{{ test_actionToRequest_param2
+    function test_actionToRequest_param2()
+    {
+        $action = 'test_foo_bar';
+        $param = array(
+            'hoge' => 'fuga',
+            'param1' => 'value1',
+            'param2' => 'value2',
+        );
+
+        $this->url_handler->action_map = $this->_complex_map;
+        list($path, $path_key) = $this->url_handler->actionToRequest($action, $param);
+        $this->assertEqual($path, 'entrypoint/foo/bar/value1/value2');
+        $this->assertEqual($path_key, array('param1', 'param2'));
+    }
+    // }}}
+}
+
+class Ethna_UrlHandler_TestClass extends Ethna_UrlHandler
+{
+    function _getPath_Entrypoint($action, $params)
+    {
+        return array('/entrypoint', array());
+    }
+
+    function _normalizerequest_Test($http_vars)
+    {
+        return $http_vars;
+    }
+
+    function _input_filter_Test($input)
+    {
+        return strtolower($input);
+    }
+
+    function _output_filter_Test($output)
+    {
+        return strtoupper($output);
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_Util_Test.php b/Idea_Plugin_Extlib/test/Ethna_Util_Test.php
new file mode 100644 (file)
index 0000000..56802ca
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Util_Test.php
+ */
+
+/**
+ *  Ethna_Utilクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Util_Test extends Ethna_UnitTestBase
+{
+    // {{{  testCheckMailAddress
+    function testCheckMailAddress()
+    {
+        $fail_words = array(
+            'hogehuga.net',
+            'untarakantara',
+            'example@example',
+            'example@.com',
+            'example@example@example.com',
+        );
+
+        foreach ($fail_words as $word) {
+            $this->assertFalse(Ethna_Util::checkMailAddress($word));
+        }
+        
+        $util = new Ethna_Util;
+        $result = $util->checkMailAddress('hogefuga.net');
+        $this->assertFalse($result);
+
+        $result = $util->checkMailAddress('hoge@fuga.net');
+        $this->assertTrue($result);
+    }
+    // }}}
+
+    // {{{  testIsAbsolute
+    function testIsAbsolute()
+    {
+        if (ETHNA_OS_WINDOWS) {
+            $absolute_paths = array(
+                'D:\root',
+                'C:\home\user\giza',
+            );
+        } else {
+            $absolute_paths = array(
+                '/root',
+                '/home/user/giza',
+            );
+        }
+
+        $invalid_params = array(
+            '',
+            false,
+            true,
+            '0x1',
+        );
+
+        foreach ($absolute_paths as $path) {
+            $this->assertTrue(Ethna_Util::isAbsolute($path));
+        }
+        
+        foreach ($invalid_params as $path) {
+            $this->assertFalse(Ethna_Util::isAbsolute($path));
+        }
+    }
+    // }}}
+
+    // {{{  testIsRootDir
+    function testIsRootDir()
+    {
+        $this->assertTrue(DIRECTORY_SEPARATOR);
+
+        $util = new Ethna_Util;
+        if (ETHNA_OS_WINDOWS) {
+            $this->assertTrue($util->isRootDir("C:\\"));
+            $this->assertFalse($util->isRootDir("C:\\Program Files\\hoge\\fuga.txt"));
+            $this->assertFalse($util->isRootDir("C:\\Program Files\\hoge"));
+            $this->assertFalse($util->isRootDir("C:\\hoge\\"));
+            $this->assertFalse($util->isRootDir("C:\\hoge.txt"));
+        } else {
+            $this->assertFalse($util->isRootDir("/home/ethna/test.txt"));
+            $this->assertFalse($util->isRootDir("/home/ethna/"));
+            $this->assertFalse($util->isRootDir("/home/ethna"));
+            $this->assertFalse($util->isRootDir("/test.txt"));
+        }
+    }
+    // }}}
+
+    // {{{  testGetRandom
+    function testGetRandom()
+    {
+        //    いかなる状態であっても
+        //    値が得られなければならない
+        $r = Ethna_Util::getRandom();
+        $this->assertNotNULL($r);
+        $this->assertEqual(64, strlen($r));
+    }
+    // }}}
+
+    // {{{ testGetEra
+    function testGetEra()
+    {
+        unset($GLOBALS['_Ethna_controller']);
+        $tmp_ctl =& new Ethna_Controller();
+        
+        //  昭和63年
+        $last_showa_t = mktime(0,0,0,12,31,1988);
+        $r = Ethna_Util::getEra($last_showa_t);
+        $this->assertEqual('昭和', $r[0]);
+        $this->assertEqual(63, $r[1]);
+
+        //  平成元年
+        $first_heisei_t = mktime(0,0,0,1,1,1989);
+        $r = Ethna_Util::getEra($first_heisei_t);
+        $this->assertEqual('平成', $r[0]);
+        $this->assertEqual(1, $r[1]);
+    }
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Ethna_ViewClass_Test.php b/Idea_Plugin_Extlib/test/Ethna_ViewClass_Test.php
new file mode 100644 (file)
index 0000000..18a932d
--- /dev/null
@@ -0,0 +1,283 @@
+<?php
+/**
+ *  Ethna_ViewClass_Test.php
+ *
+ *  @package Ethna
+ *  @author halt feits <halt.feits@gmail.com>
+ */
+
+//error_reporting(E_ALL);
+
+/**
+ *  Ethna_ViewClassクラスのテストケース
+ *
+ *  @package Ethna
+ *  @author halt feits <halt.feits@gmail.com>
+ *  @access public
+ */
+class Ethna_ViewClass_Test extends Ethna_UnitTestBase
+{
+    /**
+     * Ethna_ViewClass
+     * @var     Ethna_ViewClass
+     * @access  protected
+     */
+    var $viewclass;
+
+    /**
+     * setUp
+     *
+     * make Ethna_ViewClass class
+     *
+     * @access public
+     * @param void
+     */
+    function setUp()
+    {
+        $this->viewclass =& $this->ctl->getView();
+    }
+
+    /**
+     * test_getFormInput_Html
+     *
+     * @access public
+     * @param void
+     */
+    function test_getFormInput_Html()
+    {
+        $actual = '<input type="text" name="test" value="&lt;&amp;&gt;" />';
+
+        $test_attr = array(
+            'type' => 'text',
+            'name' => 'test',
+            'value' => '<&>',
+        );
+
+        $result = $this->viewclass->_getFormInput_Html('input', $test_attr);
+
+        $this->assertEqual($result, $actual);
+    }
+
+    function test_getFormInput_Text()
+    {
+        $name = "test_text";
+        $def = array(
+            'max' => 20,
+        );
+        $params = array();
+
+        $test_form = array(
+            'test_text' => array(
+                'name' => 'TestTestText',
+                'form_type' => FORM_TYPE_TEXT,
+                'type' => VAR_TYPE_STRING,
+            ),                    
+        );
+
+        $this->viewclass->af->setDef(null, $test_form);
+
+        $result = $this->viewclass->_getFormInput_Text($name, $def, $params);
+    }
+
+    function test_getFormInput_Textarea()
+    {
+        $name = "content";
+        $params = array();
+
+        $test_form = array(
+            $name => array(
+                'name' => 'TestTestText',
+                'form_type' => FORM_TYPE_TEXTAREA,
+                'type' => VAR_TYPE_STRING,
+                'required' => true,
+            ),                    
+        );
+
+        $this->viewclass->af->setDef(null, $test_form);
+
+        $result = $this->viewclass->getFormInput($name, null, $params);
+
+        $this->assertTrue(strpos($result, '</textarea>'), "can't find textarea endtag [{$result}]");
+    }
+
+    function test_getFormInput_Select()
+    {
+        $name = "select";
+        $params = array();
+
+        $test_form = array(
+            'select' => array(
+                'name' => 'TestTestText',
+                'form_type' => FORM_TYPE_SELECT,
+                'type' => VAR_TYPE_STRING,
+                'option' => array('a', 'b', 'c'),
+            ),
+        );
+
+        $this->viewclass->af->setDef($name, $test_form);
+
+        $result = $this->viewclass->getFormInput($name, null, $params);
+
+        $this->assertTrue(!empty($result), "selectbox make error");
+    }
+
+    function test_getFormInput_Checkbox()
+    {
+        $this->assertTrue(defined('FORM_TYPE_CHECKBOX'), 'undefined FORM_TYPE_CHECKBOX');
+
+        $name = "check";
+        $params = array();
+
+        $test_form = array(
+            $name => array(
+                'name' => 'TestTestText',
+                'form_type' => FORM_TYPE_CHECKBOX,
+                'type' => array(VAR_TYPE_INT),
+                'option' => array('a', 'b', 'c'),
+            ),
+        );
+
+        $this->viewclass->af->setDef($name, $test_form);
+
+        $result = $this->viewclass->getFormInput($name, null, $params);
+
+        $this->assertTrue(!empty($result), "checkbox make error");
+    }
+
+    function test_getFormInput_Submit()
+    {
+        $this->assertTrue(defined('FORM_TYPE_SUBMIT'), 'undefined FORM_TYPE_SUBMIT');
+
+        $name = "post";
+        $params = array();
+
+        $test_form = array(
+            $name => array(
+                'name' => 'Preview',
+                'form_type' => FORM_TYPE_SUBMIT,
+                'type' => VAR_TYPE_STRING,
+            ),
+        );
+
+        $this->viewclass->af->setDef(null, $test_form);
+
+        $result = $this->viewclass->getFormInput($name, null, $params);
+
+        $this->assertTrue(!empty($result), "submit make error");
+        $this->assertFalse(strpos($result, 'default '), "invalid attribute");
+    }
+
+    function testGetFormName()
+    {
+        $test_word = "TestTestTest";
+
+        $test_form = array(
+            'test_text' => array(
+                'name' => 'TestTestTest',
+                'form_type' => FORM_TYPE_TEXT,
+                'type' => VAR_TYPE_STRING
+            ),
+        );
+        $params = array();
+
+        $this->viewclass->af->form = $test_form;
+
+        $result = $this->viewclass->getFormName('test_text', null, $params);
+        $this->assertEqual($result, $test_word);
+    }
+
+    function test_getFormInput_Button()
+    {
+        $name = 'btn';
+        $def = array(
+            'form_type' => FORM_TYPE_BUTTON,
+            );
+        $params = array();
+
+        // valueなし
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('btn', null, $params);
+        $this->assertTrue(strpos($result, 'value') === false);
+
+        // defaultは指定しても無意味
+        $params['default'] = 'hoge';
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('btn', null, $params);
+        $this->assertTrue(strpos($result, 'value') === false);
+
+        // valueを指定
+        $params['value'] = 'fuga';
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('btn', null, $params);
+        $this->assertTrue(strpos($result, 'value="fuga"'));
+    }
+
+    function test_getFormInput_Checkbox2()
+    {
+        $name = 'chkbx';
+        $def = array(
+            'form_type' => FORM_TYPE_CHECKBOX,
+            'type' => array(VAR_TYPE_STRING),
+            'option' => array(1=>1, 2=>2),
+            'default' => 2,
+            );
+        $params = array('separator' => "\n");
+
+        $expected =<<<EOS
+<label for="chkbx_1"><input type="checkbox" name="chkbx[]" value="1" id="chkbx_1" />1</label>
+<label for="chkbx_2"><input type="checkbox" name="chkbx[]" value="2" id="chkbx_2" checked="checked" />2</label>
+EOS;
+
+        // def の default 指定で int(2) に check
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('chkbx', null, $params);
+        $this->assertEqual($result, $expected);
+
+        // params の default 指定で int(2) に check
+        $def['default'] = 1;
+        $params['default'] = 2; // paramsが優先
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('chkbx', null, $params);
+        $this->assertEqual($result, $expected);
+
+        // params の default 指定で string(1) "2" に check
+        $params['default'] = '2';
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('chkbx', null, $params);
+        $this->assertEqual($result, $expected);
+    }
+
+    function test_default_value()
+    {
+        $name = 'testform';
+        $def = array();
+        $params = array();
+
+        // defaultもvalueもないとき
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('testform', null, $params);
+        $this->assertTrue(strpos($result, 'value=""'));
+
+        // defaultがあるとき
+        $params['default'] = 'hoge';
+        unset($params['value']);
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('testform', null, $params);
+        $this->assertTrue(strpos($result, 'value="hoge"'));
+
+        // valueがあるとき
+        unset($params['default']);
+        $params['value'] = 'fuga';
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('testform', null, $params);
+        $this->assertTrue(strpos($result, 'value="fuga"'));
+
+        // default, value両方があるとき: valueが優先
+        $params['default'] = 'hogefuga';
+        $params['value'] = 'foobar';
+        $this->viewclass->af->setDef($name, $def);
+        $result = $this->viewclass->getFormInput('testform', null, $params);
+        $this->assertTrue(strpos($result, 'value="foobar"'));
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Cachemanager/Ethna_Plugin_Cachemanager_Localfile_Test.php b/Idea_Plugin_Extlib/test/Plugin/Cachemanager/Ethna_Plugin_Cachemanager_Localfile_Test.php
new file mode 100644 (file)
index 0000000..546731f
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ *  Ethna_Plugin_Cachemanager_Localfile_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Cachemanager_Localfileクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Cachemanager_Localfile_Test extends Ethna_UnitTestBase
+{
+    function rm($path){
+        if (is_dir($path)) {
+            if ($handle = opendir($path)) {
+                while (false !== ($file = readdir($handle))) {
+                    if ($file != "." && $file != "..") {
+                        $this->rm("$path/$file");
+                    }
+                }
+                closedir($handle);
+            }
+            if (!rmdir($path)) {
+                printf("fail rmdir[$path]\n");
+            }
+        } else {
+            if (!unlink($path)) {
+                printf("fail unlink[$path]\n");
+            }
+        }
+    }
+
+    function testCachemanagerLocalfile()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $cm = $plugin->getPlugin('Cachemanager', 'Localfile');
+
+        // 文字列のキャッシュ
+        $string_key = 'string_key';
+        $string_value = "cache\ncontent";
+        $cm->set($string_key, $string_value, mktime(0, 0, 0, 7, 1, 2000));
+        $cache_string = $cm->get($string_key);
+        $this->assertTrue($cm->isCached($string_key));
+        $this->assertEqual(mktime(0, 0, 0, 7, 1, 2000), $cm->getLastModified($string_key));
+        $this->assertTrue($string_value, $cache_string);
+
+        // 整数のキャッシュ + namespace
+        $int_key = 'int_key';
+        $int_value = 777;
+        $namespace = 'test';
+        $cm->set($int_key, $int_value, mktime(0, 0, 0, 7, 1, 2000), $namespace);
+        $cache_int = $cm->get($int_key, mktime(0, 0, 0, 7, 1, 2000), $namespace);
+        $this->assertTrue($cm->isCached($int_key, mktime(0, 0, 0, 7, 1, 2000), $namespace));
+        $this->assertTrue($int_value, $cache_int);
+
+        // オブジェクトのキャッシュ
+        $object_key = 'object_key';
+        $object_value =& $cm;
+        $cm->set($object_key, $object_value);
+        $this->assertTrue($cm->isCached($object_key));
+        // キャッシュされたインスタンス
+        $cache_object = $cm->get($object_key);
+        $this->assertTrue($string_value, $cache_object->get($string_key));
+
+        // キャッシュのクリアをテスト
+        $cm->clear($object_key);
+        $this->assertFalse($cm->isCached($object_key));
+
+        // キャッシュされていないのに呼び出そうとした場合
+        $nocache_key = 'nocache_key';
+        $cm->clear($nocache_key);
+        $pear_error = $cm->get($nocache_key);
+        $this->assertEqual(E_CACHE_NO_VALUE, $pear_error->getCode());
+        $this->assertEqual('fopen failed', $pear_error->getMessage());
+
+        // ファイルに読み込み権限がない場合
+        // PHP 4, PHP5 ともに、Windows上ではmodeをどのように設定しても
+        // read権限が残るためskip.(PHP 4.4.8, 5.2.6 on Windows XP)
+        if (!ETHNA_OS_WINDOWS) {
+            Ethna_Util::chmod($cm->_getCacheFile(null, $string_key), 0222);
+            $pear_error = $cm->get($string_key);
+            $this->assertEqual(E_CACHE_NO_VALUE, $pear_error->getCode());
+            $this->assertEqual('fopen failed', $pear_error->getMessage());
+            Ethna_Util::chmod($cm->_getCacheFile(null, $string_key), 0666);
+        }
+
+        // lifetime切れの場合
+        $pear_error = $cm->get($string_key, 1);
+        $this->assertEqual(E_CACHE_EXPIRED, $pear_error->getCode());
+        $this->assertEqual('fopen failed', $pear_error->getMessage());
+
+        // ディレクトリ名と同じファイルがあってディレクトリが作成できない場合
+        $tmp_key = 'tmpkey';
+        $tmp_dirname = $cm->_getCacheDir(null, $tmp_key);
+        Ethna_Util::mkdir(dirname($tmp_dirname), 0777);
+        $tmp_file = fopen($tmp_dirname, 'w');
+        fclose($tmp_file);
+        $pear_error = $cm->set($tmp_key, $string_value);
+        $this->assertEqual(E_USER_WARNING, $pear_error->getCode());
+        $this->assertEqual("mkdir($tmp_dirname) failed", $pear_error->getMessage());
+
+        $this->rm($cm->backend->getTmpdir());
+
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Csrf/Ethna_Plugin_Csrf_Session_Test.php b/Idea_Plugin_Extlib/test/Plugin/Csrf/Ethna_Plugin_Csrf_Session_Test.php
new file mode 100755 (executable)
index 0000000..82491c8
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+/**
+ *  Ethna_Plugin_Validator_Required_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Requiredクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Csrf_Session_Test extends Ethna_UnitTestBase
+{
+    /**
+     * Description of the Variable
+     * @var     Ethna_Plugin_Csrf_Session
+     * @access  private
+     */
+    var $csrf;
+
+    function setUp()
+    {
+    }
+
+    function tearDown()
+    {
+        $_SERVER['REQUEST_METHOD'] = NULL;
+    }
+
+    function testMakeInstance()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->csrf =& $plugin->getPlugin('Csrf', 'Session');
+        $this->assertTrue(is_object($this->csrf), 'getPlugin failed');
+        $this->csrf->session =& new Ethna_Session_Dummy($ctl->appid, '',  $ctl->getLogger());
+    }
+
+    function testGetName()
+    {
+        $this->assertTrue(strlen($this->csrf->getName()), 'token name not found');
+    }
+
+    function testCheckCsrfSession()
+    {
+        $this->assertTrue($this->csrf->set());
+        $this->csrfid = $this->csrf->get();
+    }
+
+    function testPostRequest()
+    {
+        $_SERVER['REQUEST_METHOD'] = "post";
+        $_POST[$this->csrf->getName()] = "";
+        $this->assertFalse($this->csrf->isValid());
+
+        $_POST[$this->csrf->getName()] = $this->csrfid;
+        $this->assertTrue($this->csrf->isValid());
+    }
+
+    function testGetRequest()
+    {
+        $_SERVER['REQUEST_METHOD'] = "get";
+        $_GET[$this->csrf->getName()] = "";
+        $this->assertFalse($this->csrf->isValid());
+
+        $_GET[$this->csrf->getName()] = $this->csrfid;
+        $this->assertTrue($this->csrf->isValid());
+    }
+
+}
+
+/**
+ *  SessionClassの_Dummy
+ *
+ *  @access public
+ */
+// {{{ Ethna_Session
+/**
+ *  セッションクラスのダミー
+ *
+ *  @author     Keita Arai <cocoiti@comio.info>
+ *  @access     public
+ *  @package    Ethna
+ */
+class Ethna_Session_Dummy extends Ethna_Session
+{
+    var $dummy_session = array();
+
+
+    /**
+     *  セッションを復帰する
+     *
+     *  @access public
+     */
+    function restore()
+    {
+        $this->session_start = true;
+        return true;
+    }
+
+    /**
+     *  セッションの正当性チェック
+     *
+     *  @access public
+     *  @return bool    true:正当なセッション false:不当なセッション
+     */
+    function isValid()
+    {
+        return true;
+    }
+
+    /**
+     *  セッションを開始する
+     *
+     *  @access public
+     *  @param  int     $lifetime   セッション有効期間(秒単位, 0ならセッションクッキー)
+     *  @return bool    true:正常終了 false:エラー
+     */
+    function start($lifetime = 0, $anonymous = false)
+    {
+        $_SESSION['REMOTE_ADDR'] = "DUMMY";
+        $_SESSION['__anonymous__'] = $anonymous;
+        $this->session_start = true;
+        return true;
+    }
+
+    /**
+     *  セッションを破棄する
+     *
+     *  @access public
+     *  @return bool    true:正常終了 false:エラー
+     */
+    function destroy()
+    {
+        return true;
+    }
+
+    /**
+     *  セッション値へのアクセサ(R)
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @return mixed   取得した値(null:セッションが開始されていない)
+     */
+    function get($name)
+    {
+        if (!isset($this->dummy_session[$name])) {
+            return null;
+        }
+        return $this->dummy_session[$name];
+    }
+
+    /**
+     *  セッション値へのアクセサ(W)
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @param  string  $value  値
+     *  @return bool    true:正常終了 false:エラー(セッションが開始されていない)
+     */
+    function set($name, $value)
+    {
+        if (!$this->session_start) {
+            // no way
+            return false;
+        }
+
+        $this->dummy_session[$name] = $value;
+
+        return true;
+    }
+
+    /**
+     *  セッションの値を破棄する
+     *
+     *  @access public
+     *  @param  string  $name   キー
+     *  @return bool    true:正常終了 false:エラー(セッションが開始されていない)
+     */
+    function remove($name)
+    {
+        if (!$this->session_start) {
+            return false;
+        }
+
+        unset($this->dummy_session[$name]);
+
+        return true;
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Handle/Ethna_Plugin_Handle_AddTemplate_Test.php b/Idea_Plugin_Extlib/test/Plugin/Handle/Ethna_Plugin_Handle_AddTemplate_Test.php
new file mode 100644 (file)
index 0000000..e0eabd7
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_AddTemplate_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/test/Ethna_MockProject.php';
+
+//{{{  Ethna_Plugin_Handle_AddTemplate_Test
+/**
+ *  Test Case For Ethna_Plugin_Handle_AddTemplate_Test
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Handle_AddTemplate_Test extends Ethna_UnitTestBase 
+{
+    var $proj;
+
+    function setUp()
+    {
+        $this->proj = new Ethna_MockProject();
+        $r = $this->proj->create();
+        if (Ethna::isError($r)) {
+            $this->fail($r->getMessage());    
+        }
+    }
+
+    function tearDown()
+    {
+        $this->proj->delete();
+    }
+
+    function test_template_dir_exists()
+    {
+        $ctl =& $this->proj->getController(); 
+
+        //    default locale 
+        $r = $this->proj->runCmd('add-template', array('test'));
+        $template_dir = $ctl->getTemplatedir();
+        $this->assertTrue(file_exists($template_dir));
+
+        //    new locale 
+        $r = $this->proj->runCmd('add-template', array('-l', 'en_US', 'test'));
+        $template_dir = $ctl->getTemplatedir();
+        $this->assertTrue(file_exists($template_dir));
+    }
+
+    function test_cmd_option()
+    {
+        //    unrecognized option
+        $r = $this->proj->runCmd('add-template', array('-k'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option -k', $r->getMessage());
+
+        //    skel requires an argument
+        $r = $this->proj->runCmd('add-template', array('-s'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option -s requires an argument', $r->getMessage());
+
+        $r = $this->proj->runCmd('add-template', array('--skelfile'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option --skelfile requires an argument', $r->getMessage());
+
+        //    locale requires an argument
+        $r = $this->proj->runCmd('add-template', array('-l'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option -l requires an argument', $r->getMessage());
+
+        $r = $this->proj->runCmd('add-template', array('--locale'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option --locale requires an argument', $r->getMessage());
+
+        //    template name isn't set
+        $r = $this->proj->runCmd('add-template', array());
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('template name isn\'t set.', $r->getMessage());
+
+        //    invalid locale
+        $r = $this->proj->runCmd('add-template', array('-l', 'invalid::locale', 'test'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('You specified locale, but invalid : invalid::locale', $r->getMessage());
+
+        //    normal command exexute
+        $r = $this->proj->runCmd('add-template', array('-l', 'ja_JP', 'test'));
+        $this->assertFalse(Ethna::isError($r));
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Handle/Ethna_Plugin_Handle_I18n_Test.php b/Idea_Plugin_Extlib/test/Plugin/Handle/Ethna_Plugin_Handle_I18n_Test.php
new file mode 100644 (file)
index 0000000..96d1bb8
--- /dev/null
@@ -0,0 +1,179 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_I18n_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/test/Ethna_MockProject.php';
+
+//{{{  Ethna_Plugin_Handle_I18n_Test
+/**
+ *  Test Case For Ethna_Plugin_Handle_I18n_Test 
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Handle_I18n_Test extends Ethna_UnitTestBase 
+{
+    var $proj;
+    var $mock_ctl;
+    var $msg_file;
+    var $i18n;
+
+    function setUp()
+    {
+        $this->proj = new Ethna_MockProject();
+        $r = $this->proj->create();
+        if (Ethna::isError($r)) {
+            $this->fail($r->getMessage());    
+        }
+        $this->mock_ctl =& $this->proj->getController();
+        $locale_dir = $this->mock_ctl->getDirectory('locale');
+        $locale = $this->mock_ctl->getLocale();
+        $this->msg_file = $locale_dir . '/'
+                        . $locale . '/'
+                        . 'LC_MESSAGES/'
+                        . "$locale.ini";
+        $this->i18n = $this->mock_ctl->getI18N();
+    }
+
+    function tearDown()
+    {
+        $this->proj->delete();
+    }
+
+    function test_Action()
+    {
+        $skel = ETHNA_TEST_SKELDIR . 'skel.action.i18ntest.php';   
+        $r = $this->proj->runCmd('add-action',
+                          array(
+                              '-s', $skel,
+                              'i18n', 
+                          ) 
+             );
+        if (Ethna::isError($r)) {
+            $this->fail($r->getMessage());
+        }
+        $this->run_i18n_cmd();
+        $catalog = $this->i18n->parseEthnaMsgCatalog($this->msg_file);
+
+        //  assert ActionForm definition.
+        $this->assertTrue(isset($catalog['name_i18n']));
+        $this->assertTrue(isset($catalog['required_error_i18n']));
+        $this->assertTrue(isset($catalog['type_error_i18n']));
+        $this->assertTrue(isset($catalog['min_error_i18n']));
+        $this->assertTrue(isset($catalog['max_error_i18n']));
+        $this->assertTrue(isset($catalog['regexp_error_i18n']));
+        $this->assertTrue(isset($catalog['name_i18n_all']));
+        $this->assertTrue(isset($catalog['required_error_i18n_all']));
+        $this->assertTrue(isset($catalog['type_error_i18n_all']));
+        $this->assertTrue(isset($catalog['min_error_i18n_all']));
+        $this->assertTrue(isset($catalog['max_error_i18n_all']));
+        $this->assertTrue(isset($catalog['regexp_error_i18n_all']));
+        
+        $this->assertTrue(isset($catalog['actionform filter']));
+
+        //  assert Action
+        $this->assertTrue(isset($catalog['action prepare']));
+        $this->assertTrue(isset($catalog["action\nprepare\n multiple\n  line"]));
+        
+        $this->assertTrue(isset($catalog['action perform']));
+    } 
+
+    function test_View()
+    {
+        $skel = ETHNA_TEST_SKELDIR . 'skel.view.i18ntest.php';   
+        $r = $this->proj->runCmd('add-view',
+                          array(
+                              '-s', $skel,
+                              'i18n', 
+                          ) 
+             );
+        if (Ethna::isError($r)) {
+            $this->fail($r->getMessage());
+        }
+        $this->run_i18n_cmd();
+        $catalog = $this->i18n->parseEthnaMsgCatalog($this->msg_file);
+
+        //  assert view 
+        $this->assertTrue(isset($catalog['view global']));
+        $this->assertTrue(isset($catalog['view prepare']));
+        $this->assertTrue(isset($catalog["view\n\n   prepare\n multiple\n  line"]));
+    } 
+
+    function test_Template()
+    {
+        $skel = ETHNA_TEST_SKELDIR . 'skel.template.i18ntest.tpl';   
+        $r = $this->proj->runCmd('add-template',
+                          array(
+                              '-s', $skel,
+                              'i18n', 
+                          ) 
+             );
+        if (Ethna::isError($r)) {
+            $this->fail($r->getMessage());
+        }
+        $this->run_i18n_cmd();
+        $catalog = $this->i18n->parseEthnaMsgCatalog($this->msg_file);
+
+        //  assert template 
+        $this->assertTrue(isset($catalog['template i18n']));
+        $this->assertTrue(isset($catalog['template i18n modifier']));
+        $this->assertTrue(isset($catalog['template i18n multiple modifier']));
+    } 
+
+    function test_cmd_option()
+    {
+        //    unrecognized option
+        $r = $this->proj->runCmd('i18n', array('-k'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option -k', $r->getMessage());
+
+        //    --locale(requires an argument)
+        $r = $this->proj->runCmd('i18n', array('-l'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option -l requires an argument', $r->getMessage());
+
+        $r = $this->proj->runCmd('i18n', array('-l', 'ko_KR'));
+        $this->assertFalse(Ethna::isError($r));
+        
+        $r = $this->proj->runCmd('i18n', array('--locale'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option --locale requires an argument', $r->getMessage());
+
+        //    --gettext option only
+        $r = $this->proj->runCmd('i18n', array('-g'));
+        $this->assertFalse(Ethna::isError($r));
+
+        $r = $this->proj->runCmd('i18n', array('--gettext'));
+        $this->assertFalse(Ethna::isError($r));
+
+        //    --gettext not allowed an argument 
+        $r = $this->proj->runCmd('i18n', array('--gettext=foo'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual("option --gettext doesn't allow an argument", $r->getMessage());
+
+        //    --locale and --gettext mixin
+        $r = $this->proj->runCmd('i18n', array('-g', '-l', 'ko_KR'));
+        $this->assertFalse(Ethna::isError($r));
+
+        $r = $this->proj->runCmd('i18n', array('--gettext', '--locale=ko_KR'));
+        $this->assertFalse(Ethna::isError($r));
+    }
+   
+    function run_i18n_cmd()
+    {
+        $r = $this->proj->runCmd('i18n');
+        if (Ethna::isError($r)) {
+            $this->fail($r->getMessage());
+            return;
+        }
+    } 
+
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Handle/Ethna_Plugin_Handle_PearLocal_Test.php b/Idea_Plugin_Extlib/test/Plugin/Handle/Ethna_Plugin_Handle_PearLocal_Test.php
new file mode 100644 (file)
index 0000000..29e682c
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Handle_PearLocal_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/test/Ethna_MockProject.php';
+
+//{{{  Ethna_Plugin_Handle_PearLocal_Test
+/**
+ *  Test Case For Ethna_Plugin_Handle_PearLocal_Test
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Handle_PearLocal_Test extends Ethna_UnitTestBase 
+{
+    var $proj;
+
+    function setUp()
+    {
+        $this->proj = new Ethna_MockProject();
+        $r = $this->proj->create();
+        if (Ethna::isError($r)) {
+            $this->fail($r->getMessage());    
+        }
+    }
+
+    function tearDown()
+    {
+        $this->proj->delete();
+    }
+
+    function test_cmd_option()
+    {
+        //    unrecognized option
+        $r = $this->proj->runCmd('pear-local', array('-k'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option -k', $r->getMessage());
+
+        //    pear list -a(get no error)
+        //    @see http://sourceforge.jp/ticket/browse.php?group_id=1343&tid=15760
+        $r = $this->proj->runCmd('pear-local', array('list', '-a'));
+        $this->assertFalse(Ethna::isError($r));
+
+        //    channel requires an argument
+        $r = $this->proj->runCmd('pear-local', array('-c'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option -c requires an argument', $r->getMessage());
+
+        $r = $this->proj->runCmd('pear-local', array('--channel'));
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option --channel requires an argument', $r->getMessage());
+
+        //    normal command exexute(offline only)
+        $r = $this->proj->runCmd('pear-local', array('config-set', 'default_channel', 'pear.php.net'));
+        $this->assertFalse(Ethna::isError($r));
+    }
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Logwriter/Ethna_Plugin_Logwriter_Echo_Test.php b/Idea_Plugin_Extlib/test/Plugin/Logwriter/Ethna_Plugin_Logwriter_Echo_Test.php
new file mode 100644 (file)
index 0000000..042c43d
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ *  Ethna_Plugin_Logwriter_Echo_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Logwriter_Echoクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Logwriter_Echo_Test extends Ethna_UnitTestBase
+{
+    var $plugin;
+    var $lw;
+
+    function setUp()
+    {
+        $this->plugin =& $this->ctl->getPlugin();
+        $this->lw = $this->plugin->getPlugin('Logwriter', 'Echo');
+
+        $option = array(
+                        'ident' => 'testident',
+                        'facility' => 'mail',
+                        );
+        $this->lw->setOption($option);
+    }
+
+    function testLogwriterEcho()
+    {
+        $message = 'comment';
+
+        $level_array = array(LOG_EMERG,
+            LOG_ALERT,
+            LOG_CRIT,
+            LOG_ERR,
+            LOG_WARNING,
+            LOG_NOTICE,
+            LOG_INFO,
+            LOG_DEBUG,);
+
+        foreach ($level_array as $level) {
+            ob_start();         // コンソールへの出力をキャプチャ開始
+
+            // 関数が返す文字列に改行タグ付与の是非
+            $funcout = $this->lw->log($level, $message)
+                . sprintf("%s", $this->ctl->getGateway() != GATEWAY_WWW ? "" : "<br />");
+
+            $stdout = trim(ob_get_contents());
+            $this->assertEqual($funcout, $stdout);
+
+            ob_end_clean();     // コンソールへの出力をキャプチャ終了
+        }
+    }
+
+    /**
+     * testBug9009
+     *
+     * @see http://sourceforge.jp/tracker/index.php?func=detail&aid=9009&group_id=1343&atid=5092
+     */
+    function testBug9009()
+    {
+        $level = LOG_WARNING;
+        $message = "SELECT * FROM item WHERE name LIKE '%salt%';";
+
+        ob_start();         // コンソールへの出力をキャプチャ開始
+
+        // 関数が返す文字列に改行タグ付与の是非
+        $funcout = $this->lw->log($level, $message)
+            . sprintf("%s", $this->ctl->getGateway() != GATEWAY_WWW ? "" : "<br />");
+
+        $stdout = trim(ob_get_contents());
+        $this->assertEqual($funcout, $stdout);
+
+        ob_end_clean();     // コンソールへの出力をキャプチャ終了
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Logwriter/Ethna_Plugin_Logwriter_File_Test.php b/Idea_Plugin_Extlib/test/Plugin/Logwriter/Ethna_Plugin_Logwriter_File_Test.php
new file mode 100644 (file)
index 0000000..89b7a1b
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ *  Ethna_Plugin_Logwriter_File_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Logwriter_Fileクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Logwriter_File_Test extends Ethna_UnitTestBase
+{
+    function testLogwriterFile()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $lw = $plugin->getPlugin('Logwriter', 'File');
+
+        $option = array(
+                        'ident'    => 'hoge',
+                        'facility' => 'mail',
+                        'file'     => 'logfile',
+                        'mode'     => '0666',
+                        );
+        $lw->setOption($option);
+
+        $level = LOG_WARNING;
+        $message = 'comment';
+        $lw->begin();
+        $_before_size = filesize($option['file']);
+        $this->assertTrue(file_exists($option['file']));
+        $lw->log($level, $message);
+        $lw->end();
+        clearstatcache();
+        $_after_size = filesize($option['file']);
+        // ログを出力したファイルのサイズが大きくなったことを確認
+        $this->assertTrue($_before_size < $_after_size);
+
+        $file = file($option['file']);
+        $line_count = count($file); // 最後に追記した行番号
+        // 年月日時分の一致、秒のフォーマット、ID・ログレベル・メッセージの一致を確認
+        $this->assertTrue(preg_match('/^'.preg_quote(strftime('%Y/%m/%d %H:%M:'), '/')
+                            .'[0-5][0-9] '
+                            .preg_quote($option['ident'].'('
+                                        .$lw->_getLogLevelName($level).'): '
+                                        .$message)
+                            .'/', trim($file[$line_count - 1])));
+
+
+        $option = array(
+                        'pid'      => true,
+                        'ident'    => 'hoge',
+                        'facility' => 'mail',
+                        'file'     => 'logfile',
+                        'mode'     => '0666',
+                        );
+        $lw->setOption($option);
+
+        $level = LOG_WARNING;
+        $message = 'comment';
+        $lw->begin();
+        $_before_size = filesize($option['file']);
+        $this->assertTrue(file_exists($option['file']));
+        $lw->log($level, $message);
+        $lw->end();
+        clearstatcache();
+        $_after_size = filesize($option['file']);
+        // ログを出力したファイルのサイズが大きくなったことを確認
+        $this->assertTrue($_before_size < $_after_size);
+
+        $file = file($option['file']);
+        $line_count = count($file); // 最後に追記した行番号
+        // 年月日時分の一致、秒のフォーマット、ID・PID・ログレベル・メッセージの一致を確認
+        $this->assertTrue(preg_match('/^'.preg_quote(strftime('%Y/%m/%d %H:%M:'), '/')
+                            .'[0-5][0-9] '
+                            .preg_quote($option['ident'].'['.getmypid().']('
+                                        .$lw->_getLogLevelName($level).'): '
+                                        .$message)
+                            .'/', trim($file[$line_count - 1])));
+
+        unlink($option['file']);
+
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_function_select_Test.php b/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_function_select_Test.php
new file mode 100644 (file)
index 0000000..f1bf849
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Smarty_function_select_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Smarty/function.select.php';
+
+//{{{    Ethna_Plugin_Smarty_function_select_Test
+/**
+ *  Test Case For function.select.php
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Smarty_function_select_Test extends Ethna_UnitTestBase
+{
+    // {{{ test_smarty_function_select
+    function test_smarty_function_select()
+    {
+        $params = array('list'  => array(
+                                       '1' => array('name' => 'foo'),
+                                       'value' => array('name' => 'bar'),
+                                   ),
+                        'name'  => 'name',
+                        'value' => 'value',
+                        'empty' => false,
+                  );
+        $dummy_smarty = null;
+        $expected = "<select name=\"name\">\n"
+                  . "<option value=\"1\" >foo</option>\n"
+                  . "<option value=\"value\" selected=\"selected\">bar</option>\n"
+                  . "</select>\n";
+
+        ob_start();
+        smarty_function_select($params, $dummy_smarty);
+        $actual = ob_get_clean();
+        $this->assertEqual($expected, $actual); 
+
+        $params['empty'] = '-- please select --';
+        $expected = "<select name=\"name\">\n"
+                  . "<option value=\"\">-- please select --</option>\n"
+                  . "<option value=\"1\" >foo</option>\n"
+                  . "<option value=\"value\" selected=\"selected\">bar</option>\n"
+                  . "</select>\n";
+        ob_start();
+        smarty_function_select($params, $dummy_smarty);
+        $actual = ob_get_clean();
+        $this->assertEqual($expected, $actual); 
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_checkbox_Test.php b/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_checkbox_Test.php
new file mode 100644 (file)
index 0000000..8e84ccf
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Smarty_modifier_checkbox_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Smarty/modifier.checkbox.php';
+
+//{{{    Ethna_Plugin_Smarty_modifier_checkbox_Test
+/**
+ *  Test Case For modifier.checkbox.php
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Smarty_modifier_checkbox_Test extends Ethna_UnitTestBase
+{
+    // {{{  test_smarty_modifier_checkbox
+    function test_smarty_modifier_checkbox()
+    {
+        //  文字列型で0と空文字列以外は確実に checked
+        $expected = 'checked="checked"';
+        $actual = smarty_modifier_checkbox("hoge");
+        $this->assertEqual($expected, $actual);
+
+        $actual = smarty_modifier_checkbox("yes");
+        $this->assertEqual($expected, $actual);
+
+        $actual = smarty_modifier_checkbox("n");
+        $this->assertEqual($expected, $actual);
+
+        $actual = smarty_modifier_checkbox(1);  // numeric other than zero.
+        $this->assertEqual($expected, $actual);
+
+        $actual = smarty_modifier_checkbox(4.001);  // float
+        $this->assertEqual($expected, $actual);
+
+        //   0 と空文字列の場合はNULLになる
+        $actual = smarty_modifier_checkbox(0);  // numeric zero
+        $this->assertNULL($actual);
+
+        $actual = smarty_modifier_checkbox(0.0);  // float zero
+        $this->assertNULL($actual);
+
+        $actual = smarty_modifier_checkbox("0");
+        $this->assertNULL($actual);
+
+        $actual = smarty_modifier_checkbox("");
+        $this->assertNULL($actual);
+
+        //   null や false も 0 や空文字列と同じ扱い
+        $actual = smarty_modifier_checkbox(NULL);
+        $this->assertNULL($actual);
+
+        $actual = smarty_modifier_checkbox(false);
+        $this->assertNULL($actual);
+
+        //  array, object, resource も checkedにはしない
+        $actual = smarty_modifier_checkbox(array());
+        $this->assertNULL($actual);
+
+        $actual = smarty_modifier_checkbox(new stdClass());
+        $this->assertNULL($actual);
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_select_Test.php b/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_select_Test.php
new file mode 100644 (file)
index 0000000..354a60f
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Smarty_modifier_select_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Smarty/modifier.select.php';
+
+//{{{    Ethna_Plugin_Smarty_modifier_select_Test.php
+/**
+ *  Test Case For modifier.select.php
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Smarty_modifier_select_Test extends Ethna_UnitTestBase
+{
+    // {{{ test_smarty_modifier_select
+    function test_smarty_modifier_select()
+    {
+        $r = smarty_modifier_select('a', 'b');
+        $this->assertNull($r);
+
+        $r = smarty_modifier_select('a', 'a');
+        $this->assertEqual($r, 'selected="selected"');
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_unique_Test.php b/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_unique_Test.php
new file mode 100644 (file)
index 0000000..7d73646
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Smarty_modifier_unique_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Smarty/modifier.unique.php';
+
+//{{{    Ethna_Plugin_Smarty_modifier_unique_Test
+/**
+ *  Test Case For modifier.unique.php
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Smarty_modifier_unique_Test extends Ethna_UnitTestBase
+{
+    // {{{  test_smarty_modifier_unique
+    function test_smarty_modifier_unique()
+    {
+        //  配列でない場合
+        $result = smarty_modifier_unique('a');
+        $this->assertTrue('a', $result);
+
+        $result = smarty_modifier_unique(NULL);
+        $this->assertNULL($result);
+
+        //  第2引数なしの場合
+        $input = array(1, 2, 1, 1, 3, 2, 4);
+        $result = smarty_modifier_unique($input);
+        $this->assertTrue(is_numeric(array_search(1, $result)));
+        $this->assertTrue(is_numeric(array_search(2, $result)));
+        $this->assertTrue(is_numeric(array_search(3, $result)));
+        $this->assertTrue(is_numeric(array_search(4, $result)));
+        $this->assertFalse(is_numeric(array_search(5, $result)));
+
+        //  第2引数ありの場合
+        $input = array(
+                     array("foo" => 1, "bar" => 4),
+                     array("foo" => 1, "bar" => 4),
+                     array("foo" => 1, "bar" => 4),
+                     array("foo" => 2, "bar" => 5),
+                     array("foo" => 3, "bar" => 6),
+                     array("foo" => 2, "bar" => 5),
+                 );
+        $result = smarty_modifier_unique($input, 'bar');
+        $this->assertTrue(is_numeric(array_search(4, $result)));
+        $this->assertTrue(is_numeric(array_search(5, $result)));
+        $this->assertTrue(is_numeric(array_search(6, $result)));
+        $this->assertFalse(is_numeric(array_search(1, $result)));
+        $this->assertFalse(is_numeric(array_search(2, $result)));
+        $this->assertFalse(is_numeric(array_search(3, $result)));
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_wordwrap_i18n_Test.php b/Idea_Plugin_Extlib/test/Plugin/Smarty/Ethna_Plugin_Smarty_modifier_wordwrap_i18n_Test.php
new file mode 100644 (file)
index 0000000..d4386b8
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Smarty_modifier_wordwrap_i18n_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Plugin/Smarty/modifier.wordwrap_i18n.php';
+
+//{{{    Ethna_Plugin_Smarty_modifier_wordwrap_i18n_Test
+/**
+ *  Test Case For modifier.wordwrap_i18n.php
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Smarty_modifier_wordwrap_i18n_Test extends Ethna_UnitTestBase
+{
+    function tearDown()
+    {
+        $ctl =& new Ethna_Controller();
+        $ctl->setClientEncoding('UTF-8');
+    }
+
+    // {{{  test_smarty_modifier_wordwrap_i18n
+    function test_smarty_modifier_wordwrap_i18n()
+    {
+        unset($GLOBALS['_Ethna_controller']);
+
+        //    UTF-8
+        $input_str = 'あいうaえaおaかきaaaくけこ';
+        $expected = "あいうa\n"
+                  . "えaおaか\n"
+                  . "きaaaく\n"
+                  . 'けこ';
+
+        $ctl =& new Ethna_Controller();
+        $actual = smarty_modifier_wordwrap_i18n($input_str, 8);
+        $this->assertEqual($expected, $actual);
+        unset($GLOBALS['_Ethna_controller']);
+
+        //     SJIS
+        $ctl =& new Ethna_Controller();
+        $ctl->setClientEncoding('SJIS');
+
+        $sjis_input = mb_convert_encoding($input_str, 'SJIS', 'UTF-8');
+        $sjis_expected = mb_convert_encoding($expected, 'SJIS', 'UTF-8');
+        $sjis_actual = smarty_modifier_wordwrap_i18n($sjis_input, 8);
+        $this->assertEqual($sjis_expected, $sjis_actual);
+        unset($GLOBALS['_Ethna_controller']);
+
+        //     EUC-JP
+        $ctl =& new Ethna_Controller();
+        $ctl->setClientEncoding('EUC-JP');
+
+        $eucjp_input = mb_convert_encoding($input_str, 'EUC-JP', 'UTF-8');
+        $eucjp_expected = mb_convert_encoding($expected, 'EUC-JP', 'UTF-8');
+        $eucjp_actual = smarty_modifier_wordwrap_i18n($eucjp_input, 8);
+        $this->assertEqual($eucjp_expected, $eucjp_actual);
+    }
+
+    function test_smarty_modifier_wordwrap_i18n_indent()
+    {
+        //
+        //    indent を指定した場合、はじめの行は
+        //    インデントされないので注意
+        //
+
+        unset($GLOBALS['_Ethna_controller']);
+
+        //    UTF-8
+        $input_str = 'あいうaえaおaかきaaaくけこ';
+        $expected = "あいうa\n"
+                  . "    えaおaか\n"
+                  . "    きaaaく\n"
+                  . '    けこ';
+
+        $ctl =& new Ethna_Controller();
+        $actual = smarty_modifier_wordwrap_i18n($input_str, 8, "\n", 4);
+        $this->assertEqual($expected, $actual);
+        unset($GLOBALS['_Ethna_controller']);
+
+        //     SJIS
+        $ctl =& new Ethna_Controller();
+        $ctl->setClientEncoding('SJIS');
+
+        $sjis_input = mb_convert_encoding($input_str, 'SJIS', 'UTF-8');
+        $sjis_expected = mb_convert_encoding($expected, 'SJIS', 'UTF-8');
+        $sjis_actual = smarty_modifier_wordwrap_i18n($sjis_input, 8, "\n", 4);
+        $this->assertEqual($sjis_expected, $sjis_actual);
+        unset($GLOBALS['_Ethna_controller']);
+
+        //     EUC-JP
+        $ctl =& new Ethna_Controller();
+        $ctl->setClientEncoding('EUC-JP');
+
+        $eucjp_input = mb_convert_encoding($input_str, 'EUC-JP', 'UTF-8');
+        $eucjp_expected = mb_convert_encoding($expected, 'EUC-JP', 'UTF-8');
+        $eucjp_actual = smarty_modifier_wordwrap_i18n($eucjp_input, 8, "\n", 4);
+        $this->assertEqual($eucjp_expected, $eucjp_actual);
+    }
+
+    function test_smarty_modifier_wordwrap_i18n_break()
+    {
+        unset($GLOBALS['_Ethna_controller']);
+
+        //    UTF-8
+        $input_str = 'あいうaえaおaかきaaaくけこ';
+        $expected = "あいうa\r\n"
+                  . "    えaおaか\r\n"
+                  . "    きaaaく\r\n"
+                  . '    けこ';
+
+        $ctl =& new Ethna_Controller();
+        $actual = smarty_modifier_wordwrap_i18n($input_str, 8, "\r\n", 4);
+        $this->assertEqual($expected, $actual);
+        unset($GLOBALS['_Ethna_controller']);
+
+        //     SJIS
+        $ctl =& new Ethna_Controller();
+        $ctl->setClientEncoding('SJIS');
+
+        $sjis_input = mb_convert_encoding($input_str, 'SJIS', 'UTF-8');
+        $sjis_expected = mb_convert_encoding($expected, 'SJIS', 'UTF-8');
+        $sjis_actual = smarty_modifier_wordwrap_i18n($sjis_input, 8, "\r\n", 4);
+        $this->assertEqual($sjis_expected, $sjis_actual);
+        unset($GLOBALS['_Ethna_controller']);
+
+        //     EUC-JP
+        $ctl =& new Ethna_Controller();
+        $ctl->setClientEncoding('EUC-JP');
+
+        $eucjp_input = mb_convert_encoding($input_str, 'EUC-JP', 'UTF-8');
+        $eucjp_expected = mb_convert_encoding($expected, 'EUC-JP', 'UTF-8');
+        $eucjp_actual = smarty_modifier_wordwrap_i18n($eucjp_input, 8, "\r\n", 4);
+        $this->assertEqual($eucjp_expected, $eucjp_actual);
+    }
+    // }}}
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Custom_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Custom_Test.php
new file mode 100644 (file)
index 0000000..0038512
--- /dev/null
@@ -0,0 +1,195 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Custom_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Customクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Custom_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Custom');
+    }
+
+    // {{{ checkMailAddress
+    function test_checkMailAddress()
+    {
+        // mailaddressカスタムチェックのテスト
+        $form_string = array(
+                             'type'          => VAR_TYPE_STRING,
+                             'required'      => true,
+                             'custom' => 'checkMailaddress',
+                             );
+        $this->vld->af->form_vars['namae_string'] = 'hoge@fuga.net';
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = '-hoge@fuga.net';
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = '.hoge@fuga.net';
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = '+hoge@fuga.net';
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        // @がない
+        $this->vld->af->form_vars['namae_string'] = 'hogefuga.net';
+        $this->assertFalse($this->vld->validate('namae_string', '', $form_string));
+
+        // @の前に文字がない
+        $this->vld->af->form_vars['namae_string'] = '@hogefuga.net';
+        $this->assertFalse($this->vld->validate('namae_string', '', $form_string));
+
+        // @の後に文字がない
+        $this->vld->af->form_vars['namae_string'] = 'hogefuga.net@';
+        $this->assertFalse($this->vld->validate('namae_string', '', $form_string));
+
+        // 先頭文字が許されていない
+        $this->vld->af->form_vars['namae_string'] = '%hoge@fuga.net';
+        $this->assertFalse($this->vld->validate('namae_string', '', $form_string));
+
+        // 末尾文字が許されていない
+        $this->vld->af->form_vars['namae_string'] = 'hoge@fuga.net.';
+        $this->assertFalse($this->vld->validate('namae_string', '', $form_string));
+    }
+    // }}}
+
+    // {{{ checkBoolean
+    function test_checkBoolean()
+    {
+        //    このテストは、純粋に Ethna_ActionForm#checkBoolean のテスト
+        //    であり、Ethna_ActionForm#validate を通していないことに注意。
+        //
+        //    空文字列, false, 空配列などは、Ethna_ActionForm#Validateを
+        //    通すと、'required' => true という設定の時点でエラーと判定さ
+        //    れる。また、HTML Form からboolean型が入ることは基本的にない。
+        //
+        //    @see http://php.benscom.com/manual/ja/types.comparisons.php
+        $form_boolean = array(
+                              'type'          => VAR_TYPE_BOOLEAN,
+                              'required'      => true,
+                              'custom' => 'checkBoolean',
+                              );
+
+        $this->vld->af->form_vars['namae_boolean'] = true;
+        $this->assertTrue($this->vld->validate('namae_boolean', '', $form_boolean));
+
+        $this->vld->af->form_vars['namae_boolean'] = false;
+        $this->assertTrue($this->vld->validate('namae_boolean', '', $form_boolean));
+
+        $this->vld->af->form_vars['namae_boolean'] = '';
+        $this->assertTrue($this->vld->validate('namae_boolean', '', $form_boolean));
+
+        $this->vld->af->form_vars['namae_boolean'] = array();
+        $this->assertTrue($this->vld->validate('namae_boolean', '', $form_boolean));
+
+        $this->vld->af->form_vars['namae_boolean'] = array(true);
+        $this->assertTrue($this->vld->validate('namae_boolean', '', $form_boolean));
+
+        // 0,1以外の値
+        $this->vld->af->form_vars['namae_boolean'] = 3;
+        $this->assertFalse($this->vld->validate('namae_boolean', '', $form_boolean));
+    }
+    // }}}
+
+    // {{{ checkURL
+    function test_checkURL()
+    {
+        //    このテストは、純粋に Ethna_ActionForm#checkBoolean のテスト
+        //    であり、Ethna_ActionForm#validate を通していないことに注意。
+        //
+        //    空文字列, false, 空配列などは、Ethna_ActionForm#Validateを
+        //    通すと、'required' => true という設定の時点でエラーと判定さ
+        //    れる。
+        $form_url = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'required'      => true,
+                          'custom' => 'checkURL',
+                          );
+        $this->vld->af->form_vars['namae_url'] = 'http://uga.net';
+        $this->assertTrue($this->vld->validate('namae_url', '', $form_url));
+
+        $this->vld->af->form_vars['namae_url'] = 'https://uga.net';
+        $this->assertTrue($this->vld->validate('namae_url', '', $form_url));
+
+        $this->vld->af->form_vars['namae_url'] = 'ftp://uga.net';
+        $this->assertTrue($this->vld->validate('namae_url', '', $form_url));
+
+        $this->vld->af->form_vars['namae_url'] = 'http://';
+        $this->assertTrue($this->vld->validate('namae_url', '', $form_url));
+
+        $this->vld->af->form_vars['namae_url'] = '';
+        $this->assertTrue($this->vld->validate('namae_url', '', $form_url));
+
+        // '/'が足りない
+        $this->vld->af->form_vars['namae_url'] = 'http:/';
+        $this->assertFalse($this->vld->validate('namae_url', '', $form_url));
+
+        // 接頭辞がない
+        $this->vld->af->form_vars['namae_url'] = 'hoge@fuga.net';
+        $this->assertFalse($this->vld->validate('namae_url', '', $form_url));
+    }
+    // }}}
+
+    // {{{ checkVendorChar
+    function test_checkVendorChar()
+    {
+        $form_string = array(
+                             'type'          => VAR_TYPE_STRING,
+                             'required'      => true,
+                             'custom' => 'checkVendorChar',
+                             );
+        $this->vld->af->form_vars['namae_string'] = 'http://uga.net';
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0x00);
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0x79);
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0x80);
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0x8e);
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0x8f);
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0xae);
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0xf8);
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0xfd);
+        $this->assertTrue($this->vld->validate('namae_string', '', $form_string));
+
+        /* IBM拡張文字 / NEC選定IBM拡張文字 */
+        //$c == 0xad || ($c >= 0xf9 && $c <= 0xfc)
+        $this->vld->af->form_vars['namae_string'] = chr(0xad);
+        $this->assertFalse($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0xf9);
+        $this->assertFalse($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0xfa);
+        $this->assertFalse($this->vld->validate('namae_string', '', $form_string));
+
+        $this->vld->af->form_vars['namae_string'] = chr(0xfc);
+        $this->assertFalse($this->vld->validate('namae_string', '', $form_string));
+    }
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Max_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Max_Test.php
new file mode 100644 (file)
index 0000000..d5e02c4
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Max_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Maxクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Max_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Max');
+    }
+
+    // {{{ test max integer
+    function test_max_integer()
+    {
+        $form_int = array(
+                          'type'          => VAR_TYPE_INT,
+                          'required'      => true,
+                          'max'           => '10',
+                          'error'         => '{form}には10以下の数字(整数)を入力して下さい'
+                          );
+        $this->vld->af->setDef('namae_int', $form_int);
+
+        $pear_error = $this->vld->validate('namae_int', 9, $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_int', 10, $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_int', '', $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_int', 9.5, $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // maxを超えた値
+        $pear_error = $this->vld->validate('namae_int', 11, $form_int);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MAX_INT,$pear_error->getCode());
+        $this->assertEqual($form_int['error'], $pear_error->getMessage());
+    } 
+    // }}}
+
+    // {{{ test max float
+    function test_max_float()
+    {
+        $form_float = array(
+                            'type'          => VAR_TYPE_FLOAT,
+                            'required'      => true,
+                            'max'           => '10.000000',
+                            'error'         => '{form}には10.000000以下の数字(小数)を入力して下さい'
+                            );
+        $this->vld->af->setDef('namae_float', $form_float);
+
+        $pear_error = $this->vld->validate('namae_float', 10, $form_float);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_float', '', $form_float);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // maxを超えた値
+        $pear_error = $this->vld->validate('namae_float', 10.11, $form_float);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MAX_FLOAT, $pear_error->getCode());
+        $this->assertEqual($form_float['error'], $pear_error->getMessage());
+
+        // maxを超えた値
+        $pear_error = $this->vld->validate('namae_float', 11, $form_float);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MAX_FLOAT, $pear_error->getCode());
+        $this->assertEqual($form_float['error'], $pear_error->getMessage());
+    }
+    // }}}
+
+    // {{{ test max string
+    function test_max_string()
+    {
+        $form_string = array(
+                             'type'          => VAR_TYPE_STRING,
+                             'required'      => true,
+                             'max'           => '2',
+                             'error'         => '{form}は全角2文字以下(半角1文字以下)で入力して下さい'
+                             );
+        $this->vld->af->setDef('namae_string', $form_string);
+
+        $pear_error = $this->vld->validate('namae_string', '', $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_string', 'as', $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // maxを超えた文字列長
+        $pear_error = $this->vld->validate('namae_string', 'ddd', $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MAX_STRING, $pear_error->getCode());
+        $this->assertEqual($form_string['error'], $pear_error->getMessage());
+
+        // maxを超えた文字列長
+        $pear_error = $this->vld->validate('namae_string', 118888, $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MAX_STRING, $pear_error->getCode());
+        $this->assertEqual($form_string['error'], $pear_error->getMessage());
+
+        // multibyte string.
+        $pear_error = $this->vld->validate('namae_string', 'ああ', $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+        $pear_error = $this->vld->validate('namae_string', 'あああ', $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+    }
+    // }}}
+
+    // {{{ test max datetime
+    function test_max_datetime()
+    {
+        $form_datetime = array(
+                               'type'          => VAR_TYPE_DATETIME,
+                               'required'      => true,
+                               'max'           => '-1 day',
+                               'error'         => '{form}には-1 day以前の日付を入力して下さい'
+                               );
+        $this->vld->af->setDef('namae_datetime', $form_datetime);
+
+        $pear_error = $this->vld->validate('namae_datetime', '-2 day', $form_datetime);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_datetime', '-1 day', $form_datetime);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_datetime', '', $form_datetime);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // maxを超えた日付
+        $pear_error = $this->vld->validate('namae_datetime', '+3 day', $form_datetime);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MAX_DATETIME, $pear_error->getCode());
+        $this->assertEqual($form_datetime['error'], $pear_error->getMessage());
+    }
+    // }}}
+
+}
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Mbregexp_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Mbregexp_Test.php
new file mode 100644 (file)
index 0000000..0bb928b
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Mbregexp_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+// {{{    Ethna_Plugin_Validator_Mbregexp_Test
+/**
+ *  Test Case For Ethna_ActionForm(Mbegexp Validator)
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Mbregexp_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Mbregexp');
+    }
+
+    // {{{ Validator Mbregexp. 
+    function test_Validate_Mbregexp()
+    {
+        $form_def = array(
+                        'type' => VAR_TYPE_STRING,
+                        'form_type' => FORM_TYPE_TEXT,
+                        'required' => true,
+                        'mbregexp' => '^[あ-ん]+$',  // 全角ひらがなonly
+                        'mbregexp_encoding' => 'UTF-8',
+                    );        
+
+        $this->vld->af->setDef('input', $form_def);
+        $pear_error = $this->vld->validate('input', 9, $form_def);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('input', 'あいう', $form_def);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        //    encoding に指定された文字コード以外の文字列
+        $euc_input = mb_convert_encoding('あいう', 'EUC-JP', 'UTF-8');
+        $pear_error = $this->vld->validate('input', $euc_input, $form_def);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+    }
+    // }}}
+
+}
+// }}}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmax_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmax_Test.php
new file mode 100644 (file)
index 0000000..c4daa4f
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Mbstrmax_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Mbstrmaxクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Mbstrmax_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Mbstrmax');
+    }
+
+    // {{{ test max mbstr 
+    function test_max_mbstr()
+    {
+        $form_mbstr = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'required'      => true,
+                          'mbstrmax'      => '3',
+                          );
+        $this->vld->af->setDef('namae_mbstr', $form_mbstr);
+
+        $pear_error = $this->vld->validate('namae_mbstr', 'あいう', $form_mbstr);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_mbstr', 'あいうえ', $form_mbstr);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MAX_STRING,$pear_error->getCode());
+
+        //  TODO: Error Message Test.
+    } 
+    // }}}
+
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmin_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Mbstrmin_Test.php
new file mode 100644 (file)
index 0000000..05783ae
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Mbstrmin_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Mbstrminクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Mbstrmin_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Mbstrmin');
+    }
+
+    // {{{ test min mbstr 
+    function test_min_mbstr()
+    {
+        $form_mbstr = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'required'      => true,
+                          'mbstrmin'      => '3',
+                          );
+        $this->vld->af->setDef('namae_mbstr', $form_mbstr);
+
+        $pear_error = $this->vld->validate('namae_mbstr', 'あいう', $form_mbstr);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_mbstr', 'あい', $form_mbstr);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MIN_STRING,$pear_error->getCode());
+
+        //  TODO: Error Message Test.
+    } 
+    // }}}
+
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Min_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Min_Test.php
new file mode 100644 (file)
index 0000000..a991df6
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Min_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Minクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Min_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Min');
+    }
+
+    // {{{  test min integer
+    function test_min_integer()
+    {
+        $form_int = array(
+                          'type'          => VAR_TYPE_INT,
+                          'required'      => true,
+                          'min'           => '10',
+                          'error'         => '{form}には10以上の数字(整数)を入力して下さい'
+                          );
+        $this->vld->af->setDef('namae_int', $form_int);
+
+        $pear_error = $this->vld->validate('namae_int', 12, $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_int', 10, $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_int', '', $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_int', 10.5, $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // minより小さい値
+        $pear_error = $this->vld->validate('namae_int', -2, $form_int);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MIN_INT,$pear_error->getCode());
+        $this->assertEqual($form_int['error'], $pear_error->getMessage());
+    }
+    // }}}
+
+    // {{{  test min float
+    function test_min_float()
+    {
+        $form_float = array(
+                            'type'          => VAR_TYPE_FLOAT,
+                            'required'      => true,
+                            'min'           => '10.000000',
+                            'error'         => '{form}には10.000000以上の数字(小数)を入力して下さい'
+                            );
+        $this->vld->af->setDef('namae_float', $form_float);
+
+        $pear_error = $this->vld->validate('namae_float', 10.0, $form_float);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_float', '', $form_float);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // minより小さい値
+        $pear_error = $this->vld->validate('namae_float', 9.11, $form_float);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MIN_FLOAT, $pear_error->getCode());
+        $this->assertEqual($form_float['error'], $pear_error->getMessage());
+
+        // minより小さい値
+        $pear_error = $this->vld->validate('namae_float', 9, $form_float);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MIN_FLOAT, $pear_error->getCode());
+        $this->assertEqual($form_float['error'], $pear_error->getMessage());
+    }
+    // }}}
+    
+    // {{{ test min string
+    function test_min_string()
+    {
+        $form_string = array(
+                             'type'          => VAR_TYPE_STRING,
+                             'required'      => true,
+                             'min'           => '2',
+                             'error'         => '{form}は全角2文字以上(半角1文字以上)で入力して下さい'
+                             );
+        $this->vld->af->setDef('namae_string', $form_string);
+
+        $pear_error = $this->vld->validate('namae_string', 'ddd', $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_string', '', $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_string', 'd', $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MIN_STRING, $pear_error->getCode());
+        $this->assertEqual($form_string['error'], $pear_error->getMessage());
+
+        // minを短い文字列長
+        $pear_error = $this->vld->validate('namae_string', 8, $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MIN_STRING, $pear_error->getCode());
+        $this->assertEqual($form_string['error'], $pear_error->getMessage());
+
+        // multibyte string.
+        $pear_error = $this->vld->validate('namae_string', 'ああ', $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_string', 'あ', $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+    }
+    // }}}
+
+    // {{{ test min datetime
+    function test_min_datetime()
+    {
+        $form_datetime = array(
+                               'type'          => VAR_TYPE_DATETIME,
+                               'required'      => true,
+                               'min'           => '-1 day',
+                               'error'         => '{form}には-1 day以降の日付を入力して下さい'
+                               );
+        $this->vld->af->setDef('namae_datetime', $form_datetime);
+
+        $pear_error = $this->vld->validate('namae_datetime', '+2 day', $form_datetime);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_datetime', '-1 day', $form_datetime);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_datetime', '', $form_datetime);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // minより古い日付
+        $pear_error = $this->vld->validate('namae_datetime', '-3 day', $form_datetime);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MIN_DATETIME, $pear_error->getCode());
+        $this->assertEqual($form_datetime['error'], $pear_error->getMessage());
+    }
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Regexp_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Regexp_Test.php
new file mode 100644 (file)
index 0000000..aaeb9db
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Regexp_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Regexpクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Regexp_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Regexp');
+    }
+
+    // {{{  test regexp string
+    function test_regexp_string()
+    {
+        $form_string = array(
+                             'type'          => VAR_TYPE_STRING,
+                             'required'      => true,
+                             'regexp'        => '/^[a-zA-Z]+$/',
+                             'error'         => '{form}を正しく入力してください'
+                             );
+        $this->vld->af->setDef('namae_string', $form_string);
+
+        $pear_error = $this->vld->validate('namae_string', 'fromA', $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 許されていない文字列
+        $pear_error = $this->vld->validate('namae_string', '7.6', $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REGEXP, $pear_error->getCode());
+        $this->assertEqual($form_string['error'], $pear_error->getMessage());
+
+        $pear_error = $this->vld->validate('namae_string', '', $form_string);
+        // requiredとの関係上
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+    }
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Required_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Required_Test.php
new file mode 100644 (file)
index 0000000..6634cf2
--- /dev/null
@@ -0,0 +1,302 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Required_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Requiredクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Required_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Required');
+    }
+
+    // {{{ test form type text.
+    function test_formtext()
+    {
+        //    required => false でテキストフォームに文字列を入力する場合
+        $form_string = array(
+                             'type'          => VAR_TYPE_STRING,
+                             'required'      => false,
+                             'form_type'     => FORM_TYPE_TEXT,
+                             );
+        $this->vld->af->setDef('namae_string', $form_string);
+
+        $pear_error = $this->vld->validate('namae_string', 10, $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_string', '', $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_string', false, $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_string', null, $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        //    required => true でテキストフォームに文字列を入力する場合
+        $form_string = array(
+                             'type'          => VAR_TYPE_STRING,
+                             'required'      => true,
+                             'form_type'     => FORM_TYPE_TEXT,
+                             'error'         => 'フォーム値必須エラー'
+                             );
+        $this->vld->af->setDef('namae_string', $form_string);
+
+        $pear_error = $this->vld->validate('namae_string', 10, $form_string);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 必須フォームに入力がない
+        $pear_error = $this->vld->validate('namae_string', '', $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_string['error'], $pear_error->getMessage());
+
+        $pear_error = $this->vld->validate('namae_string', false, $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual($form_string['error'], $pear_error->getMessage());
+
+        $pear_error = $this->vld->validate('namae_string', null, $form_string);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual($form_string['error'], $pear_error->getMessage());
+    }
+    // }}}
+
+    // {{{ test form type select 
+    function test_formselect()
+    {
+        $form_select = array(
+                             'type'          => VAR_TYPE_STRING,
+                             'required'      => false,
+                             'form_type'     => FORM_TYPE_SELECT,
+                             'error'         => 'フォーム値必須エラー'
+                             );
+        $this->vld->af->setDef('namae_select', $form_select);
+
+        $pear_error = $this->vld->validate('namae_select', 'selection', $form_select);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_select', '', $form_select);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_select', false, $form_select);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_select', null, $form_select);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $form_select = array(
+                             'type'          => VAR_TYPE_STRING,
+                             'required'      => true,
+                             'form_type'     => FORM_TYPE_SELECT,
+                             'error'         => 'フォーム値必須エラー'
+                             );
+        $this->vld->af->setDef('namae_select', $form_select);
+
+        $pear_error = $this->vld->validate('namae_select', 'selection', $form_select);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 必須フォームが選択されない
+        $pear_error = $this->vld->validate('namae_select', '', $form_select);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_select['error'], $pear_error->getMessage());
+
+        $pear_error = $this->vld->validate('namae_select', false, $form_select);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_select['error'], $pear_error->getMessage());
+
+        $pear_error = $this->vld->validate('namae_select', null, $form_select);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_select['error'], $pear_error->getMessage());
+    }
+    //  }}}
+
+    // {{{ test form type radio
+    function test_formradio()
+    {
+        $form_radio = array(
+                            'type'          => VAR_TYPE_STRING,
+                            'required'      => false,
+                            'form_type'     => FORM_TYPE_RADIO,
+                            'error'         => 'フォーム値必須エラー'
+                            );
+        $this->vld->af->setDef('namae_radio', $form_radio);
+
+        $pear_error = $this->vld->validate('namae_radio', 'radio', $form_radio);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_radio', '', $form_radio);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_radio', false, $form_radio);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_radio', null, $form_radio);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+
+        $form_radio = array(
+                            'type'          => VAR_TYPE_STRING,
+                            'required'      => true,
+                            'form_type'     => FORM_TYPE_RADIO,
+                            'error'         => 'フォーム値必須エラー'
+                            );
+        $this->vld->af->setDef('namae_radio', $form_radio);
+
+        $pear_error = $this->vld->validate('namae_radio', 'radio', $form_radio);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 必須フォームが選択されない
+        $pear_error = $this->vld->validate('namae_radio', '', $form_radio);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_radio['error'], $pear_error->getMessage());
+
+        $pear_error = $this->vld->validate('namae_radio', false, $form_radio);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_radio['error'], $pear_error->getMessage());
+
+        $pear_error = $this->vld->validate('namae_radio', null, $form_radio);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_radio['error'], $pear_error->getMessage());
+    }
+    //  }}}
+
+    // {{{ test form type checkbox 
+    function test_formcheckbox()
+    {
+        $form_checkbox = array(
+                               'required'      => false,
+                               'form_type'     => FORM_TYPE_CHECKBOX,
+                               'type'          => array(VAR_TYPE_BOOLEAN),
+                               'error'         => 'フォーム値必須エラー',
+                               );
+        $this->vld->af->setDef('namae_checkbox', $form_checkbox);
+
+        $checks = array(
+                        '1st' => 0,
+                        '2nd' => 1,
+                        '3rd' => 3,
+                        '4th' => 'value'
+                        );
+        $pear_error = $this->vld->validate('namae_checkbox', $checks, $form_checkbox);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $checks = array();
+        $pear_error = $this->vld->validate('namae_checkbox', $checks, $form_checkbox);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_checkbox', null, $form_checkbox);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_checkbox', false, $form_checkbox);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+
+        $form_checkbox = array(
+                               'required'      => true,
+                               'form_type'     => FORM_TYPE_CHECKBOX,
+                               'type'          => array(VAR_TYPE_BOOLEAN),
+                               'error'         => 'フォーム値必須エラー',
+                               );
+        $this->vld->af->setDef('namae_checkbox', $form_checkbox);
+
+        $checks = array(
+                        '1st' => 0,
+                        '2nd' => 1,
+                        '3rd' => 3,
+                        '4th' => 'value'
+                        );
+        $pear_error = $this->vld->validate('namae_checkbox', $checks, $form_checkbox);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 必須フォームが選択されない
+        $checks = array();
+        $pear_error = $this->vld->validate('namae_checkbox', $checks, $form_checkbox);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_checkbox['error'], $pear_error->getMessage());
+
+        $pear_error = $this->vld->validate('namae_checkbox', null, $form_checkbox);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_checkbox['error'], $pear_error->getMessage());
+
+        $pear_error = $this->vld->validate('namae_checkbox', false, $form_checkbox);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_checkbox['error'], $pear_error->getMessage());
+
+
+        $form_checkbox = array(
+                               'required'      => true,
+                               'form_type'     => FORM_TYPE_CHECKBOX,
+                               'type'          => array(VAR_TYPE_BOOLEAN),
+                               'error'         => 'フォーム値必須エラー',
+                               'key'           => '4th',
+                               'num'           => 4,
+                               );
+        $this->vld->af->setDef('namae_checkbox', $form_checkbox);
+
+        $checks = array(
+                        '1st' => 0,
+                        '2nd' => 1,
+                        '3rd' => 3,
+                        '4th' => 'value'
+                        );
+        $pear_error = $this->vld->validate('namae_checkbox', $checks, $form_checkbox);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 何らかの処理でfalseに書き換えてしまった場合はエラー
+        $checks = array(
+                        '1st' => 0,
+                        '2nd' => 1,
+                        '3rd' => 3,
+                        '4th' => false
+                        );
+        $pear_error = $this->vld->validate('namae_checkbox', $checks, $form_checkbox);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_checkbox['error'], $pear_error->getMessage());
+
+        // num error
+        $checks = array(
+                        '1st' => 0,
+                        '2nd' => 1,
+                        '3rd' => 'value'
+                        );
+        $pear_error = $this->vld->validate('namae_checkbox', $checks, $form_checkbox);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_checkbox['error'], $pear_error->getMessage());
+
+        // key error
+        $checks = array(
+                        '1st' => 0,
+                        '2nd' => 'value',
+                        '3rd' => 2,
+                        '4' => 3
+                        );
+        $pear_error = $this->vld->validate('namae_checkbox', $checks, $form_checkbox);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_REQUIRED, $pear_error->getCode());
+        $this->assertEqual($form_checkbox['error'], $pear_error->getMessage());
+    }
+    //  }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmax_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmax_Test.php
new file mode 100644 (file)
index 0000000..1ec6994
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Strmax_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Strmaxクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Strmax_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Strmax');
+    }
+
+    // {{{ test max str 
+    function test_max_str()
+    {
+        $form_str = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'required'      => true,
+                          'strmax'      => '3',
+                          );
+        $this->vld->af->setDef('namae_mbstr', $form_str);
+
+        $pear_error = $this->vld->validate('namae_mbstr', 'abc', $form_str);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_mbstr', 'abcd', $form_str);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MAX_STRING,$pear_error->getCode());
+
+        //  TODO: Error Message Test.
+    } 
+    // }}}
+
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmaxcompat_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmaxcompat_Test.php
new file mode 100644 (file)
index 0000000..4240864
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Strmaxcompat_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Strmaxcompatクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Strmaxcompat_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+    var $local_ctl;
+
+    function setUp()
+    {
+        $ctl =& new Ethna_Controller();
+        $ctl->setClientEncoding('EUC-JP');
+        $ctl->action_form =& new Ethna_ActionForm($ctl);
+        $this->local_ctl =& $ctl;
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Strmaxcompat');
+    }
+
+    function tearDown()
+    {
+        unset($GLOBALS['_Ethna_controller']);
+    }
+
+    // {{{ test max str (compatible class, EUC-JP)
+    function test_max_str_compat_euc()
+    {
+        if (extension_loaded('mbstring')) {
+            $form_str = array(
+                              'type'          => VAR_TYPE_STRING,
+                              'required'      => true,
+                              'strmaxcompat'  => '4',  // 半角4、全角2文字
+                        );
+            $this->vld->af->setDef('namae_str', $form_str);
+    
+            //    ascii.
+            $input_str = 'abcd';
+            $pear_error = $this->vld->validate('namae_str', $input_str, $form_str);
+            $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+    
+            $error_str = 'abcde';
+            $pear_error = $this->vld->validate('namae_str', $error_str, $form_str);
+            $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+            $this->assertEqual(E_FORM_MAX_STRING,$pear_error->getCode());
+
+            //    multibyte string
+            $input_str = 'あい';
+            $input_str_euc = mb_convert_encoding($input_str, 'EUC-JP', 'UTF-8');
+            $pear_error = $this->vld->validate('namae_str', $input_str_euc, $form_str);
+            $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+    
+            $error_str = 'あいう';
+            $error_str_euc = mb_convert_encoding($error_str, 'EUC-JP', 'UTF-8');
+            $pear_error = $this->vld->validate('namae_str', $error_str_euc, $form_str);
+            $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+            $this->assertEqual(E_FORM_MAX_STRING,$pear_error->getCode());
+
+        } else {
+            echo " ... skipped because mbstring extension is not installed.";
+        }
+
+        //  TODO: Error Message Test.
+    } 
+    // }}}
+
+    // {{{ test max str (compatible class, SJIS)
+    function test_max_str_compat_sjis()
+    {
+        if (extension_loaded('mbstring')) {
+
+            $this->local_ctl->setClientEncoding('SJIS');
+            $form_str = array(
+                              'type'          => VAR_TYPE_STRING,
+                              'required'      => true,
+                              'strmaxcompat'  => '4',  // 半角4、全角2文字
+                        );
+            $this->vld->af->setDef('namae_str', $form_str);
+    
+            //    ascii.
+            $input_str = 'abcd';
+            $pear_error = $this->vld->validate('namae_str', $input_str, $form_str);
+            $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+    
+            $error_str = 'abcde';
+            $pear_error = $this->vld->validate('namae_str', $error_str, $form_str);
+            $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+            $this->assertEqual(E_FORM_MAX_STRING,$pear_error->getCode());
+
+            //    multibyte string
+            $input_str = 'あい';
+            $input_str_sjis = mb_convert_encoding($input_str, 'SJIS', 'UTF-8');
+            $pear_error = $this->vld->validate('namae_str', $input_str_sjis, $form_str);
+            $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+    
+            $error_str = 'あいう';
+            $error_str_sjis = mb_convert_encoding($error_str, 'SJIS', 'UTF-8');
+            $pear_error = $this->vld->validate('namae_str', $error_str_sjis, $form_str);
+            $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+            $this->assertEqual(E_FORM_MAX_STRING,$pear_error->getCode());
+
+        } else {
+            echo " ... skipped because mbstring extension is not installed.";
+        }
+
+        //  TODO: Error Message Test.
+    } 
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmin_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmin_Test.php
new file mode 100644 (file)
index 0000000..ff9ec56
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Strmin_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Strminクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Strmin_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Strmin');
+    }
+
+    // {{{ test min str 
+    function test_min_str()
+    {
+        $form_str = array(
+                          'type'          => VAR_TYPE_STRING,
+                          'required'      => true,
+                          'strmin'      => '3',
+                          );
+        $this->vld->af->setDef('namae_str', $form_str);
+
+        $pear_error = $this->vld->validate('namae_str', 'abc', $form_str);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_str', 'ab', $form_str);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_MIN_STRING,$pear_error->getCode());
+
+        //  TODO: Error Message Test.
+    } 
+    // }}}
+
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmincompat_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Strmincompat_Test.php
new file mode 100644 (file)
index 0000000..d3d2799
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Strmincompat_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Strmincompatクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Strmincompat_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+    var $local_ctl;
+
+    function setUp()
+    {
+        $ctl =& new Ethna_Controller();
+        $ctl->setClientEncoding('EUC-JP');
+        $ctl->action_form =& new Ethna_ActionForm($ctl);
+        $this->local_ctl =& $ctl;
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Strmincompat');
+    }
+
+    function tearDown()
+    {
+        unset($GLOBALS['_Ethna_controller']);
+    }
+
+    // {{{ test min str (compatible class, EUC-JP)
+    function test_min_str_compat_euc()
+    {
+        if (extension_loaded('mbstring')) {
+            $form_str = array(
+                              'type'          => VAR_TYPE_STRING,
+                              'required'      => true,
+                              'strmincompat'  => '4',  // 半角4、全角2文字
+                        );
+            $this->vld->af->setDef('namae_str', $form_str);
+    
+            //    ascii.
+            $input_str = 'abcd';
+            $pear_error = $this->vld->validate('namae_str', $input_str, $form_str);
+            $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+    
+            $error_str = 'abc';
+            $pear_error = $this->vld->validate('namae_str', $error_str, $form_str);
+            $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+            $this->assertEqual(E_FORM_MIN_STRING,$pear_error->getCode());
+
+            //    multibyte string
+            $input_str = 'あい';
+            $input_str_euc = mb_convert_encoding($input_str, 'EUC-JP', 'UTF-8');
+            $pear_error = $this->vld->validate('namae_str', $input_str_euc, $form_str);
+            $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+    
+            $error_str = 'あ';
+            $error_str_euc = mb_convert_encoding($error_str, 'EUC-JP', 'UTF-8');
+            $pear_error = $this->vld->validate('namae_str', $error_str_euc, $form_str);
+            $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+            $this->assertEqual(E_FORM_MIN_STRING,$pear_error->getCode());
+
+        } else {
+            echo " ... skipped because mbstring extension is not installed.";
+        }
+
+        //  TODO: Error Message Test.
+    } 
+    // }}}
+
+    // {{{ test min str (compatible class, SJIS)
+    function test_min_str_compat_sjis()
+    {
+        if (extension_loaded('mbstring')) {
+
+            $this->local_ctl->setClientEncoding('SJIS');
+
+            $form_str = array(
+                              'type'          => VAR_TYPE_STRING,
+                              'required'      => true,
+                              'strmincompat'  => '4',  // 半角4、全角2文字
+                        );
+            $this->vld->af->setDef('namae_str', $form_str);
+    
+            //    ascii.
+            $input_str = 'abcd';
+            $pear_error = $this->vld->validate('namae_str', $input_str, $form_str);
+            $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+    
+            $error_str = 'abc';
+            $pear_error = $this->vld->validate('namae_str', $error_str, $form_str);
+            $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+            $this->assertEqual(E_FORM_MIN_STRING,$pear_error->getCode());
+
+            //    multibyte string(sjis)
+            $input_str = 'あい';
+            $input_str_sjis = mb_convert_encoding($input_str, 'SJIS', 'UTF-8');
+            $pear_error = $this->vld->validate('namae_str', $input_str_sjis, $form_str);
+            $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+    
+            $error_str = 'あ';
+            $error_str_sjis = mb_convert_encoding($error_str, 'SJIS', 'UTF-8');
+            $pear_error = $this->vld->validate('namae_str', $error_str_sjis, $form_str);
+            $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+            $this->assertEqual(E_FORM_MIN_STRING,$pear_error->getCode());
+
+        } else {
+            echo " ... skipped because mbstring extension is not installed.";
+        }
+
+        //  TODO: Error Message Test.
+    } 
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Type_Test.php b/Idea_Plugin_Extlib/test/Plugin/Validator/Ethna_Plugin_Validator_Type_Test.php
new file mode 100644 (file)
index 0000000..fbe633e
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Plugin_Validator_Type_Test.php
+ */
+
+/**
+ *  Ethna_Plugin_Validator_Typeクラスのテストケース
+ *
+ *  @access public
+ */
+class Ethna_Plugin_Validator_Type_Test extends Ethna_UnitTestBase
+{
+    var $vld;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $plugin =& $ctl->getPlugin();
+        $this->vld = $plugin->getPlugin('Validator', 'Type');
+    }
+
+    // {{{ check Type Integer
+    function test_Check_Type_Integer()
+    {
+        $form_int = array(
+                          'type'          => VAR_TYPE_INT,
+                          'required'      => true,
+                          'error'         => '{form}には数字(整数)を入力して下さい'
+                          );
+        $this->vld->af->setDef('namae_int', $form_int);
+
+        $pear_error = $this->vld->validate('namae_int', 10, $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_int', '', $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_int', '76', $form_int);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 整数以外の文字列が入力された
+        $pear_error = $this->vld->validate('namae_int', '11asd', $form_int);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_WRONGTYPE_INT, $pear_error->getCode());
+        $this->assertEqual($form_int['error'], $pear_error->getMessage());
+
+        // 整数以外の文字列が入力された
+        $pear_error = $this->vld->validate('namae_int', '7.6', $form_int);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_WRONGTYPE_INT, $pear_error->getCode());
+        $this->assertEqual($form_int['error'], $pear_error->getMessage());
+    }
+    // }}}
+
+    // {{{ check Type Float 
+    function test_Check_Type_Float()
+    {
+        $form_float = array(
+                            'type'          => VAR_TYPE_FLOAT,
+                            'required'      => true,
+                            'error'         => '{form}には数字(小数)を入力して下さい'
+                            );
+        $this->vld->af->setDef('namae_float', $form_float);
+
+        $pear_error = $this->vld->validate('namae_float', 10.1, $form_float);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_float', 10, $form_float);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_float', '', $form_float);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 数字以外の文字列が入力された
+        $pear_error = $this->vld->validate('namae_float', '1-0.1', $form_float);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_WRONGTYPE_FLOAT, $pear_error->getCode());
+        $this->assertEqual($form_float['error'], $pear_error->getMessage());
+    }
+    // }}}
+
+    // {{{ check Type Boolean 
+    function test_Check_Type_Boolean()
+    {
+        $form_boolean = array(
+                             'type'          => VAR_TYPE_BOOLEAN,
+                             'required'      => true,
+                             'error'         => '{form}には1または0のみ入力できます'
+                             );
+        $this->vld->af->setDef('namae_boolean', $form_boolean);
+
+        $pear_error = $this->vld->validate('namae_boolean', 1, $form_boolean);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_boolean', 0, $form_boolean);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        $pear_error = $this->vld->validate('namae_boolean', '', $form_boolean);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 0,1以外の文字が入力された
+        $pear_error = $this->vld->validate('namae_boolean', 'aaa', $form_boolean);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_WRONGTYPE_BOOLEAN, $pear_error->getCode());
+        $this->assertEqual($form_boolean['error'], $pear_error->getMessage());
+
+        // 0,1以外の文字が入力された
+        $pear_error = $this->vld->validate('namae_boolean', 10.1, $form_boolean);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_WRONGTYPE_BOOLEAN, $pear_error->getCode());
+        $this->assertEqual($form_boolean['error'], $pear_error->getMessage());
+    }
+    // }}}
+
+    // {{{ check Type Datetime 
+    function test_Check_Type_Datetime()
+    { 
+        $form_datetime = array(
+                               'type'          => VAR_TYPE_DATETIME,
+                               'required'      => true,
+                               'error'         => '{form}には日付を入力して下さい'
+                               );
+        $this->vld->af->setDef('namae_datetime', $form_datetime);
+
+        // 正常な日付
+        $pear_error = $this->vld->validate('namae_datetime', "July 1, 2000 00:00:00 UTC", $form_datetime);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+        $pear_error = $this->vld->validate('namae_datetime', "+89 day", $form_datetime);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // empty は required でやるので type ではチェックしない
+        $pear_error = $this->vld->validate('namae_datetime', "", $form_datetime);
+        $this->assertFalse(is_a($pear_error, 'Ethna_Error'));
+
+        // 日付に変換できない文字列が入力された
+        $pear_error = $this->vld->validate('namae_datetime', "monkey", $form_datetime);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_WRONGTYPE_DATETIME, $pear_error->getCode());
+        $this->assertEqual($form_datetime['error'], $pear_error->getMessage());
+
+        // 日付に変換できない文字列が入力された
+        $pear_error = $this->vld->validate('namae_datetime', "--1", $form_datetime);
+        $this->assertTrue(is_a($pear_error, 'Ethna_Error'));
+        $this->assertEqual(E_FORM_WRONGTYPE_DATETIME, $pear_error->getCode());
+        $this->assertEqual($form_datetime['error'], $pear_error->getMessage());
+    }
+    // }}}
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/TextDetailReporter.php b/Idea_Plugin_Extlib/test/TextDetailReporter.php
new file mode 100644 (file)
index 0000000..627150b
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+/**
+ * TextDetailReporter.php
+ *
+ */
+
+/**
+ * TextDetailReporter
+ *
+ */
+class TextDetailReporter extends SimpleReporter {
+
+    /**
+     *    Does nothing yet. The first output will
+     *    be sent on the first test start.
+     *    @access public
+     */
+    function TextDetailReporter()
+    {
+        $this->SimpleReporter();
+    }
+
+    /**
+     *    Paints the title only.
+     *    @param string $test_name        Name class of test.
+     *    @access public
+     */
+    function paintHeader($test_name) {
+        if (!SimpleReporter::inCli()) {
+            header('Content-type: text/plain');
+        }
+        print "{$test_name}\n";
+        flush();
+    }
+
+    /**
+     *    Paints the end of the test with a summary of
+     *    the passes and failures.
+     *    @param string $test_name        Name class of test.
+     *    @access public
+     */
+    function paintFooter($test_name) {
+        if ($this->getFailCount() + $this->getExceptionCount() == 0) {
+            print "\nAll OK\n";
+        } else {
+            print "\nFAILURES!!!\n";
+        }
+        print "Test cases run: " . $this->getTestCaseProgress() .
+            "/" . $this->getTestCaseCount() .
+            ", Passes: " . $this->getPassCount() .
+            ", Failures: " . $this->getFailCount() .
+            ", Exceptions: " . $this->getExceptionCount() . "\n";
+    }
+
+    /**
+     *    Paints the test failure as a stack trace.
+     *    @param string $message    Failure message displayed in
+     *                              the context of the other tests.
+     *    @access public
+     */
+    function paintFail($message) {
+        parent::paintFail($message);
+        print $this->getFailCount() . ") $message\n";
+        $breadcrumb = $this->getTestList();
+        array_shift($breadcrumb);
+        print "\tin " . implode("\n\tin ", array_reverse($breadcrumb));
+        print "\n";
+    }
+
+    /**
+     *    Paints a PHP error or exception.
+     *    @param string $message        Message is ignored.
+     *    @access public
+     *    @abstract
+     */
+    function paintError($message) {
+        parent::paintError($message);
+        print "Exception " . $this->getExceptionCount() . "!\n$message\n";
+    }
+
+    /**
+     *    Paints formatted text such as dumped variables.
+     *    @param string $message        Text to show.
+     *    @access public
+     */
+    function paintFormattedMessage($message) {
+        print "$message\n";
+        flush();
+    }
+
+    function paintMethodStart($test_name)
+    {
+        //print "Start {$test_name} Test\n";
+        print "  |--- {$test_name}";
+        $this->before_fails = $this->_fails;
+    }
+
+    var $before_fails = 0;
+    
+    function paintMethodEnd($test_name)
+    {
+        //print "End {$test_name} Test\n";
+        if ($this->before_fails != $this->_fails) {
+            print " - NG";
+        } else {
+            print " - OK";
+        }
+        print "\n";
+    }
+
+    function paintCaseStart($test_name)
+    {
+        print "\n {$test_name}\n";
+        return parent::paintCaseStart($test_name);
+    }
+}
+?>
diff --git a/Idea_Plugin_Extlib/test/run_test.stdin.txt b/Idea_Plugin_Extlib/test/run_test.stdin.txt
new file mode 100644 (file)
index 0000000..af992b3
--- /dev/null
@@ -0,0 +1,3 @@
+y
+n
+
diff --git a/Idea_Plugin_Extlib/test/skel/skel.action.i18ntest.php b/Idea_Plugin_Extlib/test/skel/skel.action.i18ntest.php
new file mode 100644 (file)
index 0000000..ae56b26
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+/**
+ *  {$action_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id: skel.action.php 568 2008-06-07 22:55:10Z mumumu-org $
+ */
+
+/**
+ *  {$action_name} Form implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_form} extends {$project_id}_ActionForm
+{
+    /**
+     *  @access private
+     *  @var    array   form definition.
+     */
+    var $form = array(
+       'i18n_sample_name' => array(
+           'type' => VAR_TYPE_STRING, 
+           'name' => 'name_i18n',
+       ),
+       'i18n_sample_required_error' => array(
+           'type' => VAR_TYPE_STRING, 
+           'required_error' => 'required_error_i18n',
+       ),
+       'i18n_sample_type_error' => array(
+           'type' => VAR_TYPE_STRING, 
+           'type_error' => 'type_error_i18n',
+       ),
+       'i18n_sample_min_error' => array(
+           'type' => VAR_TYPE_STRING, 
+           'min_error' => 'min_error_i18n',
+       ),
+       'i18n_sample_max_error' => array(
+           'type' => VAR_TYPE_STRING, 
+           'max_error' => 'max_error_i18n',
+       ),
+       'i18n_sample_regexp_error' => array(
+           'type' => VAR_TYPE_STRING, 
+           'regexp_error' => 'regexp_error_i18n',
+       ),
+       'i18n_sample_all' => array(
+           'type' => VAR_TYPE_STRING, 
+           'name' => 'name_i18n_all',
+           'required_error' => 'required_error_i18n_all',
+           'type_error' => 'type_error_i18n_all',
+           'min_error' => 'min_error_i18n_all',
+           'max_error' => 'max_error_i18n_all',
+           'regexp_error' => 'regexp_error_i18n_all',
+       ),
+    );
+
+    function _filter_sample($value)
+    {
+        _et('actionform filter');
+    }
+}
+
+/**
+ *  {$action_name} action implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$action_class} extends {$project_id}_ActionClass
+{
+    /**
+     *  preprocess of {$action_name} Action.
+     *
+     *  @access public
+     *  @return string    forward name(null: success.
+     *                                false: in case you want to exit.)
+     */
+    function prepare()
+    {
+        _et('action prepare');
+        _et("action
+prepare
+ multiple
+  line");
+        return null;
+    }
+
+    /**
+     *  {$action_name} action implementation.
+     *
+     *  @access public
+     *  @return string  forward name.
+     */
+    function perform()
+    {
+        _et("action perform");
+        return '{$action_name}';
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/skel/skel.template.i18ntest.tpl b/Idea_Plugin_Extlib/test/skel/skel.template.i18ntest.tpl
new file mode 100644 (file)
index 0000000..8d18b10
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset={$client_enc}">
+    </head>
+    <body>
+        <h1>{$project_id}</h1>
+        {'template i18n'|i18n}
+        {'template i18n modifier'|upper|i18n}
+        {'template i18n multiple modifier'|lower|upper|i18n}
+    </body>
+</html>
diff --git a/Idea_Plugin_Extlib/test/skel/skel.view.i18ntest.php b/Idea_Plugin_Extlib/test/skel/skel.view.i18ntest.php
new file mode 100644 (file)
index 0000000..c46d12f
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/**
+ *  {$view_path}
+ *
+ *  @author     {$author}
+ *  @package    {$project_id}
+ *  @version    $Id: skel.view.php 532 2008-05-13 22:41:22Z mumumu-org $
+ */
+
+ _et("view global");
+
+/**
+ *  {$forward_name} view implementation.
+ *
+ *  @author     {$author}
+ *  @access     public
+ *  @package    {$project_id}
+ */
+class {$view_class} extends {$project_id}_ViewClass
+{
+    /**
+     *  preprocess before forwarding.
+     *
+     *  @access public
+     */
+    function preforward()
+    {
+        _et('view prepare');
+        _et("view
+
+   prepare
+ multiple
+  line");
+
+    }
+}
+
+?>
diff --git a/Idea_Plugin_Extlib/test/test_message_catalog.ini b/Idea_Plugin_Extlib/test/test_message_catalog.ini
new file mode 100644 (file)
index 0000000..73f9739
--- /dev/null
@@ -0,0 +1,41 @@
+;
+;   test_message_catalog.ini
+;
+;   テスト用メッセージカタログファイルです。パーサのテストの為、
+;   = や不要なスペースが入っている行, 見苦しい行が多数あります。 
+;
+;   $Id$
+;
+
+;   正常な翻訳行 (1行) 
+"{form} contains machine dependent code."   =  "{form}に機種依存文字が入力されています"
+
+;   以下の行はスペースが20個入った後に改行が入っています
+                     
+;   parse_ini_file 関数でパースできない値
+"yes"         =               "はい"
+"no"    =   "いいえ" 
+"{"  = "開き括弧左"
+"}"            = "開き括弧右"
+"&"                           = "アンパサンド"
+"~"         =   "チルダ"
+"!"     = "ビックリマーク"
+
+;   別の記号類 
+"%Y/%m/%d \"%H:%M:%S" = "%Y年%m月%d日 \" %H時%M分%S秒"
+
+;   複数行に跨がる翻訳行
+"
+hoge" = "  
+fuga    "
+
+"abcd\"efg
+ hijklmn"     =   "あいうえお \"
+ かきくけこ 
+さしすせそ"
+
+;  ダブルクォートを連続
+"\"\"\"\"\"\"" = " ab
+
+
+ cdefg "
diff --git a/Idea_Plugin_Extlib/tpl/info.tpl b/Idea_Plugin_Extlib/tpl/info.tpl
new file mode 100644 (file)
index 0000000..ccc84e5
--- /dev/null
@@ -0,0 +1,264 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
+<html>
+    <head>
+        <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
+        <style type="text/css">
+        <!--
+        {literal}
+            body {
+                margin: auto;
+                background-color: #ffffff;
+                color: #000000;
+            }
+            body, td, th, h1, h2 {font-family: sans-serif;}
+            pre {margin: 0px; font-family: monospace;}
+            a:link {color: #000099; text-decoration: none; background-color: #ffffff;}
+            a:hover {text-decoration: underline;}
+            table { margin: auto; border-collapse: collapse;}
+            .center {text-align: center;}
+            .center table { text-align: left;}
+            .center th { text-align: center !important; }
+            td, th { border: 1px solid #000000; font-size: 75%; vertical-align: top;}
+            h1 {font-size: 150%;}
+            h2 {font-size: 125%;}
+            .p {text-align: left;}
+            .e {background-color: #ccccff; font-weight: bold; color: #000000;}
+            .h {background-color: #9999cc; font-weight: bold; color: #000000;}
+            .v {background-color: #cccccc; color: #000000;}
+            i {color: #666666; background-color: #cccccc;}
+            img {float: right; border: 0px;}
+            hr {width: 600px; background-color: #cccccc; border: 0px; height: 1px; color: #000000;}
+            .header {
+                margin: auto;
+                width:600px;
+                background-color: #9999cc;
+                padding:0.2em;
+                border:solid 1px black;
+            }
+        {/literal}
+        //-->
+        </style>
+        <title>{$app.app_id} - Ethna Info</title>
+    </head>
+    <body>
+        <div class="center">
+            <div class="header">
+                <h1>{$app.app_id}</h1>
+            </div>
+            <br />
+            <hr />
+            Contents
+            <hr />
+            <table border="0" cellpadding="3" width="600">
+                <tr>
+                    <td style="border:0px">
+                        <ul>
+                            <li><a href="#actions">Actions</a></li>
+                                <ol>
+                                {foreach from=$app.action_list key=action_name item=action}
+                                    <li><a href="#action_{$action_name}">{$action_name}</a></li>
+                                {/foreach}
+                                </ol>
+                            <li><a href="#forwards">Forwards</a></li>
+                                <ol>
+                                {foreach from=$app.forward_list key=forward_name item=forward}
+                                    <li><a href="#forward_{$forward_name}">{$forward_name}</a></li>
+                                {/foreach}
+                                </ol>
+                            <li><a href="#configuration">Configuration</a></li>
+                            <li><a href="#plugins">Plugins</a></li>
+                                <ol>
+                                {foreach from=$app.plugin_list key=plugin_type item=plugin}
+                                    <li><a href="#plugin_{$plugin_type}">{$plugin_type}</a></li>
+                                {/foreach}
+                                </ol>
+                        </ul>
+                    </td>
+                </tr>
+            </table>
+            <br />
+            <a name="actions"></a>
+            <h1>Actions</h1>
+
+            <table border="0" cellpadding="3" width="600">
+                {foreach from=$app.action_list key=action_name item=action}
+                <tr class="h">
+                    <th colspan="3"><a name="action_{$action_name}"></a>{$action_name}</th>
+                </tr>
+                <tr>
+                    <td class="e">アクションクラス</td>
+                    <td class="v" colspan="2">
+                        {$action.action_class}{if $action.action_class_info.undef}<i>(未定義)</i>{/if}
+                    </td>
+                </tr>
+                <tr>
+                    <td class="e">アクションフォーム</td>
+                    <td class="v" colspan="2">{$action.action_form|default:"<i>(未定義)</i>"}{if $action.action_form_info.undef}<i>(未定義)</i>{/if}</td>
+                </tr>
+                <tr>
+                    {if $action.action_form_info.form|@count == 0}
+                    <td class="e">フォーム値</td>
+                    {else}
+                    <td class="e" rowspan="{$action.action_form_info.form|@count}">フォーム値</td>
+                    {/if}
+                    {foreach name="form" from=$action.action_form_info.form key=form_name item=form}
+                        {if !$smarty.foreach.form.first}<tr>{/if}
+                        <td class="v">{$form_name}</td>
+                        <td class="v">
+                            <table cellpadding="1">
+                                <tr>
+                                    <td style="border:0px; font-size:100%;">表示名</td>
+                                    <td style="border:0px; font-size:100%;">{$form.name|default:"<i>未定義</i>"}</td>
+                                </tr>
+                                <tr>
+                                    <td style="border:0px; font-size:100%;">必須</td>
+                                    <td style="border:0px; font-size:100%;">{$form.required|default:"false"}</td>
+                                </tr>
+                                <tr>
+                                    <td style="border:0px; font-size:100%;">最大値</td>
+                                    <td style="border:0px; font-size:100%;">{$form.max|default:"<i>未定義</i>"}</td>
+                                </tr>
+                                <tr>
+                                    <td style="border:0px; font-size:100%;">最小値</td>
+                                    <td style="border:0px; font-size:100%;">{$form.min|default:"<i>未定義</i>"}</td>
+                                </tr>
+                                <tr>
+                                    <td style="border:0px; font-size:100%;">正規表現</td>
+                                    <td style="border:0px; font-size:100%;">{$form.regexp|default:"<i>未定義</i>"}</td>
+                                </tr>
+                                <tr>
+                                    <td style="border:0px; font-size:100%;">チェックメソッド</td>
+                                    <td style="border:0px; font-size:100%;">{$form.custom|default:"<i>未定義</i>"}</td>
+                                </tr>
+                                <tr>
+                                    <td style="border:0px; font-size:100%;">変換フィルタ</td>
+                                    <td style="border:0px; font-size:100%;">{$form.filter|default:"<i>未定義</i>"|nl2br}</td>
+                                </tr>
+                                <tr>
+                                    <td style="border:0px; font-size:100%;">フォームタイプ</td>
+                                    <td style="border:0px; font-size:100%;">{$form.form_type|default:"<i>未定義</i>"}</td>
+                                </tr>
+                                <tr>
+                                    <td style="border:0px; font-size:100%;">タイプ</td>
+                                    <td style="border:0px; font-size:100%;">{$form.type|default:"<i>未定義</i>"}</td>
+                                </tr>
+                            </table>
+                        </td>
+                        {if !$smarty.foreach.form.last}</tr>{/if}
+                    {foreachelse}
+                        <td class="v" colspan="2"></td>
+                    {/foreach}
+                </tr>
+                <tr>
+                    {if $action.action_class_info.return.prepare|@count == 0}
+                    <td class="e">遷移先(prepare)</td>
+                    {else}
+                    <td class="e" rowspan="{$action.action_class_info.return.prepare|@count}">遷移先(prepare)</td>
+                    {/if}
+                    {foreach name="return_prepare" from=$action.action_class_info.return.prepare item=forward}
+                        {if !$smarty.foreach.return_prepare.first}<tr>{/if}
+                        <td class="v" colspan="2">{$forward}</td>
+                        {if !$smarty.foreach.return_prepare.last}</tr>{/if}
+                    
+                    {foreachelse}
+                        
+                        <td class="v" colspan="2"></td>
+                    
+                    {/foreach}
+                
+                </tr>
+                <tr>
+                    {if $action.action_class_info.return.perform|@count == 0}
+                    <td class="e" >遷移先(perform)</td>
+                    {else}
+                    <td class="e" rowspan="{$action.action_class_info.return.perform|@count}">遷移先(perform)</td>
+                    {/if}
+                    {foreach name="return_perform" from=$action.action_class_info.return.perform item=forward}
+                        {if !$smarty.foreach.return_perform.first}<tr>{/if}
+                        <td class="v" colspan="2">{$forward}</td>
+                        {if !$smarty.foreach.return_perform.last}</tr>{/if}
+                    {foreachelse}
+                        <td class="v" colspan="2"></td>
+                    {/foreach}
+                </tr>
+                {/foreach}
+            </table>
+            <br />
+
+            <a name="forwards"></a>
+            <h1>Forwards</h1>
+
+            <table border="0" cellpadding="3" width="600">
+                {foreach from=$app.forward_list key=forward_name item=forward}
+                <tr class="h">
+                    <th colspan="2">
+                        <a name="forward_{$forward_name}"></a>
+                        {$forward_name}
+                    </th>
+                </tr>
+                <tr>
+                    <td class="e">ビュークラス</td>
+                    <td class="v">{$forward.view_class|default:"<i>未定義</i>"}</td>
+                </tr>
+                <tr>
+                    <td class="e">テンプレートファイル</td>
+                    <td class="v">{$forward.template_file}</td>
+                </tr>
+                {/foreach}
+            </table>
+            <br />
+
+            <a name="configuration"></a>
+            <h1>Configuration</h1>
+
+            <table border="0" cellpadding="3" width="600">
+            {foreach from=$app.configuration key=section_name item=section}
+                {if $section_name}
+                    <tr class="h">
+                        <th class="h" colspan="2">{$section_name}</th>
+                    </tr>
+                {/if}
+                {if count($section) == 0}
+                    <tr>
+                        <td class="v" colspan="2">N/A</td>
+                    </tr>
+                {else}
+                    {foreach from=$section key=entry_name item=entry}
+                    <tr>
+                        <td class="e">{$entry_name}</td>
+                        <td class="v">{$entry|nl2br}</td>
+                    </tr>
+                    {/foreach}
+                {/if}
+            {/foreach}
+            </table>
+
+            <br />
+
+            <a name="plugins"></a>
+            <h1>Plugins</h1>
+
+            <table border="0" cellpadding="3" width="600">
+                {foreach from=$app.plugin_list key=plugin_type item=plugin}
+                <tr class="h">
+                    <th colspan="2">
+                        <a name="plugin_{$plugin_type}"></a>
+                        {$plugin_type}
+                    </th>
+                </tr>
+                    {foreach from=$plugin key=plugin_name item=plugin_class}
+                    <tr>
+                        <td class="e">{$plugin_name}</td>
+                        <td class="v">{$plugin_class}</td>
+                    </tr>
+                    {/foreach}
+                {/foreach}
+            </table>
+            <br />
+
+            <hr />
+            powered by <a href="http://ethna.jp/">Ethna {$app.ethna_version}</a> (experimental)
+            <hr />
+        </div>
+    </body>
+</html>
diff --git a/Idea_Plugin_Extlib/tpl/unittest.tpl b/Idea_Plugin_Extlib/tpl/unittest.tpl
new file mode 100644 (file)
index 0000000..d750417
--- /dev/null
@@ -0,0 +1,110 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html lang="ja">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset="UTF-8">
+  <meta http-equiv="Content-Style-Type" content="text/css">
+    <title>{$app.app_id} - Ethna UnitTest</title>
+    <style type="text/css">
+        <!--
+        {literal}
+            body {
+                margin: auto;
+                background-color: #ffffff;
+                color: #000000;
+            }
+            body, td, th, h1, h2 {font-family: sans-serif;}
+            pre {margin: 0px; font-family: monospace;}
+            a:link {color: #000099; text-decoration: none; background-color: #ffffff;}
+            a:hover {text-decoration: underline;}
+            table { margin: auto; border-collapse: collapse;}
+            .center {text-align: center;}
+            .center table { text-align: left;}
+            .center th { text-align: center !important; }
+            td, th { border: 1px solid #000000; font-size: 75%; vertical-align: top;}
+            h1 {font-size: 150%;}
+            h2 {font-size: 125%;}
+            .p {text-align: left;}
+            .e {background-color: #ccccff; font-weight: bold; color: #000000;}
+            .h {background-color: #9999cc; font-weight: bold; color: #000000;}
+            .v {background-color: #cccccc; color: #000000;}
+            .vf {background-color: #cccccc; color: red;}
+            i {color: #666666; background-color: #cccccc;}
+            img {float: right; border: 0px;}
+            hr {width: 600px; background-color: #cccccc; border: 0px; height: 1px; color: #000000;}
+            .header {
+                margin: auto;
+                width:600px;
+                background-color: #9999cc;
+                padding:0.2em;
+                border:solid 1px black;
+            }
+        {/literal}
+        //-->
+        </style>
+</head>
+  <body>
+    <div class="center">
+      <div class="header"><h1>{$app.app_id}</h1></div>
+      <h2>Report</h2>
+      <table border="0" cellpadding="2" width="600">
+      {foreach from=$app.report key="key" item="item"}
+        {if $item.type=='Pass'}
+          <tr>
+            <th class="e">{$item.test}</th>
+            <td class="v">{$item.message}</td>
+          </tr>
+        {elseif $item.type=='CaseEnd'}
+        {elseif $item.type=='CaseStart'}
+          <tr class="h">
+            <th colspan="2">{$item.test_name}</th>
+          </tr>
+        {elseif $item.type=='Exception'}
+          <tr>
+            <th class="e">{$item.test}</th>
+            <td class="vf">Exception 
+              <ul>{foreach from=$item.breadcrumb item="crumb"}<li>{$crumb}</li>{/foreach}</ul><strong>{$message|escape:"html"}</strong><br />
+            </td>
+          </tr>
+        {elseif $item.type=='Fail'}
+          <tr>
+            <th class="e">{$item.test}</th>
+            <td class="vf"><strong>Fail</strong> {$item.message}</td>
+          </tr>
+        {else}
+          <tr class="v">
+            <td colspan="2">{$item.message}</td>
+          </tr>
+        {/if} 
+      {foreachelse}
+          <tr>
+            <td>You don't create any Testcase.</td>
+          </tr>
+          <tr>
+            <td>
+              <div class="header">
+                <p>
+                  you can generate testcase with the following commands.
+                </p>
+                <p>
+                  ethna add-action-test [testcasename]<br>
+                  ethna add-view-test   [testcasename]<br>
+                  ethna add-test        [testcasename]
+                </p>
+              </div>
+          </tr>
+      {/foreach}
+      </table>
+      <h2>Result</h2>
+      <p>
+        {$app.result.TestCaseProgress}/{$app.result.TestCaseCount} test cases complete:
+        <strong>{$app.result.PassCount}</strong> passes, 
+        <strong>{$app.result.FailCount}</strong> fails and 
+        <strong>{$app.result.ExceptionCount}</strong> exceptions.
+      </p>
+      <br />
+      <hr />
+      powered by <a href="http://ethna.jp/">Ethna {$app.ethna_version}</a>
+      <hr />
+    </div>
+  </body>
+</html>