From 2003fda7cb7fcbfa6a6c868338c4904cb373e2b5 Mon Sep 17 00:00:00 2001 From: j502647092 Date: Sun, 1 Nov 2015 22:25:03 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: j502647092 --- .buildpath | 5 + .gitignore | 2 + .htaccess | 8 + .project | 28 + Application/Common/Common/index.html | 1 + Application/Common/Conf/config.php | 12 + Application/Common/Conf/index.html | 1 + Application/Common/index.html | 1 + Application/Home/Conf/config.php | 13 + .../Home/Controller/IndexController.class.php | 11 + Application/Home/View/Index/index.html | 0 Application/README.md | 1 + Public/README.md | 1 + Public/css/home.css | 1 + Public/images/bg_tab_x.png | Bin 0 -> 707 bytes Public/images/close_qcode.gif | Bin 0 -> 3006 bytes Public/images/icon_client_download.png | Bin 0 -> 486 bytes Public/images/icon_control.png | Bin 0 -> 3035 bytes Public/images/icon_form.gif | Bin 0 -> 817 bytes Public/images/icon_storage.gif | Bin 0 -> 904 bytes Public/images/logo.png | Bin 0 -> 7244 bytes Public/images/mid_banner/banner_01.png | Bin 0 -> 159292 bytes Public/images/mid_banner/banner_01_repeat.png | Bin 0 -> 535 bytes Public/images/mid_banner/banner_02.gif | Bin 0 -> 40946 bytes Public/images/mid_banner/banner_02_repeat.gif | Bin 0 -> 181 bytes Public/images/mid_banner/banner_03.png | Bin 0 -> 111858 bytes Public/images/mid_banner/banner_03_repeat.png | Bin 0 -> 7520 bytes Public/images/pic_qcode.png | Bin 0 -> 39686 bytes Public/images/popup_hint.gif | Bin 0 -> 255 bytes Public/images/popup_hint.png | Bin 0 -> 457 bytes Public/images/popup_hint_bottom.gif | Bin 0 -> 256 bytes Public/images/popup_hint_bottom.png | Bin 0 -> 492 bytes Public/js/jquery-1.7.2.js | 5 + README.md | 37 + ThinkPHP/Common/functions.php | 1550 ++++++++ ThinkPHP/Conf/convention.php | 167 + ThinkPHP/Conf/debug.php | 27 + ThinkPHP/LICENSE.txt | 32 + ThinkPHP/Lang/en-us.php | 51 + ThinkPHP/Lang/pt-br.php | 51 + ThinkPHP/Lang/zh-cn.php | 51 + ThinkPHP/Lang/zh-tw.php | 51 + .../Behavior/AgentCheckBehavior.class.php | 24 + .../Library/Behavior/BorisBehavior.class.php | 42 + .../Behavior/BrowserCheckBehavior.class.php | 34 + .../Behavior/BuildLiteBehavior.class.php | 87 + .../CheckActionRouteBehavior.class.php | 194 + .../Behavior/CheckLangBehavior.class.php | 77 + .../ChromeShowPageTraceBehavior.class.php | 610 ++++ .../Behavior/ContentReplaceBehavior.class.php | 47 + .../Behavior/CronRunBehavior.class.php | 66 + .../FireShowPageTraceBehavior.class.php | 2079 +++++++++++ .../Behavior/ParseTemplateBehavior.class.php | 95 + .../Behavior/ReadHtmlCacheBehavior.class.php | 117 + .../Behavior/RobotCheckBehavior.class.php | 41 + .../Behavior/ShowPageTraceBehavior.class.php | 119 + .../Behavior/ShowRuntimeBehavior.class.php | 69 + .../Behavior/TokenBuildBehavior.class.php | 54 + .../Behavior/UpgradeNoticeBehavior.class.php | 117 + .../Behavior/WriteHtmlCacheBehavior.class.php | 29 + ThinkPHP/Library/Org/Net/Http.class.php | 271 ++ ThinkPHP/Library/Org/Net/IpLocation.class.php | 233 ++ ThinkPHP/Library/Org/Util/ArrayList.class.php | 240 ++ .../Library/Org/Util/CodeSwitch.class.php | 200 + ThinkPHP/Library/Org/Util/Date.class.php | 569 +++ ThinkPHP/Library/Org/Util/Rbac.class.php | 285 ++ ThinkPHP/Library/Org/Util/Stack.class.php | 51 + ThinkPHP/Library/Org/Util/String.class.php | 248 ++ ThinkPHP/Library/Think/App.class.php | 213 ++ ThinkPHP/Library/Think/Auth.class.php | 231 ++ ThinkPHP/Library/Think/Behavior.class.php | 24 + ThinkPHP/Library/Think/Build.class.php | 165 + ThinkPHP/Library/Think/Cache.class.php | 127 + .../Think/Cache/Driver/Apachenote.class.php | 124 + .../Library/Think/Cache/Driver/Apc.class.php | 86 + .../Library/Think/Cache/Driver/Db.class.php | 138 + .../Think/Cache/Driver/Eaccelerator.class.php | 77 + .../Library/Think/Cache/Driver/File.class.php | 181 + .../Think/Cache/Driver/Memcache.class.php | 103 + .../Think/Cache/Driver/Memcached.class.php | 102 + .../Think/Cache/Driver/Memcachesae.class.php | 144 + .../Think/Cache/Driver/Redis.class.php | 107 + .../Think/Cache/Driver/Shmop.class.php | 186 + .../Think/Cache/Driver/Sqlite.class.php | 119 + .../Think/Cache/Driver/Wincache.class.php | 88 + .../Think/Cache/Driver/Xcache.class.php | 90 + ThinkPHP/Library/Think/Controller.class.php | 307 ++ .../Controller/HproseController.class.php | 61 + .../Controller/JsonRpcController.class.php | 39 + .../Think/Controller/RestController.class.php | 234 ++ .../Think/Controller/RpcController.class.php | 56 + .../Think/Controller/YarController.class.php | 42 + ThinkPHP/Library/Think/Crypt.class.php | 53 + .../Think/Crypt/Driver/Base64.class.php | 74 + .../Think/Crypt/Driver/Crypt.class.php | 83 + .../Library/Think/Crypt/Driver/Des.class.php | 241 ++ .../Think/Crypt/Driver/Think.class.php | 86 + .../Think/Crypt/Driver/Xxtea.class.php | 116 + ThinkPHP/Library/Think/Db.class.php | 137 + ThinkPHP/Library/Think/Db/Driver.class.php | 1149 ++++++ .../Think/Db/Driver/Firebird.class.php | 151 + .../Library/Think/Db/Driver/Mongo.class.php | 821 +++++ .../Library/Think/Db/Driver/Mysql.class.php | 235 ++ .../Library/Think/Db/Driver/Oracle.class.php | 168 + .../Library/Think/Db/Driver/Pgsql.class.php | 91 + .../Library/Think/Db/Driver/Sqlite.class.php | 98 + .../Library/Think/Db/Driver/Sqlsrv.class.php | 166 + ThinkPHP/Library/Think/Db/Lite.class.php | 466 +++ ThinkPHP/Library/Think/Dispatcher.class.php | 339 ++ ThinkPHP/Library/Think/Exception.class.php | 16 + ThinkPHP/Library/Think/Hook.class.php | 121 + ThinkPHP/Library/Think/Image.class.php | 191 + .../Library/Think/Image/Driver/GIF.class.php | 567 +++ .../Library/Think/Image/Driver/Gd.class.php | 542 +++ .../Think/Image/Driver/Imagick.class.php | 593 +++ ThinkPHP/Library/Think/Log.class.php | 104 + .../Library/Think/Log/Driver/File.class.php | 50 + .../Library/Think/Log/Driver/Sae.class.php | 49 + ThinkPHP/Library/Think/Model.class.php | 1910 ++++++++++ .../Library/Think/Model/AdvModel.class.php | 595 +++ .../Library/Think/Model/MergeModel.class.php | 403 +++ .../Library/Think/Model/MongoModel.class.php | 422 +++ .../Think/Model/RelationModel.class.php | 412 +++ .../Library/Think/Model/ViewModel.class.php | 243 ++ ThinkPHP/Library/Think/Page.class.php | 145 + ThinkPHP/Library/Think/Route.class.php | 316 ++ .../Library/Think/Session/Driver/Db.class.php | 173 + .../Think/Session/Driver/Memcache.class.php | 79 + .../Think/Session/Driver/Mysqli.class.php | 184 + ThinkPHP/Library/Think/Storage.class.php | 40 + .../Think/Storage/Driver/File.class.php | 123 + .../Think/Storage/Driver/Sae.class.php | 193 + ThinkPHP/Library/Think/Template.class.php | 700 ++++ .../Think/Template/Driver/Ease.class.php | 41 + .../Think/Template/Driver/Lite.class.php | 39 + .../Think/Template/Driver/Mobile.class.php | 28 + .../Think/Template/Driver/Smart.class.php | 40 + .../Think/Template/Driver/Smarty.class.php | 41 + .../Library/Think/Template/TagLib.class.php | 246 ++ .../Think/Template/TagLib/Cx.class.php | 614 ++++ .../Think/Template/TagLib/Html.class.php | 523 +++ ThinkPHP/Library/Think/Think.class.php | 344 ++ ThinkPHP/Library/Think/Upload.class.php | 429 +++ .../Library/Think/Upload/Driver/Bcs.class.php | 238 ++ .../Think/Upload/Driver/Bcs/bcs.class.php | 1318 +++++++ .../Upload/Driver/Bcs/mimetypes.class.php | 137 + .../Upload/Driver/Bcs/requestcore.class.php | 837 +++++ .../Library/Think/Upload/Driver/Ftp.class.php | 163 + .../Think/Upload/Driver/Local.class.php | 118 + .../Think/Upload/Driver/Qiniu.class.php | 102 + .../Driver/Qiniu/QiniuStorage.class.php | 333 ++ .../Library/Think/Upload/Driver/Sae.class.php | 106 + .../Think/Upload/Driver/Upyun.class.php | 218 ++ ThinkPHP/Library/Think/Verify.class.php | 293 ++ ThinkPHP/Library/Think/Verify/bgs/1.jpg | Bin 0 -> 30428 bytes ThinkPHP/Library/Think/Verify/bgs/2.jpg | Bin 0 -> 29677 bytes ThinkPHP/Library/Think/Verify/bgs/3.jpg | Bin 0 -> 32109 bytes ThinkPHP/Library/Think/Verify/bgs/4.jpg | Bin 0 -> 29081 bytes ThinkPHP/Library/Think/Verify/bgs/5.jpg | Bin 0 -> 27940 bytes ThinkPHP/Library/Think/Verify/bgs/6.jpg | Bin 0 -> 31381 bytes ThinkPHP/Library/Think/Verify/bgs/7.jpg | Bin 0 -> 30234 bytes ThinkPHP/Library/Think/Verify/bgs/8.jpg | Bin 0 -> 30188 bytes ThinkPHP/Library/Think/Verify/ttfs/1.ttf | Bin 0 -> 57520 bytes ThinkPHP/Library/Think/Verify/ttfs/2.ttf | Bin 0 -> 28328 bytes ThinkPHP/Library/Think/Verify/ttfs/3.ttf | Bin 0 -> 39308 bytes ThinkPHP/Library/Think/Verify/ttfs/4.ttf | Bin 0 -> 34852 bytes ThinkPHP/Library/Think/Verify/ttfs/5.ttf | Bin 0 -> 32664 bytes ThinkPHP/Library/Think/Verify/ttfs/6.ttf | Bin 0 -> 28036 bytes ThinkPHP/Library/Think/View.class.php | 229 ++ ThinkPHP/Library/Vendor/Boris/Boris.php | 174 + .../Vendor/Boris/CLIOptionsHandler.php | 85 + .../Library/Vendor/Boris/ColoredInspector.php | 273 ++ ThinkPHP/Library/Vendor/Boris/Config.php | 85 + .../Library/Vendor/Boris/DumpInspector.php | 16 + ThinkPHP/Library/Vendor/Boris/EvalWorker.php | 247 ++ .../Library/Vendor/Boris/ExportInspector.php | 14 + ThinkPHP/Library/Vendor/Boris/Inspector.php | 19 + .../Library/Vendor/Boris/ReadlineClient.php | 109 + .../Library/Vendor/Boris/ShallowParser.php | 233 ++ .../Vendor/EaseTemplate/template.core.php | 970 +++++ .../Vendor/EaseTemplate/template.ease.php | 42 + .../Vendor/Hprose/HproseClassManager.php | 53 + .../Library/Vendor/Hprose/HproseClient.php | 134 + .../Library/Vendor/Hprose/HproseCommon.php | 816 +++++ .../Library/Vendor/Hprose/HproseFormatter.php | 40 + .../Vendor/Hprose/HproseHttpClient.php | 314 ++ .../Vendor/Hprose/HproseHttpServer.php | 483 +++ ThinkPHP/Library/Vendor/Hprose/HproseIO.php | 29 + .../Library/Vendor/Hprose/HproseIOStream.php | 349 ++ .../Library/Vendor/Hprose/HproseReader.php | 672 ++++ ThinkPHP/Library/Vendor/Hprose/HproseTags.php | 62 + .../Library/Vendor/Hprose/HproseWriter.php | 301 ++ ThinkPHP/Library/Vendor/README.txt | 1 + .../SmartTemplate/class.smarttemplate.php | 392 ++ .../class.smarttemplatedebugger.php | 456 +++ .../class.smarttemplateparser.php | 365 ++ .../Library/Vendor/Smarty/Smarty.class.php | 1473 ++++++++ .../Library/Vendor/Smarty/SmartyBC.class.php | 460 +++ ThinkPHP/Library/Vendor/Smarty/debug.tpl | 133 + .../Smarty/plugins/block.textformat.php | 113 + .../Smarty/plugins/function.counter.php | 78 + .../Vendor/Smarty/plugins/function.cycle.php | 106 + .../Vendor/Smarty/plugins/function.fetch.php | 216 ++ .../plugins/function.html_checkboxes.php | 216 ++ .../Smarty/plugins/function.html_image.php | 138 + .../Smarty/plugins/function.html_options.php | 174 + .../Smarty/plugins/function.html_radios.php | 200 + .../plugins/function.html_select_date.php | 394 ++ .../plugins/function.html_select_time.php | 366 ++ .../Smarty/plugins/function.html_table.php | 177 + .../Vendor/Smarty/plugins/function.mailto.php | 152 + .../Vendor/Smarty/plugins/function.math.php | 87 + .../Smarty/plugins/modifier.capitalize.php | 65 + .../Smarty/plugins/modifier.date_format.php | 62 + .../plugins/modifier.debug_print_var.php | 105 + .../Vendor/Smarty/plugins/modifier.escape.php | 143 + .../Smarty/plugins/modifier.regex_replace.php | 55 + .../Smarty/plugins/modifier.replace.php | 33 + .../Smarty/plugins/modifier.spacify.php | 27 + .../Smarty/plugins/modifier.truncate.php | 59 + .../Smarty/plugins/modifiercompiler.cat.php | 30 + .../modifiercompiler.count_characters.php | 33 + .../modifiercompiler.count_paragraphs.php | 28 + .../modifiercompiler.count_sentences.php | 28 + .../plugins/modifiercompiler.count_words.php | 32 + .../plugins/modifiercompiler.default.php | 35 + .../plugins/modifiercompiler.escape.php | 90 + .../plugins/modifiercompiler.from_charset.php | 34 + .../plugins/modifiercompiler.indent.php | 32 + .../Smarty/plugins/modifiercompiler.lower.php | 31 + .../plugins/modifiercompiler.noprint.php | 25 + .../modifiercompiler.string_format.php | 26 + .../Smarty/plugins/modifiercompiler.strip.php | 33 + .../plugins/modifiercompiler.strip_tags.php | 33 + .../plugins/modifiercompiler.to_charset.php | 34 + .../plugins/modifiercompiler.unescape.php | 48 + .../Smarty/plugins/modifiercompiler.upper.php | 30 + .../plugins/modifiercompiler.wordwrap.php | 46 + .../plugins/outputfilter.trimwhitespace.php | 92 + .../plugins/shared.escape_special_chars.php | 51 + .../plugins/shared.literal_compiler_param.php | 33 + .../Smarty/plugins/shared.make_timestamp.php | 42 + .../Smarty/plugins/shared.mb_str_replace.php | 55 + .../Smarty/plugins/shared.mb_unicode.php | 48 + .../Smarty/plugins/shared.mb_wordwrap.php | 83 + .../variablefilter.htmlspecialchars.php | 21 + .../sysplugins/smarty_cacheresource.php | 381 ++ .../smarty_cacheresource_custom.php | 238 ++ .../smarty_cacheresource_keyvaluestore.php | 463 +++ .../sysplugins/smarty_config_source.php | 95 + .../smarty_internal_cacheresource_file.php | 264 ++ .../smarty_internal_compile_append.php | 53 + .../smarty_internal_compile_assign.php | 77 + .../smarty_internal_compile_block.php | 238 ++ .../smarty_internal_compile_break.php | 77 + .../smarty_internal_compile_call.php | 130 + .../smarty_internal_compile_capture.php | 98 + .../smarty_internal_compile_config_load.php | 85 + .../smarty_internal_compile_continue.php | 78 + .../smarty_internal_compile_debug.php | 43 + .../smarty_internal_compile_eval.php | 73 + .../smarty_internal_compile_extends.php | 121 + .../smarty_internal_compile_for.php | 151 + .../smarty_internal_compile_foreach.php | 231 ++ .../smarty_internal_compile_function.php | 165 + .../sysplugins/smarty_internal_compile_if.php | 207 ++ .../smarty_internal_compile_include.php | 215 ++ .../smarty_internal_compile_include_php.php | 108 + .../smarty_internal_compile_insert.php | 142 + .../smarty_internal_compile_ldelim.php | 41 + .../smarty_internal_compile_nocache.php | 73 + ..._internal_compile_private_block_plugin.php | 87 + ...ternal_compile_private_function_plugin.php | 73 + ...arty_internal_compile_private_modifier.php | 81 + ..._compile_private_object_block_function.php | 88 + ...ternal_compile_private_object_function.php | 79 + ...ernal_compile_private_print_expression.php | 156 + ...ernal_compile_private_registered_block.php | 113 + ...al_compile_private_registered_function.php | 81 + ...ernal_compile_private_special_variable.php | 104 + .../smarty_internal_compile_rdelim.php | 41 + .../smarty_internal_compile_section.php | 203 ++ .../smarty_internal_compile_setfilter.php | 72 + .../smarty_internal_compile_while.php | 94 + .../smarty_internal_compilebase.php | 176 + .../sysplugins/smarty_internal_config.php | 303 ++ .../smarty_internal_config_file_compiler.php | 144 + .../smarty_internal_configfilelexer.php | 606 ++++ .../smarty_internal_configfileparser.php | 928 +++++ .../sysplugins/smarty_internal_data.php | 551 +++ .../sysplugins/smarty_internal_debug.php | 206 ++ .../smarty_internal_filter_handler.php | 70 + .../smarty_internal_function_call_handler.php | 55 + .../smarty_internal_get_include_path.php | 43 + .../smarty_internal_nocache_insert.php | 53 + .../sysplugins/smarty_internal_parsetree.php | 395 ++ .../smarty_internal_resource_eval.php | 94 + .../smarty_internal_resource_extends.php | 148 + .../smarty_internal_resource_file.php | 90 + .../smarty_internal_resource_php.php | 114 + .../smarty_internal_resource_registered.php | 95 + .../smarty_internal_resource_stream.php | 76 + .../smarty_internal_resource_string.php | 96 + ...smarty_internal_smartytemplatecompiler.php | 127 + .../sysplugins/smarty_internal_template.php | 684 ++++ .../smarty_internal_templatebase.php | 763 ++++ .../smarty_internal_templatecompilerbase.php | 626 ++++ .../smarty_internal_templatelexer.php | 1190 ++++++ .../smarty_internal_templateparser.php | 3218 +++++++++++++++++ .../sysplugins/smarty_internal_utility.php | 810 +++++ .../sysplugins/smarty_internal_write_file.php | 70 + .../Smarty/sysplugins/smarty_resource.php | 820 +++++ .../sysplugins/smarty_resource_custom.php | 96 + .../sysplugins/smarty_resource_recompiled.php | 36 + .../sysplugins/smarty_resource_uncompiled.php | 44 + .../Smarty/sysplugins/smarty_security.php | 427 +++ .../Vendor/TemplateLite/class.compiler.php | 986 +++++ .../Vendor/TemplateLite/class.config.php | 165 + .../Vendor/TemplateLite/class.template.php | 926 +++++ .../internal/compile.compile_config.php | 74 + .../internal/compile.compile_custom_block.php | 60 + .../compile.compile_custom_function.php | 44 + .../internal/compile.compile_if.php | 154 + ...compile.generate_compiler_debug_output.php | 35 + .../TemplateLite/internal/compile.include.php | 56 + .../internal/compile.parse_is_expr.php | 77 + .../internal/compile.section_start.php | 129 + .../Vendor/TemplateLite/internal/debug.tpl | 77 + .../internal/template.build_dir.php | 28 + .../internal/template.config_loader.php | 76 + .../internal/template.destroy_dir.php | 68 + .../template.fetch_compile_include.php | 42 + .../template.generate_debug_output.php | 37 + .../Library/Vendor/jsonRPC/jsonRPCClient.php | 165 + .../Library/Vendor/jsonRPC/jsonRPCServer.php | 85 + ThinkPHP/Library/Vendor/phpRPC/bigint.php | 369 ++ ThinkPHP/Library/Vendor/phpRPC/compat.php | 242 ++ ThinkPHP/Library/Vendor/phpRPC/dhparams.php | 77 + .../Library/Vendor/phpRPC/dhparams/1024.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/128.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/1536.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/160.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/192.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/2048.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/256.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/3072.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/4096.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/512.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/768.dhp | 1 + .../Library/Vendor/phpRPC/dhparams/96.dhp | 1 + .../Library/Vendor/phpRPC/pecl/xxtea/CREDITS | 2 + .../Library/Vendor/phpRPC/pecl/xxtea/INSTALL | 66 + .../Library/Vendor/phpRPC/pecl/xxtea/LICENSE | 68 + .../Library/Vendor/phpRPC/pecl/xxtea/README | 28 + .../Vendor/phpRPC/pecl/xxtea/config.m4 | 7 + .../Vendor/phpRPC/pecl/xxtea/config.w32 | 6 + .../Vendor/phpRPC/pecl/xxtea/php_xxtea.c | 193 + .../Vendor/phpRPC/pecl/xxtea/php_xxtea.dsp | 179 + .../Vendor/phpRPC/pecl/xxtea/php_xxtea.h | 49 + .../Vendor/phpRPC/pecl/xxtea/php_xxtea.sln | 25 + .../Vendor/phpRPC/pecl/xxtea/php_xxtea.vcproj | 520 +++ .../Vendor/phpRPC/pecl/xxtea/test/test.php | 8 + .../Library/Vendor/phpRPC/pecl/xxtea/xxtea.c | 54 + .../Library/Vendor/phpRPC/pecl/xxtea/xxtea.h | 47 + .../Library/Vendor/phpRPC/phprpc_client.php | 583 +++ .../Library/Vendor/phpRPC/phprpc_date.php | 522 +++ .../Library/Vendor/phpRPC/phprpc_server.php | 496 +++ ThinkPHP/Library/Vendor/phpRPC/xxtea.php | 134 + ThinkPHP/Library/Vendor/spyc/COPYING | 21 + ThinkPHP/Library/Vendor/spyc/README.md | 30 + ThinkPHP/Library/Vendor/spyc/Spyc.php | 1147 ++++++ ThinkPHP/Library/Vendor/spyc/composer.json | 27 + .../Vendor/spyc/examples/yaml-dump.php | 25 + .../Vendor/spyc/examples/yaml-load.php | 21 + ThinkPHP/Library/Vendor/spyc/php4/5to4.php | 17 + ThinkPHP/Library/Vendor/spyc/php4/spyc.php4 | 1023 ++++++ ThinkPHP/Library/Vendor/spyc/php4/test.php4 | 162 + ThinkPHP/Library/Vendor/spyc/spyc.yaml | 219 ++ .../Library/Vendor/spyc/tests/DumpTest.php | 136 + .../Library/Vendor/spyc/tests/IndentTest.php | 65 + .../Library/Vendor/spyc/tests/ParseTest.php | 401 ++ .../Vendor/spyc/tests/RoundTripTest.php | 78 + .../Library/Vendor/spyc/tests/comments.yaml | 3 + .../Library/Vendor/spyc/tests/failing1.yaml | 2 + .../Library/Vendor/spyc/tests/indent_1.yaml | 65 + .../Library/Vendor/spyc/tests/quotes.yaml | 8 + ThinkPHP/Mode/Api/App.class.php | 143 + ThinkPHP/Mode/Api/Controller.class.php | 92 + ThinkPHP/Mode/Api/Dispatcher.class.php | 232 ++ ThinkPHP/Mode/Api/functions.php | 1109 ++++++ ThinkPHP/Mode/Lite/App.class.php | 156 + ThinkPHP/Mode/Lite/Controller.class.php | 275 ++ ThinkPHP/Mode/Lite/Dispatcher.class.php | 264 ++ ThinkPHP/Mode/Lite/Model.class.php | 1485 ++++++++ ThinkPHP/Mode/Lite/View.class.php | 293 ++ ThinkPHP/Mode/Lite/convention.php | 163 + ThinkPHP/Mode/Lite/functions.php | 1378 +++++++ ThinkPHP/Mode/Sae/convention.php | 39 + ThinkPHP/Mode/api.php | 44 + ThinkPHP/Mode/common.php | 71 + ThinkPHP/Mode/lite.php | 47 + ThinkPHP/Mode/sae.php | 68 + ThinkPHP/ThinkPHP.php | 97 + ThinkPHP/Tpl/dispatch_jump.tpl | 49 + ThinkPHP/Tpl/page_trace.tpl | 67 + ThinkPHP/Tpl/think_exception.tpl | 58 + ThinkPHP/logo.png | Bin 0 -> 7396 bytes composer.json | 18 + index.php | 26 + 409 files changed, 77938 insertions(+) create mode 100644 .buildpath create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 .project create mode 100644 Application/Common/Common/index.html create mode 100644 Application/Common/Conf/config.php create mode 100644 Application/Common/Conf/index.html create mode 100644 Application/Common/index.html create mode 100644 Application/Home/Conf/config.php create mode 100644 Application/Home/Controller/IndexController.class.php create mode 100644 Application/Home/View/Index/index.html create mode 100644 Application/README.md create mode 100644 Public/README.md create mode 100644 Public/css/home.css create mode 100644 Public/images/bg_tab_x.png create mode 100644 Public/images/close_qcode.gif create mode 100644 Public/images/icon_client_download.png create mode 100644 Public/images/icon_control.png create mode 100644 Public/images/icon_form.gif create mode 100644 Public/images/icon_storage.gif create mode 100644 Public/images/logo.png create mode 100644 Public/images/mid_banner/banner_01.png create mode 100644 Public/images/mid_banner/banner_01_repeat.png create mode 100644 Public/images/mid_banner/banner_02.gif create mode 100644 Public/images/mid_banner/banner_02_repeat.gif create mode 100644 Public/images/mid_banner/banner_03.png create mode 100644 Public/images/mid_banner/banner_03_repeat.png create mode 100644 Public/images/pic_qcode.png create mode 100644 Public/images/popup_hint.gif create mode 100644 Public/images/popup_hint.png create mode 100644 Public/images/popup_hint_bottom.gif create mode 100644 Public/images/popup_hint_bottom.png create mode 100644 Public/js/jquery-1.7.2.js create mode 100644 README.md create mode 100644 ThinkPHP/Common/functions.php create mode 100644 ThinkPHP/Conf/convention.php create mode 100644 ThinkPHP/Conf/debug.php create mode 100644 ThinkPHP/LICENSE.txt create mode 100644 ThinkPHP/Lang/en-us.php create mode 100644 ThinkPHP/Lang/pt-br.php create mode 100644 ThinkPHP/Lang/zh-cn.php create mode 100644 ThinkPHP/Lang/zh-tw.php create mode 100644 ThinkPHP/Library/Behavior/AgentCheckBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/BorisBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/BrowserCheckBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/BuildLiteBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/CheckActionRouteBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/CheckLangBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/ChromeShowPageTraceBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/ContentReplaceBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/CronRunBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/FireShowPageTraceBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/ReadHtmlCacheBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/RobotCheckBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/ShowPageTraceBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/ShowRuntimeBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/TokenBuildBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/UpgradeNoticeBehavior.class.php create mode 100644 ThinkPHP/Library/Behavior/WriteHtmlCacheBehavior.class.php create mode 100644 ThinkPHP/Library/Org/Net/Http.class.php create mode 100644 ThinkPHP/Library/Org/Net/IpLocation.class.php create mode 100644 ThinkPHP/Library/Org/Util/ArrayList.class.php create mode 100644 ThinkPHP/Library/Org/Util/CodeSwitch.class.php create mode 100644 ThinkPHP/Library/Org/Util/Date.class.php create mode 100644 ThinkPHP/Library/Org/Util/Rbac.class.php create mode 100644 ThinkPHP/Library/Org/Util/Stack.class.php create mode 100644 ThinkPHP/Library/Org/Util/String.class.php create mode 100644 ThinkPHP/Library/Think/App.class.php create mode 100644 ThinkPHP/Library/Think/Auth.class.php create mode 100644 ThinkPHP/Library/Think/Behavior.class.php create mode 100644 ThinkPHP/Library/Think/Build.class.php create mode 100644 ThinkPHP/Library/Think/Cache.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Apachenote.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Apc.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Db.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Eaccelerator.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/File.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Memcache.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Memcached.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Memcachesae.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Redis.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Shmop.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Sqlite.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Wincache.class.php create mode 100644 ThinkPHP/Library/Think/Cache/Driver/Xcache.class.php create mode 100644 ThinkPHP/Library/Think/Controller.class.php create mode 100644 ThinkPHP/Library/Think/Controller/HproseController.class.php create mode 100644 ThinkPHP/Library/Think/Controller/JsonRpcController.class.php create mode 100644 ThinkPHP/Library/Think/Controller/RestController.class.php create mode 100644 ThinkPHP/Library/Think/Controller/RpcController.class.php create mode 100644 ThinkPHP/Library/Think/Controller/YarController.class.php create mode 100644 ThinkPHP/Library/Think/Crypt.class.php create mode 100644 ThinkPHP/Library/Think/Crypt/Driver/Base64.class.php create mode 100644 ThinkPHP/Library/Think/Crypt/Driver/Crypt.class.php create mode 100644 ThinkPHP/Library/Think/Crypt/Driver/Des.class.php create mode 100644 ThinkPHP/Library/Think/Crypt/Driver/Think.class.php create mode 100644 ThinkPHP/Library/Think/Crypt/Driver/Xxtea.class.php create mode 100644 ThinkPHP/Library/Think/Db.class.php create mode 100644 ThinkPHP/Library/Think/Db/Driver.class.php create mode 100644 ThinkPHP/Library/Think/Db/Driver/Firebird.class.php create mode 100644 ThinkPHP/Library/Think/Db/Driver/Mongo.class.php create mode 100644 ThinkPHP/Library/Think/Db/Driver/Mysql.class.php create mode 100644 ThinkPHP/Library/Think/Db/Driver/Oracle.class.php create mode 100644 ThinkPHP/Library/Think/Db/Driver/Pgsql.class.php create mode 100644 ThinkPHP/Library/Think/Db/Driver/Sqlite.class.php create mode 100644 ThinkPHP/Library/Think/Db/Driver/Sqlsrv.class.php create mode 100644 ThinkPHP/Library/Think/Db/Lite.class.php create mode 100644 ThinkPHP/Library/Think/Dispatcher.class.php create mode 100644 ThinkPHP/Library/Think/Exception.class.php create mode 100644 ThinkPHP/Library/Think/Hook.class.php create mode 100644 ThinkPHP/Library/Think/Image.class.php create mode 100644 ThinkPHP/Library/Think/Image/Driver/GIF.class.php create mode 100644 ThinkPHP/Library/Think/Image/Driver/Gd.class.php create mode 100644 ThinkPHP/Library/Think/Image/Driver/Imagick.class.php create mode 100644 ThinkPHP/Library/Think/Log.class.php create mode 100644 ThinkPHP/Library/Think/Log/Driver/File.class.php create mode 100644 ThinkPHP/Library/Think/Log/Driver/Sae.class.php create mode 100644 ThinkPHP/Library/Think/Model.class.php create mode 100644 ThinkPHP/Library/Think/Model/AdvModel.class.php create mode 100644 ThinkPHP/Library/Think/Model/MergeModel.class.php create mode 100644 ThinkPHP/Library/Think/Model/MongoModel.class.php create mode 100644 ThinkPHP/Library/Think/Model/RelationModel.class.php create mode 100644 ThinkPHP/Library/Think/Model/ViewModel.class.php create mode 100644 ThinkPHP/Library/Think/Page.class.php create mode 100644 ThinkPHP/Library/Think/Route.class.php create mode 100644 ThinkPHP/Library/Think/Session/Driver/Db.class.php create mode 100644 ThinkPHP/Library/Think/Session/Driver/Memcache.class.php create mode 100644 ThinkPHP/Library/Think/Session/Driver/Mysqli.class.php create mode 100644 ThinkPHP/Library/Think/Storage.class.php create mode 100644 ThinkPHP/Library/Think/Storage/Driver/File.class.php create mode 100644 ThinkPHP/Library/Think/Storage/Driver/Sae.class.php create mode 100644 ThinkPHP/Library/Think/Template.class.php create mode 100644 ThinkPHP/Library/Think/Template/Driver/Ease.class.php create mode 100644 ThinkPHP/Library/Think/Template/Driver/Lite.class.php create mode 100644 ThinkPHP/Library/Think/Template/Driver/Mobile.class.php create mode 100644 ThinkPHP/Library/Think/Template/Driver/Smart.class.php create mode 100644 ThinkPHP/Library/Think/Template/Driver/Smarty.class.php create mode 100644 ThinkPHP/Library/Think/Template/TagLib.class.php create mode 100644 ThinkPHP/Library/Think/Template/TagLib/Cx.class.php create mode 100644 ThinkPHP/Library/Think/Template/TagLib/Html.class.php create mode 100644 ThinkPHP/Library/Think/Think.class.php create mode 100644 ThinkPHP/Library/Think/Upload.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Bcs.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Bcs/bcs.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Bcs/mimetypes.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Bcs/requestcore.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Ftp.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Local.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Qiniu.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Qiniu/QiniuStorage.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Sae.class.php create mode 100644 ThinkPHP/Library/Think/Upload/Driver/Upyun.class.php create mode 100644 ThinkPHP/Library/Think/Verify.class.php create mode 100644 ThinkPHP/Library/Think/Verify/bgs/1.jpg create mode 100644 ThinkPHP/Library/Think/Verify/bgs/2.jpg create mode 100644 ThinkPHP/Library/Think/Verify/bgs/3.jpg create mode 100644 ThinkPHP/Library/Think/Verify/bgs/4.jpg create mode 100644 ThinkPHP/Library/Think/Verify/bgs/5.jpg create mode 100644 ThinkPHP/Library/Think/Verify/bgs/6.jpg create mode 100644 ThinkPHP/Library/Think/Verify/bgs/7.jpg create mode 100644 ThinkPHP/Library/Think/Verify/bgs/8.jpg create mode 100644 ThinkPHP/Library/Think/Verify/ttfs/1.ttf create mode 100644 ThinkPHP/Library/Think/Verify/ttfs/2.ttf create mode 100644 ThinkPHP/Library/Think/Verify/ttfs/3.ttf create mode 100644 ThinkPHP/Library/Think/Verify/ttfs/4.ttf create mode 100644 ThinkPHP/Library/Think/Verify/ttfs/5.ttf create mode 100644 ThinkPHP/Library/Think/Verify/ttfs/6.ttf create mode 100644 ThinkPHP/Library/Think/View.class.php create mode 100644 ThinkPHP/Library/Vendor/Boris/Boris.php create mode 100644 ThinkPHP/Library/Vendor/Boris/CLIOptionsHandler.php create mode 100644 ThinkPHP/Library/Vendor/Boris/ColoredInspector.php create mode 100644 ThinkPHP/Library/Vendor/Boris/Config.php create mode 100644 ThinkPHP/Library/Vendor/Boris/DumpInspector.php create mode 100644 ThinkPHP/Library/Vendor/Boris/EvalWorker.php create mode 100644 ThinkPHP/Library/Vendor/Boris/ExportInspector.php create mode 100644 ThinkPHP/Library/Vendor/Boris/Inspector.php create mode 100644 ThinkPHP/Library/Vendor/Boris/ReadlineClient.php create mode 100644 ThinkPHP/Library/Vendor/Boris/ShallowParser.php create mode 100644 ThinkPHP/Library/Vendor/EaseTemplate/template.core.php create mode 100644 ThinkPHP/Library/Vendor/EaseTemplate/template.ease.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseClassManager.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseClient.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseCommon.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseFormatter.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseHttpClient.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseHttpServer.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseIO.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseIOStream.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseReader.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseTags.php create mode 100644 ThinkPHP/Library/Vendor/Hprose/HproseWriter.php create mode 100644 ThinkPHP/Library/Vendor/README.txt create mode 100644 ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplate.php create mode 100644 ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplatedebugger.php create mode 100644 ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplateparser.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/Smarty.class.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/SmartyBC.class.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/debug.tpl create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/block.textformat.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.counter.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.cycle.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.fetch.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.html_checkboxes.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.html_image.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.html_options.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.html_radios.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.html_select_date.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.html_select_time.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.html_table.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.mailto.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/function.math.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifier.capitalize.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifier.date_format.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifier.debug_print_var.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifier.escape.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifier.regex_replace.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifier.replace.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifier.spacify.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifier.truncate.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.cat.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.count_characters.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.count_paragraphs.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.count_sentences.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.count_words.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.default.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.escape.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.from_charset.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.indent.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.lower.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.noprint.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.string_format.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.strip.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.strip_tags.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.to_charset.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.unescape.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.upper.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/modifiercompiler.wordwrap.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/outputfilter.trimwhitespace.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/shared.escape_special_chars.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/shared.literal_compiler_param.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/shared.make_timestamp.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/shared.mb_str_replace.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/shared.mb_unicode.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/shared.mb_wordwrap.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/plugins/variablefilter.htmlspecialchars.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_cacheresource.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_cacheresource_custom.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_cacheresource_keyvaluestore.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_config_source.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_cacheresource_file.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_append.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_assign.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_block.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_break.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_call.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_capture.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_config_load.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_continue.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_debug.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_eval.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_extends.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_for.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_foreach.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_function.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_if.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_include.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_include_php.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_insert.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_ldelim.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_nocache.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_private_block_plugin.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_private_function_plugin.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_private_modifier.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_private_object_block_function.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_private_object_function.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_private_print_expression.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_private_registered_block.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_private_registered_function.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_private_special_variable.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_rdelim.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_section.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_setfilter.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compile_while.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_compilebase.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_config.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_config_file_compiler.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_configfilelexer.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_configfileparser.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_data.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_debug.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_filter_handler.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_function_call_handler.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_get_include_path.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_nocache_insert.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_parsetree.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_resource_eval.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_resource_extends.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_resource_file.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_resource_php.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_resource_registered.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_resource_stream.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_resource_string.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_smartytemplatecompiler.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_template.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_templatebase.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_templatecompilerbase.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_templatelexer.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_templateparser.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_utility.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_internal_write_file.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_resource.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_resource_custom.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_resource_recompiled.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_resource_uncompiled.php create mode 100644 ThinkPHP/Library/Vendor/Smarty/sysplugins/smarty_security.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/class.compiler.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/class.config.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/class.template.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/compile.compile_config.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/compile.compile_custom_block.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/compile.compile_custom_function.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/compile.compile_if.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/compile.generate_compiler_debug_output.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/compile.include.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/compile.parse_is_expr.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/compile.section_start.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/debug.tpl create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/template.build_dir.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/template.config_loader.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/template.destroy_dir.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/template.fetch_compile_include.php create mode 100644 ThinkPHP/Library/Vendor/TemplateLite/internal/template.generate_debug_output.php create mode 100644 ThinkPHP/Library/Vendor/jsonRPC/jsonRPCClient.php create mode 100644 ThinkPHP/Library/Vendor/jsonRPC/jsonRPCServer.php create mode 100644 ThinkPHP/Library/Vendor/phpRPC/bigint.php create mode 100644 ThinkPHP/Library/Vendor/phpRPC/compat.php create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams.php create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/1024.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/128.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/1536.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/160.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/192.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/2048.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/256.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/3072.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/4096.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/512.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/768.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/dhparams/96.dhp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/CREDITS create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/INSTALL create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/LICENSE create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/README create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/config.m4 create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/config.w32 create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/php_xxtea.c create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/php_xxtea.dsp create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/php_xxtea.h create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/php_xxtea.sln create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/php_xxtea.vcproj create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/test/test.php create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/xxtea.c create mode 100644 ThinkPHP/Library/Vendor/phpRPC/pecl/xxtea/xxtea.h create mode 100644 ThinkPHP/Library/Vendor/phpRPC/phprpc_client.php create mode 100644 ThinkPHP/Library/Vendor/phpRPC/phprpc_date.php create mode 100644 ThinkPHP/Library/Vendor/phpRPC/phprpc_server.php create mode 100644 ThinkPHP/Library/Vendor/phpRPC/xxtea.php create mode 100644 ThinkPHP/Library/Vendor/spyc/COPYING create mode 100644 ThinkPHP/Library/Vendor/spyc/README.md create mode 100644 ThinkPHP/Library/Vendor/spyc/Spyc.php create mode 100644 ThinkPHP/Library/Vendor/spyc/composer.json create mode 100644 ThinkPHP/Library/Vendor/spyc/examples/yaml-dump.php create mode 100644 ThinkPHP/Library/Vendor/spyc/examples/yaml-load.php create mode 100644 ThinkPHP/Library/Vendor/spyc/php4/5to4.php create mode 100644 ThinkPHP/Library/Vendor/spyc/php4/spyc.php4 create mode 100644 ThinkPHP/Library/Vendor/spyc/php4/test.php4 create mode 100644 ThinkPHP/Library/Vendor/spyc/spyc.yaml create mode 100644 ThinkPHP/Library/Vendor/spyc/tests/DumpTest.php create mode 100644 ThinkPHP/Library/Vendor/spyc/tests/IndentTest.php create mode 100644 ThinkPHP/Library/Vendor/spyc/tests/ParseTest.php create mode 100644 ThinkPHP/Library/Vendor/spyc/tests/RoundTripTest.php create mode 100644 ThinkPHP/Library/Vendor/spyc/tests/comments.yaml create mode 100644 ThinkPHP/Library/Vendor/spyc/tests/failing1.yaml create mode 100644 ThinkPHP/Library/Vendor/spyc/tests/indent_1.yaml create mode 100644 ThinkPHP/Library/Vendor/spyc/tests/quotes.yaml create mode 100644 ThinkPHP/Mode/Api/App.class.php create mode 100644 ThinkPHP/Mode/Api/Controller.class.php create mode 100644 ThinkPHP/Mode/Api/Dispatcher.class.php create mode 100644 ThinkPHP/Mode/Api/functions.php create mode 100644 ThinkPHP/Mode/Lite/App.class.php create mode 100644 ThinkPHP/Mode/Lite/Controller.class.php create mode 100644 ThinkPHP/Mode/Lite/Dispatcher.class.php create mode 100644 ThinkPHP/Mode/Lite/Model.class.php create mode 100644 ThinkPHP/Mode/Lite/View.class.php create mode 100644 ThinkPHP/Mode/Lite/convention.php create mode 100644 ThinkPHP/Mode/Lite/functions.php create mode 100644 ThinkPHP/Mode/Sae/convention.php create mode 100644 ThinkPHP/Mode/api.php create mode 100644 ThinkPHP/Mode/common.php create mode 100644 ThinkPHP/Mode/lite.php create mode 100644 ThinkPHP/Mode/sae.php create mode 100644 ThinkPHP/ThinkPHP.php create mode 100644 ThinkPHP/Tpl/dispatch_jump.tpl create mode 100644 ThinkPHP/Tpl/page_trace.tpl create mode 100644 ThinkPHP/Tpl/think_exception.tpl create mode 100644 ThinkPHP/logo.png create mode 100644 composer.json create mode 100644 index.php diff --git a/.buildpath b/.buildpath new file mode 100644 index 0000000..8bcb4b5 --- /dev/null +++ b/.buildpath @@ -0,0 +1,5 @@ + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02f0596 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/RunTime/* +.settings \ No newline at end of file diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..929995c --- /dev/null +++ b/.htaccess @@ -0,0 +1,8 @@ + + Options +FollowSymlinks + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] + \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..29c17fb --- /dev/null +++ b/.project @@ -0,0 +1,28 @@ + + + MinecraftAccount + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.dltk.core.scriptbuilder + + + + + + org.eclipse.php.core.PHPNature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/Application/Common/Common/index.html b/Application/Common/Common/index.html new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/Application/Common/Common/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Application/Common/Conf/config.php b/Application/Common/Conf/config.php new file mode 100644 index 0000000..4a57ea8 --- /dev/null +++ b/Application/Common/Conf/config.php @@ -0,0 +1,12 @@ +'配置值' + // 添加数据库配置信息 + 'DB_TYPE' => 'mysql', // 数据库类型 + 'DB_HOST' => '127.0.0.1', // 服务器地址 + 'DB_NAME' => 'minecraft', // 数据库名 + 'DB_USER' => 'minecraft', // 用户名 + 'DB_PWD' => '325325', // 密码 + 'DB_PORT' => 3306, // 端口 + 'DB_CHARSET' => 'utf8' +); // 数据库字符集 diff --git a/Application/Common/Conf/index.html b/Application/Common/Conf/index.html new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/Application/Common/Conf/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Application/Common/index.html b/Application/Common/index.html new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/Application/Common/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Application/Home/Conf/config.php b/Application/Home/Conf/config.php new file mode 100644 index 0000000..74bc338 --- /dev/null +++ b/Application/Home/Conf/config.php @@ -0,0 +1,13 @@ + 'mysql', // 数据库类型 + 'DB_HOST' => '127.0.0.1', // 服务器地址 + 'DB_NAME' => 'minecraft', // 数据库名 + 'DB_USER' => 'minecraft', // 用户名 + 'DB_PWD' => '325325', // 密码 + 'DB_PORT' => 3306, // 端口 + 'DB_PREFIX' => '', // 数据库表前缀 + 'DB_CHARSET' => 'utf8' +) // 数据库字符集 +; \ No newline at end of file diff --git a/Application/Home/Controller/IndexController.class.php b/Application/Home/Controller/IndexController.class.php new file mode 100644 index 0000000..a66e027 --- /dev/null +++ b/Application/Home/Controller/IndexController.class.php @@ -0,0 +1,11 @@ +select(); + $this->data->$data; + $this->display(); + //$this->show('

:)

欢迎使用 ThinkPHP


版本 V{$Think.version}
','utf-8'); + } +} \ No newline at end of file diff --git a/Application/Home/View/Index/index.html b/Application/Home/View/Index/index.html new file mode 100644 index 0000000..e69de29 diff --git a/Application/README.md b/Application/README.md new file mode 100644 index 0000000..5bc1d44 --- /dev/null +++ b/Application/README.md @@ -0,0 +1 @@ +项目目录 \ No newline at end of file diff --git a/Public/README.md b/Public/README.md new file mode 100644 index 0000000..40f011a --- /dev/null +++ b/Public/README.md @@ -0,0 +1 @@ +资源文件目录 \ No newline at end of file diff --git a/Public/css/home.css b/Public/css/home.css new file mode 100644 index 0000000..9cb6033 --- /dev/null +++ b/Public/css/home.css @@ -0,0 +1 @@ +body{margin:0;font:12px/1.8 Tahoma,Geneva,'\5B8B\4F53';color:#333;background:white;}input,textarea,select{margin:0;padding:0;font-size:12px;outline:none;resize:none;}html:root body,html:root input,html:root button,html:root textarea,html:root select{font-family:Tahoma,Geneva,'\5fae\8f6f\96c5\9ed1','\5B8B\4F53';}form,ul,ol,li,dl,dt,dd,h1,h2,h3,h4,h5,p{margin:0;padding:0;list-style:none;}a{text-decoration:none;color:#224892;outline:none;}a:hover{text-decoration:underline;}a img{border:none;}i,em{font-style:normal;}b{color:#F63;}button{cursor:pointer;}button i{display:none;}.text,textarea{font-size:14px;font-weight:bold;color:#333;border:1px solid;border-color:#CECECF;border-radius:0;background:white;box-shadow:inset 1px 1px 2px rgba(0, 0, 0, 0.1);-webkit-appearance:none;}.text:focus,textarea:focus{outline:none;border-color:#92AFED;box-shadow:0 0 5px #92AFEC,inset 1px 1px 2px rgba(0, 0, 0, 0.1);}.text{width:170px;height:24px;padding:7px 9px;line-height:24px;}input.err,input.err:focus{color:#900;border-color:#E06341;background:#FFEFEC;box-shadow:0 0 5px #E06341;}textarea{width:270px;height:100px;padding:5px 9px;line-height:1.7;font-size:14px;overflow:auto;resize:none;}.button{display:inline-block;height:40px;padding:0 15px;line-height:40px;text-align:center;font-size:18px;font-family:'Heiti SC','\5fae\8f6f\96c5\9ed1','\9ed1\4f53';color:white;border:0 none;background-color:#167efb;background-repeat:repeat-x;background-position:0 0;cursor:pointer;}.button:hover{text-decoration:none;background-color:#1672e3;}.button:active{background-color:#3482e1;}.button .icon{position:relative;top:11px;vertical-align:top;font-weight:normal;}.button span{position:relative;top:11px;display:inline-block;height:16px;line-height:16px;vertical-align:top;padding-left:8px;}.button em{display:none;}.btn-gray{color:#666;background-color:#eee;}.btn-gray:hover{background-color:#ddd;}.btn-gray:active{background-color:#e7e7e7;}.btn-green{height:50px;line-height:50px;background-color:#9ed04d;}.btn-green:hover{background-color:#76bf48;}.btn-green:active{background-color:#51b73d;}.btn-disabled,.btn-disabled:hover,.btn-disabled:active{background-color:#e7e7e7;color:#A1A1A1;cursor:not-allowed;}button.button{height:40px;padding:0 10px;line-height:normal;}.top-login .bottom .qcode-switch s,.quick-login dt{background-image:url(../images/icon_login_top.gif?v=201406241538);background-repeat:no-repeat;}.icon-form{position:absolute;z-index:3;width:18px;height:18px;line-height:10;overflow:hidden;background-image:url(../images/icon_form.gif?v=201406241538);background-repeat:no-repeat;cursor:pointer;}.ifm-war{z-index:4;background-position:0 0;}.ifm-view{background-position:0 -18px;}.ifm-secure{background-position:0 -36px;}.login-popup-hint,.login-popup-hint i{background-image:url(../images/popup_hint.png?v=201406241538);background-repeat:no-repeat;_background-image:url(../images/popup_hint.gif?v=201406241538);}.login-popup-hint{position:absolute;top:-35px;right:0;z-index:4;height:35px;padding-right:10px;line-height:30px;white-space:nowrap;font-size:12px;color:#FFF;background-position:right 0;}.login-popup-hint i{position:absolute;top:0;left:-10px;width:10px;height:30px;overflow:hidden;background-position:0 0;}.header{position:relative;z-index:3;min-width:980px;height:80px;}.header .con{position:relative;width:980px;height:80px;margin:auto;}h1.logo{position:absolute;top:20px;left:0;width:183px;height:42px;line-height:20;overflow:hidden;font-size:12px;background:url(../images/logo.png?v=201406241538) no-repeat 0 0;}.top-login{position:absolute;top:13px;right:0;zoom:1;}.top-login .cell{position:relative;z-index:1;float:left;width:162px;height:32px;}.top-login .cell label{position:absolute;top:0;left:0;z-index:1;padding:0 10px;line-height:32px;font-size:12px;color:#999;cursor:text;}.top-login .cell .text{float:left;width:132px;height:16px;padding:7px 9px;line-height:16px;font-size:14px;font-weight:bold;}.top-login button.button{float:left;width:76px;height:32px;padding:0;font-size:16px;*line-height:28px;_line-height:30px;background-position:0 -8px;}.top-login button.button:hover{background-position:0 -48px;}.top-login button.button:active{background-position:0 -88px;}.top-login button.btn-gray{background-position:0 -128px;}.top-login button.btn-gray:hover{background-position:0 -168px;}.top-login button.btn-gray:active{background-position:0 -208px;}.top-login .bottom{position:absolute;top:40px;left:0;width:400px;height:16px;line-height:16px;font-size:12px;color:#6b7284;}.top-login .bottom a{color:#6b7284;}.top-login .bottom .qcode-switch{float:left;height:16px;margin-right:81px;}.top-login .bottom .qcode-switch s{float:left;width:16px;height:16px;margin-right:5px;overflow:hidden;background-position:0 0;}.top-login .bottom .qcode-switch:hover{text-decoration:none;}.top-login .bottom input{position:relative;top:1px;float:left;width:14px;height:14px;margin-right:4px;}.top-login .bottom label{position:relative;top:1px;float:left;height:14px;line-height:14px;margin-right:10px;padding-right:10px;border-right:1px solid #6b7284;}.top-login .icon-form{top:7px;right:15px;}.top-login .login-popup-hint,.top-login .login-popup-hint i{background-image:url(../images/popup_hint_bottom.png?v=201406241538);_background-image:url(../images/popup_hint_bottom.gif?v=201406241538);}.top-login .login-popup-hint{top:26px;right:4px;height:30px;padding-top:5px;}.top-login .login-popup-hint i{top:5px;background-position:0 -5px;}.quick-login{position:absolute;right:0;top:40px;}.quick-login dt{position:absolute;top:0;right:1px;width:74px;height:16px;line-height:10;overflow:hidden;background-position:-18px 0;cursor:pointer;}.quick-login dd{position:absolute;top:-8px;right:-4px;width:260px;border:1px solid #4B628B;border-radius:3px;background:#F9FAFF;box-shadow:0 2px 5px rgba(0, 0, 0, 0.2);z-index:4;}.quick-login dd s{position:absolute;top:6px;right:5px;width:0;height:0;overflow:hidden;border:6px solid;border-color:transparent transparent #1C3E7B;_border-color:pink pink #1C3E7B;_filter:chroma(color=pink);}.quick-login h3{height:30px;padding:0 10px;line-height:30px;font-size:12px;font-weight:normal;color:#666;cursor:pointer;}.quick-login ul{padding:2px 0 0 10px;overflow:hidden;zoom:1;}.quick-login li{float:left;width:115px;padding:0 10px 10px 0;}.quick-login li a{position:relative;display:block;height:28px;padding-left:30px;line-height:28px;color:#666;border:1px solid #AFB7C8;border-radius:3px;background:white;cursor:pointer;}.quick-login li a:hover{text-decoration:none;border-color:#909DB7;background:#F7F9FF;background:-webkit-linear-gradient(top, #F7F9FF, #E5E9F8);background:-moz-linear-gradient(top, #F7F9FF, #E5E9F8);background:-ms-linear-gradient(top, #F7F9FF, #E5E9F8);background:linear-gradient(top, #F7F9FF, #E5E9F8);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFF7F9FF', endColorstr='#FFE5E9F8');}.quick-login li .ico-acc{position:absolute;top:6px;left:8px;width:16px;height:16px;overflow:hidden;background-image:url(../images/icon_access.gif?v=201406241538);background-repeat:no-repeat;}.acc-qq{background-position:0 0;}.acc-sina{background-position:-16px 0;}.acc-tencent{background-position:-32px 0;}.acc-renren{background-position:-48px 0;}.acc-neteasy{background-position:-64px 0;}.acc-douban{background-position:-80px 0;}.acc-baidu{background-position:-96px 0;}.acc-surfing{background-position:-112px 0;}.acc-alipay{background-position:-128px 0;}.wrap{position:relative;z-index:0;height:600px;}.banner-show{position:absolute;top:0;left:0;z-index:0;width:100%;min-width:980px;height:600px;overflow:hidden;}.banner-show .cell{position:absolute;top:0;left:0;width:100%;overflow:hidden;background-repeat:repeat-x;background-position:center top;}.banner-show .cell .con{position:relative;height:600px;background-repeat:no-repeat;background-position:center top;}.bns-01{background-image:url(../images/mid_banner/banner_01_repeat.png?v=201406241538);}.bns-01 .con{background-image:url(../images/mid_banner/banner_01.png?v=201406241538);}.bns-02{background-image:url(../images/mid_banner/banner_02_repeat.gif?v=201406241538);}.bns-02 .con{background-image:url(../images/mid_banner/banner_02.gif?v=201406241538);}.bns-03{background-image:url(../images/mid_banner/banner_03_repeat.png?v=201406241538);}.bns-03 .con{background-image:url(../images/mid_banner/banner_03.png?v=201406241538);}.banner-link{position:absolute;top:100px;left:50%;width:400px;height:400px;margin-left:-430px;}.banner-link i{display:none;}.banner-control{position:absolute;top:0;left:50%;width:980px;margin-left:-490px;}.banner-control a{position:absolute;top:258px;width:30px;height:30px;line-height:10;overflow:hidden;background-image:url(../images/icon_control.png?v=201406241538);background-repeat:no-repeat;background-repeat:no-repeat;}.banner-control a.left{left:-55px;background-position:0 0;}.banner-control a.left:hover{background-position:0 -30px;}.banner-control a.right{right:-55px;background-position:-30px 0;}.banner-control a.right:hover{background-position:-30px -30px;}.container{position:relative;top:600px;z-index:2;width:980px;height:0;margin:auto;}.storage-info{display:none;position:absolute;top:0;width:980px;height:30px;padding:20px 0;text-align:right;overflow:hidden;}.storage-info span{position:relative;display:inline-block;height:30px;margin:0 0 0 40px;padding-left:48px;line-height:30px;zoom:1;font-size:18px;color:white;}.storage-info i{position:absolute;top:0;left:0;width:36px;height:30px;overflow:hidden;background-image:url(../images/icon_storage.gif?v=201406241538);background-repeat:no-repeat;}.ist-user{background-position:0 0;}.ist-storage{background-position:-36px 0;}.ist-file{background-position:-72px 0;}.register-box{position:absolute;top:-550px;right:0;width:396px;height:476px;background:white;border-radius:3px;background:rgba(255, 255, 255, 0.95);}.reg-tab{position:relative;width:396px;height:57px;zoom:1;overflow:hidden;border-radius:3px 3px 0 0;}.reg-tab span{float:left;width:198px;height:57px;margin-left:-1px;line-height:57px;text-align:center;font-size:24px;color:#444;border-left:1px solid #d2d3d4;background:url(../images/bg_tab_x.png?v=201406241538) repeat-x 0 0;cursor:pointer;}.reg-tab span:first-child{border-top-left-radius:3px;}.reg-tab span:last-child{border-top-right-radius:3px;}.reg-tab span.current{background:none;cursor:default;}.reg-slogan{height:50px;line-height:50px;text-align:center;font-size:16px;}.reg-form{width:328px;height:280px;margin:auto;}.reg-form .cell{position:relative;height:40px;margin-bottom:22px;zoom:1;}.reg-form .cell label{position:absolute;top:0;left:0;z-index:1;padding:0 12px;line-height:40px;font-size:16px;color:#999;font-weight:bold;cursor:text;}.reg-form .cell input{position:absolute;top:0;left:0;width:304px;padding:7px 11px;font-size:16px;background:none;}.reg-form .cell .icon-form{top:11px;right:10px;}.reg-form .vcode input{width:110px;}.reg-form .vcode .button{position:absolute;top:0;left:144px;width:182px;padding:0;font-size:14px;}.reg-form .vcode img{position:absolute;top:0;left:144px;width:110px;height:40px;}.reg-form .vcode span{position:absolute;top:0;left:264px;line-height:40px;font-size:14px;}.reg-form .vcode .icon-form{right:204px;}.reg-form .vcode .login-popup-hint{right:194px;}.reg-form .user-agreement{height:16px;margin-top:-22px;padding:18px 0;line-height:16px;}.reg-form .user-agreement input{float:left;width:14px;height:14px;margin:1px 5px 0 0;}.reg-form .bottom{height:40px;}.reg-form .bottom .button{display:block;border-radius:3px;}.client-download{position:absolute;bottom:0;left:0;width:100%;height:86px;text-align:center;border-radius:0 0 3px 3px;background:#f6f3f0;}.client-download a{position:relative;top:13px;display:inline-block;width:64px;margin:0 10px;color:#999;}.client-download a i{display:block;width:32px;height:32px;margin:0 auto 4px;overflow:hidden;background-image:url(../images/icon_client.gif?v=201406241538);background-repeat:no-repeat;opacity:0.5;filter:alpha(opacity=50);}.client-download a span{display:block;line-height:24px;text-align:center;font-size:14px;}.client-download a:hover{text-decoration:none;color:#666;}.client-download a:hover i{opacity:1;filter:alpha(opacity=100);}.icd-cloud{background-position:-0px 0;}.icd-iphone{background-position:-32px 0;}.icd-ipad{background-position:-64px 0;}.icd-android{background-position:-96px 0;}.footer{padding:25px 0;}.copy-right{width:980px;margin:auto;text-align:center;color:#999;}.copy-right .cell{position:relative;height:26px;line-height:26px;}.copy-right a{color:#999;}.copy-right a:hover{color:#333;}.localization-box{display:inline-block;padding:4px 0;vertical-align:top;*display:inline;*zoom:1;*vertical-align:middle;}.localization-box li{position:relative;float:left;height:18px;margin-left:15px;padding-left:20px;line-height:19px;vertical-align:top;overflow:hidden;_display:inline;}.localization-box .ico-local{position:absolute;top:4px;left:0;width:16px;height:11px;overflow:hidden;background-image:url(../images/icon_localization.gif?v=201406241538);background-repeat:no-repeat;}.iclo-cn{background-position:0 0;}.iclo-tw{background-position:0 -12px;}.iclo-us{background-position:0 -24px;}.qcode-login{position:absolute;top:0;left:0;z-index:11;width:100%;height:600px;background:#333945;}.qcode-login .con{position:relative;width:980px;margin:auto;}.qcode-login .close{position:absolute;top:20px;right:0px;width:48px;height:48px;line-height:20;overflow:hidden;background:url(../images/close_qcode.gif?v=201406241538) no-repeat 0 0;}.qcode-login .close:hover{background-position:0 -48px;}.qcode-flag{display:none;position:absolute;top:15px;left:67px;width:836px;height:77px;overflow:hidden;background:url(../images/flag_qcode.gif?v=201406241538) no-repeat 0 0;}.qcode-pic{position:absolute;top:92px;left:640px;z-index:1;width:295px;height:415px;background:url(../images/pic_qcode.png?v=201406241538) no-repeat right top;opacity:0;filter:alpha(opacity=0);-webkit-transform:translate(-315px, 0) scale(0.3);-moz-transform:translate(-315px, 0) scale(0.3);-ms-transform:translate(-315px, 0) scale(0.3);-webkit-transform-origin:50% 40%;-moz-transform-origin:50% 40%;-ms-transform-origin:50% 40%;-webkit-transition:all 0.5s ease 0.25s;-moz-transition:all 0.5s ease 0.25s;-ms-transition:all 0.5s ease 0.25s;}.qcode-show:hover .qcode-pic{opacity:1;filter:alpha(opacity=100);-webkit-transform:translate(0, 0) scale(1);-moz-transform:translate(0, 0) scale(1);-ms-transform:translate(0, 0) scale(1);}.qcode-box{position:absolute;top:118px;left:50%;width:300px;height:300px;margin-left:-150px;background:white;box-shadow:0 0 10px rgba(0, 0, 0, 0.5);}.qcode-box img{width:300px;height:300px;}.qcode-download{position:absolute;top:418px;left:50%;width:300px;margin-left:-150px;padding-top:20px;}.qcode-download a{display:block;height:24px;padding:13px;line-height:24px;text-align:center;font-size:18px;overflow:hidden;color:#91969e;background:#434954;}.qcode-download a:before{content:"";display:inline-block;width:16px;height:24px;margin:-6px 10px 0 0;vertical-align:middle;background:url(../images/icon_client_download.png) no-repeat 0 0;}.qcode-download a:hover{text-decoration:none;color:white;background:#5c616a;}.qcode-download a:hover:before{background-position:-16px 0;}.qcode-desc-text{position:absolute;top:565px;left:0;width:100%;line-height:24px;text-align:center;color:#686f7c;}.qcode-hint{position:absolute;top:436px;left:50%;width:278px;margin-left:-150px;padding:10px;line-height:30px;text-align:center;font-size:16px;color:white;border:1px solid #006700;background:#64b832;}.qcode-hint .arrow{position:absolute;top:-17px;left:50%;width:0;height:0;margin-left:-8px;overflow:hidden;border:8px solid;border-color:transparent transparent #006700;_border-color:pink pink #006700;_filter:chroma(color=pink);}.qcode-hint s.arrow{top:-16px;border-bottom-color:#64b832;} \ No newline at end of file diff --git a/Public/images/bg_tab_x.png b/Public/images/bg_tab_x.png new file mode 100644 index 0000000000000000000000000000000000000000..4937131ebdf409f1b0df8cff1b21d147bc0280e4 GIT binary patch literal 707 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 literal 0 HcmV?d00001 diff --git a/Public/images/icon_client_download.png b/Public/images/icon_client_download.png new file mode 100644 index 0000000000000000000000000000000000000000..b560aa790012d1c459c05116c7078fcb857da7ef GIT binary patch literal 486 zcmV@P)eKg%0000SbVXQnQ*UN;cVTj606}DLVr3vkX>w(EZ*psMAVX6&=)AIw0003! zNkl`wCsyJxEXTyHSjgCC@~K zG>d)+hS9wFpG2`_tn)K~BkHugzrEr&d?Ub5;rU%ymyAWe5eAe%twcT!&|c1`MKjep ze*`cm<<=OZ;z_Rj;ab$1mwL74Y42gL@g92Y0M>b0=UF?PLDd27q5lAz0M_}@I^SjL zoppYc29g{}Zg`R%$qk=-6Mg^~tn+eG zwku;ykq>M80g@0`smwDWRz7E53=oBw08F=-Cm|*PQ^_32?}0eP%dN&q2$6rhzu}(+ z2xq)X*7@hO7S4E;NUnUm2WIQMc1BLKaPDL!{RD0J)Jo(vd!YI_*6asU7T literal 0 HcmV?d00001 diff --git a/Public/images/icon_control.png b/Public/images/icon_control.png new file mode 100644 index 0000000000000000000000000000000000000000..9b2b7cc9f6549c2756c745bc89fcb5c11725b7e1 GIT binary patch literal 3035 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 aHTRBcT)1@~q6HkXK~@%a<}@>(TmJ>H_`~!7 literal 0 HcmV?d00001 diff --git a/Public/images/icon_form.gif b/Public/images/icon_form.gif new file mode 100644 index 0000000000000000000000000000000000000000..9e5f33107123437d8be8563d78d9d131a1658ba3 GIT binary patch literal 817 zcmZ?wbhEHb6k;%Ac*ekxUpw>2$*V7Y9L`_4`zpZY`qMA9or~WUXZ<+5f5PmI>rcP> z@a^}?<1hX^e)zGsZRy(mZ{L5~fA!s)l*q?#e_Ve2`RBRQS8qPtvisDJ-+$|SmY%%x zVdmoPrOk6+fBLog-0M3pzO6p_@>Ph(|NsAg9zFQ;?5R(ad%pho^Yh}l_mu_ze*Qdu z_WF^V?^`FXeEROk_6u*`q{jkPUb=q&Wq`}==U)rzW}d$L@zuwlYfru0c=q-ESKn&7 z7k~ft_f@FZ>(9RrUVp#(_{-1V|Ngps;pc_3C(qt&pSW`C`PX&bOWveLeV*ET^Xb=# zvo{{S`F`icH*YfH&ffp{{rBIupMU**{P1;%*U7sdAHV(i@b&kf7taDMWf&l!_>+Z^ zogth-2V_4eP8it#H-tAesjIrPu(+!_HTQ<|YA{WjJV}GM&(UVeY$hAWX5LwIX4^~; zXP?8tF^4@|bg`z=Vo}w_N}J}iZQC)sefMIPMYA^^&|kc2u@K+lGfaFc$5+o0T|P%s zb4}C2*(atfZJv2{?)+Zf6^q%I^{rDCGE($az4H3Y*Kgl{{QT8t$gd+K6JgU4c%F%y zfkRj+CGp@zRw)KEo(2VuLk+wP88H$f3=6p&#dst#DqL9)F*NZoco=x7GB7s_?Gc%h znCjhPz3;qQTlYVc?x_c^AdxE6Y z42^^X4vfOQ#tkNi1RB@e-k#T|oFI_Ww5FTMtc>GU;^GGGO=@h0I|@@7TetBj1w<4) zWovdzS%08Z`{&068zmfk*cun+DD(69^t7&Cu&&*$uQ;QPTj}@%H*JY64u8y?xY@L& zG7}sYo_eAsB9W0{()fm5dVyrchL4RoqVC2g)~%4XGHYh~V$~29u;4%hzwEoK717S} RBAWs>=_TALja6W<1^`?$Z|(p9 literal 0 HcmV?d00001 diff --git a/Public/images/icon_storage.gif b/Public/images/icon_storage.gif new file mode 100644 index 0000000000000000000000000000000000000000..c87a42ae3c783f56488b3164491287b9e99ba0db GIT binary patch literal 904 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 LNkvXXu0mjf6(O+< literal 0 HcmV?d00001 diff --git a/Public/images/mid_banner/banner_01_repeat.png b/Public/images/mid_banner/banner_01_repeat.png new file mode 100644 index 0000000000000000000000000000000000000000..bfd9d844f5a7e8dfcd925aad7b8e8bbbbcf61717 GIT binary patch literal 535 zcmV+y0_gpTP)OlLBIFFeM{1Wv-6W(0UyI`ZU00k}dl3fBnmrWxB$6%;rsgaY8ROFIF-PJs`! zX26$~cH#)|AEkL?uY>{#m$*iN_msZDar@`B8}R1R0I!9Sz!(1eDib&f?-2sLN2qYZ zZUtN+;hD}zTqCdcb4x2Y1MpfX(2Sci+wUo2of#AgG-Krq7M4R{JCo^*)m?~%<*@2H z(`%~_IXAlQ{yCn4&?>mx*FD0}t|QOQNKcQDC8b9Qo^lYg$wD}mBl8?rK?v~4LYC%o Zd;zuj35V3X*sK5m002ovPDHLkV1i~v-KYQn literal 0 HcmV?d00001 diff --git a/Public/images/mid_banner/banner_02.gif b/Public/images/mid_banner/banner_02.gif new file mode 100644 index 0000000000000000000000000000000000000000..6166cf51b823279349cdd1e753a24957c523c2f1 GIT binary patch literal 40946 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%0DbWd?oVfoeqlkz|c+`d`I75s9#a-g{ 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�T{K3&E2C;h3rCt~ABBSYbm-59EI#2y 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

$>2q^|k!`&*lWd-lJ*QjwE5) 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%0A6U%-gda+ji*g8$wDs>k^vG8HNtPUv7uN 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 literal 0 HcmV?d00001 diff --git a/Public/images/pic_qcode.png b/Public/images/pic_qcode.png new file mode 100644 index 0000000000000000000000000000000000000000..85106ea9537666eb3e25fcbe78ebcd4df17c1d52 GIT binary patch literal 39686 zcmXtg2RN1Q|Np_kG14(Y=D{(Ny|U-QIkK~}SM~}YduQ)iMlv(9iOi5qc2>$rc9~hx z|MvY~KUc1SSLK;E{1ab$1MrlJJSmfX{ABG3M60jsf3;u#W zm&fS9z()YgIvTtta6=nDhd_wPZa=Z6mV^F+FH(6b8h9#r*jRYlySgBB?44~O0(|^J zJR&>-LOauEvEVIqw{LmkYU}x*g@+A9&f3+|2BBo{Ve_A>hvRdE!V{6L_WF1T1OdUI z#jLFbPCac?Te7gTM* z8#_xr0z09?NE9xqCQ4RxRTO+wH}Bgl{!Z$6m6Mm(vGj1H$+ye&dE29_QNi)i&&S_1 zf?->t8Hm*Rnpw6 z;)gsAWPQxFugEL_C)J>K4xHVQnd}si3r0VQLUkD!)u0~cJD$E}5w;sSD?MG*Hj7D) z7fOxRocW#RoVE3JpY7f?)_y?a37-Qgx==0fo!)nGamJzKm@odPbCeNEN$fe$g!s5Or!J;G3 zLcvwcD6I>rIMlK7*!U}0VL66qJiP3%oP$$F9lP)duKS%v#`~xQ^^Xj7l3v(v4ui)^ zH@@DFV2_;tS^h>+<0M!P0*POjT$|5DbX;E2V>q#|J0 zg$|Jw|FT{y^$8MGZ4+tfG)+2{EOeFehpzXtju|s&5~H}8$+6%BgX}>lf)>qPA6%^1 zVJRc0g_+2S8&wCyIG0v>SCX43@#{^w6bidYJWMJXW03`!r(uAfANjGxAmqRG&eOhnMKKWLo8#8O1M*ma6XZA&AO_w zr~P5MZyzB~9%}t;(?DJ3`h-@EZRC!ST7EAd9yKvBM{F2JEO^LVBl0V_(P~|KDnc6S zebB7n>zuO(!H4MssS#@glXla8S1Q?TiSx^1FsyxLi=>5X4U~)6JI5S;7!J!a*2bUT z3cLO;j(1XDvH$8V^GkhsP&ikjI1npx_=rbqL@%c4LPYox5{8Y$4Cf$VN5{SHP9{jQ zo96Go|2@^!-C#h)geeR99?)Eokv<-U8?_3V9p5?m#v`kwF$QrU6 zty^$3dIZml()sH72eE{A&)f>`_s8R5z>Lai4aejdub*jIV#x6M?Q_8ouP8Fa8XzoB;(F|6V2}!!&a7={6~-3 zC@`V?e7Bd!M@)=|!$nC%-5-KR#%pp!XmXs7XJ_v`3y^l@F8tJ8Zkq9FimaR?iZWP6 zJOKZalFZi-y&>Ycr8%%QX{EoopJ-@k_==C3nodkRtgw(-M^~4HE+zyCJ7^Cu^|Em6>9h zsUzh$DC7}v?D6N5pJDRfJWnX#a7C;z_^+MNPanruoaB)`J^OzUbR<}^@n=fxsjpv@-9d+Nc=N`jZi+?p z%GGk=M?*$DU{Sq%*~GO*?+~(yb@NWoL_|cKgdFezT;|Hb~z7VfM`{=Wp5oHd1z^BhJf35TeQCz z92x?xQA9vcaK+<8a^}yl1*gzH%g_aqrzMksmHfWy9Zr9`^x+1k}!S=mpO zHSM8R1BuKM>{PR}v#=-}b811ny9^P6pQ+>OtOm?SUrthsc|ZvX3D*XhW3&rtb%|o2U~iwqlSuUTMvchK5nZ<4cUb~9K+2TA?T8Uf_MwnCA2=)*8+Y!*j9p1c2iwQgQ#pzS4<7bc4ck@Eku*7mz|Ad&vN#qJkZ zEnVH^+1{Pf=nB!WiP_h=?|WAVLqOH}Dbdh*j7(+!2`uO z&9y?CE3#LOFHkcM35$*jIB%-EZX#vw(gCNTbT{qBz zMCg|&(N@1rztz093Q&V)i}Y-$MD*?VINXzW(RLtq<>D|z4$sZQIQcZVA5@_M> zM=a%a7JQah#vqldd=I+u_aA5Rbnr+?UE4-lu-Tn3;y);y{e8v)HWFFPAh{SPGg0k= z6CX_iq!7gd3zHlw`5P;XKs=@<HuNeW|>Zl7oZ!UTajf;>#eC!~I~rmLqn)Zr%T# zNgL(vm+L#y`7-5o@A2f|V5D%wI{5Jl`S!NbEDIV`N!1$Uxy|nnj*f;5S%(d*$IwX| zpFEU`GtrTNZoC>s#Pjj% zhv)9ScOHcla`;HT7TFB)jL@MY!G9h2$FTkH(Fg|*Pvo*oRJS=g)*wKd@k`Ye?U#4H z_}_!O1h)_S!yx0&L5thowg<}sTD?m%zI1+<+K+5PR4}n$>7g(KbjsM!t#xm3co<+G zl(X|#>+tZA7i0&Xp^o%YE_L z2n;j(3KXI=b#-5?tyx0D<%EQU%#VMLiSE8@-~(;h*k|>{;Mf@Mh|(P|A^NJ&I^Sd4 zug}M&M17BtN5{wR+a*~&t8Q=V>PT}$oaGf2aq;fZMBIC*XMXf!NP9U}WH~wF!Fd%t zws-a8(ZX8(I2m;dgH$(MVr~5)MKHjLi4L z2l&UL_MgyqY_uK%$C^){;wKGE_mL4|$(b|78dD_}9GeR)ta!90zkY=mw}9Q;Hu==p z_=FZB99<#7^ocB1{BP%6GDZN&+`;zMH0Z)+t|sWTa>+?#$rYVC>bxBRAPZ=o=7y9! zSwtylC=#;r&P54i6cOkJu{D2nE(-&W~e>2t)- z(N{Q$>)>YM9y}>snd`a{1}J>Ux@S1!A^-EAs`0dss{8oUv-Hh3hV^S5W}n(Qc{YpN zzTQlJ{hFkqFCu%#k64v4QrzrLPdfhgXHW3Xu+0l^+I9&NY)M8rPli)GxSP0OO3E*J z+qC`S`lBC=ySuxOSoFkG!WL(C68Dz3D_o0<(CLqgnjQ%HS=|KhRy7NBnEp+5@8JOdv(I#r$D@KidvycJSy@rfUi;!b%MTP`2s*bI z%@!u&8tMP>9g2;0yC&=X3G-dSGR!Hk>XtP^YGOR!ww;Pfb0He{^8<^3KMT*?#PjO+ zi;HrEC{O)u>gc_i9$}c*L`jHyKe{xp=l}c5ZtA;n@5j%dg#`szW^MU1dSn zM+Z?dk}rys;|@=zHJDgfI%aFWnDc$4bwafY6U}KDL_fzf!w#Mxf-@82;T6tjL~0DN zEIvhq+4RDcnPD$_Zkxx#9u=e(n~ypBKcz>`DoLdt>ZG#cTpW4tRMOC#OyIZK%yBn= ztsk^);6}oFO1;RmC~iwObkn!}poggC;%5%O--+FiA3s*I9jm*eNm;-({lx;e+zS{G zzrzeidREq>*(6xO`}dT+QFrIK`h5EC>Az`g)Va|O23v&>eD~f$1O9C#MBbr|tf=5L zcJI4qXJ;2}ijvra4qG+0GaIE6Vp7Y>%ey~@;?z0JR=gAl1@Hni0;M9-aj7#pfG|ED zOMVMHGoh38f&cC}V*nUeU1Gnn<#2J|Tk_yzV9Rc`#KC|}^~e016Ax^RB6L78d37{m zj{c)eun~@H3xnbI!W%Rp-2jV-%)XBN@WA#1t(qawwU{Vk{b=Y(@KxE4?ZOaFg&clx z;T{$19wrs!R2E#Y*^lNGlH4v+Q{}^Mb=$jc^!r4sjEWe?dfLxiOqUE$W8o{CDF&`jQ`~NI9wBv8E$Zw^Fa5un@4G9+zeo zYT_Ozk&&)*$PfMeas0OB#Q(krQf=l3OA81HXc`&Ce!uzC{8HGF^1bVKl1Ph{;|p!h z&yUGX>toPX*3@`hBVkC)d4TkStyP@?0FH-Wp1Q977)k?U*1L^VI?HlFM%9PW3BdYW-`diMKoPC1VW*hv+sSSnKba^?#Ga_ga~aXl?P_B zchgiCnG@_>GBPsep4r$)si*#cRFS8rr>_h!2BSc?yxk*C%dPh+^{N2C?2qH^jCSq3 zz@swtVPaFujsW!$aJAojTX=v0+%E6!n@ww|*wQdO#7;sqR>MT-8p-i+3+mCFM|WSm zc;WWJ_((L7K`Pew_-FUNcldgX`mr%`M;Q*KPUwH>7F;>YNgKECEfHdyqrySa19~zk ziL#<)RBtHwomeve{tABcS-%4wB3Me$wg7%baC6_G8|dDS-*CwqiFy0lkM6&3A0T&S z{&Gm4ul$B9k|HsoHY^+xhM}XQ#6Mat<={{N1`5Cjku&Qgp>s!yYDigCtc`Ci7g2Vp zg~(Gpf63SRaMm7(d&Xt{5vL7B?Cm-I1+7Lj=&$IXU(2Ak^Wrbr*xFitiYBF&36c!44I3I6QSkK@ zizL3!yksbvs9oRQq*)L%A*&@$H#}8GBths&Hrf5md_A>_7;d@ z3rR_$+yLz*##49_%!L-H5b)lJ_+{j;wgZ;N4#BD>XinV=5n59HK|xklK`77vuT|nz zW9b*<*G%rGw!tf*_?d=WnM(x)){jI))@Jq1`f4qD-4<-JR!ehSx`tx``IXXbIYmek{o{jSafK97%D<*}=6|iv>-QD^7 zlNi5SiuD88C9W?_-z@On?RUiA7lg;4;3QER3%s>`mfe6;@VS1Iw6;c#Fu2i0g@s{% z_1(;aL$SbcwXv~j*~okf8Yk~y9dGK{+xB)kR#sNf$Up(pQB}n`@KFP+bNs?g=xK^oS=d8;RjEI(cgRe2kdO*yoXm z%U{XQKc{rJs{W7>NHVIe!*f8hjIA$PNSm&R#aW>koOD(ea7ZX6I$!?p`&jGEnDH8sEgu--3eZKRB2wGb#s7o{eNA|=MNB5sCQOQei%rky=}WS@_K)uUKS?#}Q3 z5uH`;Y4r;3?Kt}um7Tq)W9&ye*%mgtCfBpAs_gB}maEQAv+VM3buByzM>N!~9RnFt zOrMc+qJvboNo_dmI$B3c`kidq;Ns%ekU~kR9+DBhSUL0&S{F|5%Pm2QZbYbvtg1WS z>+FO)P?U{LR->?}{EF005sB-Y`jHxcccJFpyPm4%8=4%E>$7xIeMvvRQa8tFN{wLv zhmEp{T$&{UwkAStAc-uQs$mm}(Hm_~CXASwF(_svWDQh#6WI0}IQS%A-zXwaPfyF< zy+d(Oq&_*Y&rT#*)Fz8d$1iGXis(cmWz$itflqyF(ot3SB;Wq!kuQLAK76QUX2v)d z?BYTKo)VO|{eVlA&5Fvn09rRkW5$Zg63$>6Ytoyg^%Ts(JdH+WqC2mCwUMq5B$+*) zKH>0+3YCR`T1i186Gb7Pe2r!zCi8UDei3`^#cEroeS16=qvNl*2$*lrDqpVpvv{c& z2pE@PJ$06`2fs_JJwwpVg>25iba$l#bLzM2@B-t5N&=%Fq|bbGMSeMy#S{yZF=4^) z-MiPouAY0|M41BW^-XJQ>(JP}hcW2OuFEVz(()}k#u-#q=XbJ{GS+(lm717vE8lSP zd+$*@eeb;Y=&PHQow#1Bma8^DN6B*O^&Iwz9 zy&IQ`nuJdDKnD;{MpzghhXip{ug6V%{geT-M2tFfxw7MwS)m#yb4fMHGd^m!{U*Ux zPw_gN5j{AR;yZWA{F?3@iLqkZY7>y}pz&-(@6;+05xo|j)z#jD*(0JVF@f5l#Vt09 zZ9GFvH~%t?JCTs1{pRnVFONoeW!@Wb#mxqyb`A6p6ogG^YK7>;7<@{cby+q}H6w`uG)aLSz@IPgAI zP{1+BR8&$@QZ;+>R=qou+sxwfWSfJNGgcz28$wMi5?#o#nb&CbsYBd=o01t8>!eNk zKz1p!Jc#$V#B3@?07a(H^QNq9tLJ!JCO9n{4IaLOg9Fe-ZQ7!ZXeN&aw@_23nvWxm zUNdfA72DGUbukJH3&WzUMp#K0B&nC)JNM3%XT1^oAZ#=);vmQ(L2*t=Z_Ajh0!g+O z$}9sk&9ufi=*b9OLCh0-LHh*sOf@?h1av&H<~MIvOyqSrKmI-|YHg*F93QxE&@>30 z(9Ql8i6Sfd@*i7Yoll492kQ6GhJ|=q*LEy+^>ko(gsb(N(CAg(vuz-t8(;#f|FB+^ zQ~0ps8#gh$)@UEDSYYoph+rhdeUESuBzPkxs@9R1=p?w!DPTXej9fzR?Cu8Vyn1!P zUN*yDGZ%Mxc}ab3gkj!~A)lP|LL-Ga&mN;>;bWL|Z8;Z&5!rFJV^9nLl|4Q32347* zWu{mRNmP=&VaTOp;*-xbY=;Q*x_Mcx$*WQKcFI>}k45^Ehsv`pxP6@)DO;IoX=oTc zKNu%fNO-g@yR|L5XY@3-3l8hkOW_!iHfqB<1^hBMeMRXKUmo2dtEhC;G&AE(Q+~YT z!f6#(ymN`fgxM_c1XErI$pA6#)yflW9(}f45(!mX^1ngB#qZvoJbe%^h8;VJ#Zajdp+b@R9pqF)!D$D^20y*u5W!h+B+2;f<=E%XVUu5TP@{K$t){gXVrMZN1Z-RR&4YLiD9N747H$M zXkd*k%*D?nhRQ|XPtwU@BlUS5HE9jBGry-g@)waM*{X}G$e^x&hH-r!S@q?ybqt_D0g;tZW(c7qY&|Z0cZ-^(~#r=X+w!W zPc*h@=98??Xb1U{R8=EpGH!o@?L!|1eR6^UcQCKAde@qeWU~5>ek?N)g}Hx+ufb0D z^72A!Xe1H1rPZf2!4&D&csumZs&FGM;Dj$~{m)z^uTQ3d#`9^SK*4?W1=U(bWwrdj zn`y zn5#Aco4e=V&F_C}X&Ta4YSS+TpOK&iS_ylAmOvQShn+b6*&N{Z5&r*v8Q+4!4<9}Z ze*UavVq)U)r~{|8Kqm#`h?1``1M2lZ{i;P2GwICS96;@qtYot6DNnF40?EbUCnrBu zj=_ZT_~Z6;MD0DBc4csA6EYo*3|m|@Dt`NRz{d}WN+_*#T?U(cl_Hm?uos)YEyg^# zqVCwgJFj?dv9QR%F0SD1B3as>uhl^PJj^aW|tlUY-G7T zRWb;`FdwzkVM`+OJ$E*4+*V+z1_oZc=BaP~dmu3xQ|vECp*X+q4Oqp?G254_Y03%}jWW@qFpom0r)V>+>P>tFO0uUXSBS2?5@`tIfn4`pi z6(9>IksPPNjA~_*=ZcFpr=|okpQJB-kn<6T16c(EdGEZ$rf~_{1SyLW==7^OuAOpn za#$W79ss&_AGdSaJXTr+WQ+RI_W;5Ah6a9M_yDdE8%t2VN~gG2U6eO{x`C*vGHvNz6V_tsN{>%UDQ-lvJ)9tI+9;q z|ANvpGP*76%x5cfTeeGcYF)oe1GqJsFT)6k?V~+!e^r=7FCPal?*NdewRG=s6Psvh zg#z%n0!V0o&kG{=>(jaQoywa2ra9BNg@$KVfb9GaQLn}E0+YvqMh(<=eNDM8Bx?jW zZ3yWf4*BF0GxH)hwR@`MF@SxUJSKz?pRIx@2#S>oFdBLZZ(upBR8%&)&qw}W7NEN4 z*K(Wt(GLuGD%1%|w|o;A1}*D}61C1tU|>aq?Hjf(Y8-U2ap2eSQqordcp~mAAF*z& zDacybpGJhq_ZV{a18}8li;guf=62O~vcpdsKEwv{l^!+iIQts>P`!HkRP?GmC z(huo)7D6$8IB%77OVH29T!U~RKnVr5Zp-m!2`mVh)jmMhFc)}ru-7miW@C7M9Rv#J ztN-r~gATtYE@!|m!4*j!4YK%v*=$i~zy}05-&gR&^@Ez=Ya*b$41fi&u`goneFw$J z4-F1R^AFqF+V-O+**Mj!_9RkT*AIR89KUcm&DUZv2LI*%EOlml%Ur-d0OSR9k&BB9 zP|&?V`HKUj1}x2ih6wOjlv)2s)kB zS147_yEF?!SZ4Zwot9Q~&x?865B)wRKj@O+R%L4S+T{a{1fmsV)g6ZGv)?rL<4=R_ z81M(W&2EmUAX-mjXC3;$hK&TUb_H~#Zs7JDUVZ<^02mKoKQCp1rN300!~u1Np`@e) z^vS4Ymw{WIW_VbIPCCHgj~g0YT+Q*Y<&|owXmcF`2KeyQD3-&639ajPOhuvx!Cjj> zWp+kdmSYYY5nqkQxhF41g59K-qa; zjshN;qOR_}`uh4?JrgkMtl81=aSb;LtSA=xg$0k2rojRQ{9;k^@~LdS+?aM_yJr}- z7~<;Te>U=l4Yq23(jPqeQW;a+*=d@FPLCAWs7##cb!aLCxaN3s93SHJt^NK|v#T=D z<_Fy8n;fYqxs9Mn12{#Ku-BWUFesnzk@c-Qb$#|7f582v*mE|}8VR&0>H@F4Z_5c% zxO?w~Jky1Q6c`fjz{nc5P9GGl+}PHl(G4xjdfVA4-LI*diUc=c2~%9=g?C|FGTQ@K z#sAZ1qhaUJ2aH8NuN|ET`GS`Yp4u_E<*u=X6Z7*N1F7hySk`R?8Vyl8m(z>?fJ(!O zGd!QE!;;~USn43?y=U+<_+~TsODa+f?fYhFUx|-;21%BOY9&eKKm8bL=hblbJ(yAb zEjduK@Zo_ik6gZT)O@f6?9QDIAVfa zoD)VfWk&Q&7=c7YSU2LU%YX4uxoM$WUG2#e97JaABJF>vb^*f%)Lt?%I$T3P3r++c z{Bw=dFIBUmTL0EakSE7BG>`cKX#(bkRJkd9Wb?Rt_tb%>w2B-v2P{l5>sO~WWL|We zCreR*(z_k5z+__^TL%U`a3;X>0G;mPLj?te@I8{{cHaiBFOR=BQRoPJZcT_JXmRtG z5h(h-cY6QRSSxJ^3Q&b|w2G$2f%P6ccY*S(9Mc-O2zu5u>pdhOrWqL~jtvVzm3yF> z4H8*`u6vE>vorQyusP}k+6^73Y|*Q|hPt3@KS2CL5rrFkYHZ{HX!1KR3K7!?!MH}i z1zUj7j?ejO6c&p8FKd=DHbAvtiOqN#IPMc%o&Em))17mcfm`0*$q9-{VJnZ-~da+X+EUAT@nap znKl+pMz_rw^0CEz9fHy=`y&5w)=IbXfl5veg~3~5hcx+btVLC+A*oIICk*Jh8ENfp z;t|!#siAh?V=gdEj1}N0)yPzejCrH#gv9C>9I>}48EQPBiPf~v`vE{#0cN_64itcI zAje`s7<^W+er#@zY*6;D@&T9|1175ZOqmu?`ar9>wK-_XE$C=2FS{PU03u#(+dZ-*RAzqTBCo% zgDaQZ@j&~lGLQtn1^)}A6Lo_SQF48Jn;#U*7<7(BM8$*B(jtY0eJs-|;qtQ z&`M5L=UB!eiNcY&IcNFRvQF~%d_9R8=t?54--r>}s_%herl9sZ9P0#CHK zj2dB(A1}%6xqtrd`o{otx?Aru#4f#*Bli4qw?<1}KkDLm(`vQnMa$1@$6BWa0Wtg% zKnf}2aJ}24cnoV;?G%Sq#;Fyx0EYtQ#Q!HPKAsp%<^Gh&R^IKv-;H^nYCdIfEpom> zR(?x{4AQ!^tQw{w8#3_E13E>e^w?8>5y-*KL22*J^8?R|L{U-E1M*Vm>wrtM)!DCQ zNa3$9fZ^pz`jY+68wf{Y0Cc(q057HdML@r+wVPzVwbTI90JU*DtVc&b0S`?}OA8A! zIF?X-a=Gv<-vYpOQK0tr;C?2TyHG99e^m0&C@g)u(%8J?cZGy=e$@Wy&qm4(6`;tg z3g(P~>-(_m;%h={!n}P0pa3<$B@~DnkTArpKMIgE$@ zZRN?PbKQLZcNKhFx`2d1Ah!@0I4fPZZsz=l=j=eK1i(HCkc(UE6Y}HK@dn(@`fLAR z-Sc%;hkaCL2wvX1bOZmhw*h`T1ek9XAk=UGLIC230Z_1?yFY_#CIZn{ph-+h*eHrlyv#_iI{ZWqLA@DdYav zMtkxZ+r|RJmK+~(ZffV9 zayN2DK8;L=%ujt-jLqC**Y@k0YD>$^j0D`7GJMW&>3FGluxWSRx?Rku+Wa&K8&c=` zz0;7mS5;3W)#1UGHyQmh*3PEJg_BCCCmFCyMs1&0rqnuDO?;`E41H{uwg##f%gMxM3X_Q#T{~v{HD2CrhX5Ww>^2ZL{5Ap z`M=ScbWBd>C8L*DeWgF+?z@S~f=R`{W2(U5P>deK=y5Vpav8YYm4kcu6zcG(>$`e- zK12FRm!zhXp;Oc6zGI4io^Wm#=h?+s7h*VEJ5Kf9&pIT;JV-fF&qH~hK<%X7IxNME z*`{^KIZSPF|6sy~V8Qp>b5Yi*jXDDV{raj1l^fisG{{UyQ~9CpB|kYi_9jR=V=9KC zisnx&3H&rNl%H-&v^ zTvHJ$Nd?W}T0f%Dk7mFf4rQ=yc)*5_KT!e}pO)-C+BC=R0%}@aaTirWo-?+X9$z+U zVgGr7DLd#}(YXKF@PfS;hg#P3H_~QGtV+G-Kd$vneM=@cJg$`%S}H1NfYL`$Wa728 z^;xr$M{=Cy;X|V9tFyEvgDMd^8B>NVE6PCJkYBW`Rqt`iRAE!b8fEj`L$eMEa9a6 zwCiVBm1;dslE=#!qlDzcimwD(la#+X!HVGz#G} zlTuQ!V%WH;P``Pee>68YvY!II}*tv+ZKHjOJPhTAb@T8YaiEx?e@!Wekd|C(q}F zyryd9X4#!c;hMSMH(6$YC2i*PvSZBFKGl59q{oP7y)(qRA#!8C8=tWeSY?hGcM2!N z#ACL$q@n{eV_J63^(t|p1<2HIsVD5ET#wj4>(zU1Pl>7g$9J1!G&JFyoOFD{nitW_ zWuSN8QsbkONFRp1BV2P4f5>(!b@krxILO|*>UjfQso-`wS&AV|TMt7uIq0mSn~cWn zoZ@-9NWCP{6+vf0pE!qVSsNeacCz9)8KbX(_kVOh@>Q8r@&CRt9W}+3hca(|RO99- zaKD?+`iTSf!i1dKxR=tOC(l8PcEZg%O$ zX4e3zGP^>Ys*rB(P#XJqh)N$Rw24R!Tmk-uAP>w=OMH7IqVVKrW7*og)BA_%_>sMI z)ctQhkR+1OcMuF8_@x$qbgx6oO*gjZmK{+KML#TP9#8tB0*6CxQO4UzJ4xk*ves6% zMRar4*4D;Es`%cY=^x|wBV!yZPw>jGcB+nxSzQy`$MR)@IUyH`bzDSkV-#64ZxxhX zUx;5M*(^|#6>zA0-qa#f8JEX57DML>(>3r)NL(?Ws_SPz)_3B>~W)~T?LRQ z;E<1ZW%jlszKM%`K~-BAa$W$a>Ptd_1X;IVBwr>sNxjGiDkN0o#dI;jc;Q%^`7#8O zyygRmCM&+u8=I=oDnB+A_^Vg1_)Y0zR6Us|C-s@6{y7K~*c+wEYO8T-cNi5-&ik!X zgeg7zL)NfBr@-dWb-`~2L-d(fXzldYeh0P}F++;6IhuxnDHt<5z}a6x@{} z?`Q~<;y;Y8<)`??@H8Z$MEJb!OL#L$I@5qPomWri~?T=f+%?LKS>*FOJK~c zeIj_NTAF^6&jrW{z;c?JniD1K6O%!Wlr5zwvq6M<+0R=E{Q~miewB067hCgGgn7vW zrw6Vd8)P!glSg7B9!e966^nrXz~w{6Jn*&WIE1=+XOc4|;$z5Gw2{o6K#T;i zoQlum2O4M&7JaezeEwaZ-G zhpYglv>)fLWC%#D+zRUeG54HoPv2%;a(#ckYWa6_wVsI4&=>$Y8gVbW=bN9w;RQLs z{t1bQ_=9f(fba2`=K~aEJSHS<7nN>VPyrdP!7Mw+yi7SA}GUVBwLg`QTk)l`NFWI zct)sP{I4Pc?A^x|2OC3v)Ew9nmCMPaEK$o?=Y|YHiBar5a!^?^G)6WTs0BaAat=4L z%zz!T8pHm2!s%JV!pC|p$eZeF65xd3U{XO)q{56|KuP1LuW5Dv28a*^{q+GNuz0m= zcy~zG&1sE6n>ROu?+;SdR|1O9>4ek1FnFsN(2iWgk5c+gijJ9LKX#4mm=_LmtO}EcXKlD`mSMd?WB0jqjlOuh zpgPERUz0q3rJw=N;)x+Wb33FpEurpKy9NaZthg{Rkk+1}>Iv8Y00=7rDlSp`} zJs{4vD)+NoQZ+RJYT}lYsmDP3Q)NqYYSN$U($!nHG^7N^A5iV$?}sn#_uqP3fR+FK z^Q?RvV`(L~s>_OsQyG+8FNvRaob5t{muw4uaNk(qZh`(WF;Zh?*$eM4z zXGZy8Sx!rXYmSB}!V$0#s!uKBcB#o{f=H~al+YJ!5~G=9;24G5l5_Ja2ypZ^+D}{V zcqP(_k%63^?N@$wjn|(5gPz8)M6ca$K|qn+KZ|Xef6f)-|3G)ktz9}VezsVFWPTKg zU#QbZB__(zPgX>r*e1s<6@L3i#I@w1(FHMs#lq2$ucRbNucfFp)aYWhUE%t9nJ=1o zXmH3CdqzCYyBhFU&(b1STX-WL9t$3RQMA&eiZ!2{$8Pue=5{W|5L0BZ7zI*ZmJ9VZ zpeuq*sok<#WZ9{N)|#IG?H1=UsD*$B&5)$r0IQ?n8u7jq^(-cl(i7N5kmU)qT;^`;-wEGqESb ze`7MiF{dZC=hlWI+wOeC(op8t{_s8u+jIKaFBpC{68^)-$B<{E1dlp01ycz0CXibLc`$J122W87#KwR| zDB-;)>*trFri=l{XRz!j)4DEx(mE~F<8E(n15p!+5&3YBKu2G{ch#-fnhqSx;>LM* zd6*FN%^%G|YLuEMfz*GzhqO;nk>Cv7x}zV=wy$x|!O;;5(%0AL0XG2ojc-9$&S1*` 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 RnbiOQ002ovPDHLkV1m8L0~Y`Q literal 0 HcmV?d00001 diff --git a/Public/images/popup_hint.gif b/Public/images/popup_hint.gif new file mode 100644 index 0000000000000000000000000000000000000000..a0e5c9c332baeeffe0b315fa441b4b793dccaf7f GIT binary patch literal 255 zcmVF( literal 0 HcmV?d00001 diff --git a/Public/images/popup_hint.png b/Public/images/popup_hint.png new file mode 100644 index 0000000000000000000000000000000000000000..48183b4a5e3af7eb713df0d18801d79126e2e80f GIT binary patch literal 457 zcmeAS@N?(olHy`uVBq!ia0y~yU@QW%l{wgeWM^aJdmzP9?Bp530R%N1DIGvQXMsm# zF#`j)5C}6~x?A@LC@5ay8d2h0l$uzQnV+W+l9`*zU}Ruuq-UyUXu>M=Vi!<}3`j|E zep*R+Vo@rCV@iHfs)Ac)QEGX9QFgI{bFkTqJ)3%f4p8uPaSW-L^Y-RO-@^_PZ4b{c zTj)Js_k!**ww6hH%Bc+LeOoG;7B?^{E@WX(`un2t)#R_C>;3jAdPZgMR_XUNV_eW= zd9rTSvb!GyKQ5oj&bZ;s=L=EQy|XPGLO)9gvaiT7UbmN_;WdY)L!lF2z?=!pBI-{V zj&f4Yy)z>vu9}6R`)u;0zqhm*B68}>t>*IGep}}8X~L7bx2_B^DSn@fd@tukRC6Av zzaI7a`8k>XJ7_}CDBmO#Dc^-%5fwMKe-|}5#8bo70 io&O`W_xqQ7pV` zUgJ6C3d)qYMwA5SrpzAwHb?Np!yxV8kRb@WvU{aJ;?qBz%_Nw;0d46BByF3|otXd@% zY8wCRoP=_6Ps|?yhUKeQeSH7pxpC4wmlKTZo`xRjw^M!Q#8<$Y@ABuxqZ$6k52tOO zd2ctzfuCi&?^dWkTEF&~#PU18^Jgu~*?!wl)icyq<>$O7-~P;G(0QPGa=ps;)vKy} zisnB_yXng?=b`tc-723$LeGCx<-2h8w2UD8i#MzHhcV0&E^2tzBD26!kv+rj1S2)L zJ1tKpz7%ArxsmAiN%>?%6(@uIr{b;m-`B6d{#xbdv?pl~9U104oA5;QgTe~DWM4fDu2h2 literal 0 HcmV?d00001 diff --git a/Public/js/jquery-1.7.2.js b/Public/js/jquery-1.7.2.js new file mode 100644 index 0000000..c338fb3 --- /dev/null +++ b/Public/js/jquery-1.7.2.js @@ -0,0 +1,5 @@ +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ + +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
"+""+"
",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
t
",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad85157 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +## 简介 + +ThinkPHP 是一个免费开源的,快速、简单的面向对象的 轻量级PHP开发框架 ,创立于2006年初,遵循Apache2开源协议发布,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。ThinkPHP从诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简的代码的同时,也注重易用性。并且拥有众多的原创功能和特性,在社区团队的积极参与下,在易用性、扩展性和性能方面不断优化和改进,已经成长为国内最领先和最具影响力的WEB应用开发框架,众多的典型案例确保可以稳定用于商业以及门户级的开发。 + +## 全面的WEB开发特性支持 + +最新的ThinkPHP为WEB应用开发提供了强有力的支持,这些支持包括: + +* MVC支持-基于多层模型(M)、视图(V)、控制器(C)的设计模式 +* ORM支持-提供了全功能和高性能的ORM支持,支持大部分数据库 +* 模板引擎支持-内置了高性能的基于标签库和XML标签的编译型模板引擎 +* RESTFul支持-通过REST控制器扩展提供了RESTFul支持,为你打造全新的URL设计和访问体验 +* 云平台支持-提供了对新浪SAE平台和百度BAE平台的强力支持,具备“横跨性”和“平滑性”,支持本地化开发和调试以及部署切换,让你轻松过渡,打造全新的开发体验。 +* CLI支持-支持基于命令行的应用开发 +* RPC支持-提供包括PHPRpc、HProse、jsonRPC和Yar在内远程调用解决方案 +* MongoDb支持-提供NoSQL的支持 +* 缓存支持-提供了包括文件、数据库、Memcache、Xcache、Redis等多种类型的缓存支持 + +## 大道至简的开发理念 + +ThinkPHP从诞生以来一直秉承大道至简的开发理念,无论从底层实现还是应用开发,我们都倡导用最少的代码完成相同的功能,正是由于对简单的执着和代码的修炼,让我们长期保持出色的性能和极速的开发体验。在主流PHP开发框架的评测数据中表现卓越,简单和快速开发是我们不变的宗旨。 + +## 安全性 + +框架在系统层面提供了众多的安全特性,确保你的网站和产品安全无忧。这些特性包括: + +* XSS安全防护 +* 表单自动验证 +* 强制数据类型转换 +* 输入数据过滤 +* 表单令牌验证 +* 防SQL注入 +* 图像上传检测 + +## 商业友好的开源协议 + +ThinkPHP遵循Apache2开源协议发布。Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再作为开源或商业软件发布。 \ No newline at end of file diff --git a/ThinkPHP/Common/functions.php b/ThinkPHP/Common/functions.php new file mode 100644 index 0000000..417f046 --- /dev/null +++ b/ThinkPHP/Common/functions.php @@ -0,0 +1,1550 @@ + +// +---------------------------------------------------------------------- + +/** + * Think 系统函数库 + */ + +/** + * 获取和设置配置参数 支持批量定义 + * @param string|array $name 配置变量 + * @param mixed $value 配置值 + * @param mixed $default 默认值 + * @return mixed + */ +function C($name=null, $value=null,$default=null) { + static $_config = array(); + // 无参数时获取所有 + if (empty($name)) { + return $_config; + } + // 优先执行设置获取或赋值 + if (is_string($name)) { + if (!strpos($name, '.')) { + $name = strtoupper($name); + if (is_null($value)) + return isset($_config[$name]) ? $_config[$name] : $default; + $_config[$name] = $value; + return null; + } + // 二维数组设置和获取支持 + $name = explode('.', $name); + $name[0] = strtoupper($name[0]); + if (is_null($value)) + return isset($_config[$name[0]][$name[1]]) ? $_config[$name[0]][$name[1]] : $default; + $_config[$name[0]][$name[1]] = $value; + return null; + } + // 批量设置 + if (is_array($name)){ + $_config = array_merge($_config, array_change_key_case($name,CASE_UPPER)); + return null; + } + return null; // 避免非法参数 +} + +/** + * 加载配置文件 支持格式转换 仅支持一级配置 + * @param string $file 配置文件名 + * @param string $parse 配置解析方法 有些格式需要用户自己解析 + * @return array + */ +function load_config($file,$parse=CONF_PARSE){ + $ext = pathinfo($file,PATHINFO_EXTENSION); + switch($ext){ + case 'php': + return include $file; + case 'ini': + return parse_ini_file($file); + case 'yaml': + return yaml_parse_file($file); + case 'xml': + return (array)simplexml_load_file($file); + case 'json': + return json_decode(file_get_contents($file), true); + default: + if(function_exists($parse)){ + return $parse($file); + }else{ + E(L('_NOT_SUPPORT_').':'.$ext); + } + } +} + +/** + * 解析yaml文件返回一个数组 + * @param string $file 配置文件名 + * @return array + */ +if (!function_exists('yaml_parse_file')) { + function yaml_parse_file($file) { + vendor('spyc.Spyc'); + return Spyc::YAMLLoad($file); + } +} + +/** + * 抛出异常处理 + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @throws Think\Exception + * @return void + */ +function E($msg, $code=0) { + throw new Think\Exception($msg, $code); +} + +/** + * 记录和统计时间(微秒)和内存使用情况 + * 使用方法: + * + * G('begin'); // 记录开始标记位 + * // ... 区间运行代码 + * G('end'); // 记录结束标签位 + * echo G('begin','end',6); // 统计区间运行时间 精确到小数后6位 + * echo G('begin','end','m'); // 统计区间内存使用情况 + * 如果end标记位没有定义,则会自动以当前作为标记位 + * 其中统计内存使用需要 MEMORY_LIMIT_ON 常量为true才有效 + * + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位或者m + * @return mixed + */ +function G($start,$end='',$dec=4) { + static $_info = array(); + static $_mem = array(); + if(is_float($end)) { // 记录时间 + $_info[$start] = $end; + }elseif(!empty($end)){ // 统计时间和内存使用 + if(!isset($_info[$end])) $_info[$end] = microtime(TRUE); + if(MEMORY_LIMIT_ON && $dec=='m'){ + if(!isset($_mem[$end])) $_mem[$end] = memory_get_usage(); + return number_format(($_mem[$end]-$_mem[$start])/1024); + }else{ + return number_format(($_info[$end]-$_info[$start]),$dec); + } + + }else{ // 记录时间和内存使用 + $_info[$start] = microtime(TRUE); + if(MEMORY_LIMIT_ON) $_mem[$start] = memory_get_usage(); + } + return null; +} + +/** + * 获取和设置语言定义(不区分大小写) + * @param string|array $name 语言变量 + * @param mixed $value 语言值或者变量 + * @return mixed + */ +function L($name=null, $value=null) { + static $_lang = array(); + // 空参数返回所有定义 + if (empty($name)) + return $_lang; + // 判断语言获取(或设置) + // 若不存在,直接返回全大写$name + if (is_string($name)) { + $name = strtoupper($name); + if (is_null($value)){ + return isset($_lang[$name]) ? $_lang[$name] : $name; + }elseif(is_array($value)){ + // 支持变量 + $replace = array_keys($value); + foreach($replace as &$v){ + $v = '{$'.$v.'}'; + } + return str_replace($replace,$value,isset($_lang[$name]) ? $_lang[$name] : $name); + } + $_lang[$name] = $value; // 语言定义 + return null; + } + // 批量定义 + if (is_array($name)) + $_lang = array_merge($_lang, array_change_key_case($name, CASE_UPPER)); + return null; +} + +/** + * 添加和获取页面Trace记录 + * @param string $value 变量 + * @param string $label 标签 + * @param string $level 日志级别 + * @param boolean $record 是否记录日志 + * @return void|array + */ +function trace($value='[think]',$label='',$level='DEBUG',$record=false) { + return Think\Think::trace($value,$label,$level,$record); +} + +/** + * 编译文件 + * @param string $filename 文件名 + * @return string + */ +function compile($filename) { + $content = php_strip_whitespace($filename); + $content = trim(substr($content, 5)); + // 替换预编译指令 + $content = preg_replace('/\/\/\[RUNTIME\](.*?)\/\/\[\/RUNTIME\]/s', '', $content); + if(0===strpos($content,'namespace')){ + $content = preg_replace('/namespace\s(.*?);/','namespace \\1{',$content,1); + }else{ + $content = 'namespace {'.$content; + } + if ('?>' == substr($content, -2)) + $content = substr($content, 0, -2); + return $content.'}'; +} + +/** + * 获取模版文件 格式 资源://模块@主题/控制器/操作 + * @param string $template 模版资源地址 + * @param string $layer 视图层(目录)名称 + * @return string + */ +function T($template='',$layer=''){ + + // 解析模版资源地址 + if(false === strpos($template,'://')){ + $template = 'http://'.str_replace(':', '/',$template); + } + $info = parse_url($template); + $file = $info['host'].(isset($info['path'])?$info['path']:''); + $module = isset($info['user'])?$info['user'].'/':MODULE_NAME.'/'; + $extend = $info['scheme']; + $layer = $layer?$layer:C('DEFAULT_V_LAYER'); + + // 获取当前主题的模版路径 + $auto = C('AUTOLOAD_NAMESPACE'); + if($auto && isset($auto[$extend])){ // 扩展资源 + $baseUrl = $auto[$extend].$module.$layer.'/'; + }elseif(C('VIEW_PATH')){ + // 改变模块视图目录 + $baseUrl = C('VIEW_PATH'); + }elseif(defined('TMPL_PATH')){ + // 指定全局视图目录 + $baseUrl = TMPL_PATH.$module; + }else{ + $baseUrl = APP_PATH.$module.$layer.'/'; + } + + // 获取主题 + $theme = substr_count($file,'/')<2 ? C('DEFAULT_THEME') : ''; + + // 分析模板文件规则 + $depr = C('TMPL_FILE_DEPR'); + if('' == $file) { + // 如果模板文件名为空 按照默认规则定位 + $file = CONTROLLER_NAME . $depr . ACTION_NAME; + }elseif(false === strpos($file, '/')){ + $file = CONTROLLER_NAME . $depr . $file; + }elseif('/' != $depr){ + $file = substr_count($file,'/')>1 ? substr_replace($file,$depr,strrpos($file,'/'),1) : str_replace('/', $depr, $file); + } + return $baseUrl.($theme?$theme.'/':'').$file.C('TMPL_TEMPLATE_SUFFIX'); +} + +/** + * 获取输入参数 支持过滤和默认值 + * 使用方法: + * + * I('id',0); 获取id参数 自动判断get或者post + * I('post.name','','htmlspecialchars'); 获取$_POST['name'] + * I('get.'); 获取$_GET + * + * @param string $name 变量的名称 支持指定类型 + * @param mixed $default 不存在的时候默认值 + * @param mixed $filter 参数过滤方法 + * @param mixed $datas 要获取的额外数据源 + * @return mixed + */ +function I($name,$default='',$filter=null,$datas=null) { + static $_PUT = null; + if(strpos($name,'/')){ // 指定修饰符 + list($name,$type) = explode('/',$name,2); + }elseif(C('VAR_AUTO_STRING')){ // 默认强制转换为字符串 + $type = 's'; + } + if(strpos($name,'.')) { // 指定参数来源 + list($method,$name) = explode('.',$name,2); + }else{ // 默认为自动判断 + $method = 'param'; + } + switch(strtolower($method)) { + case 'get' : + $input =& $_GET; + break; + case 'post' : + $input =& $_POST; + break; + case 'put' : + if(is_null($_PUT)){ + parse_str(file_get_contents('php://input'), $_PUT); + } + $input = $_PUT; + break; + case 'param' : + switch($_SERVER['REQUEST_METHOD']) { + case 'POST': + $input = $_POST; + break; + case 'PUT': + if(is_null($_PUT)){ + parse_str(file_get_contents('php://input'), $_PUT); + } + $input = $_PUT; + break; + default: + $input = $_GET; + } + break; + case 'path' : + $input = array(); + if(!empty($_SERVER['PATH_INFO'])){ + $depr = C('URL_PATHINFO_DEPR'); + $input = explode($depr,trim($_SERVER['PATH_INFO'],$depr)); + } + break; + case 'request' : + $input =& $_REQUEST; + break; + case 'session' : + $input =& $_SESSION; + break; + case 'cookie' : + $input =& $_COOKIE; + break; + case 'server' : + $input =& $_SERVER; + break; + case 'globals' : + $input =& $GLOBALS; + break; + case 'data' : + $input =& $datas; + break; + default: + return null; + } + if(''==$name) { // 获取全部变量 + $data = $input; + $filters = isset($filter)?$filter:C('DEFAULT_FILTER'); + if($filters) { + if(is_string($filters)){ + $filters = explode(',',$filters); + } + foreach($filters as $filter){ + $data = array_map_recursive($filter,$data); // 参数过滤 + } + } + }elseif(isset($input[$name])) { // 取值操作 + $data = $input[$name]; + $filters = isset($filter)?$filter:C('DEFAULT_FILTER'); + if($filters) { + if(is_string($filters)){ + if(0 === strpos($filters,'/')){ + if(1 !== preg_match($filters,(string)$data)){ + // 支持正则验证 + return isset($default) ? $default : null; + } + }else{ + $filters = explode(',',$filters); + } + }elseif(is_int($filters)){ + $filters = array($filters); + } + + if(is_array($filters)){ + foreach($filters as $filter){ + if(function_exists($filter)) { + $data = is_array($data) ? array_map_recursive($filter,$data) : $filter($data); // 参数过滤 + }else{ + $data = filter_var($data,is_int($filter) ? $filter : filter_id($filter)); + if(false === $data) { + return isset($default) ? $default : null; + } + } + } + } + } + if(!empty($type)){ + switch(strtolower($type)){ + case 'a': // 数组 + $data = (array)$data; + break; + case 'd': // 数字 + $data = (int)$data; + break; + case 'f': // 浮点 + $data = (float)$data; + break; + case 'b': // 布尔 + $data = (boolean)$data; + break; + case 's': // 字符串 + default: + $data = (string)$data; + } + } + }else{ // 变量默认值 + $data = isset($default)?$default:null; + } + is_array($data) && array_walk_recursive($data,'think_filter'); + return $data; +} + +function array_map_recursive($filter, $data) { + $result = array(); + foreach ($data as $key => $val) { + $result[$key] = is_array($val) + ? array_map_recursive($filter, $val) + : call_user_func($filter, $val); + } + return $result; + } + +/** + * 设置和获取统计数据 + * 使用方法: + * + * N('db',1); // 记录数据库操作次数 + * N('read',1); // 记录读取次数 + * echo N('db'); // 获取当前页面数据库的所有操作次数 + * echo N('read'); // 获取当前页面读取次数 + * + * @param string $key 标识位置 + * @param integer $step 步进值 + * @param boolean $save 是否保存结果 + * @return mixed + */ +function N($key, $step=0,$save=false) { + static $_num = array(); + if (!isset($_num[$key])) { + $_num[$key] = (false !== $save)? S('N_'.$key) : 0; + } + if (empty($step)){ + return $_num[$key]; + }else{ + $_num[$key] = $_num[$key] + (int)$step; + } + if(false !== $save){ // 保存结果 + S('N_'.$key,$_num[$key],$save); + } + return null; +} + +/** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param integer $type 转换类型 + * @return string + */ +function parse_name($name, $type=0) { + if ($type) { + return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function($match){return strtoupper($match[1]);}, $name)); + } else { + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } +} + +/** + * 优化的require_once + * @param string $filename 文件地址 + * @return boolean + */ +function require_cache($filename) { + static $_importFiles = array(); + if (!isset($_importFiles[$filename])) { + if (file_exists_case($filename)) { + require $filename; + $_importFiles[$filename] = true; + } else { + $_importFiles[$filename] = false; + } + } + return $_importFiles[$filename]; +} + +/** + * 区分大小写的文件存在判断 + * @param string $filename 文件地址 + * @return boolean + */ +function file_exists_case($filename) { + if (is_file($filename)) { + if (IS_WIN && APP_DEBUG) { + if (basename(realpath($filename)) != basename($filename)) + return false; + } + return true; + } + return false; +} + +/** + * 导入所需的类库 同java的Import 本函数有缓存功能 + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return boolean + */ +function import($class, $baseUrl = '', $ext=EXT) { + static $_file = array(); + $class = str_replace(array('.', '#'), array('/', '.'), $class); + if (isset($_file[$class . $baseUrl])) + return true; + else + $_file[$class . $baseUrl] = true; + $class_strut = explode('/', $class); + if (empty($baseUrl)) { + if ('@' == $class_strut[0] || MODULE_NAME == $class_strut[0]) { + //加载当前模块的类库 + $baseUrl = MODULE_PATH; + $class = substr_replace($class, '', 0, strlen($class_strut[0]) + 1); + }elseif ('Common' == $class_strut[0]) { + //加载公共模块的类库 + $baseUrl = COMMON_PATH; + $class = substr($class, 7); + }elseif (in_array($class_strut[0],array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$class_strut[0])) { + // 系统类库包和第三方类库包 + $baseUrl = LIB_PATH; + }else { // 加载其他模块的类库 + $baseUrl = APP_PATH; + } + } + if (substr($baseUrl, -1) != '/') + $baseUrl .= '/'; + $classfile = $baseUrl . $class . $ext; + if (!class_exists(basename($class),false)) { + // 如果类不存在 则导入类库文件 + return require_cache($classfile); + } + return null; +} + +/** + * 基于命名空间方式导入函数库 + * load('@.Util.Array') + * @param string $name 函数库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return void + */ +function load($name, $baseUrl='', $ext='.php') { + $name = str_replace(array('.', '#'), array('/', '.'), $name); + if (empty($baseUrl)) { + if (0 === strpos($name, '@/')) {//加载当前模块函数库 + $baseUrl = MODULE_PATH.'Common/'; + $name = substr($name, 2); + } else { //加载其他模块函数库 + $array = explode('/', $name); + $baseUrl = APP_PATH . array_shift($array).'/Common/'; + $name = implode('/',$array); + } + } + if (substr($baseUrl, -1) != '/') + $baseUrl .= '/'; + require_cache($baseUrl . $name . $ext); +} + +/** + * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面 + * @param string $class 类库 + * @param string $baseUrl 基础目录 + * @param string $ext 类库后缀 + * @return boolean + */ +function vendor($class, $baseUrl = '', $ext='.php') { + if (empty($baseUrl)) + $baseUrl = VENDOR_PATH; + return import($class, $baseUrl, $ext); +} + +/** + * 实例化模型类 格式 [资源://][模块/]模型 + * @param string $name 资源地址 + * @param string $layer 模型层名称 + * @return Think\Model + */ +function D($name='',$layer='') { + if(empty($name)) return new Think\Model; + static $_model = array(); + $layer = $layer? : C('DEFAULT_M_LAYER'); + if(isset($_model[$name.$layer])) + return $_model[$name.$layer]; + $class = parse_res_name($name,$layer); + if(class_exists($class)) { + $model = new $class(basename($name)); + }elseif(false === strpos($name,'/')){ + // 自动加载公共模块下面的模型 + if(!C('APP_USE_NAMESPACE')){ + import('Common/'.$layer.'/'.$class); + }else{ + $class = '\\Common\\'.$layer.'\\'.$name.$layer; + } + $model = class_exists($class)? new $class($name) : new Think\Model($name); + }else { + Think\Log::record('D方法实例化没找到模型类'.$class,Think\Log::NOTICE); + $model = new Think\Model(basename($name)); + } + $_model[$name.$layer] = $model; + return $model; +} + +/** + * 实例化一个没有模型文件的Model + * @param string $name Model名称 支持指定基础模型 例如 MongoModel:User + * @param string $tablePrefix 表前缀 + * @param mixed $connection 数据库连接信息 + * @return Think\Model + */ +function M($name='', $tablePrefix='',$connection='') { + static $_model = array(); + if(strpos($name,':')) { + list($class,$name) = explode(':',$name); + }else{ + $class = 'Think\\Model'; + } + $guid = (is_array($connection)?implode('',$connection):$connection).$tablePrefix . $name . '_' . $class; + if (!isset($_model[$guid])) + $_model[$guid] = new $class($name,$tablePrefix,$connection); + return $_model[$guid]; +} + +/** + * 解析资源地址并导入类库文件 + * 例如 module/controller addon://module/behavior + * @param string $name 资源地址 格式:[扩展://][模块/]资源名 + * @param string $layer 分层名称 + * @param integer $level 控制器层次 + * @return string + */ +function parse_res_name($name,$layer,$level=1){ + if(strpos($name,'://')) {// 指定扩展资源 + list($extend,$name) = explode('://',$name); + }else{ + $extend = ''; + } + if(strpos($name,'/') && substr_count($name, '/')>=$level){ // 指定模块 + list($module,$name) = explode('/',$name,2); + }else{ + $module = defined('MODULE_NAME') ? MODULE_NAME : '' ; + } + $array = explode('/',$name); + if(!C('APP_USE_NAMESPACE')){ + $class = parse_name($name, 1); + import($module.'/'.$layer.'/'.$class.$layer); + }else{ + $class = $module.'\\'.$layer; + foreach($array as $name){ + $class .= '\\'.parse_name($name, 1); + } + // 导入资源类库 + if($extend){ // 扩展资源 + $class = $extend.'\\'.$class; + } + } + return $class.$layer; +} + +/** + * 用于实例化访问控制器 + * @param string $name 控制器名 + * @param string $path 控制器命名空间(路径) + * @return Think\Controller|false + */ +function controller($name,$path=''){ + $layer = C('DEFAULT_C_LAYER'); + if(!C('APP_USE_NAMESPACE')){ + $class = parse_name($name, 1).$layer; + import(MODULE_NAME.'/'.$layer.'/'.$class); + }else{ + $class = ( $path ? basename(ADDON_PATH).'\\'.$path : MODULE_NAME ).'\\'.$layer; + $array = explode('/',$name); + foreach($array as $name){ + $class .= '\\'.parse_name($name, 1); + } + $class .= $layer; + } + if(class_exists($class)) { + return new $class(); + }else { + return false; + } +} + +/** + * 实例化多层控制器 格式:[资源://][模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param integer $level 控制器层次 + * @return Think\Controller|false + */ +function A($name,$layer='',$level=0) { + static $_action = array(); + $layer = $layer? : C('DEFAULT_C_LAYER'); + $level = $level? : ($layer == C('DEFAULT_C_LAYER')?C('CONTROLLER_LEVEL'):1); + if(isset($_action[$name.$layer])) + return $_action[$name.$layer]; + + $class = parse_res_name($name,$layer,$level); + if(class_exists($class)) { + $action = new $class(); + $_action[$name.$layer] = $action; + return $action; + }else { + return false; + } +} + + +/** + * 远程调用控制器的操作方法 URL 参数格式 [资源://][模块/]控制器/操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @return mixed + */ +function R($url,$vars=array(),$layer='') { + $info = pathinfo($url); + $action = $info['basename']; + $module = $info['dirname']; + $class = A($module,$layer); + if($class){ + if(is_string($vars)) { + parse_str($vars,$vars); + } + return call_user_func_array(array(&$class,$action.C('ACTION_SUFFIX')),$vars); + }else{ + return false; + } +} + +/** + * 处理标签扩展 + * @param string $tag 标签名称 + * @param mixed $params 传入参数 + * @return void + */ +function tag($tag, &$params=NULL) { + \Think\Hook::listen($tag,$params); +} + +/** + * 执行某个行为 + * @param string $name 行为名称 + * @param string $tag 标签名称(行为类无需传入) + * @param Mixed $params 传入的参数 + * @return void + */ +function B($name, $tag='',&$params=NULL) { + if(''==$tag){ + $name .= 'Behavior'; + } + return \Think\Hook::exec($name,$tag,$params); +} + +/** + * 去除代码中的空白和注释 + * @param string $content 代码内容 + * @return string + */ +function strip_whitespace($content) { + $stripStr = ''; + //分析php源码 + $tokens = token_get_all($content); + $last_space = false; + for ($i = 0, $j = count($tokens); $i < $j; $i++) { + if (is_string($tokens[$i])) { + $last_space = false; + $stripStr .= $tokens[$i]; + } else { + switch ($tokens[$i][0]) { + //过滤各种PHP注释 + case T_COMMENT: + case T_DOC_COMMENT: + break; + //过滤空格 + case T_WHITESPACE: + if (!$last_space) { + $stripStr .= ' '; + $last_space = true; + } + break; + case T_START_HEREDOC: + $stripStr .= "<<'; + } else { + $output = $label . print_r($var, true); + } + } else { + ob_start(); + var_dump($var); + $output = ob_get_clean(); + if (!extension_loaded('xdebug')) { + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); + $output = '
' . $label . htmlspecialchars($output, ENT_QUOTES) . '
'; + } + } + if ($echo) { + echo($output); + return null; + }else + return $output; +} + +/** + * 设置当前页面的布局 + * @param string|false $layout 布局名称 为false的时候表示关闭布局 + * @return void + */ +function layout($layout) { + if(false !== $layout) { + // 开启布局 + C('LAYOUT_ON',true); + if(is_string($layout)) { // 设置新的布局模板 + C('LAYOUT_NAME',$layout); + } + }else{// 临时关闭布局 + C('LAYOUT_ON',false); + } +} + +/** + * URL组装 支持不同URL模式 + * @param string $url URL表达式,格式:'[模块/控制器/操作#锚点@域名]?参数1=值1&参数2=值2...' + * @param string|array $vars 传入的参数,支持数组和字符串 + * @param string|boolean $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean $domain 是否显示域名 + * @return string + */ +function U($url='',$vars='',$suffix=true,$domain=false) { + // 解析URL + $info = parse_url($url); + $url = !empty($info['path'])?$info['path']:ACTION_NAME; + if(isset($info['fragment'])) { // 解析锚点 + $anchor = $info['fragment']; + if(false !== strpos($anchor,'?')) { // 解析参数 + list($anchor,$info['query']) = explode('?',$anchor,2); + } + if(false !== strpos($anchor,'@')) { // 解析域名 + list($anchor,$host) = explode('@',$anchor, 2); + } + }elseif(false !== strpos($url,'@')) { // 解析域名 + list($url,$host) = explode('@',$info['path'], 2); + } + // 解析子域名 + if(isset($host)) { + $domain = $host.(strpos($host,'.')?'':strstr($_SERVER['HTTP_HOST'],'.')); + }elseif($domain===true){ + $domain = $_SERVER['HTTP_HOST']; + if(C('APP_SUB_DOMAIN_DEPLOY') ) { // 开启子域名部署 + $domain = $domain=='localhost'?'localhost':'www'.strstr($_SERVER['HTTP_HOST'],'.'); + // '子域名'=>array('模块[/控制器]'); + foreach (C('APP_SUB_DOMAIN_RULES') as $key => $rule) { + $rule = is_array($rule)?$rule[0]:$rule; + if(false === strpos($key,'*') && 0=== strpos($url,$rule)) { + $domain = $key.strstr($domain,'.'); // 生成对应子域名 + $url = substr_replace($url,'',0,strlen($rule)); + break; + } + } + } + } + + // 解析参数 + if(is_string($vars)) { // aaa=1&bbb=2 转换成数组 + parse_str($vars,$vars); + }elseif(!is_array($vars)){ + $vars = array(); + } + if(isset($info['query'])) { // 解析地址里面参数 合并到vars + parse_str($info['query'],$params); + $vars = array_merge($params,$vars); + } + + // URL组装 + $depr = C('URL_PATHINFO_DEPR'); + $urlCase = C('URL_CASE_INSENSITIVE'); + if($url) { + if(0=== strpos($url,'/')) {// 定义路由 + $route = true; + $url = substr($url,1); + if('/' != $depr) { + $url = str_replace('/',$depr,$url); + } + }else{ + if('/' != $depr) { // 安全替换 + $url = str_replace('/',$depr,$url); + } + // 解析模块、控制器和操作 + $url = trim($url,$depr); + $path = explode($depr,$url); + $var = array(); + $varModule = C('VAR_MODULE'); + $varController = C('VAR_CONTROLLER'); + $varAction = C('VAR_ACTION'); + $var[$varAction] = !empty($path)?array_pop($path):ACTION_NAME; + $var[$varController] = !empty($path)?array_pop($path):CONTROLLER_NAME; + if($maps = C('URL_ACTION_MAP')) { + if(isset($maps[strtolower($var[$varController])])) { + $maps = $maps[strtolower($var[$varController])]; + if($action = array_search(strtolower($var[$varAction]),$maps)){ + $var[$varAction] = $action; + } + } + } + if($maps = C('URL_CONTROLLER_MAP')) { + if($controller = array_search(strtolower($var[$varController]),$maps)){ + $var[$varController] = $controller; + } + } + if($urlCase) { + $var[$varController] = parse_name($var[$varController]); + } + $module = ''; + + if(!empty($path)) { + $var[$varModule] = implode($depr,$path); + }else{ + if(C('MULTI_MODULE')) { + if(MODULE_NAME != C('DEFAULT_MODULE') || !C('MODULE_ALLOW_LIST')){ + $var[$varModule]= MODULE_NAME; + } + } + } + if($maps = C('URL_MODULE_MAP')) { + if($_module = array_search(strtolower($var[$varModule]),$maps)){ + $var[$varModule] = $_module; + } + } + if(isset($var[$varModule])){ + $module = $var[$varModule]; + unset($var[$varModule]); + } + + } + } + + if(C('URL_MODEL') == 0) { // 普通模式URL转换 + $url = __APP__.'?'.C('VAR_MODULE')."={$module}&".http_build_query(array_reverse($var)); + if($urlCase){ + $url = strtolower($url); + } + if(!empty($vars)) { + $vars = http_build_query($vars); + $url .= '&'.$vars; + } + }else{ // PATHINFO模式或者兼容URL模式 + if(isset($route)) { + $url = __APP__.'/'.rtrim($url,$depr); + }else{ + $module = (defined('BIND_MODULE') && BIND_MODULE==$module )? '' : $module; + $url = __APP__.'/'.($module?$module.MODULE_PATHINFO_DEPR:'').implode($depr,array_reverse($var)); + } + if($urlCase){ + $url = strtolower($url); + } + if(!empty($vars)) { // 添加参数 + foreach ($vars as $var => $val){ + if('' !== trim($val)) $url .= $depr . $var . $depr . urlencode($val); + } + } + if($suffix) { + $suffix = $suffix===true?C('URL_HTML_SUFFIX'):$suffix; + if($pos = strpos($suffix, '|')){ + $suffix = substr($suffix, 0, $pos); + } + if($suffix && '/' != substr($url,-1)){ + $url .= '.'.ltrim($suffix,'.'); + } + } + } + if(isset($anchor)){ + $url .= '#'.$anchor; + } + if($domain) { + $url = (is_ssl()?'https://':'http://').$domain.$url; + } + return $url; +} + +/** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return void + */ +function W($name, $data=array()) { + return R($name,$data,'Widget'); +} + +/** + * 判断是否SSL协议 + * @return boolean + */ +function is_ssl() { + if(isset($_SERVER['HTTPS']) && ('1' == $_SERVER['HTTPS'] || 'on' == strtolower($_SERVER['HTTPS']))){ + return true; + }elseif(isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'] )) { + return true; + } + return false; +} + +/** + * URL重定向 + * @param string $url 重定向的URL地址 + * @param integer $time 重定向的等待时间(秒) + * @param string $msg 重定向前的提示信息 + * @return void + */ +function redirect($url, $time=0, $msg='') { + //多行URL地址支持 + $url = str_replace(array("\n", "\r"), '', $url); + if (empty($msg)) + $msg = "系统将在{$time}秒之后自动跳转到{$url}!"; + if (!headers_sent()) { + // redirect + if (0 === $time) { + header('Location: ' . $url); + } else { + header("refresh:{$time};url={$url}"); + echo($msg); + } + exit(); + } else { + $str = ""; + if ($time != 0) + $str .= $msg; + exit($str); + } +} + +/** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @return mixed + */ +function S($name,$value='',$options=null) { + static $cache = ''; + if(is_array($options)){ + // 缓存操作的同时初始化 + $type = isset($options['type'])?$options['type']:''; + $cache = Think\Cache::getInstance($type,$options); + }elseif(is_array($name)) { // 缓存初始化 + $type = isset($name['type'])?$name['type']:''; + $cache = Think\Cache::getInstance($type,$name); + return $cache; + }elseif(empty($cache)) { // 自动初始化 + $cache = Think\Cache::getInstance(); + } + if(''=== $value){ // 获取缓存 + return $cache->get($name); + }elseif(is_null($value)) { // 删除缓存 + return $cache->rm($name); + }else { // 缓存数据 + if(is_array($options)) { + $expire = isset($options['expire'])?$options['expire']:NULL; + }else{ + $expire = is_numeric($options)?$options:NULL; + } + return $cache->set($name, $value, $expire); + } +} + +/** + * 快速文件数据读取和保存 针对简单类型数据 字符串、数组 + * @param string $name 缓存名称 + * @param mixed $value 缓存值 + * @param string $path 缓存路径 + * @return mixed + */ +function F($name, $value='', $path=DATA_PATH) { + static $_cache = array(); + $filename = $path . $name . '.php'; + if ('' !== $value) { + if (is_null($value)) { + // 删除缓存 + if(false !== strpos($name,'*')){ + return false; // TODO + }else{ + unset($_cache[$name]); + return Think\Storage::unlink($filename,'F'); + } + } else { + Think\Storage::put($filename,serialize($value),'F'); + // 缓存数据 + $_cache[$name] = $value; + return null; + } + } + // 获取缓存数据 + if (isset($_cache[$name])) + return $_cache[$name]; + if (Think\Storage::has($filename,'F')){ + $value = unserialize(Think\Storage::read($filename,'F')); + $_cache[$name] = $value; + } else { + $value = false; + } + return $value; +} + +/** + * 根据PHP各种类型变量生成唯一标识号 + * @param mixed $mix 变量 + * @return string + */ +function to_guid_string($mix) { + if (is_object($mix)) { + return spl_object_hash($mix); + } elseif (is_resource($mix)) { + $mix = get_resource_type($mix) . strval($mix); + } else { + $mix = serialize($mix); + } + return md5($mix); +} + +/** + * XML编码 + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ +function xml_encode($data, $root='think', $item='item', $attr='', $id='id', $encoding='utf-8') { + if(is_array($attr)){ + $_attr = array(); + foreach ($attr as $key => $value) { + $_attr[] = "{$key}=\"{$value}\""; + } + $attr = implode(' ', $_attr); + } + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = ""; + $xml .= "<{$root}{$attr}>"; + $xml .= data_to_xml($data, $item, $id); + $xml .= ""; + return $xml; +} + +/** + * 数据XML编码 + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ +function data_to_xml($data, $item='item', $id='id') { + $xml = $attr = ''; + foreach ($data as $key => $val) { + if(is_numeric($key)){ + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + $xml .= "<{$key}{$attr}>"; + $xml .= (is_array($val) || is_object($val)) ? data_to_xml($val, $item, $id) : $val; + $xml .= ""; + } + return $xml; +} + +/** + * session管理函数 + * @param string|array $name session名称 如果为数组则表示进行session设置 + * @param mixed $value session值 + * @return mixed + */ +function session($name='',$value='') { + $prefix = C('SESSION_PREFIX'); + if(is_array($name)) { // session初始化 在session_start 之前调用 + if(isset($name['prefix'])) C('SESSION_PREFIX',$name['prefix']); + if(C('VAR_SESSION_ID') && isset($_REQUEST[C('VAR_SESSION_ID')])){ + session_id($_REQUEST[C('VAR_SESSION_ID')]); + }elseif(isset($name['id'])) { + session_id($name['id']); + } + if('common' == APP_MODE){ // 其它模式可能不支持 + ini_set('session.auto_start', 0); + } + if(isset($name['name'])) session_name($name['name']); + if(isset($name['path'])) session_save_path($name['path']); + if(isset($name['domain'])) ini_set('session.cookie_domain', $name['domain']); + if(isset($name['expire'])) { + ini_set('session.gc_maxlifetime', $name['expire']); + ini_set('session.cookie_lifetime', $name['expire']); + } + if(isset($name['use_trans_sid'])) ini_set('session.use_trans_sid', $name['use_trans_sid']?1:0); + if(isset($name['use_cookies'])) ini_set('session.use_cookies', $name['use_cookies']?1:0); + if(isset($name['cache_limiter'])) session_cache_limiter($name['cache_limiter']); + if(isset($name['cache_expire'])) session_cache_expire($name['cache_expire']); + if(isset($name['type'])) C('SESSION_TYPE',$name['type']); + if(C('SESSION_TYPE')) { // 读取session驱动 + $type = C('SESSION_TYPE'); + $class = strpos($type,'\\')? $type : 'Think\\Session\\Driver\\'. ucwords(strtolower($type)); + $hander = new $class(); + session_set_save_handler( + array(&$hander,"open"), + array(&$hander,"close"), + array(&$hander,"read"), + array(&$hander,"write"), + array(&$hander,"destroy"), + array(&$hander,"gc")); + } + // 启动session + if(C('SESSION_AUTO_START')) session_start(); + }elseif('' === $value){ + if(''===$name){ + // 获取全部的session + return $prefix ? $_SESSION[$prefix] : $_SESSION; + }elseif(0===strpos($name,'[')) { // session 操作 + if('[pause]'==$name){ // 暂停session + session_write_close(); + }elseif('[start]'==$name){ // 启动session + session_start(); + }elseif('[destroy]'==$name){ // 销毁session + $_SESSION = array(); + session_unset(); + session_destroy(); + }elseif('[regenerate]'==$name){ // 重新生成id + session_regenerate_id(); + } + }elseif(0===strpos($name,'?')){ // 检查session + $name = substr($name,1); + if(strpos($name,'.')){ // 支持数组 + list($name1,$name2) = explode('.',$name); + return $prefix?isset($_SESSION[$prefix][$name1][$name2]):isset($_SESSION[$name1][$name2]); + }else{ + return $prefix?isset($_SESSION[$prefix][$name]):isset($_SESSION[$name]); + } + }elseif(is_null($name)){ // 清空session + if($prefix) { + unset($_SESSION[$prefix]); + }else{ + $_SESSION = array(); + } + }elseif($prefix){ // 获取session + if(strpos($name,'.')){ + list($name1,$name2) = explode('.',$name); + return isset($_SESSION[$prefix][$name1][$name2])?$_SESSION[$prefix][$name1][$name2]:null; + }else{ + return isset($_SESSION[$prefix][$name])?$_SESSION[$prefix][$name]:null; + } + }else{ + if(strpos($name,'.')){ + list($name1,$name2) = explode('.',$name); + return isset($_SESSION[$name1][$name2])?$_SESSION[$name1][$name2]:null; + }else{ + return isset($_SESSION[$name])?$_SESSION[$name]:null; + } + } + }elseif(is_null($value)){ // 删除session + if(strpos($name,'.')){ + list($name1,$name2) = explode('.',$name); + if($prefix){ + unset($_SESSION[$prefix][$name1][$name2]); + }else{ + unset($_SESSION[$name1][$name2]); + } + }else{ + if($prefix){ + unset($_SESSION[$prefix][$name]); + }else{ + unset($_SESSION[$name]); + } + } + }else{ // 设置session + if(strpos($name,'.')){ + list($name1,$name2) = explode('.',$name); + if($prefix){ + $_SESSION[$prefix][$name1][$name2] = $value; + }else{ + $_SESSION[$name1][$name2] = $value; + } + }else{ + if($prefix){ + $_SESSION[$prefix][$name] = $value; + }else{ + $_SESSION[$name] = $value; + } + } + } + return null; +} + +/** + * Cookie 设置、获取、删除 + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option cookie参数 + * @return mixed + */ +function cookie($name='', $value='', $option=null) { + // 默认设置 + $config = array( + 'prefix' => C('COOKIE_PREFIX'), // cookie 名称前缀 + 'expire' => C('COOKIE_EXPIRE'), // cookie 保存时间 + 'path' => C('COOKIE_PATH'), // cookie 保存路径 + 'domain' => C('COOKIE_DOMAIN'), // cookie 有效域名 + 'secure' => C('COOKIE_SECURE'), // cookie 启用安全传输 + 'httponly' => C('COOKIE_HTTPONLY'), // httponly设置 + ); + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option)) + $option = array('expire' => $option); + elseif (is_string($option)) + parse_str($option, $option); + $config = array_merge($config, array_change_key_case($option)); + } + if(!empty($config['httponly'])){ + ini_set("session.cookie_httponly", 1); + } + // 清除指定前缀的所有cookie + if (is_null($name)) { + if (empty($_COOKIE)) + return null; + // 要删除的cookie前缀,不指定则删除config设置的指定前缀 + $prefix = empty($value) ? $config['prefix'] : $value; + if (!empty($prefix)) {// 如果前缀为空字符串将不作处理直接返回 + foreach ($_COOKIE as $key => $val) { + if (0 === stripos($key, $prefix)) { + setcookie($key, '', time() - 3600, $config['path'], $config['domain'],$config['secure'],$config['httponly']); + unset($_COOKIE[$key]); + } + } + } + return null; + }elseif('' === $name){ + // 获取全部的cookie + return $_COOKIE; + } + $name = $config['prefix'] . str_replace('.', '_', $name); + if ('' === $value) { + if(isset($_COOKIE[$name])){ + $value = $_COOKIE[$name]; + if(0===strpos($value,'think:')){ + $value = substr($value,6); + return array_map('urldecode',json_decode(MAGIC_QUOTES_GPC?stripslashes($value):$value,true)); + }else{ + return $value; + } + }else{ + return null; + } + } else { + if (is_null($value)) { + setcookie($name, '', time() - 3600, $config['path'], $config['domain'],$config['secure'],$config['httponly']); + unset($_COOKIE[$name]); // 删除指定cookie + } else { + // 设置cookie + if(is_array($value)){ + $value = 'think:'.json_encode(array_map('urlencode',$value)); + } + $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0; + setcookie($name, $value, $expire, $config['path'], $config['domain'],$config['secure'],$config['httponly']); + $_COOKIE[$name] = $value; + } + } + return null; +} + +/** + * 加载动态扩展文件 + * @var string $path 文件路径 + * @return void + */ +function load_ext_file($path) { + // 加载自定义外部文件 + if($files = C('LOAD_EXT_FILE')) { + $files = explode(',',$files); + foreach ($files as $file){ + $file = $path.'Common/'.$file.'.php'; + if(is_file($file)) include $file; + } + } + // 加载自定义的动态配置文件 + if($configs = C('LOAD_EXT_CONFIG')) { + if(is_string($configs)) $configs = explode(',',$configs); + foreach ($configs as $key=>$config){ + $file = is_file($config)? $config : $path.'Conf/'.$config.CONF_EXT; + if(is_file($file)) { + is_numeric($key)?C(load_config($file)):C($key,load_config($file)); + } + } + } +} + +/** + * 获取客户端IP地址 + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @return mixed + */ +function get_client_ip($type = 0,$adv=false) { + $type = $type ? 1 : 0; + static $ip = NULL; + if ($ip !== NULL) return $ip[$type]; + if($adv){ + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $pos = array_search('unknown',$arr); + if(false !== $pos) unset($arr[$pos]); + $ip = trim($arr[0]); + }elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + }elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + }elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + // IP地址合法验证 + $long = sprintf("%u",ip2long($ip)); + $ip = $long ? array($ip, $long) : array('0.0.0.0', 0); + return $ip[$type]; +} + +/** + * 发送HTTP状态 + * @param integer $code 状态码 + * @return void + */ +function send_http_status($code) { + static $_status = array( + // Informational 1xx + 100 => 'Continue', + 101 => 'Switching Protocols', + // Success 2xx + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + // Redirection 3xx + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Moved Temporarily ', // 1.1 + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + // 306 is deprecated but reserved + 307 => 'Temporary Redirect', + // Client Error 4xx + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + // Server Error 5xx + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 509 => 'Bandwidth Limit Exceeded' + ); + if(isset($_status[$code])) { + header('HTTP/1.1 '.$code.' '.$_status[$code]); + // 确保FastCGI模式下正常 + header('Status:'.$code.' '.$_status[$code]); + } +} + +function think_filter(&$value){ + // TODO 其他安全过滤 + + // 过滤查询特殊字符 + if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value)){ + $value .= ' '; + } +} + +// 不区分大小写的in_array实现 +function in_array_case($value,$array){ + return in_array(strtolower($value),array_map('strtolower',$array)); +} diff --git a/ThinkPHP/Conf/convention.php b/ThinkPHP/Conf/convention.php new file mode 100644 index 0000000..4061933 --- /dev/null +++ b/ThinkPHP/Conf/convention.php @@ -0,0 +1,167 @@ + +// +---------------------------------------------------------------------- + +/** + * ThinkPHP惯例配置文件 + * 该文件请不要修改,如果要覆盖惯例配置的值,可在应用配置文件中设定和惯例不符的配置项 + * 配置名称大小写任意,系统会统一转换成小写 + * 所有配置参数都可以在生效前动态改变 + */ +defined('THINK_PATH') or exit(); +return array( + /* 应用设定 */ + 'APP_USE_NAMESPACE' => true, // 应用类库是否使用命名空间 + 'APP_SUB_DOMAIN_DEPLOY' => false, // 是否开启子域名部署 + 'APP_SUB_DOMAIN_RULES' => array(), // 子域名部署规则 + 'APP_DOMAIN_SUFFIX' => '', // 域名后缀 如果是com.cn net.cn 之类的后缀必须设置 + 'ACTION_SUFFIX' => '', // 操作方法后缀 + 'MULTI_MODULE' => true, // 是否允许多模块 如果为false 则必须设置 DEFAULT_MODULE + 'MODULE_DENY_LIST' => array('Common','Runtime'), + 'CONTROLLER_LEVEL' => 1, + 'APP_AUTOLOAD_LAYER' => 'Controller,Model', // 自动加载的应用类库层 关闭APP_USE_NAMESPACE后有效 + 'APP_AUTOLOAD_PATH' => '', // 自动加载的路径 关闭APP_USE_NAMESPACE后有效 + + /* Cookie设置 */ + 'COOKIE_EXPIRE' => 0, // Cookie有效期 + 'COOKIE_DOMAIN' => '', // Cookie有效域名 + 'COOKIE_PATH' => '/', // Cookie路径 + 'COOKIE_PREFIX' => '', // Cookie前缀 避免冲突 + 'COOKIE_SECURE' => false, // Cookie安全传输 + 'COOKIE_HTTPONLY' => '', // Cookie httponly设置 + + /* 默认设定 */ + 'DEFAULT_M_LAYER' => 'Model', // 默认的模型层名称 + 'DEFAULT_C_LAYER' => 'Controller', // 默认的控制器层名称 + 'DEFAULT_V_LAYER' => 'View', // 默认的视图层名称 + 'DEFAULT_LANG' => 'zh-cn', // 默认语言 + 'DEFAULT_THEME' => '', // 默认模板主题名称 + 'DEFAULT_MODULE' => 'Home', // 默认模块 + 'DEFAULT_CONTROLLER' => 'Index', // 默认控制器名称 + 'DEFAULT_ACTION' => 'index', // 默认操作名称 + 'DEFAULT_CHARSET' => 'utf-8', // 默认输出编码 + 'DEFAULT_TIMEZONE' => 'PRC', // 默认时区 + 'DEFAULT_AJAX_RETURN' => 'JSON', // 默认AJAX 数据返回格式,可选JSON XML ... + 'DEFAULT_JSONP_HANDLER' => 'jsonpReturn', // 默认JSONP格式返回的处理方法 + 'DEFAULT_FILTER' => 'htmlspecialchars', // 默认参数过滤方法 用于I函数... + + /* 数据库设置 */ + 'DB_TYPE' => '', // 数据库类型 + 'DB_HOST' => '', // 服务器地址 + 'DB_NAME' => '', // 数据库名 + 'DB_USER' => '', // 用户名 + 'DB_PWD' => '', // 密码 + 'DB_PORT' => '', // 端口 + 'DB_PREFIX' => '', // 数据库表前缀 + 'DB_PARAMS' => array(), // 数据库连接参数 + 'DB_DEBUG' => TRUE, // 数据库调试模式 开启后可以记录SQL日志 + 'DB_FIELDS_CACHE' => true, // 启用字段缓存 + 'DB_CHARSET' => 'utf8', // 数据库编码默认采用utf8 + 'DB_DEPLOY_TYPE' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'DB_RW_SEPARATE' => false, // 数据库读写是否分离 主从式有效 + 'DB_MASTER_NUM' => 1, // 读写分离后 主服务器数量 + 'DB_SLAVE_NO' => '', // 指定从服务器序号 + + /* 数据缓存设置 */ + 'DATA_CACHE_TIME' => 0, // 数据缓存有效期 0表示永久缓存 + 'DATA_CACHE_COMPRESS' => false, // 数据缓存是否压缩缓存 + 'DATA_CACHE_CHECK' => false, // 数据缓存是否校验缓存 + 'DATA_CACHE_PREFIX' => '', // 缓存前缀 + 'DATA_CACHE_TYPE' => 'File', // 数据缓存类型,支持:File|Db|Apc|Memcache|Shmop|Sqlite|Xcache|Apachenote|Eaccelerator + 'DATA_CACHE_PATH' => TEMP_PATH,// 缓存路径设置 (仅对File方式缓存有效) + 'DATA_CACHE_KEY' => '', // 缓存文件KEY (仅对File方式缓存有效) + 'DATA_CACHE_SUBDIR' => false, // 使用子目录缓存 (自动根据缓存标识的哈希创建子目录) + 'DATA_PATH_LEVEL' => 1, // 子目录缓存级别 + + /* 错误设置 */ + 'ERROR_MESSAGE' => '页面错误!请稍后再试~',//错误显示信息,非调试模式有效 + 'ERROR_PAGE' => '', // 错误定向页面 + 'SHOW_ERROR_MSG' => false, // 显示错误信息 + 'TRACE_MAX_RECORD' => 100, // 每个级别的错误信息 最大记录数 + + /* 日志设置 */ + 'LOG_RECORD' => false, // 默认不记录日志 + 'LOG_TYPE' => 'File', // 日志记录类型 默认为文件方式 + 'LOG_LEVEL' => 'EMERG,ALERT,CRIT,ERR',// 允许记录的日志级别 + 'LOG_FILE_SIZE' => 2097152, // 日志文件大小限制 + 'LOG_EXCEPTION_RECORD' => false, // 是否记录异常信息日志 + + /* SESSION设置 */ + 'SESSION_AUTO_START' => true, // 是否自动开启Session + 'SESSION_OPTIONS' => array(), // session 配置数组 支持type name id path expire domain 等参数 + 'SESSION_TYPE' => '', // session hander类型 默认无需设置 除非扩展了session hander驱动 + 'SESSION_PREFIX' => '', // session 前缀 + //'VAR_SESSION_ID' => 'session_id', //sessionID的提交变量 + + /* 模板引擎设置 */ + 'TMPL_CONTENT_TYPE' => 'text/html', // 默认模板输出类型 + 'TMPL_ACTION_ERROR' => THINK_PATH.'Tpl/dispatch_jump.tpl', // 默认错误跳转对应的模板文件 + 'TMPL_ACTION_SUCCESS' => THINK_PATH.'Tpl/dispatch_jump.tpl', // 默认成功跳转对应的模板文件 + 'TMPL_EXCEPTION_FILE' => THINK_PATH.'Tpl/think_exception.tpl',// 异常页面的模板文件 + 'TMPL_DETECT_THEME' => false, // 自动侦测模板主题 + 'TMPL_TEMPLATE_SUFFIX' => '.html', // 默认模板文件后缀 + 'TMPL_FILE_DEPR' => '/', //模板文件CONTROLLER_NAME与ACTION_NAME之间的分割符 + // 布局设置 + 'TMPL_ENGINE_TYPE' => 'Think', // 默认模板引擎 以下设置仅对使用Think模板引擎有效 + 'TMPL_CACHFILE_SUFFIX' => '.php', // 默认模板缓存后缀 + 'TMPL_DENY_FUNC_LIST' => 'echo,exit', // 模板引擎禁用函数 + 'TMPL_DENY_PHP' => false, // 默认模板引擎是否禁用PHP原生代码 + 'TMPL_L_DELIM' => '{', // 模板引擎普通标签开始标记 + 'TMPL_R_DELIM' => '}', // 模板引擎普通标签结束标记 + 'TMPL_VAR_IDENTIFY' => 'array', // 模板变量识别。留空自动判断,参数为'obj'则表示对象 + 'TMPL_STRIP_SPACE' => true, // 是否去除模板文件里面的html空格与换行 + 'TMPL_CACHE_ON' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'TMPL_CACHE_PREFIX' => '', // 模板缓存前缀标识,可以动态改变 + 'TMPL_CACHE_TIME' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒) + 'TMPL_LAYOUT_ITEM' => '{__CONTENT__}', // 布局模板的内容替换标识 + 'LAYOUT_ON' => false, // 是否启用布局 + 'LAYOUT_NAME' => 'layout', // 当前布局名称 默认为layout + + // Think模板引擎标签库相关设定 + 'TAGLIB_BEGIN' => '<', // 标签库标签开始标记 + 'TAGLIB_END' => '>', // 标签库标签结束标记 + 'TAGLIB_LOAD' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测 + 'TAGLIB_BUILD_IN' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序 + 'TAGLIB_PRE_LOAD' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔 + + /* URL设置 */ + 'URL_CASE_INSENSITIVE' => true, // 默认false 表示URL区分大小写 true则表示不区分大小写 + 'URL_MODEL' => 1, // URL访问模式,可选参数0、1、2、3,代表以下四种模式: + // 0 (普通模式); 1 (PATHINFO 模式); 2 (REWRITE 模式); 3 (兼容模式) 默认为PATHINFO 模式 + 'URL_PATHINFO_DEPR' => '/', // PATHINFO模式下,各参数之间的分割符号 + 'URL_PATHINFO_FETCH' => 'ORIG_PATH_INFO,REDIRECT_PATH_INFO,REDIRECT_URL', // 用于兼容判断PATH_INFO 参数的SERVER替代变量列表 + 'URL_REQUEST_URI' => 'REQUEST_URI', // 获取当前页面地址的系统变量 默认为REQUEST_URI + 'URL_HTML_SUFFIX' => 'html', // URL伪静态后缀设置 + 'URL_DENY_SUFFIX' => 'ico|png|gif|jpg', // URL禁止访问的后缀设置 + 'URL_PARAMS_BIND' => true, // URL变量绑定到Action方法参数 + 'URL_PARAMS_BIND_TYPE' => 0, // URL变量绑定的类型 0 按变量名绑定 1 按变量顺序绑定 + 'URL_PARAMS_FILTER' => false, // URL变量绑定过滤 + 'URL_PARAMS_FILTER_TYPE'=> '', // URL变量绑定过滤方法 如果为空 调用DEFAULT_FILTER + 'URL_ROUTER_ON' => false, // 是否开启URL路由 + 'URL_ROUTE_RULES' => array(), // 默认路由规则 针对模块 + 'URL_MAP_RULES' => array(), // URL映射定义规则 + + /* 系统变量名称设置 */ + 'VAR_MODULE' => 'm', // 默认模块获取变量 + 'VAR_ADDON' => 'addon', // 默认的插件控制器命名空间变量 + 'VAR_CONTROLLER' => 'c', // 默认控制器获取变量 + 'VAR_ACTION' => 'a', // 默认操作获取变量 + 'VAR_AJAX_SUBMIT' => 'ajax', // 默认的AJAX提交变量 + 'VAR_JSONP_HANDLER' => 'callback', + 'VAR_PATHINFO' => 's', // 兼容模式PATHINFO获取变量例如 ?s=/module/action/id/1 后面的参数取决于URL_PATHINFO_DEPR + 'VAR_TEMPLATE' => 't', // 默认模板切换变量 + 'VAR_AUTO_STRING' => false, // 输入变量是否自动强制转换为字符串 如果开启则数组变量需要手动传入变量修饰符获取变量 + + 'HTTP_CACHE_CONTROL' => 'private', // 网页缓存控制 + 'CHECK_APP_DIR' => true, // 是否检查应用目录是否创建 + 'FILE_UPLOAD_TYPE' => 'Local', // 文件上传方式 + 'DATA_CRYPT_TYPE' => 'Think', // 数据加密方式 + +); diff --git a/ThinkPHP/Conf/debug.php b/ThinkPHP/Conf/debug.php new file mode 100644 index 0000000..06674b9 --- /dev/null +++ b/ThinkPHP/Conf/debug.php @@ -0,0 +1,27 @@ + +// +---------------------------------------------------------------------- + +/** + * ThinkPHP 默认的调试模式配置文件 + */ +defined('THINK_PATH') or exit(); +// 调试模式下面默认设置 可以在应用配置目录下重新定义 debug.php 覆盖 +return array( + 'LOG_RECORD' => true, // 进行日志记录 + 'LOG_EXCEPTION_RECORD' => true, // 是否记录异常信息日志 + 'LOG_LEVEL' => 'EMERG,ALERT,CRIT,ERR,WARN,NOTIC,INFO,DEBUG,SQL', // 允许记录的日志级别 + 'DB_FIELDS_CACHE' => false, // 字段缓存信息 + 'DB_DEBUG' => true, // 开启调试模式 记录SQL日志 + 'TMPL_CACHE_ON' => false, // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'TMPL_STRIP_SPACE' => false, // 是否去除模板文件里面的html空格与换行 + 'SHOW_ERROR_MSG' => true, // 显示错误信息 + 'URL_CASE_INSENSITIVE' => false, // URL区分大小写 +); \ No newline at end of file diff --git a/ThinkPHP/LICENSE.txt b/ThinkPHP/LICENSE.txt new file mode 100644 index 0000000..581f906 --- /dev/null +++ b/ThinkPHP/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2014 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +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/ThinkPHP/Lang/en-us.php b/ThinkPHP/Lang/en-us.php new file mode 100644 index 0000000..706b9da --- /dev/null +++ b/ThinkPHP/Lang/en-us.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +/** + * ThinkPHP English language package + */ +return array( + /* core language package */ + '_MODULE_NOT_EXIST_' => "Module can't be loaded", + '_CONTROLLER_NOT_EXIST_' => "Controller can't be loaded", + '_ERROR_ACTION_' => 'Illegal Action', + '_LANGUAGE_NOT_LOAD_' => "Can't load language package", + '_TEMPLATE_NOT_EXIST_' => "Template doesn't exist", + '_MODULE_' => 'Module', + '_ACTION_' => 'Action', + '_MODEL_NOT_EXIST_' => "Model can't be loaded", + '_VALID_ACCESS_' => 'No access', + '_XML_TAG_ERROR_' => 'XML tag syntax errors', + '_DATA_TYPE_INVALID_' => 'Illegal data objects!', + '_OPERATION_WRONG_' => 'Operation error occurs', + '_NOT_LOAD_DB_' => 'Unable to load the database', + '_NO_DB_DRIVER_' => 'Unable to load database driver', + '_NOT_SUPPORT_DB_' => 'The system is temporarily not support database', + '_NO_DB_CONFIG_' => 'Not define the database configuration', + '_NOT_SUPPORT_' => 'The system does not support', + '_CACHE_TYPE_INVALID_' => 'Unable to load the cache type', + '_FILE_NOT_WRITABLE_' => 'Directory (file) is not writable', + '_METHOD_NOT_EXIST_' => 'The method you requested does not exist!', + '_CLASS_NOT_EXIST_' => 'Instantiating a class does not exist!', + '_CLASS_CONFLICT_' => 'Class name conflicts', + '_TEMPLATE_ERROR_' => 'Template Engine errors', + '_CACHE_WRITE_ERROR_' => 'Cache file write failed!', + '_TAGLIB_NOT_EXIST_' => 'Tag library is not defined', + '_OPERATION_FAIL_' => 'Operation failed!', + '_OPERATION_SUCCESS_' => 'Operation succeed!', + '_SELECT_NOT_EXIST_' => 'Record does not exist!', + '_EXPRESS_ERROR_' => 'Expression errors', + '_TOKEN_ERROR_' => "Form's token errors", + '_RECORD_HAS_UPDATE_' => 'Record has been updated', + '_NOT_ALLOW_PHP_' => 'PHP codes are not allowed in the template', + '_PARAM_ERROR_' => 'Parameter error or undefined', + '_ERROR_QUERY_EXPRESS_' => 'Query express error', +); diff --git a/ThinkPHP/Lang/pt-br.php b/ThinkPHP/Lang/pt-br.php new file mode 100644 index 0000000..e0ef3fa --- /dev/null +++ b/ThinkPHP/Lang/pt-br.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +/** + * ThinkPHP Portuguese language package + */ +return array( + /* core language package */ + '_MODULE_NOT_EXIST_' => "Módulo não pode ser carregado", + '_CONTROLLER_NOT_EXIST_' => "Controller não pode ser carregado", + '_ERROR_ACTION_' => 'Ação ilegal', + '_LANGUAGE_NOT_LOAD_' => "Não é possível carregar pacote da linguagem", + '_TEMPLATE_NOT_EXIST_' => "Template não existe", + '_MODULE_' => 'Módulo', + '_ACTION_' => 'Ação', + '_MODEL_NOT_EXIST_' => "Modelo não pode ser carregado", + '_VALID_ACCESS_' => 'Sem acesso', + '_XML_TAG_ERROR_' => 'Erro de sintaxe - XML tag', + '_DATA_TYPE_INVALID_' => 'Tipos de dados ilegais!', + '_OPERATION_WRONG_' => 'Erro na operação', + '_NOT_LOAD_DB_' => 'Impossível carregar banco de dados', + '_NO_DB_DRIVER_' => 'Impossível carregar driver do bando de dados', + '_NOT_SUPPORT_DB_' => 'Temporariamente sem suporte ao banco', + '_NO_DB_CONFIG_' => 'Não define a configuração do banco', + '_NOT_SUPPORT_' => 'O sistema não suporta', + '_CACHE_TYPE_INVALID_' => 'Impossível carregar o tipo de cache', + '_FILE_NOT_WRITABLE_' => 'Diretório (arquivo) não pode ser escrito', + '_METHOD_NOT_EXIST_' => 'O método solicitado não existe!', + '_CLASS_NOT_EXIST_' => 'Não existe instância da classe', + '_CLASS_CONFLICT_' => 'Conflitos com nome da classe', + '_TEMPLATE_ERROR_' => 'Erros na contrução do template', + '_CACHE_WRITE_ERROR_' => 'Escrita do arquivo de cache falhou!', + '_TAGLIB_NOT_EXIST_' => 'Biblioteca da tag não foi definida', + '_OPERATION_FAIL_' => 'Operação falhou!', + '_OPERATION_SUCCESS_' => 'Operação bem sucessida!', + '_SELECT_NOT_EXIST_' => 'Gravação não existe!', + '_EXPRESS_ERROR_' => 'Erros de expressão', + '_TOKEN_ERROR_' => 'Erro no token do formulário', + '_RECORD_HAS_UPDATE_' => 'Gravação não foi atualizada', + '_NOT_ALLOW_PHP_' => 'Código PHP não é permitido no template', + '_PARAM_ERROR_' => 'Parâmetro errado ou indefinido', + '_ERROR_QUERY_EXPRESS_' => 'Erros na expressão da query', +); diff --git a/ThinkPHP/Lang/zh-cn.php b/ThinkPHP/Lang/zh-cn.php new file mode 100644 index 0000000..57ef25b --- /dev/null +++ b/ThinkPHP/Lang/zh-cn.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +/** + * ThinkPHP 简体中文语言包 + */ +return array( + /* 核心语言变量 */ + '_MODULE_NOT_EXIST_' => '无法加载模块', + '_CONTROLLER_NOT_EXIST_' => '无法加载控制器', + '_ERROR_ACTION_' => '非法操作', + '_LANGUAGE_NOT_LOAD_' => '无法加载语言包', + '_TEMPLATE_NOT_EXIST_' => '模板不存在', + '_MODULE_' => '模块', + '_ACTION_' => '操作', + '_MODEL_NOT_EXIST_' => '模型不存在或者没有定义', + '_VALID_ACCESS_' => '没有权限', + '_XML_TAG_ERROR_' => 'XML标签语法错误', + '_DATA_TYPE_INVALID_' => '非法数据对象!', + '_OPERATION_WRONG_' => '操作出现错误', + '_NOT_LOAD_DB_' => '无法加载数据库', + '_NO_DB_DRIVER_' => '无法加载数据库驱动', + '_NOT_SUPPORT_DB_' => '系统暂时不支持数据库', + '_NO_DB_CONFIG_' => '没有定义数据库配置', + '_NOT_SUPPORT_' => '系统不支持', + '_CACHE_TYPE_INVALID_' => '无法加载缓存类型', + '_FILE_NOT_WRITABLE_' => '目录(文件)不可写', + '_METHOD_NOT_EXIST_' => '方法不存在!', + '_CLASS_NOT_EXIST_' => '实例化一个不存在的类!', + '_CLASS_CONFLICT_' => '类名冲突', + '_TEMPLATE_ERROR_' => '模板引擎错误', + '_CACHE_WRITE_ERROR_' => '缓存文件写入失败!', + '_TAGLIB_NOT_EXIST_' => '标签库未定义', + '_OPERATION_FAIL_' => '操作失败!', + '_OPERATION_SUCCESS_' => '操作成功!', + '_SELECT_NOT_EXIST_' => '记录不存在!', + '_EXPRESS_ERROR_' => '表达式错误', + '_TOKEN_ERROR_' => '表单令牌错误', + '_RECORD_HAS_UPDATE_' => '记录已经更新', + '_NOT_ALLOW_PHP_' => '模板禁用PHP代码', + '_PARAM_ERROR_' => '参数错误或者未定义', + '_ERROR_QUERY_EXPRESS_' => '错误的查询条件', +); diff --git a/ThinkPHP/Lang/zh-tw.php b/ThinkPHP/Lang/zh-tw.php new file mode 100644 index 0000000..327f334 --- /dev/null +++ b/ThinkPHP/Lang/zh-tw.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +/** + * ThinkPHP 繁体中文語言包 + */ +return array( + /* 核心語言變數 */ + '_MODULE_NOT_EXIST_' => '無法載入模組', + '_CONTROLLER_NOT_EXIST_' => '無法載入控制器', + '_ERROR_ACTION_' => '非法操作', + '_LANGUAGE_NOT_LOAD_' => '無法載入語言包', + '_TEMPLATE_NOT_EXIST_' => '模板不存在', + '_MODULE_' => '模組', + '_ACTION_' => '操作', + '_MODEL_NOT_EXIST_' => '模型不存在或者沒有定義', + '_VALID_ACCESS_' => '沒有權限', + '_XML_TAG_ERROR_' => 'XML標籤語法錯誤', + '_DATA_TYPE_INVALID_' => '非法資料物件!', + '_OPERATION_WRONG_' => '操作出現錯誤', + '_NOT_LOAD_DB_' => '無法載入資料庫', + '_NO_DB_DRIVER_' => '無法載入資料庫驅動', + '_NOT_SUPPORT_DB_' => '系統暫時不支援資料庫', + '_NO_DB_CONFIG_' => '沒有定義資料庫設定', + '_NOT_SUPPORT_' => '系統不支援', + '_CACHE_TYPE_INVALID_' => '無法載入快取類型', + '_FILE_NOT_WRITABLE_' => '目錄(檔案)不可寫', + '_METHOD_NOT_EXIST_' => '方法不存在!', + '_CLASS_NOT_EXIST_' => '實例化一個不存在的類別!', + '_CLASS_CONFLICT_' => '類別名稱衝突', + '_TEMPLATE_ERROR_' => '模板引擎錯誤', + '_CACHE_WRITE_ERROR_' => '快取檔案寫入失敗!', + '_TAGLIB_NOT_EXIST_' => '標籤庫未定義', + '_OPERATION_FAIL_' => '操作失敗!', + '_OPERATION_SUCCESS_' => '操作成功!', + '_SELECT_NOT_EXIST_' => '記錄不存在!', + '_EXPRESS_ERROR_' => '運算式錯誤', + '_TOKEN_ERROR_' => '表單權限錯誤', + '_RECORD_HAS_UPDATE_' => '記錄已經更新', + '_NOT_ALLOW_PHP_' => '模板禁用PHP代碼', + '_PARAM_ERROR_' => '參數錯誤或者未定義', + '_ERROR_QUERY_EXPRESS_' => '錯誤的查詢條件', +); diff --git a/ThinkPHP/Library/Behavior/AgentCheckBehavior.class.php b/ThinkPHP/Library/Behavior/AgentCheckBehavior.class.php new file mode 100644 index 0000000..77c271b --- /dev/null +++ b/ThinkPHP/Library/Behavior/AgentCheckBehavior.class.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 行为扩展:代理检测 + */ +class AgentCheckBehavior { + public function run(&$params) { + // 代理访问检测 + $limitProxyVisit = C('LIMIT_PROXY_VISIT',null,true); + if($limitProxyVisit && ($_SERVER['HTTP_X_FORWARDED_FOR'] || $_SERVER['HTTP_VIA'] || $_SERVER['HTTP_PROXY_CONNECTION'] || $_SERVER['HTTP_USER_AGENT_VIA'])) { + // 禁止代理访问 + exit('Access Denied'); + } + } +} diff --git a/ThinkPHP/Library/Behavior/BorisBehavior.class.php b/ThinkPHP/Library/Behavior/BorisBehavior.class.php new file mode 100644 index 0000000..7143746 --- /dev/null +++ b/ThinkPHP/Library/Behavior/BorisBehavior.class.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +use Think\Think; +/** + * Boris行为扩展 + */ +class BorisBehavior { + public function run(&$params) { + if(IS_CLI){ + if(!function_exists('pcntl_signal')) + E("pcntl_signal not working.\nRepl mode based on Linux OS or PHP for OS X(http://php-osx.liip.ch/)\n"); + Think::addMap(array( + 'Boris\Boris' => VENDOR_PATH . 'Boris/Boris.php', + 'Boris\Config' => VENDOR_PATH . 'Boris/Config.php', + 'Boris\CLIOptionsHandler' => VENDOR_PATH . 'Boris/CLIOptionsHandler.php', + 'Boris\ColoredInspector' => VENDOR_PATH . 'Boris/ColoredInspector.php', + 'Boris\DumpInspector' => VENDOR_PATH . 'Boris/DumpInspector.php', + 'Boris\EvalWorker' => VENDOR_PATH . 'Boris/EvalWorker.php', + 'Boris\ExportInspector' => VENDOR_PATH . 'Boris/ExportInspector.php', + 'Boris\Inspector' => VENDOR_PATH . 'Boris/Inspector.php', + 'Boris\ReadlineClient' => VENDOR_PATH . 'Boris/ReadlineClient.php', + 'Boris\ShallowParser' => VENDOR_PATH . 'Boris/ShallowParser.php', + )); + $boris = new \Boris\Boris(">>> "); + $config = new \Boris\Config(); + $config->apply($boris, true); + $options = new \Boris\CLIOptionsHandler(); + $options->handle($boris); + $boris->onStart(sprintf("echo 'REPL MODE FOR THINKPHP \nTHINKPHP_VERSION: %s, PHP_VERSION: %s, BORIS_VERSION: %s\n';", THINK_VERSION, PHP_VERSION, $boris::VERSION)); + $boris->start(); + } + } +} diff --git a/ThinkPHP/Library/Behavior/BrowserCheckBehavior.class.php b/ThinkPHP/Library/Behavior/BrowserCheckBehavior.class.php new file mode 100644 index 0000000..b1ed896 --- /dev/null +++ b/ThinkPHP/Library/Behavior/BrowserCheckBehavior.class.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 浏览器防刷新检测 + */ +class BrowserCheckBehavior { + public function run(&$params) { + if($_SERVER['REQUEST_METHOD'] == 'GET') { + // 启用页面防刷新机制 + $guid = md5($_SERVER['PHP_SELF']); + // 浏览器防刷新的时间间隔(秒) 默认为10 + $refleshTime = C('LIMIT_REFLESH_TIMES',null,10); + // 检查页面刷新间隔 + if(cookie('_last_visit_time_'.$guid) && cookie('_last_visit_time_'.$guid)>time()-$refleshTime) { + // 页面刷新读取浏览器缓存 + header('HTTP/1.1 304 Not Modified'); + exit; + }else{ + // 缓存当前地址访问时间 + cookie('_last_visit_time_'.$guid, $_SERVER['REQUEST_TIME']); + //header('Last-Modified:'.(date('D,d M Y H:i:s',$_SERVER['REQUEST_TIME']-C('LIMIT_REFLESH_TIMES'))).' GMT'); + } + } + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Behavior/BuildLiteBehavior.class.php b/ThinkPHP/Library/Behavior/BuildLiteBehavior.class.php new file mode 100644 index 0000000..5b8f9b8 --- /dev/null +++ b/ThinkPHP/Library/Behavior/BuildLiteBehavior.class.php @@ -0,0 +1,87 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +// 创建Lite运行文件 +// 可以替换框架入口文件运行 +// 建议绑定位置app_init +class BuildLiteBehavior { + public function run(&$params) { + if(!defined('BUILD_LITE_FILE')) return ; + $litefile = C('RUNTIME_LITE_FILE',null,RUNTIME_PATH.'lite.php'); + if(is_file($litefile)) return; + + $defs = get_defined_constants(TRUE); + $content = 'namespace {$GLOBALS[\'_beginTime\'] = microtime(TRUE);'; + if(MEMORY_LIMIT_ON) { + $content .= '$GLOBALS[\'_startUseMems\'] = memory_get_usage();'; + } + + // 生成数组定义 + unset($defs['user']['BUILD_LITE_FILE']); + $content .= $this->buildArrayDefine($defs['user']).'}'; + + // 读取编译列表文件 + $filelist = is_file(CONF_PATH.'lite.php')? + include CONF_PATH.'lite.php': + array( + THINK_PATH.'Common/functions.php', + COMMON_PATH.'Common/function.php', + CORE_PATH . 'Think'.EXT, + CORE_PATH . 'Hook'.EXT, + CORE_PATH . 'App'.EXT, + CORE_PATH . 'Dispatcher'.EXT, + CORE_PATH . 'Log'.EXT, + CORE_PATH . 'Log/Driver/File'.EXT, + CORE_PATH . 'Route'.EXT, + CORE_PATH . 'Controller'.EXT, + CORE_PATH . 'View'.EXT, + CORE_PATH . 'Storage'.EXT, + CORE_PATH . 'Storage/Driver/File'.EXT, + CORE_PATH . 'Exception'.EXT, + BEHAVIOR_PATH . 'ParseTemplateBehavior'.EXT, + BEHAVIOR_PATH . 'ContentReplaceBehavior'.EXT, + ); + + // 编译文件 + foreach ($filelist as $file){ + if(is_file($file)) { + $content .= compile($file); + } + } + + // 处理Think类的start方法 + $content = preg_replace('/\$runtimefile = RUNTIME_PATH(.+?)(if\(APP_STATUS)/','\2',$content,1); + $content .= "\nnamespace { Think\Think::addMap(".var_export(\Think\Think::getMap(),true).");"; + $content .= "\nL(".var_export(L(),true).");\nC(".var_export(C(),true).');Think\Hook::import('.var_export(\Think\Hook::get(),true).');Think\Think::start();}'; + + // 生成运行Lite文件 + file_put_contents($litefile,strip_whitespace(' $val) { + $key = strtoupper($key); + $content .= 'defined(\'' . $key . '\') or '; + if (is_int($val) || is_float($val)) { + $content .= "define('" . $key . "'," . $val . ');'; + } elseif (is_bool($val)) { + $val = ($val) ? 'true' : 'false'; + $content .= "define('" . $key . "'," . $val . ');'; + } elseif (is_string($val)) { + $content .= "define('" . $key . "','" . addslashes($val) . "');"; + } + $content .= "\n"; + } + return $content; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Behavior/CheckActionRouteBehavior.class.php b/ThinkPHP/Library/Behavior/CheckActionRouteBehavior.class.php new file mode 100644 index 0000000..5e1fb9c --- /dev/null +++ b/ThinkPHP/Library/Behavior/CheckActionRouteBehavior.class.php @@ -0,0 +1,194 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 系统行为扩展:操作路由检测 + */ +class CheckActionRouteBehavior { + + // 行为扩展的执行入口必须是run + public function run(&$config){ + // 优先检测是否存在PATH_INFO + $regx = trim($_SERVER['PATH_INFO'],'/'); + if(empty($regx)) return ; + // 路由定义文件优先于config中的配置定义 + // 路由处理 + $routes = $config['routes']; + if(!empty($routes)) { + $depr = C('URL_PATHINFO_DEPR'); + // 分隔符替换 确保路由定义使用统一的分隔符 + $regx = str_replace($depr,'/',$regx); + $regx = substr_replace($regx,'',0,strlen(__URL__)); + foreach ($routes as $rule=>$route){ + if(0===strpos($rule,'/') && preg_match($rule,$regx,$matches)) { // 正则路由 + return C('ACTION_NAME',$this->parseRegex($matches,$route,$regx)); + }else{ // 规则路由 + $len1 = substr_count($regx,'/'); + $len2 = substr_count($rule,'/'); + if($len1>=$len2) { + if('$' == substr($rule,-1,1)) {// 完整匹配 + if($len1 != $len2) { + continue; + }else{ + $rule = substr($rule,0,-1); + } + } + $match = $this->checkUrlMatch($regx,$rule); + if($match) return C('ACTION_NAME',$this->parseRule($rule,$route,$regx)); + } + } + } + } + } + + // 检测URL和规则路由是否匹配 + private function checkUrlMatch($regx,$rule) { + $m1 = explode('/',$regx); + $m2 = explode('/',$rule); + $match = true; // 是否匹配 + foreach ($m2 as $key=>$val){ + if(':' == substr($val,0,1)) {// 动态变量 + if(strpos($val,'\\')) { + $type = substr($val,-1); + if('d'==$type && !is_numeric($m1[$key])) { + $match = false; + break; + } + }elseif(strpos($val,'^')){ + $array = explode('|',substr(strstr($val,'^'),1)); + if(in_array($m1[$key],$array)) { + $match = false; + break; + } + } + }elseif(0 !== strcasecmp($val,$m1[$key])){ + $match = false; + break; + } + } + return $match; + } + + // 解析规范的路由地址 + // 地址格式 操作?参数1=值1&参数2=值2... + private function parseUrl($url) { + $var = array(); + if(false !== strpos($url,'?')) { // 操作?参数1=值1&参数2=值2... + $info = parse_url($url); + $path = $info['path']; + parse_str($info['query'],$var); + }else{ // 操作 + $path = $url; + } + $var[C('VAR_ACTION')] = $path; + return $var; + } + + // 解析规则路由 + // '路由规则'=>'操作?额外参数1=值1&额外参数2=值2...' + // '路由规则'=>array('操作','额外参数1=值1&额外参数2=值2...') + // '路由规则'=>'外部地址' + // '路由规则'=>array('外部地址','重定向代码') + // 路由规则中 :开头 表示动态变量 + // 外部地址中可以用动态变量 采用 :1 :2 的方式 + // 'news/:month/:day/:id'=>array('News/read?cate=1','status=1'), + // 'new/:id'=>array('/new.php?id=:1',301), 重定向 + private function parseRule($rule,$route,$regx) { + // 获取路由地址规则 + $url = is_array($route)?$route[0]:$route; + // 获取URL地址中的参数 + $paths = explode('/',$regx); + // 解析路由规则 + $matches = array(); + $rule = explode('/',$rule); + foreach ($rule as $item){ + if(0===strpos($item,':')) { // 动态变量获取 + if($pos = strpos($item,'^') ) { + $var = substr($item,1,$pos-1); + }elseif(strpos($item,'\\')){ + $var = substr($item,1,-2); + }else{ + $var = substr($item,1); + } + $matches[$var] = array_shift($paths); + }else{ // 过滤URL中的静态变量 + array_shift($paths); + } + } + if(0=== strpos($url,'/') || 0===strpos($url,'http')) { // 路由重定向跳转 + if(strpos($url,':')) { // 传递动态参数 + $values = array_values($matches); + $url = preg_replace('/:(\d+)/e','$values[\\1-1]',$url); + } + header("Location: $url", true,(is_array($route) && isset($route[1]))?$route[1]:301); + exit; + }else{ + // 解析路由地址 + $var = $this->parseUrl($url); + // 解析路由地址里面的动态参数 + $values = array_values($matches); + foreach ($var as $key=>$val){ + if(0===strpos($val,':')) { + $var[$key] = $values[substr($val,1)-1]; + } + } + $var = array_merge($matches,$var); + // 解析剩余的URL参数 + if($paths) { + preg_replace('@(\w+)\/([^\/]+)@e', '$var[strtolower(\'\\1\')]=strip_tags(\'\\2\');', implode('/',$paths)); + } + // 解析路由自动传入参数 + if(is_array($route) && isset($route[1])) { + parse_str($route[1],$params); + $var = array_merge($var,$params); + } + $action = $var[C('VAR_ACTION')]; + unset($var[C('VAR_ACTION')]); + $_GET = array_merge($var,$_GET); + return $action; + } + } + + // 解析正则路由 + // '路由正则'=>'[分组/模块/操作]?参数1=值1&参数2=值2...' + // '路由正则'=>array('[分组/模块/操作]?参数1=值1&参数2=值2...','额外参数1=值1&额外参数2=值2...') + // '路由正则'=>'外部地址' + // '路由正则'=>array('外部地址','重定向代码') + // 参数值和外部地址中可以用动态变量 采用 :1 :2 的方式 + // '/new\/(\d+)\/(\d+)/'=>array('News/read?id=:1&page=:2&cate=1','status=1'), + // '/new\/(\d+)/'=>array('/new.php?id=:1&page=:2&status=1','301'), 重定向 + private function parseRegex($matches,$route,$regx) { + // 获取路由地址规则 + $url = is_array($route)?$route[0]:$route; + $url = preg_replace('/:(\d+)/e','$matches[\\1]',$url); + if(0=== strpos($url,'/') || 0===strpos($url,'http')) { // 路由重定向跳转 + header("Location: $url", true,(is_array($route) && isset($route[1]))?$route[1]:301); + exit; + }else{ + // 解析路由地址 + $var = $this->parseUrl($url); + // 解析剩余的URL参数 + $regx = substr_replace($regx,'',0,strlen($matches[0])); + if($regx) { + preg_replace('@(\w+)\/([^,\/]+)@e', '$var[strtolower(\'\\1\')]=strip_tags(\'\\2\');', $regx); + } + // 解析路由自动传入参数 + if(is_array($route) && isset($route[1])) { + parse_str($route[1],$params); + $var = array_merge($var,$params); + } + $action = $var[C('VAR_ACTION')]; + unset($var[C('VAR_ACTION')]); + $_GET = array_merge($var,$_GET); + } + return $action; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Behavior/CheckLangBehavior.class.php b/ThinkPHP/Library/Behavior/CheckLangBehavior.class.php new file mode 100644 index 0000000..c4d46a2 --- /dev/null +++ b/ThinkPHP/Library/Behavior/CheckLangBehavior.class.php @@ -0,0 +1,77 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 语言检测 并自动加载语言包 + */ +class CheckLangBehavior { + + // 行为扩展的执行入口必须是run + public function run(&$params){ + // 检测语言 + $this->checkLanguage(); + } + + /** + * 语言检查 + * 检查浏览器支持语言,并自动加载语言包 + * @access private + * @return void + */ + private function checkLanguage() { + // 不开启语言包功能,仅仅加载框架语言文件直接返回 + if (!C('LANG_SWITCH_ON',null,false)){ + return; + } + $langSet = C('DEFAULT_LANG'); + $varLang = C('VAR_LANGUAGE',null,'l'); + $langList = C('LANG_LIST',null,'zh-cn'); + // 启用了语言包功能 + // 根据是否启用自动侦测设置获取语言选择 + if (C('LANG_AUTO_DETECT',null,true)){ + if(isset($_GET[$varLang])){ + $langSet = $_GET[$varLang];// url中设置了语言变量 + cookie('think_language',$langSet,3600); + }elseif(cookie('think_language')){// 获取上次用户的选择 + $langSet = cookie('think_language'); + }elseif(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])){// 自动侦测浏览器语言 + preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); + $langSet = $matches[1]; + cookie('think_language',$langSet,3600); + } + if(false === stripos($langList,$langSet)) { // 非法语言参数 + $langSet = C('DEFAULT_LANG'); + } + } + // 定义当前语言 + define('LANG_SET',strtolower($langSet)); + + // 读取框架语言包 + $file = THINK_PATH.'Lang/'.LANG_SET.'.php'; + if(LANG_SET != C('DEFAULT_LANG') && is_file($file)) + L(include $file); + + // 读取应用公共语言包 + $file = LANG_PATH.LANG_SET.'.php'; + if(is_file($file)) + L(include $file); + + // 读取模块语言包 + $file = MODULE_PATH.'Lang/'.LANG_SET.'.php'; + if(is_file($file)) + L(include $file); + + // 读取当前控制器语言包 + $file = MODULE_PATH.'Lang/'.LANG_SET.'/'.strtolower(CONTROLLER_NAME).'.php'; + if (is_file($file)) + L(include $file); + } +} diff --git a/ThinkPHP/Library/Behavior/ChromeShowPageTraceBehavior.class.php b/ThinkPHP/Library/Behavior/ChromeShowPageTraceBehavior.class.php new file mode 100644 index 0000000..8abf892 --- /dev/null +++ b/ThinkPHP/Library/Behavior/ChromeShowPageTraceBehavior.class.php @@ -0,0 +1,610 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +/** + * 将Trace信息输出到chrome浏览器的控制器,从而不影响ajax效果和页面的布局。 + * 使用前,你需要先安装 chrome log 这个插件: http://craig.is/writing/chrome-logger。 + * 定义应用的tags.php文件 Application/Common/Conf/tags.php, + * + * array( + * 'Behavior\ChromeShowPageTrace' + * ) + * ); + * + * 如果trace信息没有正常输出,请查看您的日志。 + * 这是通过http headers和chrome通信,所以要保证在输出trace信息之前不能有 + * headers输出,你可以在入口文件第一行加入代码 ob_start(); 或者配置output_buffering + * + */ +namespace Behavior; +use Think\Log; + +/** + * 系统行为扩展 页面Trace显示输出 + */ +class ChromeShowPageTraceBehavior { + + protected $tracePageTabs = array('BASE'=>'基本','FILE'=>'文件','INFO'=>'流程','ERR|NOTIC'=>'错误','SQL'=>'SQL','DEBUG'=>'调试'); + + // 行为扩展的执行入口必须是run + public function run(&$params){ + if(C('SHOW_PAGE_TRACE')) $this->showTrace(); + } + + + /** + * 显示页面Trace信息 + * @access private + */ + private function showTrace() { + // 系统默认显示信息 + $files = get_included_files(); + $info = array(); + foreach ($files as $key=>$file){ + $info[] = $file.' ( '.number_format(filesize($file)/1024,2).' KB )'; + } + $trace = array(); + $base = array( + '请求信息' => date('Y-m-d H:i:s',$_SERVER['REQUEST_TIME']).' '.$_SERVER['SERVER_PROTOCOL'].' '.$_SERVER['REQUEST_METHOD'].' : '.__SELF__, + '运行时间' => $this->showTime(), + '吞吐率' => number_format(1/G('beginTime','viewEndTime'),2).'req/s', + '内存开销' => MEMORY_LIMIT_ON?number_format((memory_get_usage() - $GLOBALS['_startUseMems'])/1024,2).' kb':'不支持', + '查询信息' => N('db_query').' queries '.N('db_write').' writes ', + '文件加载' => count(get_included_files()), + '缓存信息' => N('cache_read').' gets '.N('cache_write').' writes ', + '配置加载' => count(c()), + '会话信息' => 'SESSION_ID='.session_id(), + ); + // 读取应用定义的Trace文件 + $traceFile = COMMON_PATH.'Conf/trace.php'; + if(is_file($traceFile)) { + $base = array_merge($base,include $traceFile); + } + + $debug = trace(); + $tabs = C('TRACE_PAGE_TABS',null,$this->tracePageTabs); + foreach ($tabs as $name=>$title){ + switch(strtoupper($name)) { + case 'BASE':// 基本信息 + $trace[$title] = $base; + break; + case 'FILE': // 文件信息 + $trace[$title] = $info; + break; + default:// 调试信息 + $name = strtoupper($name); + if(strpos($name,'|')) {// 多组信息 + $array = explode('|',$name); + $result = array(); + foreach($array as $name){ + $result += isset($debug[$name])?$debug[$name]:array(); + } + $trace[$title] = $result; + }else{ + $trace[$title] = isset($debug[$name])?$debug[$name]:''; + } + } + } + chrome_debug('TRACE信息:'.__SELF__,'group'); + //输出日志 + foreach($trace as $title=>$log){ + '错误'==$title?chrome_debug($title,'group'):chrome_debug($title,'groupCollapsed'); + foreach($log as $i=>$logstr){ + chrome_debug($i.'.'.$logstr,'log'); + } + chrome_debug('','groupEnd'); + } + chrome_debug('','groupEnd'); + if($save = C('PAGE_TRACE_SAVE')) { // 保存页面Trace日志 + if(is_array($save)) {// 选择选项卡保存 + $tabs = C('TRACE_PAGE_TABS',null,$this->tracePageTabs); + $array = array(); + foreach ($save as $tab){ + $array[] = $tabs[$tab]; + } + } + $content = date('[ c ]').' '.get_client_ip().' '.$_SERVER['REQUEST_URI']."\r\n"; + foreach ($trace as $key=>$val){ + if(!isset($array) || in_array($key,$array)) { + $content .= '[ '.$key." ]\r\n"; + if(is_array($val)) { + foreach ($val as $k=>$v){ + $content .= (!is_numeric($k)?$k.':':'').print_r($v,true)."\r\n"; + } + }else{ + $content .= print_r($val,true)."\r\n"; + } + $content .= "\r\n"; + } + } + error_log(str_replace('
',"\r\n",$content), 3,LOG_PATH.date('y_m_d').'_trace.log'); + } + unset($files,$info,$base); + } + + /** + * 获取运行时间 + */ + private function showTime() { + // 显示运行时间 + G('beginTime',$GLOBALS['_beginTime']); + G('viewEndTime'); + // 显示详细运行时间 + return G('beginTime','viewEndTime').'s ( Load:'.G('beginTime','loadTime').'s Init:'.G('loadTime','initTime').'s Exec:'.G('initTime','viewStartTime').'s Template:'.G('viewStartTime','viewEndTime').'s )'; + } +} +if(!function_exists('chrome_debug')){ +//ChromePhp 输出trace的函数 +function chrome_debug($msg,$type='trace',$trace_level=1){ + if('trace'==$type){ + ChromePhp::groupCollapsed($msg); + $traces=debug_backtrace(false); + $traces=array_reverse($traces); + $max=count($traces)-$trace_level; + for($i=0;$i<$max;$i++){ + $trace=$traces[$i]; + $fun=isset($trace['class'])?$trace['class'].'::'.$trace['function']:$trace['function']; + $file=isset($trace['file'])?$trace['file']:'unknown file'; + $line=isset($trace['line'])?$trace['line']:'unknown line'; + $trace_msg='#'.$i.' '.$fun.' called at ['.$file.':'.$line.']'; + if(!empty($trace['args'])){ + ChromePhp::groupCollapsed($trace_msg); + ChromePhp::log($trace['args']); + ChromePhp::groupEnd(); + }else{ + ChromePhp::log($trace_msg); + } + } + ChromePhp::groupEnd(); + }else{ + if(method_exists('Behavior\ChromePhp',$type)){ + //支持type trace,warn,log,error,group, groupCollapsed, groupEnd等 + call_user_func(array('Behavior\ChromePhp',$type),$msg); + }else{ + //如果type不为trace,warn,log等,则为log的标签 + call_user_func_array(array('Behavior\ChromePhp','log'),func_get_args()); + } + } +} + + + +/** + * Server Side Chrome PHP debugger class + * + * @package ChromePhp + * @author Craig Campbell + */ +class ChromePhp{ + /** + * @var string + */ + const VERSION = '4.1.0'; + + /** + * @var string + */ + const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * @var string + */ + const BACKTRACE_LEVEL = 'backtrace_level'; + + /** + * @var string + */ + const LOG = 'log'; + + /** + * @var string + */ + const WARN = 'warn'; + + /** + * @var string + */ + const ERROR = 'error'; + + /** + * @var string + */ + const GROUP = 'group'; + + /** + * @var string + */ + const INFO = 'info'; + + /** + * @var string + */ + const GROUP_END = 'groupEnd'; + + /** + * @var string + */ + const GROUP_COLLAPSED = 'groupCollapsed'; + + /** + * @var string + */ + const TABLE = 'table'; + + /** + * @var string + */ + protected $_php_version; + + /** + * @var int + */ + protected $_timestamp; + + /** + * @var array + */ + protected $_json = array( + 'version' => self::VERSION, + 'columns' => array('log', 'backtrace', 'type'), + 'rows' => array() + ); + + /** + * @var array + */ + protected $_backtraces = array(); + + /** + * @var bool + */ + protected $_error_triggered = false; + + /** + * @var array + */ + protected $_settings = array( + self::BACKTRACE_LEVEL => 1 + ); + + /** + * @var ChromePhp + */ + protected static $_instance; + + /** + * Prevent recursion when working with objects referring to each other + * + * @var array + */ + protected $_processed = array(); + + /** + * constructor + */ + private function __construct() + { + $this->_php_version = phpversion(); + $this->_timestamp = $this->_php_version >= 5.1 ? $_SERVER['REQUEST_TIME'] : time(); + $this->_json['request_uri'] = $_SERVER['REQUEST_URI']; + } + + /** + * gets instance of this class + * + * @return ChromePhp + */ + public static function getInstance() + { + if (self::$_instance === null) { + self::$_instance = new self(); + } + return self::$_instance; + } + + /** + * logs a variable to the console + * + * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] + * @return void + */ + public static function log() + { + $args = func_get_args(); + return self::_log('', $args); + } + + /** + * logs a warning to the console + * + * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] + * @return void + */ + public static function warn() + { + $args = func_get_args(); + return self::_log(self::WARN, $args); + } + + /** + * logs an error to the console + * + * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] + * @return void + */ + public static function error() + { + $args = func_get_args(); + return self::_log(self::ERROR, $args); + } + + /** + * sends a group log + * + * @param string value + */ + public static function group() + { + $args = func_get_args(); + return self::_log(self::GROUP, $args); + } + + /** + * sends an info log + * + * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] + * @return void + */ + public static function info() + { + $args = func_get_args(); + return self::_log(self::INFO, $args); + } + + /** + * sends a collapsed group log + * + * @param string value + */ + public static function groupCollapsed() + { + $args = func_get_args(); + return self::_log(self::GROUP_COLLAPSED, $args); + } + + /** + * ends a group log + * + * @param string value + */ + public static function groupEnd() + { + $args = func_get_args(); + return self::_log(self::GROUP_END, $args); + } + + /** + * sends a table log + * + * @param string value + */ + public static function table() + { + $args = func_get_args(); + return self::_log(self::TABLE, $args); + } + + /** + * internal logging call + * + * @param string $type + * @return void + */ + protected static function _log($type, array $args) + { + // nothing passed in, don't do anything + if (count($args) == 0 && $type != self::GROUP_END) { + return; + } + + $logger = self::getInstance(); + + $logger->_processed = array(); + + $logs = array(); + foreach ($args as $arg) { + $logs[] = $logger->_convert($arg); + } + + $backtrace = debug_backtrace(false); + $level = $logger->getSetting(self::BACKTRACE_LEVEL); + + $backtrace_message = 'unknown'; + if (isset($backtrace[$level]['file']) && isset($backtrace[$level]['line'])) { + $backtrace_message = $backtrace[$level]['file'] . ' : ' . $backtrace[$level]['line']; + } + + $logger->_addRow($logs, $backtrace_message, $type); + } + + /** + * converts an object to a better format for logging + * + * @param Object + * @return array + */ + protected function _convert($object) + { + // if this isn't an object then just return it + if (!is_object($object)) { + return $object; + } + + //Mark this object as processed so we don't convert it twice and it + //Also avoid recursion when objects refer to each other + $this->_processed[] = $object; + + $object_as_array = array(); + + // first add the class name + $object_as_array['___class_name'] = get_class($object); + + // loop through object vars + $object_vars = get_object_vars($object); + foreach ($object_vars as $key => $value) { + + // same instance as parent object + if ($value === $object || in_array($value, $this->_processed, true)) { + $value = 'recursion - parent object [' . get_class($value) . ']'; + } + $object_as_array[$key] = $this->_convert($value); + } + + $reflection = new ReflectionClass($object); + + // loop through the properties and add those + foreach ($reflection->getProperties() as $property) { + + // if one of these properties was already added above then ignore it + if (array_key_exists($property->getName(), $object_vars)) { + continue; + } + $type = $this->_getPropertyKey($property); + + if ($this->_php_version >= 5.3) { + $property->setAccessible(true); + } + + try { + $value = $property->getValue($object); + } catch (ReflectionException $e) { + $value = 'only PHP 5.3 can access private/protected properties'; + } + + // same instance as parent object + if ($value === $object || in_array($value, $this->_processed, true)) { + $value = 'recursion - parent object [' . get_class($value) . ']'; + } + + $object_as_array[$type] = $this->_convert($value); + } + return $object_as_array; + } + + /** + * takes a reflection property and returns a nicely formatted key of the property name + * + * @param ReflectionProperty + * @return string + */ + protected function _getPropertyKey(ReflectionProperty $property) + { + $static = $property->isStatic() ? ' static' : ''; + if ($property->isPublic()) { + return 'public' . $static . ' ' . $property->getName(); + } + + if ($property->isProtected()) { + return 'protected' . $static . ' ' . $property->getName(); + } + + if ($property->isPrivate()) { + return 'private' . $static . ' ' . $property->getName(); + } + } + + /** + * adds a value to the data array + * + * @var mixed + * @return void + */ + protected function _addRow(array $logs, $backtrace, $type) + { + // if this is logged on the same line for example in a loop, set it to null to save space + if (in_array($backtrace, $this->_backtraces)) { + $backtrace = null; + } + + // for group, groupEnd, and groupCollapsed + // take out the backtrace since it is not useful + if ($type == self::GROUP || $type == self::GROUP_END || $type == self::GROUP_COLLAPSED) { + $backtrace = null; + } + + if ($backtrace !== null) { + $this->_backtraces[] = $backtrace; + } + + $row = array($logs, $backtrace, $type); + + $this->_json['rows'][] = $row; + $this->_writeHeader($this->_json); + } + + protected function _writeHeader($data) + { + header(self::HEADER_NAME . ': ' . $this->_encode($data)); + } + + /** + * encodes the data to be sent along with the request + * + * @param array $data + * @return string + */ + protected function _encode($data) + { + return base64_encode(utf8_encode(json_encode($data))); + } + + /** + * adds a setting + * + * @param string key + * @param mixed value + * @return void + */ + public function addSetting($key, $value) + { + $this->_settings[$key] = $value; + } + + /** + * add ability to set multiple settings in one call + * + * @param array $settings + * @return void + */ + public function addSettings(array $settings) + { + foreach ($settings as $key => $value) { + $this->addSetting($key, $value); + } + } + + /** + * gets a setting + * + * @param string key + * @return mixed + */ + public function getSetting($key) + { + if (!isset($this->_settings[$key])) { + return null; + } + return $this->_settings[$key]; + } +} +} diff --git a/ThinkPHP/Library/Behavior/ContentReplaceBehavior.class.php b/ThinkPHP/Library/Behavior/ContentReplaceBehavior.class.php new file mode 100644 index 0000000..c572223 --- /dev/null +++ b/ThinkPHP/Library/Behavior/ContentReplaceBehavior.class.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 系统行为扩展:模板内容输出替换 + */ +class ContentReplaceBehavior { + + // 行为扩展的执行入口必须是run + public function run(&$content){ + $content = $this->templateContentReplace($content); + } + + /** + * 模板内容替换 + * @access protected + * @param string $content 模板内容 + * @return string + */ + protected function templateContentReplace($content) { + // 系统默认的特殊变量替换 + $replace = array( + '__ROOT__' => __ROOT__, // 当前网站地址 + '__APP__' => __APP__, // 当前应用地址 + '__MODULE__' => __MODULE__, + '__ACTION__' => __ACTION__, // 当前操作地址 + '__SELF__' => htmlentities(__SELF__), // 当前页面地址 + '__CONTROLLER__'=> __CONTROLLER__, + '__URL__' => __CONTROLLER__, + '__PUBLIC__' => __ROOT__.'/Public',// 站点公共目录 + ); + // 允许用户自定义模板的字符串替换 + if(is_array(C('TMPL_PARSE_STRING')) ) + $replace = array_merge($replace,C('TMPL_PARSE_STRING')); + $content = str_replace(array_keys($replace),array_values($replace),$content); + return $content; + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Behavior/CronRunBehavior.class.php b/ThinkPHP/Library/Behavior/CronRunBehavior.class.php new file mode 100644 index 0000000..36c732e --- /dev/null +++ b/ThinkPHP/Library/Behavior/CronRunBehavior.class.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 自动执行任务 + */ +class CronRunBehavior { + + public function run(&$params) { + // 锁定自动执行 + $lockfile = RUNTIME_PATH.'cron.lock'; + if(is_writable($lockfile) && filemtime($lockfile) > $_SERVER['REQUEST_TIME'] - C('CRON_MAX_TIME',null,60)) { + return ; + } else { + touch($lockfile); + } + set_time_limit(1000); + ignore_user_abort(true); + + // 载入cron配置文件 + // 格式 return array( + // 'cronname'=>array('filename',intervals,nextruntime),... + // ); + if(is_file(RUNTIME_PATH.'~crons.php')) { + $crons = include RUNTIME_PATH.'~crons.php'; + }elseif(is_file(COMMON_PATH.'Conf/crons.php')){ + $crons = include COMMON_PATH.'Conf/crons.php'; + } + if(isset($crons) && is_array($crons)) { + $update = false; + $log = array(); + foreach ($crons as $key=>$cron){ + if(empty($cron[2]) || $_SERVER['REQUEST_TIME']>=$cron[2]) { + // 到达时间 执行cron文件 + G('cronStart'); + include COMMON_PATH.'Cron/'.$cron[0].'.php'; + G('cronEnd'); + $_useTime = G('cronStart','cronEnd', 6); + // 更新cron记录 + $cron[2] = $_SERVER['REQUEST_TIME']+$cron[1]; + $crons[$key] = $cron; + $log[] = "Cron:$key Runat ".date('Y-m-d H:i:s')." Use $_useTime s\n"; + $update = true; + } + } + if($update) { + // 记录Cron执行日志 + \Think\Log::write(implode('',$log)); + // 更新cron文件 + $content = ""; + file_put_contents(RUNTIME_PATH.'~crons.php',$content); + } + } + // 解除锁定 + unlink($lockfile); + return ; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Behavior/FireShowPageTraceBehavior.class.php b/ThinkPHP/Library/Behavior/FireShowPageTraceBehavior.class.php new file mode 100644 index 0000000..82c828a --- /dev/null +++ b/ThinkPHP/Library/Behavior/FireShowPageTraceBehavior.class.php @@ -0,0 +1,2079 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +/** + * 将Trace信息输出到火狐的firebug,从而不影响ajax效果和页面的布局。 + * 使用前,你需要先在火狐浏览器上安装firebug和firePHP两个插件。 + * 定义应用的tags.php文件, + * + * array( + * 'FireShowPageTrace' + * ) + * ); + * + * 再将此文件放到应用的Behavior文件夹中即可 + * 如果trace信息没有正常输出,请查看您的日志。 + * firePHP,是通过http headers和firebug通讯的,所以要保证在输出trace信息之前不能有 + * headers输出,你可以在入口文件第一行加入代码 ob_start(); 或者配置output_buffering + * + */ +namespace Behavior; +/** + * 系统行为扩展 页面Trace显示输出 + */ +class FireShowPageTraceBehavior { + protected $tracePagTabs = array('BASE'=>'基本','FILE'=>'文件','INFO'=>'流程','ERR|NOTIC'=>'错误','SQL'=>'SQL','DEBUG'=>'调试'); + + // 行为扩展的执行入口必须是run + public function run(&$params){ + if(C('FIRE_SHOW_PAGE_TRACE',null,true)) $this->showTrace(); + } + + /** + * 显示页面Trace信息 + * @access private + */ + private function showTrace() { + // 系统默认显示信息 + $files = get_included_files(); + $info = array(); + foreach ($files as $key=>$file){ + $info[] = $file.' ( '.number_format(filesize($file)/1024,2).' KB )'; + } + $trace = array(); + $base = array( + '请求信息'=> date('Y-m-d H:i:s',$_SERVER['REQUEST_TIME']).' '.$_SERVER['SERVER_PROTOCOL'].' '.$_SERVER['REQUEST_METHOD'].' : '.__SELF__, + '运行时间'=> $this->showTime(), + '内存开销'=> MEMORY_LIMIT_ON?number_format((memory_get_usage() - $GLOBALS['_startUseMems'])/1024,2).' kb':'不支持', + '查询信息'=> N('db_query').' queries '.N('db_write').' writes ', + '文件加载'=> count(get_included_files()), + '缓存信息'=> N('cache_read').' gets '.N('cache_write').' writes ', + '配置加载'=> count(c()), + '会话信息'=> 'SESSION_ID='.session_id(), + ); + // 读取应用定义的Trace文件 + $traceFile = CONF_PATH.'trace.php'; + if(is_file($traceFile)) { + $base = array_merge($base,include $traceFile); + } + $debug = trace(); + $tabs = C('TRACE_PAGE_TABS',null,$this->tracePagTabs); + foreach ($tabs as $name=>$title){ + switch(strtoupper($name)) { + case 'BASE':// 基本信息 + $trace[$title] = $base; + break; + case 'FILE': // 文件信息 + $trace[$title] = $info; + break; + default:// 调试信息 + if(strpos($name,'|')) {// 多组信息 + $array = explode('|',$name); + $result = array(); + foreach($array as $name){ + $result += isset($debug[$name])?$debug[$name]:array(); + } + $trace[$title] = $result; + }else{ + $trace[$title] = isset($debug[$name])?$debug[$name]:''; + } + } + } + foreach ($trace as $key=>$val){ + if(!is_array($val) && empty($val)) + $val=array(); + if(is_array($val)){ + $fire=array( + array('','') + ); + foreach($val as $k=>$v){ + $fire[]=array($k,$v); + } + fb(array($key,$fire),FirePHP::TABLE); + }else{ + fb($val,$key); + } + } + unset($files,$info,$log,$base); + } + + /** + * 获取运行时间 + */ + private function showTime() { + // 显示运行时间 + G('beginTime',$GLOBALS['_beginTime']); + G('viewEndTime'); + // 显示详细运行时间 + return G('beginTime','viewEndTime').'s ( Load:'.G('beginTime','loadTime').'s Init:'.G('loadTime','initTime').'s Exec:'.G('initTime','viewStartTime').'s Template:'.G('viewStartTime','viewEndTime').'s )'; + } + +} + + +function fb() +{ + $instance = FirePHP::getInstance(true); + + $args = func_get_args(); + return call_user_func_array(array($instance,'fb'),$args); +} + + +class FB +{ + /** + * Enable and disable logging to Firebug + * + * @see FirePHP->setEnabled() + * @param boolean $Enabled TRUE to enable, FALSE to disable + * @return void + */ + public static function setEnabled($Enabled) + { + $instance = FirePHP::getInstance(true); + $instance->setEnabled($Enabled); + } + + /** + * Check if logging is enabled + * + * @see FirePHP->getEnabled() + * @return boolean TRUE if enabled + */ + public static function getEnabled() + { + $instance = FirePHP::getInstance(true); + return $instance->getEnabled(); + } + + /** + * Specify a filter to be used when encoding an object + * + * Filters are used to exclude object members. + * + * @see FirePHP->setObjectFilter() + * @param string $Class The class name of the object + * @param array $Filter An array or members to exclude + * @return void + */ + public static function setObjectFilter($Class, $Filter) + { + $instance = FirePHP::getInstance(true); + $instance->setObjectFilter($Class, $Filter); + } + + /** + * Set some options for the library + * + * @see FirePHP->setOptions() + * @param array $Options The options to be set + * @return void + */ + public static function setOptions($Options) + { + $instance = FirePHP::getInstance(true); + $instance->setOptions($Options); + } + + /** + * Get options for the library + * + * @see FirePHP->getOptions() + * @return array The options + */ + public static function getOptions() + { + $instance = FirePHP::getInstance(true); + return $instance->getOptions(); + } + + /** + * Log object to firebug + * + * @see http://www.firephp.org/Wiki/Reference/Fb + * @param mixed $Object + * @return true + * @throws Exception + */ + public static function send() + { + $instance = FirePHP::getInstance(true); + $args = func_get_args(); + return call_user_func_array(array($instance,'fb'),$args); + } + + /** + * Start a group for following messages + * + * Options: + * Collapsed: [true|false] + * Color: [#RRGGBB|ColorName] + * + * @param string $Name + * @param array $Options OPTIONAL Instructions on how to log the group + * @return true + */ + public static function group($Name, $Options=null) + { + $instance = FirePHP::getInstance(true); + return $instance->group($Name, $Options); + } + + /** + * Ends a group you have started before + * + * @return true + * @throws Exception + */ + public static function groupEnd() + { + return self::send(null, null, FirePHP::GROUP_END); + } + + /** + * Log object with label to firebug console + * + * @see FirePHP::LOG + * @param mixes $Object + * @param string $Label + * @return true + * @throws Exception + */ + public static function log($Object, $Label=null) + { + return self::send($Object, $Label, FirePHP::LOG); + } + + /** + * Log object with label to firebug console + * + * @see FirePHP::INFO + * @param mixes $Object + * @param string $Label + * @return true + * @throws Exception + */ + public static function info($Object, $Label=null) + { + return self::send($Object, $Label, FirePHP::INFO); + } + + /** + * Log object with label to firebug console + * + * @see FirePHP::WARN + * @param mixes $Object + * @param string $Label + * @return true + * @throws Exception + */ + public static function warn($Object, $Label=null) + { + return self::send($Object, $Label, FirePHP::WARN); + } + + /** + * Log object with label to firebug console + * + * @see FirePHP::ERROR + * @param mixes $Object + * @param string $Label + * @return true + * @throws Exception + */ + public static function error($Object, $Label=null) + { + return self::send($Object, $Label, FirePHP::ERROR); + } + + /** + * Dumps key and variable to firebug server panel + * + * @see FirePHP::DUMP + * @param string $Key + * @param mixed $Variable + * @return true + * @throws Exception + */ + public static function dump($Key, $Variable) + { + return self::send($Variable, $Key, FirePHP::DUMP); + } + + /** + * Log a trace in the firebug console + * + * @see FirePHP::TRACE + * @param string $Label + * @return true + * @throws Exception + */ + public static function trace($Label) + { + return self::send($Label, FirePHP::TRACE); + } + + /** + * Log a table in the firebug console + * + * @see FirePHP::TABLE + * @param string $Label + * @param string $Table + * @return true + * @throws Exception + */ + public static function table($Label, $Table) + { + return self::send($Table, $Label, FirePHP::TABLE); + } + +} + +if (!defined('E_STRICT')) { + define('E_STRICT', 2048); +} +if (!defined('E_RECOVERABLE_ERROR')) { + define('E_RECOVERABLE_ERROR', 4096); +} +if (!defined('E_DEPRECATED')) { + define('E_DEPRECATED', 8192); +} +if (!defined('E_USER_DEPRECATED')) { + define('E_USER_DEPRECATED', 16384); +} + +/** + * Sends the given data to the FirePHP Firefox Extension. + * The data can be displayed in the Firebug Console or in the + * "Server" request tab. + * + * For more information see: http://www.firephp.org/ + * + * @copyright Copyright (C) 2007-2009 Christoph Dorn + * @author Christoph Dorn + * @license http://www.opensource.org/licenses/bsd-license.php + * @package FirePHPCore + */ +class FirePHP { + + /** + * FirePHP version + * + * @var string + */ + const VERSION = '0.3'; // @pinf replace '0.3' with '%%package.version%%' + + /** + * Firebug LOG level + * + * Logs a message to firebug console. + * + * @var string + */ + const LOG = 'LOG'; + + /** + * Firebug INFO level + * + * Logs a message to firebug console and displays an info icon before the message. + * + * @var string + */ + const INFO = 'INFO'; + + /** + * Firebug WARN level + * + * Logs a message to firebug console, displays an warning icon before the message and colors the line turquoise. + * + * @var string + */ + const WARN = 'WARN'; + + /** + * Firebug ERROR level + * + * Logs a message to firebug console, displays an error icon before the message and colors the line yellow. Also increments the firebug error count. + * + * @var string + */ + const ERROR = 'ERROR'; + + /** + * Dumps a variable to firebug's server panel + * + * @var string + */ + const DUMP = 'DUMP'; + + /** + * Displays a stack trace in firebug console + * + * @var string + */ + const TRACE = 'TRACE'; + + /** + * Displays an exception in firebug console + * + * Increments the firebug error count. + * + * @var string + */ + const EXCEPTION = 'EXCEPTION'; + + /** + * Displays an table in firebug console + * + * @var string + */ + const TABLE = 'TABLE'; + + /** + * Starts a group in firebug console + * + * @var string + */ + const GROUP_START = 'GROUP_START'; + + /** + * Ends a group in firebug console + * + * @var string + */ + const GROUP_END = 'GROUP_END'; + + /** + * Singleton instance of FirePHP + * + * @var FirePHP + */ + protected static $instance = null; + + /** + * Flag whether we are logging from within the exception handler + * + * @var boolean + */ + protected $inExceptionHandler = false; + + /** + * Flag whether to throw PHP errors that have been converted to ErrorExceptions + * + * @var boolean + */ + protected $throwErrorExceptions = true; + + /** + * Flag whether to convert PHP assertion errors to Exceptions + * + * @var boolean + */ + protected $convertAssertionErrorsToExceptions = true; + + /** + * Flag whether to throw PHP assertion errors that have been converted to Exceptions + * + * @var boolean + */ + protected $throwAssertionExceptions = false; + + /** + * Wildfire protocol message index + * + * @var int + */ + protected $messageIndex = 1; + + /** + * Options for the library + * + * @var array + */ + protected $options = array('maxDepth' => 10, + 'maxObjectDepth' => 5, + 'maxArrayDepth' => 5, + 'useNativeJsonEncode' => true, + 'includeLineNumbers' => true); + + /** + * Filters used to exclude object members when encoding + * + * @var array + */ + protected $objectFilters = array( + 'firephp' => array('objectStack', 'instance', 'json_objectStack'), + 'firephp_test_class' => array('objectStack', 'instance', 'json_objectStack') + ); + + /** + * A stack of objects used to detect recursion during object encoding + * + * @var object + */ + protected $objectStack = array(); + + /** + * Flag to enable/disable logging + * + * @var boolean + */ + protected $enabled = true; + + /** + * The insight console to log to if applicable + * + * @var object + */ + protected $logToInsightConsole = null; + + /** + * When the object gets serialized only include specific object members. + * + * @return array + */ + public function __sleep() + { + return array('options','objectFilters','enabled'); + } + + /** + * Gets singleton instance of FirePHP + * + * @param boolean $AutoCreate + * @return FirePHP + */ + public static function getInstance($AutoCreate = false) + { + if ($AutoCreate===true && !self::$instance) { + self::init(); + } + return self::$instance; + } + + /** + * Creates FirePHP object and stores it for singleton access + * + * @return FirePHP + */ + public static function init() + { + return self::setInstance(new self()); + } + + /** + * Set the instance of the FirePHP singleton + * + * @param FirePHP $instance The FirePHP object instance + * @return FirePHP + */ + public static function setInstance($instance) + { + return self::$instance = $instance; + } + + /** + * Set an Insight console to direct all logging calls to + * + * @param object $console The console object to log to + * @return void + */ + public function setLogToInsightConsole($console) + { + if(is_string($console)) { + if(get_class($this)!='FirePHP_Insight' && !is_subclass_of($this, 'FirePHP_Insight')) { + throw new Exception('FirePHP instance not an instance or subclass of FirePHP_Insight!'); + } + $this->logToInsightConsole = $this->to('request')->console($console); + } else { + $this->logToInsightConsole = $console; + } + } + + /** + * Enable and disable logging to Firebug + * + * @param boolean $Enabled TRUE to enable, FALSE to disable + * @return void + */ + public function setEnabled($Enabled) + { + $this->enabled = $Enabled; + } + + /** + * Check if logging is enabled + * + * @return boolean TRUE if enabled + */ + public function getEnabled() + { + return $this->enabled; + } + + /** + * Specify a filter to be used when encoding an object + * + * Filters are used to exclude object members. + * + * @param string $Class The class name of the object + * @param array $Filter An array of members to exclude + * @return void + */ + public function setObjectFilter($Class, $Filter) + { + $this->objectFilters[strtolower($Class)] = $Filter; + } + + /** + * Set some options for the library + * + * Options: + * - maxDepth: The maximum depth to traverse (default: 10) + * - maxObjectDepth: The maximum depth to traverse objects (default: 5) + * - maxArrayDepth: The maximum depth to traverse arrays (default: 5) + * - useNativeJsonEncode: If true will use json_encode() (default: true) + * - includeLineNumbers: If true will include line numbers and filenames (default: true) + * + * @param array $Options The options to be set + * @return void + */ + public function setOptions($Options) + { + $this->options = array_merge($this->options,$Options); + } + + /** + * Get options from the library + * + * @return array The currently set options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Set an option for the library + * + * @param string $Name + * @param mixed $Value + * @throws Exception + * @return void + */ + public function setOption($Name, $Value) + { + if (!isset($this->options[$Name])) { + throw $this->newException('Unknown option: ' . $Name); + } + $this->options[$Name] = $Value; + } + + /** + * Get an option from the library + * + * @param string $Name + * @throws Exception + * @return mixed + */ + public function getOption($Name) + { + if (!isset($this->options[$Name])) { + throw $this->newException('Unknown option: ' . $Name); + } + return $this->options[$Name]; + } + + /** + * Register FirePHP as your error handler + * + * Will throw exceptions for each php error. + * + * @return mixed Returns a string containing the previously defined error handler (if any) + */ + public function registerErrorHandler($throwErrorExceptions = false) + { + //NOTE: The following errors will not be caught by this error handler: + // E_ERROR, E_PARSE, E_CORE_ERROR, + // E_CORE_WARNING, E_COMPILE_ERROR, + // E_COMPILE_WARNING, E_STRICT + + $this->throwErrorExceptions = $throwErrorExceptions; + + return set_error_handler(array($this,'errorHandler')); + } + + /** + * FirePHP's error handler + * + * Throws exception for each php error that will occur. + * + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param int $errline + * @param array $errcontext + */ + public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) + { + // Don't throw exception if error reporting is switched off + if (error_reporting() == 0) { + return; + } + // Only throw exceptions for errors we are asking for + if (error_reporting() & $errno) { + + $exception = new ErrorException($errstr, 0, $errno, $errfile, $errline); + if ($this->throwErrorExceptions) { + throw $exception; + } else { + $this->fb($exception); + } + } + } + + /** + * Register FirePHP as your exception handler + * + * @return mixed Returns the name of the previously defined exception handler, + * or NULL on error. + * If no previous handler was defined, NULL is also returned. + */ + public function registerExceptionHandler() + { + return set_exception_handler(array($this,'exceptionHandler')); + } + + /** + * FirePHP's exception handler + * + * Logs all exceptions to your firebug console and then stops the script. + * + * @param Exception $Exception + * @throws Exception + */ + function exceptionHandler($Exception) + { + + $this->inExceptionHandler = true; + + header('HTTP/1.1 500 Internal Server Error'); + + try { + $this->fb($Exception); + } catch (Exception $e) { + echo 'We had an exception: ' . $e; + } + $this->inExceptionHandler = false; + } + + /** + * Register FirePHP driver as your assert callback + * + * @param boolean $convertAssertionErrorsToExceptions + * @param boolean $throwAssertionExceptions + * @return mixed Returns the original setting or FALSE on errors + */ + public function registerAssertionHandler($convertAssertionErrorsToExceptions = true, $throwAssertionExceptions = false) + { + $this->convertAssertionErrorsToExceptions = $convertAssertionErrorsToExceptions; + $this->throwAssertionExceptions = $throwAssertionExceptions; + + if ($throwAssertionExceptions && !$convertAssertionErrorsToExceptions) { + throw $this->newException('Cannot throw assertion exceptions as assertion errors are not being converted to exceptions!'); + } + + return assert_options(ASSERT_CALLBACK, array($this, 'assertionHandler')); + } + + /** + * FirePHP's assertion handler + * + * Logs all assertions to your firebug console and then stops the script. + * + * @param string $file File source of assertion + * @param int $line Line source of assertion + * @param mixed $code Assertion code + */ + public function assertionHandler($file, $line, $code) + { + if ($this->convertAssertionErrorsToExceptions) { + + $exception = new ErrorException('Assertion Failed - Code[ '.$code.' ]', 0, null, $file, $line); + + if ($this->throwAssertionExceptions) { + throw $exception; + } else { + $this->fb($exception); + } + + } else { + $this->fb($code, 'Assertion Failed', FirePHP::ERROR, array('File'=>$file,'Line'=>$line)); + } + } + + /** + * Start a group for following messages. + * + * Options: + * Collapsed: [true|false] + * Color: [#RRGGBB|ColorName] + * + * @param string $Name + * @param array $Options OPTIONAL Instructions on how to log the group + * @return true + * @throws Exception + */ + public function group($Name, $Options = null) + { + + if (!$Name) { + throw $this->newException('You must specify a label for the group!'); + } + + if ($Options) { + if (!is_array($Options)) { + throw $this->newException('Options must be defined as an array!'); + } + if (array_key_exists('Collapsed', $Options)) { + $Options['Collapsed'] = ($Options['Collapsed'])?'true':'false'; + } + } + + return $this->fb(null, $Name, FirePHP::GROUP_START, $Options); + } + + /** + * Ends a group you have started before + * + * @return true + * @throws Exception + */ + public function groupEnd() + { + return $this->fb(null, null, FirePHP::GROUP_END); + } + + /** + * Log object with label to firebug console + * + * @see FirePHP::LOG + * @param mixes $Object + * @param string $Label + * @return true + * @throws Exception + */ + public function log($Object, $Label = null, $Options = array()) + { + return $this->fb($Object, $Label, FirePHP::LOG, $Options); + } + + /** + * Log object with label to firebug console + * + * @see FirePHP::INFO + * @param mixes $Object + * @param string $Label + * @return true + * @throws Exception + */ + public function info($Object, $Label = null, $Options = array()) + { + return $this->fb($Object, $Label, FirePHP::INFO, $Options); + } + + /** + * Log object with label to firebug console + * + * @see FirePHP::WARN + * @param mixes $Object + * @param string $Label + * @return true + * @throws Exception + */ + public function warn($Object, $Label = null, $Options = array()) + { + return $this->fb($Object, $Label, FirePHP::WARN, $Options); + } + + /** + * Log object with label to firebug console + * + * @see FirePHP::ERROR + * @param mixes $Object + * @param string $Label + * @return true + * @throws Exception + */ + public function error($Object, $Label = null, $Options = array()) + { + return $this->fb($Object, $Label, FirePHP::ERROR, $Options); + } + + /** + * Dumps key and variable to firebug server panel + * + * @see FirePHP::DUMP + * @param string $Key + * @param mixed $Variable + * @return true + * @throws Exception + */ + public function dump($Key, $Variable, $Options = array()) + { + if (!is_string($Key)) { + throw $this->newException('Key passed to dump() is not a string'); + } + if (strlen($Key)>100) { + throw $this->newException('Key passed to dump() is longer than 100 characters'); + } + if (!preg_match_all('/^[a-zA-Z0-9-_\.:]*$/', $Key, $m)) { + throw $this->newException('Key passed to dump() contains invalid characters [a-zA-Z0-9-_\.:]'); + } + return $this->fb($Variable, $Key, FirePHP::DUMP, $Options); + } + + /** + * Log a trace in the firebug console + * + * @see FirePHP::TRACE + * @param string $Label + * @return true + * @throws Exception + */ + public function trace($Label) + { + return $this->fb($Label, FirePHP::TRACE); + } + + /** + * Log a table in the firebug console + * + * @see FirePHP::TABLE + * @param string $Label + * @param string $Table + * @return true + * @throws Exception + */ + public function table($Label, $Table, $Options = array()) + { + return $this->fb($Table, $Label, FirePHP::TABLE, $Options); + } + + /** + * Insight API wrapper + * + * @see Insight_Helper::to() + */ + public static function to() + { + $instance = self::getInstance(); + if (!method_exists($instance, "_to")) { + throw new Exception("FirePHP::to() implementation not loaded"); + } + $args = func_get_args(); + return call_user_func_array(array($instance, '_to'), $args); + } + + /** + * Insight API wrapper + * + * @see Insight_Helper::plugin() + */ + public static function plugin() + { + $instance = self::getInstance(); + if (!method_exists($instance, "_plugin")) { + throw new Exception("FirePHP::plugin() implementation not loaded"); + } + $args = func_get_args(); + return call_user_func_array(array($instance, '_plugin'), $args); + } + + /** + * Check if FirePHP is installed on client + * + * @return boolean + */ + public function detectClientExtension() + { + // Check if FirePHP is installed on client via User-Agent header + if (@preg_match_all('/\sFirePHP\/([\.\d]*)\s?/si',$this->getUserAgent(),$m) && + version_compare($m[1][0],'0.0.6','>=')) { + return true; + } else + // Check if FirePHP is installed on client via X-FirePHP-Version header + if (@preg_match_all('/^([\.\d]*)$/si',$this->getRequestHeader("X-FirePHP-Version"),$m) && + version_compare($m[1][0],'0.0.6','>=')) { + return true; + } + return false; + } + + /** + * Log varible to Firebug + * + * @see http://www.firephp.org/Wiki/Reference/Fb + * @param mixed $Object The variable to be logged + * @return true Return TRUE if message was added to headers, FALSE otherwise + * @throws Exception + */ + public function fb($Object) + { + if($this instanceof FirePHP_Insight && method_exists($this, '_logUpgradeClientMessage')) { + if(!FirePHP_Insight::$upgradeClientMessageLogged) { // avoid infinite recursion as _logUpgradeClientMessage() logs a message + $this->_logUpgradeClientMessage(); + } + } + + static $insightGroupStack = array(); + + if (!$this->getEnabled()) { + return false; + } + + if ($this->headersSent($filename, $linenum)) { + // If we are logging from within the exception handler we cannot throw another exception + if ($this->inExceptionHandler) { + // Simply echo the error out to the page + echo '
FirePHP ERROR: Headers already sent in '.$filename.' on line '.$linenum.'. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.
'; + } else { + throw $this->newException('Headers already sent in '.$filename.' on line '.$linenum.'. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.'); + } + } + + $Type = null; + $Label = null; + $Options = array(); + + if (func_num_args()==1) { + } else + if (func_num_args()==2) { + switch(func_get_arg(1)) { + case self::LOG: + case self::INFO: + case self::WARN: + case self::ERROR: + case self::DUMP: + case self::TRACE: + case self::EXCEPTION: + case self::TABLE: + case self::GROUP_START: + case self::GROUP_END: + $Type = func_get_arg(1); + break; + default: + $Label = func_get_arg(1); + break; + } + } else + if (func_num_args()==3) { + $Type = func_get_arg(2); + $Label = func_get_arg(1); + } else + if (func_num_args()==4) { + $Type = func_get_arg(2); + $Label = func_get_arg(1); + $Options = func_get_arg(3); + } else { + throw $this->newException('Wrong number of arguments to fb() function!'); + } + + if($this->logToInsightConsole!==null && (get_class($this)=='FirePHP_Insight' || is_subclass_of($this, 'FirePHP_Insight'))) { + $msg = $this->logToInsightConsole; + if ($Object instanceof Exception) { + $Type = self::EXCEPTION; + } + if($Label && $Type!=self::TABLE && $Type!=self::GROUP_START) { + $msg = $msg->label($Label); + } + switch($Type) { + case self::DUMP: + case self::LOG: + return $msg->log($Object); + case self::INFO: + return $msg->info($Object); + case self::WARN: + return $msg->warn($Object); + case self::ERROR: + return $msg->error($Object); + case self::TRACE: + return $msg->trace($Object); + case self::EXCEPTION: + return $this->plugin('engine')->handleException($Object, $msg); + case self::TABLE: + if (isset($Object[0]) && !is_string($Object[0]) && $Label) { + $Object = array($Label, $Object); + } + return $msg->table($Object[0], array_slice($Object[1],1), $Object[1][0]); + case self::GROUP_START: + $insightGroupStack[] = $msg->group(md5($Label))->open(); + return $msg->log($Label); + case self::GROUP_END: + if(count($insightGroupStack)==0) { + throw new Error('Too many groupEnd() as opposed to group() calls!'); + } + $group = array_pop($insightGroupStack); + return $group->close(); + default: + return $msg->log($Object); + } + } + + if (!$this->detectClientExtension()) { + return false; + } + + $meta = array(); + $skipFinalObjectEncode = false; + + if ($Object instanceof Exception) { + + $meta['file'] = $this->_escapeTraceFile($Object->getFile()); + $meta['line'] = $Object->getLine(); + + $trace = $Object->getTrace(); + if ($Object instanceof ErrorException + && isset($trace[0]['function']) + && $trace[0]['function']=='errorHandler' + && isset($trace[0]['class']) + && $trace[0]['class']=='FirePHP') { + + $severity = false; + switch($Object->getSeverity()) { + case E_WARNING: $severity = 'E_WARNING'; break; + case E_NOTICE: $severity = 'E_NOTICE'; break; + case E_USER_ERROR: $severity = 'E_USER_ERROR'; break; + case E_USER_WARNING: $severity = 'E_USER_WARNING'; break; + case E_USER_NOTICE: $severity = 'E_USER_NOTICE'; break; + case E_STRICT: $severity = 'E_STRICT'; break; + case E_RECOVERABLE_ERROR: $severity = 'E_RECOVERABLE_ERROR'; break; + case E_DEPRECATED: $severity = 'E_DEPRECATED'; break; + case E_USER_DEPRECATED: $severity = 'E_USER_DEPRECATED'; break; + } + + $Object = array('Class'=>get_class($Object), + 'Message'=>$severity.': '.$Object->getMessage(), + 'File'=>$this->_escapeTraceFile($Object->getFile()), + 'Line'=>$Object->getLine(), + 'Type'=>'trigger', + 'Trace'=>$this->_escapeTrace(array_splice($trace,2))); + $skipFinalObjectEncode = true; + } else { + $Object = array('Class'=>get_class($Object), + 'Message'=>$Object->getMessage(), + 'File'=>$this->_escapeTraceFile($Object->getFile()), + 'Line'=>$Object->getLine(), + 'Type'=>'throw', + 'Trace'=>$this->_escapeTrace($trace)); + $skipFinalObjectEncode = true; + } + $Type = self::EXCEPTION; + + } else + if ($Type==self::TRACE) { + + $trace = debug_backtrace(); + if (!$trace) return false; + for( $i=0 ; $i_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php' + || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) { + /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */ + } else + if (isset($trace[$i]['class']) + && isset($trace[$i+1]['file']) + && $trace[$i]['class']=='FirePHP' + && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') { + /* Skip fb() */ + } else + if ($trace[$i]['function']=='fb' + || $trace[$i]['function']=='trace' + || $trace[$i]['function']=='send') { + + $Object = array('Class'=>isset($trace[$i]['class'])?$trace[$i]['class']:'', + 'Type'=>isset($trace[$i]['type'])?$trace[$i]['type']:'', + 'Function'=>isset($trace[$i]['function'])?$trace[$i]['function']:'', + 'Message'=>$trace[$i]['args'][0], + 'File'=>isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):'', + 'Line'=>isset($trace[$i]['line'])?$trace[$i]['line']:'', + 'Args'=>isset($trace[$i]['args'])?$this->encodeObject($trace[$i]['args']):'', + 'Trace'=>$this->_escapeTrace(array_splice($trace,$i+1))); + + $skipFinalObjectEncode = true; + $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):''; + $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:''; + break; + } + } + + } else + if ($Type==self::TABLE) { + + if (isset($Object[0]) && is_string($Object[0])) { + $Object[1] = $this->encodeTable($Object[1]); + } else { + $Object = $this->encodeTable($Object); + } + + $skipFinalObjectEncode = true; + + } else + if ($Type==self::GROUP_START) { + + if (!$Label) { + throw $this->newException('You must specify a label for the group!'); + } + + } else { + if ($Type===null) { + $Type = self::LOG; + } + } + + if ($this->options['includeLineNumbers']) { + if (!isset($meta['file']) || !isset($meta['line'])) { + + $trace = debug_backtrace(); + for( $i=0 ; $trace && $i_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php' + || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) { + /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */ + } else + if (isset($trace[$i]['class']) + && isset($trace[$i+1]['file']) + && $trace[$i]['class']=='FirePHP' + && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') { + /* Skip fb() */ + } else + if (isset($trace[$i]['file']) + && substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php') { + /* Skip FB::fb() */ + } else { + $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):''; + $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:''; + break; + } + } + } + } else { + unset($meta['file']); + unset($meta['line']); + } + + $this->setHeader('X-Wf-Protocol-1','http://meta.wildfirehq.org/Protocol/JsonStream/0.2'); + $this->setHeader('X-Wf-1-Plugin-1','http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/'.self::VERSION); + + $structure_index = 1; + if ($Type==self::DUMP) { + $structure_index = 2; + $this->setHeader('X-Wf-1-Structure-2','http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1'); + } else { + $this->setHeader('X-Wf-1-Structure-1','http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'); + } + + if ($Type==self::DUMP) { + $msg = '{"'.$Label.'":'.$this->jsonEncode($Object, $skipFinalObjectEncode).'}'; + } else { + $msg_meta = $Options; + $msg_meta['Type'] = $Type; + if ($Label!==null) { + $msg_meta['Label'] = $Label; + } + if (isset($meta['file']) && !isset($msg_meta['File'])) { + $msg_meta['File'] = $meta['file']; + } + if (isset($meta['line']) && !isset($msg_meta['Line'])) { + $msg_meta['Line'] = $meta['line']; + } + $msg = '['.$this->jsonEncode($msg_meta).','.$this->jsonEncode($Object, $skipFinalObjectEncode).']'; + } + + $parts = explode("\n",chunk_split($msg, 5000, "\n")); + + for( $i=0 ; $i2) { + // Message needs to be split into multiple parts + $this->setHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex, + (($i==0)?strlen($msg):'') + . '|' . $part . '|' + . (($isetHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex, + strlen($part) . '|' . $part . '|'); + } + + $this->messageIndex++; + + if ($this->messageIndex > 99999) { + throw $this->newException('Maximum number (99,999) of messages reached!'); + } + } + } + + $this->setHeader('X-Wf-1-Index',$this->messageIndex-1); + + return true; + } + + /** + * Standardizes path for windows systems. + * + * @param string $Path + * @return string + */ + protected function _standardizePath($Path) + { + return preg_replace('/\\\\+/','/',$Path); + } + + /** + * Escape trace path for windows systems + * + * @param array $Trace + * @return array + */ + protected function _escapeTrace($Trace) + { + if (!$Trace) return $Trace; + for( $i=0 ; $i_escapeTraceFile($Trace[$i]['file']); + } + if (isset($Trace[$i]['args'])) { + $Trace[$i]['args'] = $this->encodeObject($Trace[$i]['args']); + } + } + return $Trace; + } + + /** + * Escape file information of trace for windows systems + * + * @param string $File + * @return string + */ + protected function _escapeTraceFile($File) + { + /* Check if we have a windows filepath */ + if (strpos($File,'\\')) { + /* First strip down to single \ */ + + $file = preg_replace('/\\\\+/','\\',$File); + + return $file; + } + return $File; + } + + /** + * Check if headers have already been sent + * + * @param string $Filename + * @param integer $Linenum + */ + protected function headersSent(&$Filename, &$Linenum) + { + return headers_sent($Filename, $Linenum); + } + + /** + * Send header + * + * @param string $Name + * @param string $Value + */ + protected function setHeader($Name, $Value) + { + return header($Name.': '.$Value); + } + + /** + * Get user agent + * + * @return string|false + */ + protected function getUserAgent() + { + if (!isset($_SERVER['HTTP_USER_AGENT'])) return false; + return $_SERVER['HTTP_USER_AGENT']; + } + + /** + * Get all request headers + * + * @return array + */ + public static function getAllRequestHeaders() { + static $_cached_headers = false; + if($_cached_headers!==false) { + return $_cached_headers; + } + $headers = array(); + if(function_exists('getallheaders')) { + foreach( getallheaders() as $name => $value ) { + $headers[strtolower($name)] = $value; + } + } else { + foreach($_SERVER as $name => $value) { + if(substr($name, 0, 5) == 'HTTP_') { + $headers[strtolower(str_replace(' ', '-', str_replace('_', ' ', substr($name, 5))))] = $value; + } + } + } + return $_cached_headers = $headers; + } + + /** + * Get a request header + * + * @return string|false + */ + protected function getRequestHeader($Name) + { + $headers = self::getAllRequestHeaders(); + if (isset($headers[strtolower($Name)])) { + return $headers[strtolower($Name)]; + } + return false; + } + + /** + * Returns a new exception + * + * @param string $Message + * @return Exception + */ + protected function newException($Message) + { + return new Exception($Message); + } + + /** + * Encode an object into a JSON string + * + * Uses PHP's jeson_encode() if available + * + * @param object $Object The object to be encoded + * @return string The JSON string + */ + public function jsonEncode($Object, $skipObjectEncode = false) + { + if (!$skipObjectEncode) { + $Object = $this->encodeObject($Object); + } + + if (function_exists('json_encode') + && $this->options['useNativeJsonEncode']!=false) { + + return json_encode($Object); + } else { + return $this->json_encode($Object); + } + } + + /** + * Encodes a table by encoding each row and column with encodeObject() + * + * @param array $Table The table to be encoded + * @return array + */ + protected function encodeTable($Table) + { + + if (!$Table) return $Table; + + $new_table = array(); + foreach($Table as $row) { + + if (is_array($row)) { + $new_row = array(); + + foreach($row as $item) { + $new_row[] = $this->encodeObject($item); + } + + $new_table[] = $new_row; + } + } + + return $new_table; + } + + /** + * Encodes an object including members with + * protected and private visibility + * + * @param Object $Object The object to be encoded + * @param int $Depth The current traversal depth + * @return array All members of the object + */ + protected function encodeObject($Object, $ObjectDepth = 1, $ArrayDepth = 1, $MaxDepth = 1) + { + if ($MaxDepth > $this->options['maxDepth']) { + return '** Max Depth ('.$this->options['maxDepth'].') **'; + } + + $return = array(); + + if (is_resource($Object)) { + + return '** '.(string)$Object.' **'; + + } else + if (is_object($Object)) { + + if ($ObjectDepth > $this->options['maxObjectDepth']) { + return '** Max Object Depth ('.$this->options['maxObjectDepth'].') **'; + } + + foreach ($this->objectStack as $refVal) { + if ($refVal === $Object) { + return '** Recursion ('.get_class($Object).') **'; + } + } + array_push($this->objectStack, $Object); + + $return['__className'] = $class = get_class($Object); + $class_lower = strtolower($class); + + $reflectionClass = new ReflectionClass($class); + $properties = array(); + foreach( $reflectionClass->getProperties() as $property) { + $properties[$property->getName()] = $property; + } + + $members = (array)$Object; + + foreach( $properties as $plain_name => $property ) { + + $name = $raw_name = $plain_name; + if ($property->isStatic()) { + $name = 'static:'.$name; + } + if ($property->isPublic()) { + $name = 'public:'.$name; + } else + if ($property->isPrivate()) { + $name = 'private:'.$name; + $raw_name = "\0".$class."\0".$raw_name; + } else + if ($property->isProtected()) { + $name = 'protected:'.$name; + $raw_name = "\0".'*'."\0".$raw_name; + } + + if (!(isset($this->objectFilters[$class_lower]) + && is_array($this->objectFilters[$class_lower]) + && in_array($plain_name,$this->objectFilters[$class_lower]))) { + + if (array_key_exists($raw_name,$members) + && !$property->isStatic()) { + + $return[$name] = $this->encodeObject($members[$raw_name], $ObjectDepth + 1, 1, $MaxDepth + 1); + + } else { + if (method_exists($property,'setAccessible')) { + $property->setAccessible(true); + $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1, $MaxDepth + 1); + } else + if ($property->isPublic()) { + $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1, $MaxDepth + 1); + } else { + $return[$name] = '** Need PHP 5.3 to get value **'; + } + } + } else { + $return[$name] = '** Excluded by Filter **'; + } + } + + // Include all members that are not defined in the class + // but exist in the object + foreach( $members as $raw_name => $value ) { + + $name = $raw_name; + + if ($name{0} == "\0") { + $parts = explode("\0", $name); + $name = $parts[2]; + } + + $plain_name = $name; + + if (!isset($properties[$name])) { + $name = 'undeclared:'.$name; + + if (!(isset($this->objectFilters[$class_lower]) + && is_array($this->objectFilters[$class_lower]) + && in_array($plain_name,$this->objectFilters[$class_lower]))) { + + $return[$name] = $this->encodeObject($value, $ObjectDepth + 1, 1, $MaxDepth + 1); + } else { + $return[$name] = '** Excluded by Filter **'; + } + } + } + + array_pop($this->objectStack); + + } elseif (is_array($Object)) { + + if ($ArrayDepth > $this->options['maxArrayDepth']) { + return '** Max Array Depth ('.$this->options['maxArrayDepth'].') **'; + } + + foreach ($Object as $key => $val) { + + // Encoding the $GLOBALS PHP array causes an infinite loop + // if the recursion is not reset here as it contains + // a reference to itself. This is the only way I have come up + // with to stop infinite recursion in this case. + if ($key=='GLOBALS' + && is_array($val) + && array_key_exists('GLOBALS',$val)) { + $val['GLOBALS'] = '** Recursion (GLOBALS) **'; + } + + $return[$key] = $this->encodeObject($val, 1, $ArrayDepth + 1, $MaxDepth + 1); + } + } else { + if (self::is_utf8($Object)) { + return $Object; + } else { + return utf8_encode($Object); + } + } + return $return; + } + + /** + * Returns true if $string is valid UTF-8 and false otherwise. + * + * @param mixed $str String to be tested + * @return boolean + */ + protected static function is_utf8($str) + { + if(function_exists('mb_detect_encoding')) { + return (mb_detect_encoding($str) == 'UTF-8'); + } + $c=0; $b=0; + $bits=0; + $len=strlen($str); + for($i=0; $i<$len; $i++){ + $c=ord($str[$i]); + if ($c > 128){ + if (($c >= 254)) return false; + elseif ($c >= 252) $bits=6; + elseif ($c >= 248) $bits=5; + elseif ($c >= 240) $bits=4; + elseif ($c >= 224) $bits=3; + elseif ($c >= 192) $bits=2; + else return false; + if (($i+$bits) > $len) return false; + while($bits > 1){ + $i++; + $b=ord($str[$i]); + if ($b < 128 || $b > 191) return false; + $bits--; + } + } + } + return true; + } + + /** + * Converts to and from JSON format. + * + * JSON (JavaScript Object Notation) is a lightweight data-interchange + * format. It is easy for humans to read and write. It is easy for machines + * to parse and generate. It is based on a subset of the JavaScript + * Programming Language, Standard ECMA-262 3rd Edition - December 1999. + * This feature can also be found in Python. JSON is a text format that is + * completely language independent but uses conventions that are familiar + * to programmers of the C-family of languages, including C, C++, C#, Java, + * JavaScript, Perl, TCL, and many others. These properties make JSON an + * ideal data-interchange language. + * + * This package provides a simple encoder and decoder for JSON notation. It + * is intended for use with client-side Javascript applications that make + * use of HTTPRequest to perform server communication functions - data can + * be encoded into JSON notation for use in a client-side javascript, or + * decoded from incoming Javascript requests. JSON format is native to + * Javascript, and can be directly eval()'ed with no further parsing + * overhead + * + * All strings should be in ASCII or UTF-8 format! + * + * LICENSE: 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. + * + * THIS SOFTWARE IS PROVIDED ``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 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. + * + * @category + * @package Services_JSON + * @author Michal Migurski + * @author Matt Knapp + * @author Brett Stimmerman + * @author Christoph Dorn + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + + + /** + * Keep a list of objects as we descend into the array so we can detect recursion. + */ + private $json_objectStack = array(); + + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + private function json_utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if (function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + private function json_encode($var) + { + + if (is_object($var)) { + if (in_array($var,$this->json_objectStack)) { + return '"** Recursion **"'; + } + } + + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->json_utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->json_utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->json_utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->json_utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->json_utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + + $this->json_objectStack[] = $var; + + $properties = array_map(array($this, 'json_name_value'), + array_keys($var), + array_values($var)); + + array_pop($this->json_objectStack); + + foreach($properties as $property) { + if ($property instanceof Exception) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + $this->json_objectStack[] = $var; + + // treat it like a regular array + $elements = array_map(array($this, 'json_encode'), $var); + + array_pop($this->json_objectStack); + + foreach($elements as $element) { + if ($element instanceof Exception) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = self::encodeObject($var); + + $this->json_objectStack[] = $var; + + $properties = array_map(array($this, 'json_name_value'), + array_keys($vars), + array_values($vars)); + + array_pop($this->json_objectStack); + + foreach($properties as $property) { + if ($property instanceof Exception) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return null; + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + private function json_name_value($name, $value) + { + // Encoding the $GLOBALS PHP array causes an infinite loop + // if the recursion is not reset here as it contains + // a reference to itself. This is the only way I have come up + // with to stop infinite recursion in this case. + if ($name=='GLOBALS' + && is_array($value) + && array_key_exists('GLOBALS',$value)) { + $value['GLOBALS'] = '** Recursion **'; + } + + $encoded_value = $this->json_encode($value); + + if ($encoded_value instanceof Exception) { + return $encoded_value; + } + + return $this->json_encode(strval($name)) . ':' . $encoded_value; + } + + /** + * @deprecated + */ + public function setProcessorUrl($URL) + { + trigger_error("The FirePHP::setProcessorUrl() method is no longer supported", E_USER_DEPRECATED); + } + + /** + * @deprecated + */ + public function setRendererUrl($URL) + { + trigger_error("The FirePHP::setRendererUrl() method is no longer supported", E_USER_DEPRECATED); + } +} diff --git a/ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php b/ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php new file mode 100644 index 0000000..21b6853 --- /dev/null +++ b/ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php @@ -0,0 +1,95 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +use Think\Storage; +use Think\Think; +/** + * 系统行为扩展:模板解析 + */ +class ParseTemplateBehavior { + + // 行为扩展的执行入口必须是run + public function run(&$_data){ + $engine = strtolower(C('TMPL_ENGINE_TYPE')); + $_content = empty($_data['content'])?$_data['file']:$_data['content']; + $_data['prefix'] = !empty($_data['prefix'])?$_data['prefix']:C('TMPL_CACHE_PREFIX'); + if('think'==$engine){ // 采用Think模板引擎 + if((!empty($_data['content']) && $this->checkContentCache($_data['content'],$_data['prefix'])) + || $this->checkCache($_data['file'],$_data['prefix'])) { // 缓存有效 + //载入模版缓存文件 + Storage::load(C('CACHE_PATH').$_data['prefix'].md5($_content).C('TMPL_CACHFILE_SUFFIX'),$_data['var']); + }else{ + $tpl = Think::instance('Think\\Template'); + // 编译并加载模板文件 + $tpl->fetch($_content,$_data['var'],$_data['prefix']); + } + }else{ + // 调用第三方模板引擎解析和输出 + if(strpos($engine,'\\')){ + $class = $engine; + }else{ + $class = 'Think\\Template\\Driver\\'.ucwords($engine); + } + if(class_exists($class)) { + $tpl = new $class; + $tpl->fetch($_content,$_data['var']); + }else { // 类没有定义 + E(L('_NOT_SUPPORT_').': ' . $class); + } + } + } + + /** + * 检查缓存文件是否有效 + * 如果无效则需要重新编译 + * @access public + * @param string $tmplTemplateFile 模板文件名 + * @return boolean + */ + protected function checkCache($tmplTemplateFile,$prefix='') { + if (!C('TMPL_CACHE_ON')) // 优先对配置设定检测 + return false; + $tmplCacheFile = C('CACHE_PATH').$prefix.md5($tmplTemplateFile).C('TMPL_CACHFILE_SUFFIX'); + if(!Storage::has($tmplCacheFile)){ + return false; + }elseif (filemtime($tmplTemplateFile) > Storage::get($tmplCacheFile,'mtime')) { + // 模板文件如果有更新则缓存需要更新 + return false; + }elseif (C('TMPL_CACHE_TIME') != 0 && time() > Storage::get($tmplCacheFile,'mtime')+C('TMPL_CACHE_TIME')) { + // 缓存是否在有效期 + return false; + } + // 开启布局模板 + if(C('LAYOUT_ON')) { + $layoutFile = THEME_PATH.C('LAYOUT_NAME').C('TMPL_TEMPLATE_SUFFIX'); + if(filemtime($layoutFile) > Storage::get($tmplCacheFile,'mtime')) { + return false; + } + } + // 缓存有效 + return true; + } + + /** + * 检查缓存内容是否有效 + * 如果无效则需要重新编译 + * @access public + * @param string $tmplContent 模板内容 + * @return boolean + */ + protected function checkContentCache($tmplContent,$prefix='') { + if(Storage::has(C('CACHE_PATH').$prefix.md5($tmplContent).C('TMPL_CACHFILE_SUFFIX'))){ + return true; + }else{ + return false; + } + } +} diff --git a/ThinkPHP/Library/Behavior/ReadHtmlCacheBehavior.class.php b/ThinkPHP/Library/Behavior/ReadHtmlCacheBehavior.class.php new file mode 100644 index 0000000..ce167da --- /dev/null +++ b/ThinkPHP/Library/Behavior/ReadHtmlCacheBehavior.class.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +use Think\Storage; +/** + * 系统行为扩展:静态缓存读取 + */ +class ReadHtmlCacheBehavior { + // 行为扩展的执行入口必须是run + public function run(&$params){ + // 开启静态缓存 + if(IS_GET && C('HTML_CACHE_ON')) { + $cacheTime = $this->requireHtmlCache(); + if( false !== $cacheTime && $this->checkHTMLCache(HTML_FILE_NAME,$cacheTime)) { //静态页面有效 + // 读取静态页面输出 + echo Storage::read(HTML_FILE_NAME,'html'); + exit(); + } + } + } + + // 判断是否需要静态缓存 + static private function requireHtmlCache() { + // 分析当前的静态规则 + $htmls = C('HTML_CACHE_RULES'); // 读取静态规则 + if(!empty($htmls)) { + $htmls = array_change_key_case($htmls); + // 静态规则文件定义格式 actionName=>array('静态规则','缓存时间','附加规则') + // 'read'=>array('{id},{name}',60,'md5') 必须保证静态规则的唯一性 和 可判断性 + // 检测静态规则 + $controllerName = strtolower(CONTROLLER_NAME); + $actionName = strtolower(ACTION_NAME); + if(isset($htmls[$controllerName.':'.$actionName])) { + $html = $htmls[$controllerName.':'.$actionName]; // 某个控制器的操作的静态规则 + }elseif(isset($htmls[$controllerName.':'])){// 某个控制器的静态规则 + $html = $htmls[$controllerName.':']; + }elseif(isset($htmls[$actionName])){ + $html = $htmls[$actionName]; // 所有操作的静态规则 + }elseif(isset($htmls['*'])){ + $html = $htmls['*']; // 全局静态规则 + } + if(!empty($html)) { + // 解读静态规则 + $rule = is_array($html)?$html[0]:$html; + // 以$_开头的系统变量 + $callback = function($match){ + switch($match[1]){ + case '_GET': $var = $_GET[$match[2]]; break; + case '_POST': $var = $_POST[$match[2]]; break; + case '_REQUEST': $var = $_REQUEST[$match[2]]; break; + case '_SERVER': $var = $_SERVER[$match[2]]; break; + case '_SESSION': $var = $_SESSION[$match[2]]; break; + case '_COOKIE': $var = $_COOKIE[$match[2]]; break; + } + return (count($match) == 4) ? $match[3]($var) : $var; + }; + $rule = preg_replace_callback('/{\$(_\w+)\.(\w+)(?:\|(\w+))?}/', $callback, $rule); + // {ID|FUN} GET变量的简写 + $rule = preg_replace_callback('/{(\w+)\|(\w+)}/', function($match){return $match[2]($_GET[$match[1]]);}, $rule); + $rule = preg_replace_callback('/{(\w+)}/', function($match){return $_GET[$match[1]];}, $rule); + // 特殊系统变量 + $rule = str_ireplace( + array('{:controller}','{:action}','{:module}'), + array(CONTROLLER_NAME,ACTION_NAME,MODULE_NAME), + $rule); + // {|FUN} 单独使用函数 + $rule = preg_replace_callback('/{|(\w+)}/', function($match){return $match[1]();},$rule); + $cacheTime = C('HTML_CACHE_TIME',null,60); + if(is_array($html)){ + if(!empty($html[2])) $rule = $html[2]($rule); // 应用附加函数 + $cacheTime = isset($html[1])?$html[1]:$cacheTime; // 缓存有效期 + }else{ + $cacheTime = $cacheTime; + } + + // 当前缓存文件 + define('HTML_FILE_NAME',HTML_PATH . $rule.C('HTML_FILE_SUFFIX',null,'.html')); + return $cacheTime; + } + } + // 无需缓存 + return false; + } + + /** + * 检查静态HTML文件是否有效 + * 如果无效需要重新更新 + * @access public + * @param string $cacheFile 静态文件名 + * @param integer $cacheTime 缓存有效期 + * @return boolean + */ + static public function checkHTMLCache($cacheFile='',$cacheTime='') { + if(!is_file($cacheFile) && 'sae' != APP_MODE ){ + return false; + }elseif (filemtime(\Think\Think::instance('Think\View')->parseTemplate()) > Storage::get($cacheFile,'mtime','html')) { + // 模板文件如果更新静态文件需要更新 + return false; + }elseif(!is_numeric($cacheTime) && function_exists($cacheTime)){ + return $cacheTime($cacheFile); + }elseif ($cacheTime != 0 && NOW_TIME > Storage::get($cacheFile,'mtime','html')+$cacheTime) { + // 文件是否在有效期 + return false; + } + //静态文件有效 + return true; + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Behavior/RobotCheckBehavior.class.php b/ThinkPHP/Library/Behavior/RobotCheckBehavior.class.php new file mode 100644 index 0000000..77242db --- /dev/null +++ b/ThinkPHP/Library/Behavior/RobotCheckBehavior.class.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 机器人检测 + * @author liu21st + */ +class RobotCheckBehavior { + + public function run(&$params) { + // 机器人访问检测 + if(C('LIMIT_ROBOT_VISIT',null,true) && self::isRobot()) { + // 禁止机器人访问 + exit('Access Denied'); + } + } + + static private function isRobot() { + static $_robot = null; + if(is_null($_robot)) { + $spiders = 'Bot|Crawl|Spider|slurp|sohu-search|lycos|robozilla'; + $browsers = 'MSIE|Netscape|Opera|Konqueror|Mozilla'; + if(preg_match("/($browsers)/", $_SERVER['HTTP_USER_AGENT'])) { + $_robot = false ; + } elseif(preg_match("/($spiders)/", $_SERVER['HTTP_USER_AGENT'])) { + $_robot = true; + } else { + $_robot = false; + } + } + return $_robot; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Behavior/ShowPageTraceBehavior.class.php b/ThinkPHP/Library/Behavior/ShowPageTraceBehavior.class.php new file mode 100644 index 0000000..4fc1197 --- /dev/null +++ b/ThinkPHP/Library/Behavior/ShowPageTraceBehavior.class.php @@ -0,0 +1,119 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +use Think\Log; +/** + * 系统行为扩展:页面Trace显示输出 + */ +class ShowPageTraceBehavior { + protected $tracePageTabs = array('BASE'=>'基本','FILE'=>'文件','INFO'=>'流程','ERR|NOTIC'=>'错误','SQL'=>'SQL','DEBUG'=>'调试'); + + // 行为扩展的执行入口必须是run + public function run(&$params){ + if(!IS_AJAX && !IS_CLI && C('SHOW_PAGE_TRACE')) { + echo $this->showTrace(); + } + } + + /** + * 显示页面Trace信息 + * @access private + */ + private function showTrace() { + // 系统默认显示信息 + $files = get_included_files(); + $info = array(); + foreach ($files as $key=>$file){ + $info[] = $file.' ( '.number_format(filesize($file)/1024,2).' KB )'; + } + $trace = array(); + $base = array( + '请求信息' => date('Y-m-d H:i:s',$_SERVER['REQUEST_TIME']).' '.$_SERVER['SERVER_PROTOCOL'].' '.$_SERVER['REQUEST_METHOD'].' : '.__SELF__, + '运行时间' => $this->showTime(), + '吞吐率' => number_format(1/G('beginTime','viewEndTime'),2).'req/s', + '内存开销' => MEMORY_LIMIT_ON?number_format((memory_get_usage() - $GLOBALS['_startUseMems'])/1024,2).' kb':'不支持', + '查询信息' => N('db_query').' queries '.N('db_write').' writes ', + '文件加载' => count(get_included_files()), + '缓存信息' => N('cache_read').' gets '.N('cache_write').' writes ', + '配置加载' => count(C()), + '会话信息' => 'SESSION_ID='.session_id(), + ); + // 读取应用定义的Trace文件 + $traceFile = COMMON_PATH.'Conf/trace.php'; + if(is_file($traceFile)) { + $base = array_merge($base,include $traceFile); + } + $debug = trace(); + $tabs = C('TRACE_PAGE_TABS',null,$this->tracePageTabs); + foreach ($tabs as $name=>$title){ + switch(strtoupper($name)) { + case 'BASE':// 基本信息 + $trace[$title] = $base; + break; + case 'FILE': // 文件信息 + $trace[$title] = $info; + break; + default:// 调试信息 + $name = strtoupper($name); + if(strpos($name,'|')) {// 多组信息 + $names = explode('|',$name); + $result = array(); + foreach($names as $name){ + $result += isset($debug[$name])?$debug[$name]:array(); + } + $trace[$title] = $result; + }else{ + $trace[$title] = isset($debug[$name])?$debug[$name]:''; + } + } + } + if($save = C('PAGE_TRACE_SAVE')) { // 保存页面Trace日志 + if(is_array($save)) {// 选择选项卡保存 + $tabs = C('TRACE_PAGE_TABS',null,$this->tracePageTabs); + $array = array(); + foreach ($save as $tab){ + $array[] = $tabs[$tab]; + } + } + $content = date('[ c ]').' '.get_client_ip().' '.$_SERVER['REQUEST_URI']."\r\n"; + foreach ($trace as $key=>$val){ + if(!isset($array) || in_array_case($key,$array)) { + $content .= '[ '.$key." ]\r\n"; + if(is_array($val)) { + foreach ($val as $k=>$v){ + $content .= (!is_numeric($k)?$k.':':'').print_r($v,true)."\r\n"; + } + }else{ + $content .= print_r($val,true)."\r\n"; + } + $content .= "\r\n"; + } + } + error_log(str_replace('
',"\r\n",$content), 3,C('LOG_PATH').date('y_m_d').'_trace.log'); + } + unset($files,$info,$base); + // 调用Trace页面模板 + ob_start(); + include C('TMPL_TRACE_FILE')?C('TMPL_TRACE_FILE'):THINK_PATH.'Tpl/page_trace.tpl'; + return ob_get_clean(); + } + + /** + * 获取运行时间 + */ + private function showTime() { + // 显示运行时间 + G('beginTime',$GLOBALS['_beginTime']); + G('viewEndTime'); + // 显示详细运行时间 + return G('beginTime','viewEndTime').'s ( Load:'.G('beginTime','loadTime').'s Init:'.G('loadTime','initTime').'s Exec:'.G('initTime','viewStartTime').'s Template:'.G('viewStartTime','viewEndTime').'s )'; + } +} diff --git a/ThinkPHP/Library/Behavior/ShowRuntimeBehavior.class.php b/ThinkPHP/Library/Behavior/ShowRuntimeBehavior.class.php new file mode 100644 index 0000000..66360d3 --- /dev/null +++ b/ThinkPHP/Library/Behavior/ShowRuntimeBehavior.class.php @@ -0,0 +1,69 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 系统行为扩展:运行时间信息显示 + */ +class ShowRuntimeBehavior { + + // 行为扩展的执行入口必须是run + public function run(&$content){ + if(C('SHOW_RUN_TIME')){ + if(false !== strpos($content,'{__NORUNTIME__}')) { + $content = str_replace('{__NORUNTIME__}','',$content); + }else{ + $runtime = $this->showTime(); + if(strpos($content,'{__RUNTIME__}')) + $content = str_replace('{__RUNTIME__}',$runtime,$content); + else + $content .= $runtime; + } + }else{ + $content = str_replace(array('{__NORUNTIME__}','{__RUNTIME__}'),'',$content); + } + } + + /** + * 显示运行时间、数据库操作、缓存次数、内存使用信息 + * @access private + * @return string + */ + private function showTime() { + // 显示运行时间 + G('beginTime',$GLOBALS['_beginTime']); + G('viewEndTime'); + $showTime = 'Process: '.G('beginTime','viewEndTime').'s '; + if(C('SHOW_ADV_TIME')) { + // 显示详细运行时间 + $showTime .= '( Load:'.G('beginTime','loadTime').'s Init:'.G('loadTime','initTime').'s Exec:'.G('initTime','viewStartTime').'s Template:'.G('viewStartTime','viewEndTime').'s )'; + } + if(C('SHOW_DB_TIMES') ) { + // 显示数据库操作次数 + $showTime .= ' | DB :'.N('db_query').' queries '.N('db_write').' writes '; + } + if(C('SHOW_CACHE_TIMES') ) { + // 显示缓存读写次数 + $showTime .= ' | Cache :'.N('cache_read').' gets '.N('cache_write').' writes '; + } + if(MEMORY_LIMIT_ON && C('SHOW_USE_MEM')) { + // 显示内存开销 + $showTime .= ' | UseMem:'. number_format((memory_get_usage() - $GLOBALS['_startUseMems'])/1024).' kb'; + } + if(C('SHOW_LOAD_FILE')) { + $showTime .= ' | LoadFile:'.count(get_included_files()); + } + if(C('SHOW_FUN_TIMES')) { + $fun = get_defined_functions(); + $showTime .= ' | CallFun:'.count($fun['user']).','.count($fun['internal']); + } + return $showTime; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Behavior/TokenBuildBehavior.class.php b/ThinkPHP/Library/Behavior/TokenBuildBehavior.class.php new file mode 100644 index 0000000..6e7888a --- /dev/null +++ b/ThinkPHP/Library/Behavior/TokenBuildBehavior.class.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 系统行为扩展:表单令牌生成 + */ +class TokenBuildBehavior { + + public function run(&$content){ + if(C('TOKEN_ON')) { + list($tokenName,$tokenKey,$tokenValue)=$this->getToken(); + $input_token = ''; + $meta_token = ''; + if(strpos($content,'{__TOKEN__}')) { + // 指定表单令牌隐藏域位置 + $content = str_replace('{__TOKEN__}',$input_token,$content); + }elseif(preg_match('/<\/form(\s*)>/is',$content,$match)) { + // 智能生成表单令牌隐藏域 + $content = str_replace($match[0],$input_token.$match[0],$content); + } + $content = str_ireplace('',$meta_token.'',$content); + }else{ + $content = str_replace('{__TOKEN__}','',$content); + } + } + + //获得token + private function getToken(){ + $tokenName = C('TOKEN_NAME',null,'__hash__'); + $tokenType = C('TOKEN_TYPE',null,'md5'); + if(!isset($_SESSION[$tokenName])) { + $_SESSION[$tokenName] = array(); + } + // 标识当前页面唯一性 + $tokenKey = md5($_SERVER['REQUEST_URI']); + if(isset($_SESSION[$tokenName][$tokenKey])) {// 相同页面不重复生成session + $tokenValue = $_SESSION[$tokenName][$tokenKey]; + }else{ + $tokenValue = is_callable($tokenType) ? $tokenType(microtime(true)) : md5(microtime(true)); + $_SESSION[$tokenName][$tokenKey] = $tokenValue; + if(IS_AJAX && C('TOKEN_RESET',null,true)) + header($tokenName.': '.$tokenKey.'_'.$tokenValue); //ajax需要获得这个header并替换页面中meta中的token值 + } + return array($tokenName,$tokenKey,$tokenValue); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Behavior/UpgradeNoticeBehavior.class.php b/ThinkPHP/Library/Behavior/UpgradeNoticeBehavior.class.php new file mode 100644 index 0000000..d9c67d0 --- /dev/null +++ b/ThinkPHP/Library/Behavior/UpgradeNoticeBehavior.class.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +/** + * 升级短信通知, 如果有ThinkPHP新版升级,或者重要的更新,会发送短信通知你。 + * 需要使用SAE的短信服务。请先找一个SAE的应用开通短信服务。 + * 使用步骤如下: + * 1,在项目的Conf目录下建立tags.php配置文件,内容如下: + * + * array('UpgradeNotice') + * ); + * + * + * 2,将此文件放在应用的Lib/Behavior文件夹下。 + *注:在SAE上面使用时,以上两步可以省略 + * 3,在config.php中配置: + * 'UPGRADE_NOTICE_ON'=>true,//开启短信升级提醒功能 + * 'UPGRADE_NOTICE_AKEY'=>'your akey',//SAE应用的AKEY,如果在SAE上使用可以不填 + * 'UPGRADE_NOTICE_SKEY'=>'your skey',//SAE应用的SKEY,如果在SAE上使用可以不填 + *'UPGRADE_NOTICE_MOBILE'=>'136456789',//接受短信的手机号 + *'UPGRADE_NOTICE_CHECK_INTERVAL' => 604800,//检测频率,单位秒,默认是一周 + *'UPGRADE_CURRENT_VERSION'=>'0',//升级后的版本号,会在短信中告诉你填写什么 + *UPGRADE_NOTICE_DEBUG=>true, //调试默认,如果为true,UPGRADE_NOTICE_CHECK_INTERVAL配置不起作用,每次都会进行版本检查,此时用于调试,调试完毕后请设置次配置为false + * + */ + +class UpgradeNoticeBehavior { + + protected $header_ = ''; + protected $httpCode_; + protected $httpDesc_; + protected $accesskey_; + protected $secretkey_; + public function run(&$params) { + if (C('UPGRADE_NOTICE_ON') && (!S('think_upgrade_interval') || C('UPGRADE_NOTICE_DEBUG'))) { + if(IS_SAE && C('UPGRADE_NOTICE_QUEUE') && !isset($_POST['think_upgrade_queque'])){ + $queue=new SaeTaskQueue(C('UPGRADE_NOTICE_QUEUE')); + $queue->addTask('http://'.$_SERVER['HTTP_HOST'].__APP__,'think_upgrade_queque=1'); + if(!$queue->push()){ + trace('升级提醒队列执行失败,错误原因:'.$queue->errmsg(), '升级通知出错', 'NOTIC', true); + } + return ; + } + $akey = C('UPGRADE_NOTICE_AKEY',null,''); + $skey = C('UPGRADE_NOTICE_SKEY',null,''); + $this->accesskey_ = $akey ? $akey : (defined('SAE_ACCESSKEY') ? SAE_ACCESSKEY : ''); + $this->secretkey_ = $skey ? $skey : (defined('SAE_SECRETKEY') ? SAE_SECRETKEY : ''); + $current_version = C('UPGRADE_CURRENT_VERSION',null,0); + //读取接口 + $info = $this->send('http://sinaclouds.sinaapp.com/thinkapi/upgrade.php?v=' . $current_version); + if ($info['version'] != $current_version) { + if($this->send_sms($info['msg'])) trace($info['msg'], '升级通知成功', 'NOTIC', true); //发送升级短信 + } + S('think_upgrade_interval', true, C('UPGRADE_NOTICE_CHECK_INTERVAL',null,604800)); + } + } + private function send_sms($msg) { + $timestamp=time(); + $url = 'http://inno.smsinter.sina.com.cn/sae_sms_service/sendsms.php'; //发送短信的接口地址 + $content = "FetchUrl" . $url . "TimeStamp" . $timestamp . "AccessKey" . $this->accesskey_; + $signature = (base64_encode(hash_hmac('sha256', $content, $this->secretkey_, true))); + $headers = array( + "FetchUrl: $url", + "AccessKey: ".$this->accesskey_, + "TimeStamp: " . $timestamp, + "Signature: $signature" + ); + $data = array( + 'mobile' => C('UPGRADE_NOTICE_MOBILE',null,'') , + 'msg' => $msg, + 'encoding' => 'UTF-8' + ); + if(!$ret = $this->send('http://g.apibus.io', $data, $headers)){ + return false; + } + if (isset($ret['ApiBusError'])) { + trace('errno:' . $ret['ApiBusError']['errcode'] . ',errmsg:' . $ret['ApiBusError']['errdesc'], '升级通知出错', 'NOTIC', true); + + return false; + } + + return true; + } + private function send($url, $params = array() , $headers = array()) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + if (!empty($params)) { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $params); + } + if (!empty($headers)) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $txt = curl_exec($ch); + if (curl_errno($ch)) { + trace(curl_error($ch) , '升级通知出错', 'NOTIC', true); + + return false; + } + curl_close($ch); + $ret = json_decode($txt, true); + if (!$ret) { + trace('接口[' . $url . ']返回格式不正确', '升级通知出错', 'NOTIC', true); + + return false; + } + + return $ret; + } +} diff --git a/ThinkPHP/Library/Behavior/WriteHtmlCacheBehavior.class.php b/ThinkPHP/Library/Behavior/WriteHtmlCacheBehavior.class.php new file mode 100644 index 0000000..6248867 --- /dev/null +++ b/ThinkPHP/Library/Behavior/WriteHtmlCacheBehavior.class.php @@ -0,0 +1,29 @@ + +// +---------------------------------------------------------------------- +namespace Behavior; +use Think\Storage; +/** + * 系统行为扩展:静态缓存写入 + */ +class WriteHtmlCacheBehavior { + + // 行为扩展的执行入口必须是run + public function run(&$content) { + //2014-11-28 修改 如果有HTTP 4xx 3xx 5xx 头部,禁止存储 + //2014-12-1 修改 对注入的网址 防止生成,例如 /game/lst/SortType/hot/-e8-90-8c-e5-85-94-e7-88-b1-e6-b6-88-e9-99-a4/-e8-bf-9b-e5-87-bb-e7-9a-84-e9-83-a8-e8-90-bd/-e9-a3-8e-e4-ba-91-e5-a4-a9-e4-b8-8b/index.shtml + if (C('HTML_CACHE_ON') && defined('HTML_FILE_NAME') + && !preg_match('/Status.*[345]{1}\d{2}/i', implode(' ', headers_list())) + && !preg_match('/(-[a-z0-9]{2}){3,}/i',HTML_FILE_NAME)) { + //静态文件写入 + Storage::put(HTML_FILE_NAME, $content, 'html'); + } + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Org/Net/Http.class.php b/ThinkPHP/Library/Org/Net/Http.class.php new file mode 100644 index 0000000..7fe2c2b --- /dev/null +++ b/ThinkPHP/Library/Org/Net/Http.class.php @@ -0,0 +1,271 @@ + +// +---------------------------------------------------------------------- +namespace Org\Net; +/** + * Http 工具类 + * 提供一系列的Http方法 + * @author liu21st + */ +class Http { + + /** + * 采集远程文件 + * @access public + * @param string $remote 远程文件名 + * @param string $local 本地保存文件名 + * @return mixed + */ + static public function curlDownload($remote,$local) { + $cp = curl_init($remote); + $fp = fopen($local,"w"); + curl_setopt($cp, CURLOPT_FILE, $fp); + curl_setopt($cp, CURLOPT_HEADER, 0); + curl_exec($cp); + curl_close($cp); + fclose($fp); + } + + /** + * 使用 fsockopen 通过 HTTP 协议直接访问(采集)远程文件 + * 如果主机或服务器没有开启 CURL 扩展可考虑使用 + * fsockopen 比 CURL 稍慢,但性能稳定 + * @static + * @access public + * @param string $url 远程URL + * @param array $conf 其他配置信息 + * int limit 分段读取字符个数 + * string post post的内容,字符串或数组,key=value&形式 + * string cookie 携带cookie访问,该参数是cookie内容 + * string ip 如果该参数传入,$url将不被使用,ip访问优先 + * int timeout 采集超时时间 + * bool block 是否阻塞访问,默认为true + * @return mixed + */ + static public function fsockopenDownload($url, $conf = array()) { + $return = ''; + if(!is_array($conf)) return $return; + + $matches = parse_url($url); + !isset($matches['host']) && $matches['host'] = ''; + !isset($matches['path']) && $matches['path'] = ''; + !isset($matches['query']) && $matches['query'] = ''; + !isset($matches['port']) && $matches['port'] = ''; + $host = $matches['host']; + $path = $matches['path'] ? $matches['path'].($matches['query'] ? '?'.$matches['query'] : '') : '/'; + $port = !empty($matches['port']) ? $matches['port'] : 80; + + $conf_arr = array( + 'limit' => 0, + 'post' => '', + 'cookie' => '', + 'ip' => '', + 'timeout' => 15, + 'block' => TRUE, + ); + + foreach (array_merge($conf_arr, $conf) as $k=>$v) ${$k} = $v; + + if($post) { + if(is_array($post)) + { + $post = http_build_query($post); + } + $out = "POST $path HTTP/1.0\r\n"; + $out .= "Accept: */*\r\n"; + //$out .= "Referer: $boardurl\r\n"; + $out .= "Accept-Language: zh-cn\r\n"; + $out .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n"; + $out .= "Host: $host\r\n"; + $out .= 'Content-Length: '.strlen($post)."\r\n"; + $out .= "Connection: Close\r\n"; + $out .= "Cache-Control: no-cache\r\n"; + $out .= "Cookie: $cookie\r\n\r\n"; + $out .= $post; + } else { + $out = "GET $path HTTP/1.0\r\n"; + $out .= "Accept: */*\r\n"; + //$out .= "Referer: $boardurl\r\n"; + $out .= "Accept-Language: zh-cn\r\n"; + $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n"; + $out .= "Host: $host\r\n"; + $out .= "Connection: Close\r\n"; + $out .= "Cookie: $cookie\r\n\r\n"; + } + $fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout); + if(!$fp) { + return ''; + } else { + stream_set_blocking($fp, $block); + stream_set_timeout($fp, $timeout); + @fwrite($fp, $out); + $status = stream_get_meta_data($fp); + if(!$status['timed_out']) { + while (!feof($fp)) { + if(($header = @fgets($fp)) && ($header == "\r\n" || $header == "\n")) { + break; + } + } + + $stop = false; + while(!feof($fp) && !$stop) { + $data = fread($fp, ($limit == 0 || $limit > 8192 ? 8192 : $limit)); + $return .= $data; + if($limit) { + $limit -= strlen($data); + $stop = $limit <= 0; + } + } + } + @fclose($fp); + return $return; + } + } + + /** + * 下载文件 + * 可以指定下载显示的文件名,并自动发送相应的Header信息 + * 如果指定了content参数,则下载该参数的内容 + * @static + * @access public + * @param string $filename 下载文件名 + * @param string $showname 下载显示的文件名 + * @param string $content 下载的内容 + * @param integer $expire 下载内容浏览器缓存时间 + * @return void + */ + static public function download ($filename, $showname='',$content='',$expire=180) { + if(is_file($filename)) { + $length = filesize($filename); + }elseif(is_file(UPLOAD_PATH.$filename)) { + $filename = UPLOAD_PATH.$filename; + $length = filesize($filename); + }elseif($content != '') { + $length = strlen($content); + }else { + E($filename.L('下载文件不存在!')); + } + if(empty($showname)) { + $showname = $filename; + } + $showname = basename($showname); + if(!empty($filename)) { + $finfo = new \finfo(FILEINFO_MIME); + $type = $finfo->file($filename); + }else{ + $type = "application/octet-stream"; + } + //发送Http Header信息 开始下载 + header("Pragma: public"); + header("Cache-control: max-age=".$expire); + //header('Cache-Control: no-store, no-cache, must-revalidate'); + header("Expires: " . gmdate("D, d M Y H:i:s",time()+$expire) . "GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s",time()) . "GMT"); + header("Content-Disposition: attachment; filename=".$showname); + header("Content-Length: ".$length); + header("Content-type: ".$type); + header('Content-Encoding: none'); + header("Content-Transfer-Encoding: binary" ); + if($content == '' ) { + readfile($filename); + }else { + echo($content); + } + exit(); + } + + /** + * 显示HTTP Header 信息 + * @return string + */ + static function getHeaderInfo($header='',$echo=true) { + ob_start(); + $headers = getallheaders(); + if(!empty($header)) { + $info = $headers[$header]; + echo($header.':'.$info."\n"); ; + }else { + foreach($headers as $key=>$val) { + echo("$key:$val\n"); + } + } + $output = ob_get_clean(); + if ($echo) { + echo (nl2br($output)); + }else { + return $output; + } + + } + + /** + * HTTP Protocol defined status codes + * @param int $num + */ + static function sendHttpStatus($code) { + static $_status = array( + // Informational 1xx + 100 => 'Continue', + 101 => 'Switching Protocols', + + // Success 2xx + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + + // Redirection 3xx + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', // 1.1 + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + // 306 is deprecated but reserved + 307 => 'Temporary Redirect', + + // Client Error 4xx + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + + // Server Error 5xx + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 509 => 'Bandwidth Limit Exceeded' + ); + if(isset($_status[$code])) { + header('HTTP/1.1 '.$code.' '.$_status[$code]); + } + } +}//类定义结束 \ No newline at end of file diff --git a/ThinkPHP/Library/Org/Net/IpLocation.class.php b/ThinkPHP/Library/Org/Net/IpLocation.class.php new file mode 100644 index 0000000..1d57307 --- /dev/null +++ b/ThinkPHP/Library/Org/Net/IpLocation.class.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- +namespace Org\Net; +/** + * IP 地理位置查询类 修改自 CoolCode.CN + * 由于使用UTF8编码 如果使用纯真IP地址库的话 需要对返回结果进行编码转换 + * @author liu21st + */ +class IpLocation { + /** + * QQWry.Dat文件指针 + * + * @var resource + */ + private $fp; + + /** + * 第一条IP记录的偏移地址 + * + * @var int + */ + private $firstip; + + /** + * 最后一条IP记录的偏移地址 + * + * @var int + */ + private $lastip; + + /** + * IP记录的总条数(不包含版本信息记录) + * + * @var int + */ + private $totalip; + + /** + * 构造函数,打开 QQWry.Dat 文件并初始化类中的信息 + * + * @param string $filename + * @return IpLocation + */ + public function __construct($filename = "UTFWry.dat") { + $this->fp = 0; + if (($this->fp = fopen(dirname(__FILE__).'/'.$filename, 'rb')) !== false) { + $this->firstip = $this->getlong(); + $this->lastip = $this->getlong(); + $this->totalip = ($this->lastip - $this->firstip) / 7; + } + } + + /** + * 返回读取的长整型数 + * + * @access private + * @return int + */ + private function getlong() { + //将读取的little-endian编码的4个字节转化为长整型数 + $result = unpack('Vlong', fread($this->fp, 4)); + return $result['long']; + } + + /** + * 返回读取的3个字节的长整型数 + * + * @access private + * @return int + */ + private function getlong3() { + //将读取的little-endian编码的3个字节转化为长整型数 + $result = unpack('Vlong', fread($this->fp, 3).chr(0)); + return $result['long']; + } + + /** + * 返回压缩后可进行比较的IP地址 + * + * @access private + * @param string $ip + * @return string + */ + private function packip($ip) { + // 将IP地址转化为长整型数,如果在PHP5中,IP地址错误,则返回False, + // 这时intval将Flase转化为整数-1,之后压缩成big-endian编码的字符串 + return pack('N', intval(ip2long($ip))); + } + + /** + * 返回读取的字符串 + * + * @access private + * @param string $data + * @return string + */ + private function getstring($data = "") { + $char = fread($this->fp, 1); + while (ord($char) > 0) { // 字符串按照C格式保存,以\0结束 + $data .= $char; // 将读取的字符连接到给定字符串之后 + $char = fread($this->fp, 1); + } + return $data; + } + + /** + * 返回地区信息 + * + * @access private + * @return string + */ + private function getarea() { + $byte = fread($this->fp, 1); // 标志字节 + switch (ord($byte)) { + case 0: // 没有区域信息 + $area = ""; + break; + case 1: + case 2: // 标志字节为1或2,表示区域信息被重定向 + fseek($this->fp, $this->getlong3()); + $area = $this->getstring(); + break; + default: // 否则,表示区域信息没有被重定向 + $area = $this->getstring($byte); + break; + } + return $area; + } + + /** + * 根据所给 IP 地址或域名返回所在地区信息 + * + * @access public + * @param string $ip + * @return array + */ + public function getlocation($ip='') { + if (!$this->fp) return null; // 如果数据文件没有被正确打开,则直接返回空 + if(empty($ip)) $ip = get_client_ip(); + $location['ip'] = gethostbyname($ip); // 将输入的域名转化为IP地址 + $ip = $this->packip($location['ip']); // 将输入的IP地址转化为可比较的IP地址 + // 不合法的IP地址会被转化为255.255.255.255 + // 对分搜索 + $l = 0; // 搜索的下边界 + $u = $this->totalip; // 搜索的上边界 + $findip = $this->lastip; // 如果没有找到就返回最后一条IP记录(QQWry.Dat的版本信息) + while ($l <= $u) { // 当上边界小于下边界时,查找失败 + $i = floor(($l + $u) / 2); // 计算近似中间记录 + fseek($this->fp, $this->firstip + $i * 7); + $beginip = strrev(fread($this->fp, 4)); // 获取中间记录的开始IP地址 + // strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式 + // 以便用于比较,后面相同。 + if ($ip < $beginip) { // 用户的IP小于中间记录的开始IP地址时 + $u = $i - 1; // 将搜索的上边界修改为中间记录减一 + } + else { + fseek($this->fp, $this->getlong3()); + $endip = strrev(fread($this->fp, 4)); // 获取中间记录的结束IP地址 + if ($ip > $endip) { // 用户的IP大于中间记录的结束IP地址时 + $l = $i + 1; // 将搜索的下边界修改为中间记录加一 + } + else { // 用户的IP在中间记录的IP范围内时 + $findip = $this->firstip + $i * 7; + break; // 则表示找到结果,退出循环 + } + } + } + + //获取查找到的IP地理位置信息 + fseek($this->fp, $findip); + $location['beginip'] = long2ip($this->getlong()); // 用户IP所在范围的开始地址 + $offset = $this->getlong3(); + fseek($this->fp, $offset); + $location['endip'] = long2ip($this->getlong()); // 用户IP所在范围的结束地址 + $byte = fread($this->fp, 1); // 标志字节 + switch (ord($byte)) { + case 1: // 标志字节为1,表示国家和区域信息都被同时重定向 + $countryOffset = $this->getlong3(); // 重定向地址 + fseek($this->fp, $countryOffset); + $byte = fread($this->fp, 1); // 标志字节 + switch (ord($byte)) { + case 2: // 标志字节为2,表示国家信息又被重定向 + fseek($this->fp, $this->getlong3()); + $location['country'] = $this->getstring(); + fseek($this->fp, $countryOffset + 4); + $location['area'] = $this->getarea(); + break; + default: // 否则,表示国家信息没有被重定向 + $location['country'] = $this->getstring($byte); + $location['area'] = $this->getarea(); + break; + } + break; + case 2: // 标志字节为2,表示国家信息被重定向 + fseek($this->fp, $this->getlong3()); + $location['country'] = $this->getstring(); + fseek($this->fp, $offset + 8); + $location['area'] = $this->getarea(); + break; + default: // 否则,表示国家信息没有被重定向 + $location['country'] = $this->getstring($byte); + $location['area'] = $this->getarea(); + break; + } + if (trim($location['country']) == 'CZ88.NET') { // CZ88.NET表示没有有效信息 + $location['country'] = '未知'; + } + if (trim($location['area']) == 'CZ88.NET') { + $location['area'] = ''; + } + return $location; + } + + /** + * 析构函数,用于在页面执行结束后自动关闭打开的文件。 + * + */ + public function __destruct() { + if ($this->fp) { + fclose($this->fp); + } + $this->fp = 0; + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Org/Util/ArrayList.class.php b/ThinkPHP/Library/Org/Util/ArrayList.class.php new file mode 100644 index 0000000..4aae889 --- /dev/null +++ b/ThinkPHP/Library/Org/Util/ArrayList.class.php @@ -0,0 +1,240 @@ + +// +---------------------------------------------------------------------- +namespace Org\Util; +/** + * ArrayList实现类 + * @category Think + * @package Think + * @subpackage Util + * @author liu21st + */ +class ArrayList implements \IteratorAggregate { + + /** + * 集合元素 + * @var array + * @access protected + */ + protected $_elements = array(); + + /** + * 架构函数 + * @access public + * @param string $elements 初始化数组元素 + */ + public function __construct($elements = array()) { + if (!empty($elements)) { + $this->_elements = $elements; + } + } + + /** + * 若要获得迭代因子,通过getIterator方法实现 + * @access public + * @return ArrayObject + */ + public function getIterator() { + return new ArrayObject($this->_elements); + } + + /** + * 增加元素 + * @access public + * @param mixed $element 要添加的元素 + * @return boolean + */ + public function add($element) { + return (array_push($this->_elements, $element)) ? true : false; + } + + // + public function unshift($element) { + return (array_unshift($this->_elements,$element))?true : false; + } + + // + public function pop() { + return array_pop($this->_elements); + } + + /** + * 增加元素列表 + * @access public + * @param ArrayList $list 元素列表 + * @return boolean + */ + public function addAll($list) { + $before = $this->size(); + foreach( $list as $element) { + $this->add($element); + } + $after = $this->size(); + return ($before < $after); + } + + /** + * 清除所有元素 + * @access public + */ + public function clear() { + $this->_elements = array(); + } + + /** + * 是否包含某个元素 + * @access public + * @param mixed $element 查找元素 + * @return string + */ + public function contains($element) { + return (array_search($element, $this->_elements) !== false ); + } + + /** + * 根据索引取得元素 + * @access public + * @param integer $index 索引 + * @return mixed + */ + public function get($index) { + return $this->_elements[$index]; + } + + /** + * 查找匹配元素,并返回第一个元素所在位置 + * 注意 可能存在0的索引位置 因此要用===False来判断查找失败 + * @access public + * @param mixed $element 查找元素 + * @return integer + */ + public function indexOf($element) { + return array_search($element, $this->_elements); + } + + /** + * 判断元素是否为空 + * @access public + * @return boolean + */ + public function isEmpty() { + return empty($this->_elements); + } + + /** + * 最后一个匹配的元素位置 + * @access public + * @param mixed $element 查找元素 + * @return integer + */ + public function lastIndexOf($element) { + for ($i = (count($this->_elements) - 1); $i > 0; $i--) { + if ($element == $this->get($i)) { return $i; } + } + } + + public function toJson() { + return json_encode($this->_elements); + } + + /** + * 根据索引移除元素 + * 返回被移除的元素 + * @access public + * @param integer $index 索引 + * @return mixed + */ + public function remove($index) { + $element = $this->get($index); + if (!is_null($element)) { array_splice($this->_elements, $index, 1); } + return $element; + } + + /** + * 移出一定范围的数组列表 + * @access public + * @param integer $offset 开始移除位置 + * @param integer $length 移除长度 + */ + public function removeRange($offset , $length) { + array_splice($this->_elements, $offset , $length); + } + + /** + * 移出重复的值 + * @access public + */ + public function unique() { + $this->_elements = array_unique($this->_elements); + } + + /** + * 取出一定范围的数组列表 + * @access public + * @param integer $offset 开始位置 + * @param integer $length 长度 + */ + public function range($offset,$length=null) { + return array_slice($this->_elements,$offset,$length); + } + + /** + * 设置列表元素 + * 返回修改之前的值 + * @access public + * @param integer $index 索引 + * @param mixed $element 元素 + * @return mixed + */ + public function set($index, $element) { + $previous = $this->get($index); + $this->_elements[$index] = $element; + return $previous; + } + + /** + * 获取列表长度 + * @access public + * @return integer + */ + public function size() { + return count($this->_elements); + } + + /** + * 转换成数组 + * @access public + * @return array + */ + public function toArray() { + return $this->_elements; + } + + // 列表排序 + public function ksort() { + ksort($this->_elements); + } + + // 列表排序 + public function asort() { + asort($this->_elements); + } + + // 逆向排序 + public function rsort() { + rsort($this->_elements); + } + + // 自然排序 + public function natsort() { + natsort($this->_elements); + } + +} diff --git a/ThinkPHP/Library/Org/Util/CodeSwitch.class.php b/ThinkPHP/Library/Org/Util/CodeSwitch.class.php new file mode 100644 index 0000000..ee728c8 --- /dev/null +++ b/ThinkPHP/Library/Org/Util/CodeSwitch.class.php @@ -0,0 +1,200 @@ + +// +---------------------------------------------------------------------- +namespace Org\Util; +class CodeSwitch { + // 错误信息 + static private $error = array(); + // 提示信息 + static private $info = array(); + // 记录错误 + static private function error($msg) { + self::$error[] = $msg; + } + // 记录信息 + static private function info($info) { + self::$info[] = $info; + } + /** + * 编码转换函数,对整个文件进行编码转换 + * 支持以下转换 + * GB2312、UTF-8 WITH BOM转换为UTF-8 + * UTF-8、UTF-8 WITH BOM转换为GB2312 + * @access public + * @param string $filename 文件名 + * @param string $out_charset 转换后的文件编码,与iconv使用的参数一致 + * @return void + */ + static function DetectAndSwitch($filename,$out_charset) { + $fpr = fopen($filename,"r"); + $char1 = fread($fpr,1); + $char2 = fread($fpr,1); + $char3 = fread($fpr,1); + + $originEncoding = ""; + + if($char1==chr(239) && $char2==chr(187) && $char3==chr(191))//UTF-8 WITH BOM + $originEncoding = "UTF-8 WITH BOM"; + elseif($char1==chr(255) && $char2==chr(254))//UNICODE LE + { + self::error("不支持从UNICODE LE转换到UTF-8或GB编码"); + fclose($fpr); + return; + }elseif($char1==chr(254) && $char2==chr(255)){//UNICODE BE + self::error("不支持从UNICODE BE转换到UTF-8或GB编码"); + fclose($fpr); + return; + }else{//没有文件头,可能是GB或UTF-8 + if(rewind($fpr)===false){//回到文件开始部分,准备逐字节读取判断编码 + self::error($filename."文件指针后移失败"); + fclose($fpr); + return; + } + + while(!feof($fpr)){ + $char = fread($fpr,1); + //对于英文,GB和UTF-8都是单字节的ASCII码小于128的值 + if(ord($char)<128) + continue; + + //对于汉字GB编码第一个字节是110*****第二个字节是10******(有特例,比如联字) + //UTF-8编码第一个字节是1110****第二个字节是10******第三个字节是10****** + //按位与出来结果要跟上面非星号相同,所以应该先判断UTF-8 + //因为使用GB的掩码按位与,UTF-8的111得出来的也是110,所以要先判断UTF-8 + if((ord($char)&224)==224) { + //第一个字节判断通过 + $char = fread($fpr,1); + if((ord($char)&128)==128) { + //第二个字节判断通过 + $char = fread($fpr,1); + if((ord($char)&128)==128) { + $originEncoding = "UTF-8"; + break; + } + } + } + if((ord($char)&192)==192) { + //第一个字节判断通过 + $char = fread($fpr,1); + if((ord($char)&128)==128) { + //第二个字节判断通过 + $originEncoding = "GB2312"; + break; + } + } + } + } + + if(strtoupper($out_charset)==$originEncoding) { + self::info("文件".$filename."转码检查完成,原始文件编码".$originEncoding); + fclose($fpr); + }else { + //文件需要转码 + $originContent = ""; + + if($originEncoding == "UTF-8 WITH BOM") { + //跳过三个字节,把后面的内容复制一遍得到utf-8的内容 + fseek($fpr,3); + $originContent = fread($fpr,filesize($filename)-3); + fclose($fpr); + }elseif(rewind($fpr)!=false){//不管是UTF-8还是GB2312,回到文件开始部分,读取内容 + $originContent = fread($fpr,filesize($filename)); + fclose($fpr); + }else{ + self::error("文件编码不正确或指针后移失败"); + fclose($fpr); + return; + } + + //转码并保存文件 + $content = iconv(str_replace(" WITH BOM","",$originEncoding),strtoupper($out_charset),$originContent); + $fpw = fopen($filename,"w"); + fwrite($fpw,$content); + fclose($fpw); + + if($originEncoding!="") + self::info("对文件".$filename."转码完成,原始文件编码".$originEncoding.",转换后文件编码".strtoupper($out_charset)); + elseif($originEncoding=="") + self::info("文件".$filename."中没有出现中文,但是可以断定不是带BOM的UTF-8编码,没有进行编码转换,不影响使用"); + } + } + + /** + * 目录遍历函数 + * @access public + * @param string $path 要遍历的目录名 + * @param string $mode 遍历模式,一般取FILES,这样只返回带路径的文件名 + * @param array $file_types 文件后缀过滤数组 + * @param int $maxdepth 遍历深度,-1表示遍历到最底层 + * @return void + */ + static function searchdir($path,$mode = "FULL",$file_types = array(".html",".php"),$maxdepth = -1,$d = 0) { + if(substr($path,strlen($path)-1) != '/') + $path .= '/'; + $dirlist = array(); + if($mode != "FILES") + $dirlist[] = $path; + if($handle = @opendir($path)) { + while(false !== ($file = readdir($handle))) + { + if($file != '.' && $file != '..') + { + $file = $path.$file ; + if(!is_dir($file)) + { + if($mode != "DIRS") + { + $extension = ""; + $extpos = strrpos($file, '.'); + if($extpos!==false) + $extension = substr($file,$extpos,strlen($file)-$extpos); + $extension=strtolower($extension); + if(in_array($extension, $file_types)) + $dirlist[] = $file; + } + } + elseif($d >= 0 && ($d < $maxdepth || $maxdepth < 0)) + { + $result = self::searchdir($file.'/',$mode,$file_types,$maxdepth,$d + 1) ; + $dirlist = array_merge($dirlist,$result); + } + } + } + closedir ( $handle ) ; + } + if($d == 0) + natcasesort($dirlist); + + return($dirlist) ; + } + + /** + * 对整个项目目录中的PHP和HTML文件行进编码转换 + * @access public + * @param string $app 要遍历的项目路径 + * @param string $mode 遍历模式,一般取FILES,这样只返回带路径的文件名 + * @param array $file_types 文件后缀过滤数组 + * @return void + */ + static function CodingSwitch($app = "./",$charset='UTF-8',$mode = "FILES",$file_types = array(".html",".php")) { + self::info("注意: 程序使用的文件编码检测算法可能对某些特殊字符不适用"); + $filearr = self::searchdir($app,$mode,$file_types); + foreach($filearr as $file) + self::DetectAndSwitch($file,$charset); + } + + static public function getError() { + return self::$error; + } + + static public function getInfo() { + return self::$info; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Org/Util/Date.class.php b/ThinkPHP/Library/Org/Util/Date.class.php new file mode 100644 index 0000000..42346a7 --- /dev/null +++ b/ThinkPHP/Library/Org/Util/Date.class.php @@ -0,0 +1,569 @@ + +// +---------------------------------------------------------------------- + +namespace Org\Util; +/** + * 日期时间操作类 + * @category ORG + * @package ORG + * @subpackage Date + * @author liu21st + * @version $Id: Date.class.php 2662 2012-01-26 06:32:50Z liu21st $ + */ +class Date { + + /** + * 日期的时间戳 + * @var integer + * @access protected + */ + protected $date; + + /** + * 时区 + * @var integer + * @access protected + */ + protected $timezone; + + /** + * 年 + * @var integer + * @access protected + */ + protected $year; + + /** + * 月 + * @var integer + * @access protected + */ + protected $month; + + /** + * 日 + * @var integer + * @access protected + */ + protected $day; + + /** + * 时 + * @var integer + * @access protected + */ + protected $hour; + + /** + * 分 + * @var integer + * @access protected + */ + protected $minute; + + /** + * 秒 + * @var integer + * @access protected + */ + protected $second; + + /** + * 星期的数字表示 + * @var integer + * @access protected + */ + protected $weekday; + + /** + * 星期的完整表示 + * @var string + * @access protected + */ + protected $cWeekday; + + /** + * 一年中的天数 0-365 + * @var integer + * @access protected + */ + protected $yDay; + + /** + * 月份的完整表示 + * @var string + * @access protected + */ + protected $cMonth; + + /** + * 日期CDATE表示 + * @var string + * @access protected + */ + protected $CDATE; + + /** + * 日期的YMD表示 + * @var string + * @access protected + */ + protected $YMD; + + /** + * 时间的输出表示 + * @var string + * @access protected + */ + protected $CTIME; + + // 星期的输出 + protected $Week = array("日","一","二","三","四","五","六"); + + /** + * 架构函数 + * 创建一个Date对象 + * @param mixed $date 日期 + * @static + * @access public + */ + public function __construct($date='') { + //分析日期 + $this->date = $this->parse($date); + $this->setDate($this->date); + } + + /** + * 日期分析 + * 返回时间戳 + * @static + * @access public + * @param mixed $date 日期 + * @return string + */ + public function parse($date) { + if (is_string($date)) { + if (($date == "") || strtotime($date) == -1) { + //为空默认取得当前时间戳 + $tmpdate = time(); + } else { + //把字符串转换成UNIX时间戳 + $tmpdate = strtotime($date); + } + } elseif (is_null($date)) { + //为空默认取得当前时间戳 + $tmpdate = time(); + + } elseif (is_numeric($date)) { + //数字格式直接转换为时间戳 + $tmpdate = $date; + + } else { + if (get_class($date) == "Date") { + //如果是Date对象 + $tmpdate = $date->date; + } else { + //默认取当前时间戳 + $tmpdate = time(); + } + } + return $tmpdate; + } + + /** + * 验证日期数据是否有效 + * @access public + * @param mixed $date 日期数据 + * @return string + */ + public function valid($date) { + + } + + /** + * 日期参数设置 + * @static + * @access public + * @param integer $date 日期时间戳 + * @return void + */ + public function setDate($date) { + $dateArray = getdate($date); + $this->date = $dateArray[0]; //时间戳 + $this->second = $dateArray["seconds"]; //秒 + $this->minute = $dateArray["minutes"]; //分 + $this->hour = $dateArray["hours"]; //时 + $this->day = $dateArray["mday"]; //日 + $this->month = $dateArray["mon"]; //月 + $this->year = $dateArray["year"]; //年 + + $this->weekday = $dateArray["wday"]; //星期 0~6 + $this->cWeekday = '星期'.$this->Week[$this->weekday];//$dateArray["weekday"]; //星期完整表示 + $this->yDay = $dateArray["yday"]; //一年中的天数 0-365 + $this->cMonth = $dateArray["month"]; //月份的完整表示 + + $this->CDATE = $this->format("%Y-%m-%d");//日期表示 + $this->YMD = $this->format("%Y%m%d"); //简单日期 + $this->CTIME = $this->format("%H:%M:%S");//时间表示 + + return ; + } + + /** + * 日期格式化 + * 默认返回 1970-01-01 11:30:45 格式 + * @access public + * @param string $format 格式化参数 + * @return string + */ + public function format($format = "%Y-%m-%d %H:%M:%S") { + return strftime($format, $this->date); + } + + /** + * 是否为闰年 + * @static + * @access public + * @return string + */ + public function isLeapYear($year='') { + if(empty($year)) { + $year = $this->year; + } + return ((($year % 4) == 0) && (($year % 100) != 0) || (($year % 400) == 0)); + } + + /** + * 计算日期差 + * + * w - weeks + * d - days + * h - hours + * m - minutes + * s - seconds + * @static + * @access public + * @param mixed $date 要比较的日期 + * @param string $elaps 比较跨度 + * @return integer + */ + public function dateDiff($date, $elaps = "d") { + $__DAYS_PER_WEEK__ = (7); + $__DAYS_PER_MONTH__ = (30); + $__DAYS_PER_YEAR__ = (365); + $__HOURS_IN_A_DAY__ = (24); + $__MINUTES_IN_A_DAY__ = (1440); + $__SECONDS_IN_A_DAY__ = (86400); + //计算天数差 + $__DAYSELAPS = ($this->parse($date) - $this->date) / $__SECONDS_IN_A_DAY__ ; + switch ($elaps) { + case "y"://转换成年 + $__DAYSELAPS = $__DAYSELAPS / $__DAYS_PER_YEAR__; + break; + case "M"://转换成月 + $__DAYSELAPS = $__DAYSELAPS / $__DAYS_PER_MONTH__; + break; + case "w"://转换成星期 + $__DAYSELAPS = $__DAYSELAPS / $__DAYS_PER_WEEK__; + break; + case "h"://转换成小时 + $__DAYSELAPS = $__DAYSELAPS * $__HOURS_IN_A_DAY__; + break; + case "m"://转换成分钟 + $__DAYSELAPS = $__DAYSELAPS * $__MINUTES_IN_A_DAY__; + break; + case "s"://转换成秒 + $__DAYSELAPS = $__DAYSELAPS * $__SECONDS_IN_A_DAY__; + break; + } + return $__DAYSELAPS; + } + + /** + * 人性化的计算日期差 + * @static + * @access public + * @param mixed $time 要比较的时间 + * @param mixed $precision 返回的精度 + * @return string + */ + public function timeDiff( $time ,$precision=false) { + if(!is_numeric($precision) && !is_bool($precision)) { + static $_diff = array('y'=>'年','M'=>'个月','d'=>'天','w'=>'周','s'=>'秒','h'=>'小时','m'=>'分钟'); + return ceil($this->dateDiff($time,$precision)).$_diff[$precision].'前'; + } + $diff = abs($this->parse($time) - $this->date); + static $chunks = array(array(31536000,'年'),array(2592000,'个月'),array(604800,'周'),array(86400,'天'),array(3600 ,'小时'),array(60,'分钟'),array(1,'秒')); + $count =0; + $since = ''; + for($i=0;$i=$chunks[$i][0]) { + $num = floor($diff/$chunks[$i][0]); + $since .= sprintf('%d'.$chunks[$i][1],$num); + $diff = (int)($diff-$chunks[$i][0]*$num); + $count++; + if(!$precision || $count>=$precision) { + break; + } + } + } + return $since.'前'; + } + + /** + * 返回周的某一天 返回Date对象 + * @access public + * @return Date + */ + public function getDayOfWeek($n){ + $week = array(0=>'sunday',1=>'monday',2=>'tuesday',3=>'wednesday',4=>'thursday',5=>'friday',6=>'saturday'); + return (new Date($week[$n])); + } + + /** + * 计算周的第一天 返回Date对象 + * @access public + * @return Date + */ + public function firstDayOfWeek() { + return $this->getDayOfWeek(1); + } + + /** + * 计算月份的第一天 返回Date对象 + * @access public + * @return Date + */ + public function firstDayOfMonth() { + return (new Date(mktime(0, 0, 0,$this->month,1,$this->year ))); + } + + /** + * 计算年份的第一天 返回Date对象 + * @access public + * @return Date + */ + public function firstDayOfYear() { + return (new Date(mktime(0, 0, 0, 1, 1, $this->year))); + } + + /** + * 计算周的最后一天 返回Date对象 + * @access public + * @return Date + */ + public function lastDayOfWeek() { + return $this->getDayOfWeek(0); + } + + /** + * 计算月份的最后一天 返回Date对象 + * @access public + * @return Date + */ + public function lastDayOfMonth() { + return (new Date(mktime(0, 0, 0, $this->month + 1, 0, $this->year ))); + } + + /** + * 计算年份的最后一天 返回Date对象 + * @access public + * @return Date + */ + public function lastDayOfYear() { + return (new Date(mktime(0, 0, 0, 1, 0, $this->year + 1))); + } + + /** + * 计算月份的最大天数 + * @access public + * @return integer + */ + public function maxDayOfMonth() { + $result = $this->dateDiff(strtotime($this->dateAdd(1,'m')),'d'); + return $result; + } + + /** + * 取得指定间隔日期 + * + * yyyy - 年 + * q - 季度 + * m - 月 + * y - day of year + * d - 日 + * w - 周 + * ww - week of year + * h - 小时 + * n - 分钟 + * s - 秒 + * @access public + * @param integer $number 间隔数目 + * @param string $interval 比较类型 + * @return Date + */ + public function dateAdd($number = 0, $interval = "d") { + $hours = $this->hour; + $minutes = $this->minute; + $seconds = $this->second; + $month = $this->month; + $day = $this->day; + $year = $this->year; + + switch ($interval) { + case "yyyy": + //---Add $number to year + $year += $number; + break; + + case "q": + //---Add $number to quarter + $month += ($number*3); + break; + + case "m": + //---Add $number to month + $month += $number; + break; + + case "y": + case "d": + case "w": + //---Add $number to day of year, day, day of week + $day += $number; + break; + + case "ww": + //---Add $number to week + $day += ($number*7); + break; + + case "h": + //---Add $number to hours + $hours += $number; + break; + + case "n": + //---Add $number to minutes + $minutes += $number; + break; + + case "s": + //---Add $number to seconds + $seconds += $number; + break; + } + + return (new Date(mktime($hours, + $minutes, + $seconds, + $month, + $day, + $year))); + + } + + /** + * 日期数字转中文 + * 用于日和月、周 + * @static + * @access public + * @param integer $number 日期数字 + * @return string + */ + public function numberToCh($number) { + $number = intval($number); + $array = array('一','二','三','四','五','六','七','八','九','十'); + $str = ''; + if($number ==0) { $str .= "十" ;} + if($number < 10){ + $str .= $array[$number-1] ; + } + elseif($number < 20 ){ + $str .= "十".$array[$number-11]; + } + elseif($number < 30 ){ + $str .= "二十".$array[$number-21]; + } + else{ + $str .= "三十".$array[$number-31]; + } + return $str; + } + + /** + * 年份数字转中文 + * @static + * @access public + * @param integer $yearStr 年份数字 + * @param boolean $flag 是否显示公元 + * @return string + */ + public function yearToCh( $yearStr ,$flag=false ) { + $array = array('零','一','二','三','四','五','六','七','八','九'); + $str = $flag? '公元' : ''; + for($i=0;$i<4;$i++){ + $str .= $array[substr($yearStr,$i,1)]; + } + return $str; + } + + /** + * 判断日期 所属 干支 生肖 星座 + * type 参数:XZ 星座 GZ 干支 SX 生肖 + * + * @static + * @access public + * @param string $type 获取信息类型 + * @return string + */ + public function magicInfo($type) { + $result = ''; + $m = $this->month; + $y = $this->year; + $d = $this->day; + + switch ($type) { + case 'XZ'://星座 + $XZDict = array('摩羯','宝瓶','双鱼','白羊','金牛','双子','巨蟹','狮子','处女','天秤','天蝎','射手'); + $Zone = array(1222,122,222,321,421,522,622,722,822,922,1022,1122,1222); + if((100*$m+$d)>=$Zone[0]||(100*$m+$d)<$Zone[1]) + $i=0; + else + for($i=1;$i<12;$i++){ + if((100*$m+$d)>=$Zone[$i]&&(100*$m+$d)<$Zone[$i+1]) + break; + } + $result = $XZDict[$i].'座'; + break; + + case 'GZ'://干支 + $GZDict = array( + array('甲','乙','丙','丁','戊','己','庚','辛','壬','癸'), + array('子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥') + ); + $i= $y -1900+36 ; + $result = $GZDict[0][$i%10].$GZDict[1][$i%12]; + break; + + case 'SX'://生肖 + $SXDict = array('鼠','牛','虎','兔','龙','蛇','马','羊','猴','鸡','狗','猪'); + $result = $SXDict[($y-4)%12]; + break; + + } + return $result; + } + + public function __toString() { + return $this->format(); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Org/Util/Rbac.class.php b/ThinkPHP/Library/Org/Util/Rbac.class.php new file mode 100644 index 0000000..b9c18e8 --- /dev/null +++ b/ThinkPHP/Library/Org/Util/Rbac.class.php @@ -0,0 +1,285 @@ + +// +---------------------------------------------------------------------- +namespace Org\Util; +use Think\Db; +/** + +------------------------------------------------------------------------------ + * 基于角色的数据库方式验证类 + +------------------------------------------------------------------------------ + */ +// 配置文件增加设置 +// USER_AUTH_ON 是否需要认证 +// USER_AUTH_TYPE 认证类型 +// USER_AUTH_KEY 认证识别号 +// REQUIRE_AUTH_MODULE 需要认证模块 +// NOT_AUTH_MODULE 无需认证模块 +// USER_AUTH_GATEWAY 认证网关 +// RBAC_DB_DSN 数据库连接DSN +// RBAC_ROLE_TABLE 角色表名称 +// RBAC_USER_TABLE 用户表名称 +// RBAC_ACCESS_TABLE 权限表名称 +// RBAC_NODE_TABLE 节点表名称 +/* +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `think_access` ( + `role_id` smallint(6) unsigned NOT NULL, + `node_id` smallint(6) unsigned NOT NULL, + `level` tinyint(1) NOT NULL, + `module` varchar(50) DEFAULT NULL, + KEY `groupId` (`role_id`), + KEY `nodeId` (`node_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `think_node` ( + `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(20) NOT NULL, + `title` varchar(50) DEFAULT NULL, + `status` tinyint(1) DEFAULT '0', + `remark` varchar(255) DEFAULT NULL, + `sort` smallint(6) unsigned DEFAULT NULL, + `pid` smallint(6) unsigned NOT NULL, + `level` tinyint(1) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `level` (`level`), + KEY `pid` (`pid`), + KEY `status` (`status`), + KEY `name` (`name`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `think_role` ( + `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(20) NOT NULL, + `pid` smallint(6) DEFAULT NULL, + `status` tinyint(1) unsigned DEFAULT NULL, + `remark` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `pid` (`pid`), + KEY `status` (`status`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; + +CREATE TABLE IF NOT EXISTS `think_role_user` ( + `role_id` mediumint(9) unsigned DEFAULT NULL, + `user_id` char(32) DEFAULT NULL, + KEY `group_id` (`role_id`), + KEY `user_id` (`user_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +*/ +class Rbac { + // 认证方法 + static public function authenticate($map,$model='') { + if(empty($model)) $model = C('USER_AUTH_MODEL'); + //使用给定的Map进行认证 + return M($model)->where($map)->find(); + } + + //用于检测用户权限的方法,并保存到Session中 + static function saveAccessList($authId=null) { + if(null===$authId) $authId = $_SESSION[C('USER_AUTH_KEY')]; + // 如果使用普通权限模式,保存当前用户的访问权限列表 + // 对管理员开发所有权限 + if(C('USER_AUTH_TYPE') !=2 && !$_SESSION[C('ADMIN_AUTH_KEY')] ) + $_SESSION['_ACCESS_LIST'] = self::getAccessList($authId); + return ; + } + + // 取得模块的所属记录访问权限列表 返回有权限的记录ID数组 + static function getRecordAccessList($authId=null,$module='') { + if(null===$authId) $authId = $_SESSION[C('USER_AUTH_KEY')]; + if(empty($module)) $module = CONTROLLER_NAME; + //获取权限访问列表 + $accessList = self::getModuleAccessList($authId,$module); + return $accessList; + } + + //检查当前操作是否需要认证 + static function checkAccess() { + //如果项目要求认证,并且当前模块需要认证,则进行权限认证 + if( C('USER_AUTH_ON') ){ + $_module = array(); + $_action = array(); + if("" != C('REQUIRE_AUTH_MODULE')) { + //需要认证的模块 + $_module['yes'] = explode(',',strtoupper(C('REQUIRE_AUTH_MODULE'))); + }else { + //无需认证的模块 + $_module['no'] = explode(',',strtoupper(C('NOT_AUTH_MODULE'))); + } + //检查当前模块是否需要认证 + if((!empty($_module['no']) && !in_array(strtoupper(CONTROLLER_NAME),$_module['no'])) || (!empty($_module['yes']) && in_array(strtoupper(CONTROLLER_NAME),$_module['yes']))) { + if("" != C('REQUIRE_AUTH_ACTION')) { + //需要认证的操作 + $_action['yes'] = explode(',',strtoupper(C('REQUIRE_AUTH_ACTION'))); + }else { + //无需认证的操作 + $_action['no'] = explode(',',strtoupper(C('NOT_AUTH_ACTION'))); + } + //检查当前操作是否需要认证 + if((!empty($_action['no']) && !in_array(strtoupper(ACTION_NAME),$_action['no'])) || (!empty($_action['yes']) && in_array(strtoupper(ACTION_NAME),$_action['yes']))) { + return true; + }else { + return false; + } + }else { + return false; + } + } + return false; + } + + // 登录检查 + static public function checkLogin() { + //检查当前操作是否需要认证 + if(self::checkAccess()) { + //检查认证识别号 + if(!$_SESSION[C('USER_AUTH_KEY')]) { + if(C('GUEST_AUTH_ON')) { + // 开启游客授权访问 + if(!isset($_SESSION['_ACCESS_LIST'])) + // 保存游客权限 + self::saveAccessList(C('GUEST_AUTH_ID')); + }else{ + // 禁止游客访问跳转到认证网关 + redirect(PHP_FILE.C('USER_AUTH_GATEWAY')); + } + } + } + return true; + } + + //权限认证的过滤器方法 + static public function AccessDecision($appName=MODULE_NAME) { + //检查是否需要认证 + if(self::checkAccess()) { + //存在认证识别号,则进行进一步的访问决策 + $accessGuid = md5($appName.CONTROLLER_NAME.ACTION_NAME); + if(empty($_SESSION[C('ADMIN_AUTH_KEY')])) { + if(C('USER_AUTH_TYPE')==2) { + //加强验证和即时验证模式 更加安全 后台权限修改可以即时生效 + //通过数据库进行访问检查 + $accessList = self::getAccessList($_SESSION[C('USER_AUTH_KEY')]); + }else { + // 如果是管理员或者当前操作已经认证过,无需再次认证 + if( $_SESSION[$accessGuid]) { + return true; + } + //登录验证模式,比较登录后保存的权限访问列表 + $accessList = $_SESSION['_ACCESS_LIST']; + } + //判断是否为组件化模式,如果是,验证其全模块名 + if(!isset($accessList[strtoupper($appName)][strtoupper(CONTROLLER_NAME)][strtoupper(ACTION_NAME)])) { + $_SESSION[$accessGuid] = false; + return false; + } + else { + $_SESSION[$accessGuid] = true; + } + }else{ + //管理员无需认证 + return true; + } + } + return true; + } + + /** + +---------------------------------------------------------- + * 取得当前认证号的所有权限列表 + +---------------------------------------------------------- + * @param integer $authId 用户ID + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + */ + static public function getAccessList($authId) { + // Db方式权限数据 + $db = Db::getInstance(C('RBAC_DB_DSN')); + $table = array('role'=>C('RBAC_ROLE_TABLE'),'user'=>C('RBAC_USER_TABLE'),'access'=>C('RBAC_ACCESS_TABLE'),'node'=>C('RBAC_NODE_TABLE')); + $sql = "select node.id,node.name from ". + $table['role']." as role,". + $table['user']." as user,". + $table['access']." as access ,". + $table['node']." as node ". + "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=1 and node.status=1"; + $apps = $db->query($sql); + $access = array(); + foreach($apps as $key=>$app) { + $appId = $app['id']; + $appName = $app['name']; + // 读取项目的模块权限 + $access[strtoupper($appName)] = array(); + $sql = "select node.id,node.name from ". + $table['role']." as role,". + $table['user']." as user,". + $table['access']." as access ,". + $table['node']." as node ". + "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=2 and node.pid={$appId} and node.status=1"; + $modules = $db->query($sql); + // 判断是否存在公共模块的权限 + $publicAction = array(); + foreach($modules as $key=>$module) { + $moduleId = $module['id']; + $moduleName = $module['name']; + if('PUBLIC'== strtoupper($moduleName)) { + $sql = "select node.id,node.name from ". + $table['role']." as role,". + $table['user']." as user,". + $table['access']." as access ,". + $table['node']." as node ". + "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=3 and node.pid={$moduleId} and node.status=1"; + $rs = $db->query($sql); + foreach ($rs as $a){ + $publicAction[$a['name']] = $a['id']; + } + unset($modules[$key]); + break; + } + } + // 依次读取模块的操作权限 + foreach($modules as $key=>$module) { + $moduleId = $module['id']; + $moduleName = $module['name']; + $sql = "select node.id,node.name from ". + $table['role']." as role,". + $table['user']." as user,". + $table['access']." as access ,". + $table['node']." as node ". + "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=3 and node.pid={$moduleId} and node.status=1"; + $rs = $db->query($sql); + $action = array(); + foreach ($rs as $a){ + $action[$a['name']] = $a['id']; + } + // 和公共模块的操作权限合并 + $action += $publicAction; + $access[strtoupper($appName)][strtoupper($moduleName)] = array_change_key_case($action,CASE_UPPER); + } + } + return $access; + } + + // 读取模块所属的记录访问权限 + static public function getModuleAccessList($authId,$module) { + // Db方式 + $db = Db::getInstance(C('RBAC_DB_DSN')); + $table = array('role'=>C('RBAC_ROLE_TABLE'),'user'=>C('RBAC_USER_TABLE'),'access'=>C('RBAC_ACCESS_TABLE')); + $sql = "select access.node_id from ". + $table['role']." as role,". + $table['user']." as user,". + $table['access']." as access ". + "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.module='{$module}' and access.status=1"; + $rs = $db->query($sql); + $access = array(); + foreach ($rs as $node){ + $access[] = $node['node_id']; + } + return $access; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Org/Util/Stack.class.php b/ThinkPHP/Library/Org/Util/Stack.class.php new file mode 100644 index 0000000..51045e0 --- /dev/null +++ b/ThinkPHP/Library/Org/Util/Stack.class.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- +namespace Org\Util; + +/** + * Stack实现类 + * @category ORG + * @package ORG + * @subpackage Util + * @author liu21st + */ +class Stack extends ArrayList { + + /** + * 架构函数 + * @access public + * @param array $values 初始化数组元素 + */ + public function __construct($values = array()) { + parent::__construct($values); + } + + /** + * 将堆栈的内部指针指向第一个单元 + * @access public + * @return mixed + */ + public function peek() { + return reset($this->toArray()); + } + + /** + * 元素进栈 + * @access public + * @param mixed $value + * @return mixed + */ + public function push($value) { + $this->add($value); + return $value; + } + +} diff --git a/ThinkPHP/Library/Org/Util/String.class.php b/ThinkPHP/Library/Org/Util/String.class.php new file mode 100644 index 0000000..64078f4 --- /dev/null +++ b/ThinkPHP/Library/Org/Util/String.class.php @@ -0,0 +1,248 @@ + +// +---------------------------------------------------------------------- +namespace Org\Util; +class String { + + /** + * 生成UUID 单机使用 + * @access public + * @return string + */ + static public function uuid() { + $charid = md5(uniqid(mt_rand(), true)); + $hyphen = chr(45);// "-" + $uuid = chr(123)// "{" + .substr($charid, 0, 8).$hyphen + .substr($charid, 8, 4).$hyphen + .substr($charid,12, 4).$hyphen + .substr($charid,16, 4).$hyphen + .substr($charid,20,12) + .chr(125);// "}" + return $uuid; + } + + /** + * 生成Guid主键 + * @return Boolean + */ + static public function keyGen() { + return str_replace('-','',substr(String::uuid(),1,-1)); + } + + /** + * 检查字符串是否是UTF8编码 + * @param string $string 字符串 + * @return Boolean + */ + static public function isUtf8($str) { + $c=0; $b=0; + $bits=0; + $len=strlen($str); + for($i=0; $i<$len; $i++){ + $c=ord($str[$i]); + if($c > 128){ + if(($c >= 254)) return false; + elseif($c >= 252) $bits=6; + elseif($c >= 248) $bits=5; + elseif($c >= 240) $bits=4; + elseif($c >= 224) $bits=3; + elseif($c >= 192) $bits=2; + else return false; + if(($i+$bits) > $len) return false; + while($bits > 1){ + $i++; + $b=ord($str[$i]); + if($b < 128 || $b > 191) return false; + $bits--; + } + } + } + return true; + } + + /** + * 字符串截取,支持中文和其他编码 + * @static + * @access public + * @param string $str 需要转换的字符串 + * @param string $start 开始位置 + * @param string $length 截取长度 + * @param string $charset 编码格式 + * @param string $suffix 截断显示字符 + * @return string + */ + static public function msubstr($str, $start=0, $length, $charset="utf-8", $suffix=true) { + if(function_exists("mb_substr")) + $slice = mb_substr($str, $start, $length, $charset); + elseif(function_exists('iconv_substr')) { + $slice = iconv_substr($str,$start,$length,$charset); + }else{ + $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/"; + $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/"; + $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/"; + $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/"; + preg_match_all($re[$charset], $str, $match); + $slice = join("",array_slice($match[0], $start, $length)); + } + return $suffix ? $slice.'...' : $slice; + } + + /** + * 产生随机字串,可用来自动生成密码 + * 默认长度6位 字母和数字混合 支持中文 + * @param string $len 长度 + * @param string $type 字串类型 + * 0 字母 1 数字 其它 混合 + * @param string $addChars 额外字符 + * @return string + */ + static public function randString($len=6,$type='',$addChars='') { + $str =''; + switch($type) { + case 0: + $chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.$addChars; + break; + case 1: + $chars= str_repeat('0123456789',3); + break; + case 2: + $chars='ABCDEFGHIJKLMNOPQRSTUVWXYZ'.$addChars; + break; + case 3: + $chars='abcdefghijklmnopqrstuvwxyz'.$addChars; + break; + case 4: + $chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借".$addChars; + break; + default : + // 默认去掉了容易混淆的字符oOLl和数字01,要添加请使用addChars参数 + $chars='ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789'.$addChars; + break; + } + if($len>10 ) {//位数过长重复字符串一定次数 + $chars= $type==1? str_repeat($chars,$len) : str_repeat($chars,5); + } + if($type!=4) { + $chars = str_shuffle($chars); + $str = substr($chars,0,$len); + }else{ + // 中文随机字 + for($i=0;$i<$len;$i++){ + $str.= self::msubstr($chars, floor(mt_rand(0,mb_strlen($chars,'utf-8')-1)),1,'utf-8',false); + } + } + return $str; + } + + /** + * 生成一定数量的随机数,并且不重复 + * @param integer $number 数量 + * @param string $len 长度 + * @param string $type 字串类型 + * 0 字母 1 数字 其它 混合 + * @return string + */ + static public function buildCountRand ($number,$length=4,$mode=1) { + if($mode==1 && $length $val) { + $_key = self::autoCharset($key, $from, $to); + $string[$_key] = self::autoCharset($val, $from, $to); + if ($key != $_key) + unset($string[$key]); + } + return $string; + } + else { + return $string; + } + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/App.class.php b/ThinkPHP/Library/Think/App.class.php new file mode 100644 index 0000000..be15394 --- /dev/null +++ b/ThinkPHP/Library/Think/App.class.php @@ -0,0 +1,213 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP 应用程序类 执行应用过程管理 + */ +class App { + + /** + * 应用程序初始化 + * @access public + * @return void + */ + static public function init() { + // 加载动态应用公共文件和配置 + load_ext_file(COMMON_PATH); + + // 日志目录转换为绝对路径 默认情况下存储到公共模块下面 + C('LOG_PATH', realpath(LOG_PATH).'/Common/'); + + // 定义当前请求的系统常量 + define('NOW_TIME', $_SERVER['REQUEST_TIME']); + define('REQUEST_METHOD',$_SERVER['REQUEST_METHOD']); + define('IS_GET', REQUEST_METHOD =='GET' ? true : false); + define('IS_POST', REQUEST_METHOD =='POST' ? true : false); + define('IS_PUT', REQUEST_METHOD =='PUT' ? true : false); + define('IS_DELETE', REQUEST_METHOD =='DELETE' ? true : false); + + // URL调度 + Dispatcher::dispatch(); + + if(C('REQUEST_VARS_FILTER')){ + // 全局安全过滤 + array_walk_recursive($_GET, 'think_filter'); + array_walk_recursive($_POST, 'think_filter'); + array_walk_recursive($_REQUEST, 'think_filter'); + } + + // URL调度结束标签 + Hook::listen('url_dispatch'); + + define('IS_AJAX', ((isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') || !empty($_POST[C('VAR_AJAX_SUBMIT')]) || !empty($_GET[C('VAR_AJAX_SUBMIT')])) ? true : false); + + // TMPL_EXCEPTION_FILE 改为绝对地址 + C('TMPL_EXCEPTION_FILE',realpath(C('TMPL_EXCEPTION_FILE'))); + return ; + } + + /** + * 执行应用程序 + * @access public + * @return void + */ + static public function exec() { + + if(!preg_match('/^[A-Za-z](\/|\w)*$/',CONTROLLER_NAME)){ // 安全检测 + $module = false; + }elseif(C('ACTION_BIND_CLASS')){ + // 操作绑定到类:模块\Controller\控制器\操作 + $layer = C('DEFAULT_C_LAYER'); + if(is_dir(MODULE_PATH.$layer.'/'.CONTROLLER_NAME)){ + $namespace = MODULE_NAME.'\\'.$layer.'\\'.CONTROLLER_NAME.'\\'; + }else{ + // 空控制器 + $namespace = MODULE_NAME.'\\'.$layer.'\\_empty\\'; + } + $actionName = strtolower(ACTION_NAME); + if(class_exists($namespace.$actionName)){ + $class = $namespace.$actionName; + }elseif(class_exists($namespace.'_empty')){ + // 空操作 + $class = $namespace.'_empty'; + }else{ + E(L('_ERROR_ACTION_').':'.ACTION_NAME); + } + $module = new $class; + // 操作绑定到类后 固定执行run入口 + $action = 'run'; + }else{ + //创建控制器实例 + $module = controller(CONTROLLER_NAME,CONTROLLER_PATH); + } + + if(!$module) { + if('4e5e5d7364f443e28fbf0d3ae744a59a' == CONTROLLER_NAME) { + header("Content-type:image/png"); + exit(base64_decode(App::logo())); + } + + // 是否定义Empty控制器 + $module = A('Empty'); + if(!$module){ + E(L('_CONTROLLER_NOT_EXIST_').':'.CONTROLLER_NAME); + } + } + + // 获取当前操作名 支持动态路由 + if(!isset($action)){ + $action = ACTION_NAME.C('ACTION_SUFFIX'); + } + try{ + self::invokeAction($module,$action); + } catch (\ReflectionException $e) { + // 方法调用发生异常后 引导到__call方法处理 + $method = new \ReflectionMethod($module,'__call'); + $method->invokeArgs($module,array($action,'')); + } + return ; + } + public static function invokeAction($module,$action){ + if(!preg_match('/^[A-Za-z](\w)*$/',$action)){ + // 非法操作 + throw new \ReflectionException(); + } + //执行当前操作 + $method = new \ReflectionMethod($module, $action); + if($method->isPublic() && !$method->isStatic()) { + $class = new \ReflectionClass($module); + // 前置操作 + if($class->hasMethod('_before_'.$action)) { + $before = $class->getMethod('_before_'.$action); + if($before->isPublic()) { + $before->invoke($module); + } + } + // URL参数绑定检测 + if($method->getNumberOfParameters()>0 && C('URL_PARAMS_BIND')){ + switch($_SERVER['REQUEST_METHOD']) { + case 'POST': + $vars = array_merge($_GET,$_POST); + break; + case 'PUT': + parse_str(file_get_contents('php://input'), $vars); + break; + default: + $vars = $_GET; + } + $params = $method->getParameters(); + $paramsBindType = C('URL_PARAMS_BIND_TYPE'); + foreach ($params as $param){ + $name = $param->getName(); + if( 1 == $paramsBindType && !empty($vars) ){ + $args[] = array_shift($vars); + }elseif( 0 == $paramsBindType && isset($vars[$name])){ + $args[] = $vars[$name]; + }elseif($param->isDefaultValueAvailable()){ + $args[] = $param->getDefaultValue(); + }else{ + E(L('_PARAM_ERROR_').':'.$name); + } + } + // 开启绑定参数过滤机制 + if(C('URL_PARAMS_SAFE')){ + $filters = C('URL_PARAMS_FILTER')?:C('DEFAULT_FILTER'); + if($filters) { + $filters = explode(',',$filters); + foreach($filters as $filter){ + $args = array_map_recursive($filter,$args); // 参数过滤 + } + } + } + array_walk_recursive($args,'think_filter'); + $method->invokeArgs($module,$args); + }else{ + $method->invoke($module); + } + // 后置操作 + if($class->hasMethod('_after_'.$action)) { + $after = $class->getMethod('_after_'.$action); + if($after->isPublic()) { + $after->invoke($module); + } + } + }else{ + // 操作方法不是Public 抛出异常 + throw new \ReflectionException(); + } + } + /** + * 运行应用实例 入口文件使用的快捷方法 + * @access public + * @return void + */ + static public function run() { + // 应用初始化标签 + Hook::listen('app_init'); + App::init(); + // 应用开始标签 + Hook::listen('app_begin'); + // Session初始化 + if(!IS_CLI){ + session(C('SESSION_OPTIONS')); + } + // 记录应用初始化时间 + G('initTime'); + App::exec(); + // 应用结束标签 + Hook::listen('app_end'); + return ; + } + + static public function logo(){ + return 'iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjVERDVENkZGQjkyNDExRTE5REY3RDQ5RTQ2RTRDQUJCIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjVERDVENzAwQjkyNDExRTE5REY3RDQ5RTQ2RTRDQUJCIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NURENUQ2RkRCOTI0MTFFMTlERjdENDlFNDZFNENBQkIiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NURENUQ2RkVCOTI0MTFFMTlERjdENDlFNDZFNENBQkIiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5fx6IRAAAMCElEQVR42sxae3BU1Rk/9+69+8xuNtkHJAFCSIAkhMgjCCJQUi0GtEIVbP8Qq9LH2No6TmfaztjO2OnUdvqHFMfOVFTqIK0vUEEeqUBARCsEeYQkEPJoEvIiELLvvc9z+p27u2F3s5tsBB1OZiebu5dzf7/v/L7f952zMM8cWIwY+Mk2ulCp92Fnq3XvnzArr2NZnYNldDp0Gw+/OEQ4+obQn5D+4Ubb22+YOGsWi/Todh8AHglKEGkEsnHBQ162511GZFgW6ZCBM9/W4H3iNSQqIe09O196dLKX7d1O39OViP/wthtkND62if/wj/DbMpph8BY/m9xy8BoBmQk+mHqZQGNy4JYRwCoRbwa8l4JXw6M+orJxpU0U6ToKy/5bQsAiTeokGKkTx46RRxxEUgrwGgF4MWNNEJCGgYTvpgnY1IJWg5RzfqLgvcIgktX0i8dmMlFA8qCQ5L0Z/WObPLUxT1i4lWSYDISoEfBYGvM+LlMQQdkLHoWRRZ8zYQI62Thswe5WTORGwNXDcGjqeOA9AF7B8rhzsxMBEoJ8oJKaqPu4hblHMCMPwl9XeNWyb8xkB/DDGYKfMAE6aFL7xesZ389JlgG3XHEMI6UPDOP6JHHu67T2pwNPI69mCP4rEaBDUAJaKc/AOuXiwH07VCS3w5+UQMAuF/WqGI+yFIwVNBwemBD4r0wgQiKoFZa00sEYTwss32lA1tPwVxtc8jQ5/gWCwmGCyUD8vRT0sHBFW4GJDvZmrJFWRY1EkrGA6ZB8/10fOZSSj0E6F+BSP7xidiIzhBmKB09lEwHPkG+UQIyEN44EBiT5vrv2uJXyPQqSqO930fxvcvwbR/+JAkD9EfASgI9EHlp6YiHO4W+cAB20SnrFqxBbNljiXf1Pl1K2S0HCWfiog3YlAD5RGwwxK6oUjTweuVigLjyB0mX410mAFnMoVK1lvvUvgt8fUJH0JVyjuvcmg4dE5mUiFtD24AZ4qBVELxXKS+pMxN43kSdzNwudJ+bQbLlmnxvPOQoCugSap1GnSRoG8KOiKbH+rIA0lEeSAg3y6eeQ6XI2nrYnrPM89bUTgI0Pdqvl50vlNbtZxDUBcLBK0kPd5jPziyLdojJIN0pq5/mdzwL4UVvVInV5ncQEPNOUxa9d0TU+CW5l+FoI0GSDKHVVSOs+0KOsZoxwOzSZNFGv0mQ9avyLCh2Hpm+70Y0YJoJVgmQv822wnDC8Miq6VjJ5IFed0QD1YiAbT+nQE8v/RMZfmgmcCRHIIu7Bmcp39oM9fqEychcA747KxQ/AEyqQonl7hATtJmnhO2XYtgcia01aSbVMenAXrIomPcLgEBA4liGBzFZAT8zBYqW6brI67wg8sFVhxBhwLwBP2+tqBQqqK7VJKGh/BRrfTr6nWL7nYBaZdBJHqrX3kPEPap56xwE/GvjJTRMADeMCdcGpGXL1Xh4ZL8BDOlWkUpegfi0CeDzeA5YITzEnddv+IXL+UYCmqIvqC9UlUC/ki9FipwVjunL3yX7dOTLeXmVMAhbsGporPfyOBTm/BJ23gTVehsvXRnSewagUfpBXF3p5pygKS7OceqTjb7h2vjr/XKm0ZofKSI2Q/J102wHzatZkJPYQ5JoKsuK+EoHJakVzubzuLQDepCKllTZi9AG0DYg9ZLxhFaZsOu7bvlmVI5oPXJMQJcHxHClSln1apFTvAimeg48u0RWFeZW4lVcjbQWZuIQK1KozZfIDO6CSQmQQXdpBaiKZyEWThVK1uEc6v7V7uK0ysduExPZx4vysDR+4SelhBYm0R6LBuR4PXts8MYMcJPsINo4YZCDLj0sgB0/vLpPXvA2Tn42Cv5rsLulGubzW0sEd3d4W/mJt2Kck+DzDMijfPLOjyrDhXSh852B+OvflqAkoyXO1cYfujtc/i3jJSAwhgfFlp20laMLOku/bC7prgqW7lCn4auE5NhcXPd3M7x70+IceSgZvNljCd9k3fLjYsPElqLR14PXQZqD2ZNkkrAB79UeJUebFQmXpf8ZcAQt2XrMQdyNUVBqZoUzAFyp3V3xi/MubUA/mCT4Fhf038PC8XplhWnCmnK/ZzyC2BSTRSqKVOuY2kB8Jia0lvvRIVoP+vVWJbYarf6p655E2/nANBMCWkgD49DA0VAMyI1OLFMYCXiU9bmzi9/y5i/vsaTpHPHidTofzLbM65vMPva9HlovgXp0AvjtaqYMfDD0/4mAsYE92pxa+9k1QgCnRVObCpojpzsKTPvayPetTEgBdwnssjuc0kOBFX+q3HwRQxdrOLAqeYRjkMk/trTSu2Z9Lik7CfF0AvjtqAhS4NHobGXUnB5DQs8hG8p/wMX1r4+8xkmyvQ50JVq72TVeXbz3HvpWaQJi57hJYTw4kGbtS+C2TigQUtZUX+X27QQq2ePBZBru/0lxTm8fOOQ5yaZOZMAV+he4FqIMB+LQB0UgMSajANX29j+vbmly8ipRvHeSQoQOkM5iFXcPQCVwDMs5RBCQmaPOyvbNd6uwvQJ183BZQG3Zc+Eiv7vQOKu8YeDmMcJlt2ckyftVeMIGLBCmdMHl/tFILYwGPjXWO3zOfSq/+om+oa7Mlh2fpSsRGLp7RAW3FUVjNHgiMhyE6zBFjM2BdkdJGO7nP1kJXWAtBuBpPIAu7f+hhu7bFXIuC5xWrf0X2xreykOsUyKkF2gwadbrXDcXrfKxR43zGcSj4t/cCgr+a1iy6EjE5GYktUCl9fwfMeylyooGF48bN2IGLTw8x7StS7sj8TF9FmPGWQhm3rRR+o9lhvjJvSYAdfDUevI1M6bnX/OwWaDMOQ8RPgKRo0eulBTdT8AW2kl8e9L7UHghHwMfLiZPNoSpx0yugpQZaFqKWqxVSM3a2pN1SAhC2jf94I7ybBI7EL5A2Wvu5ht3xsoEt4+Ay/abXgCQAxyOeDsDlTCQzy75ohcGgv9Tra9uiymRUYTLrswOLlCdfAQf7HPDQQ4ErAH5EDXB9cMxWYpjtXApRncojS0sbV/cCgHTHwGNBJy+1PQE2x56FpaVR7wfQGZ37V+V+19EiHNvR6q1fRUjqvbjbMq1/qfHxbTrE10ePY2gPFk48D2CVMTf1AF4PXvyYR9dV6Wf7H413m3xTWQvYGhQ7mfYwA5mAX+18Vue05v/8jG/fZX/IW5MKPKtjSYlt0ellxh+/BOCPAwYaeVr0QofZFxJWVWC8znG70au6llVmktsF0bfHF6k8fvZ5esZJbwHwwnjg59tXz6sL/P0NUZDuSNu1mnJ8Vab17+cy005A9wtOpp3i0bZdpJLUil00semAwN45LgEViZYe3amNye0B6A9chviSlzXVsFtyN5/1H3gaNmMpn8Fz0GpYFp6Zw615H/LpUuRQQDMCL82n5DpBSawkvzIdN2ypiT8nSLth8Pk9jnjwdFzH3W4XW6KMBfwB569NdcGX93mC16tTflcArcYUc/mFuYbV+8zY0SAjAVoNErNgWjtwumJ3wbn/HlBFYdxHvSkJJEc+Ngal9opSwyo9YlITX2C/P/+gf8sxURSLR+mcZUmeqaS9wrh6vxW5zxFCOqFi90RbDWq/YwZmnu1+a6OvdpvRqkNxxe44lyl4OobEnpKA6Uox5EfH9xzPs/HRKrTPWdIQrK1VZDU7ETiD3Obpl+8wPPCRBbkbwNtpW9AbBe5L1SMlj3tdTxk/9W47JUmqS5HU+JzYymUKXjtWVmT9RenIhgXc+nroWLyxXJhmL112OdB8GCsk4f8oZJucnvmmtR85mBn10GZ0EKSCMUSAR3ukcXd5s7LvLD3me61WkuTCpJzYAyRurMB44EdEJzTfU271lUJC03YjXJXzYOGZwN4D8eB5jlfLrdWfzGRW7icMPfiSO6Oe7s20bmhdgLX4Z23B+s3JgQESzUDiMboSzDMHFpNMwccGePauhfwjzwnI2wu9zKGgEFg80jcZ7MHllk07s1H+5yojtUQTlH4nFdLKTGwDmPbIklOb1L1zO4T6N8NCuDLFLS/C63c0eNRimZ++s5BMBHxU11jHchI9oFVUxRh/eMDzHEzGYu0Lg8gJ7oS/tFCwoic44fyUtix0n/46vP4bf+//BRgAYwDDar4ncHIAAAAASUVORK5CYII='; + } +} diff --git a/ThinkPHP/Library/Think/Auth.class.php b/ThinkPHP/Library/Think/Auth.class.php new file mode 100644 index 0000000..4c1dea1 --- /dev/null +++ b/ThinkPHP/Library/Think/Auth.class.php @@ -0,0 +1,231 @@ +  +// +---------------------------------------------------------------------- +namespace Think; +/** + * 权限认证类 + * 功能特性: + * 1,是对规则进行认证,不是对节点进行认证。用户可以把节点当作规则名称实现对节点进行认证。 + * $auth=new Auth(); $auth->check('规则名称','用户id') + * 2,可以同时对多条规则进行认证,并设置多条规则的关系(or或者and) + * $auth=new Auth(); $auth->check('规则1,规则2','用户id','and') + * 第三个参数为and时表示,用户需要同时具有规则1和规则2的权限。 当第三个参数为or时,表示用户值需要具备其中一个条件即可。默认为or + * 3,一个用户可以属于多个用户组(think_auth_group_access表 定义了用户所属用户组)。我们需要设置每个用户组拥有哪些规则(think_auth_group 定义了用户组权限) + * + * 4,支持规则表达式。 + * 在think_auth_rule 表中定义一条规则时,如果type为1, condition字段就可以定义规则表达式。 如定义{score}>5 and {score}<100 表示用户的分数在5-100之间时这条规则才会通过。 + */ + +//数据库 +/* +-- ---------------------------- +-- think_auth_rule,规则表, +-- id:主键,name:规则唯一标识, title:规则中文名称 status 状态:为1正常,为0禁用,condition:规则表达式,为空表示存在就验证,不为空表示按照条件验证 +-- ---------------------------- + DROP TABLE IF EXISTS `think_auth_rule`; +CREATE TABLE `think_auth_rule` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `name` char(80) NOT NULL DEFAULT '', + `title` char(20) NOT NULL DEFAULT '', + `type` tinyint(1) NOT NULL DEFAULT '1', + `status` tinyint(1) NOT NULL DEFAULT '1', + `condition` char(100) NOT NULL DEFAULT '', # 规则附件条件,满足附加条件的规则,才认为是有效的规则 + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- ---------------------------- +-- think_auth_group 用户组表, +-- id:主键, title:用户组中文名称, rules:用户组拥有的规则id, 多个规则","隔开,status 状态:为1正常,为0禁用 +-- ---------------------------- + DROP TABLE IF EXISTS `think_auth_group`; +CREATE TABLE `think_auth_group` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `title` char(100) NOT NULL DEFAULT '', + `status` tinyint(1) NOT NULL DEFAULT '1', + `rules` char(80) NOT NULL DEFAULT '', + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- ---------------------------- +-- think_auth_group_access 用户组明细表 +-- uid:用户id,group_id:用户组id +-- ---------------------------- +DROP TABLE IF EXISTS `think_auth_group_access`; +CREATE TABLE `think_auth_group_access` ( + `uid` mediumint(8) unsigned NOT NULL, + `group_id` mediumint(8) unsigned NOT NULL, + UNIQUE KEY `uid_group_id` (`uid`,`group_id`), + KEY `uid` (`uid`), + KEY `group_id` (`group_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + */ + +class Auth{ + + //默认配置 + protected $_config = array( + 'AUTH_ON' => true, // 认证开关 + 'AUTH_TYPE' => 1, // 认证方式,1为实时认证;2为登录认证。 + 'AUTH_GROUP' => 'auth_group', // 用户组数据表名 + 'AUTH_GROUP_ACCESS' => 'auth_group_access', // 用户-用户组关系表 + 'AUTH_RULE' => 'auth_rule', // 权限规则表 + 'AUTH_USER' => 'member' // 用户信息表 + ); + + public function __construct() { + $prefix = C('DB_PREFIX'); + $this->_config['AUTH_GROUP'] = $prefix.$this->_config['AUTH_GROUP']; + $this->_config['AUTH_RULE'] = $prefix.$this->_config['AUTH_RULE']; + $this->_config['AUTH_USER'] = $prefix.$this->_config['AUTH_USER']; + $this->_config['AUTH_GROUP_ACCESS'] = $prefix.$this->_config['AUTH_GROUP_ACCESS']; + if (C('AUTH_CONFIG')) { + //可设置配置项 AUTH_CONFIG, 此配置项为数组。 + $this->_config = array_merge($this->_config, C('AUTH_CONFIG')); + } + } + + /** + * 检查权限 + * @param name string|array 需要验证的规则列表,支持逗号分隔的权限规则或索引数组 + * @param uid int 认证用户的id + * @param string mode 执行check的模式 + * @param relation string 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证 + * @return boolean 通过验证返回true;失败返回false + */ + public function check($name, $uid, $type=1, $mode='url', $relation='or') { + if (!$this->_config['AUTH_ON']) + return true; + $authList = $this->getAuthList($uid,$type); //获取用户需要验证的所有有效规则列表 + if (is_string($name)) { + $name = strtolower($name); + if (strpos($name, ',') !== false) { + $name = explode(',', $name); + } else { + $name = array($name); + } + } + $list = array(); //保存验证通过的规则名 + if ($mode=='url') { + $REQUEST = unserialize( strtolower(serialize($_REQUEST)) ); + } + foreach ( $authList as $auth ) { + $query = preg_replace('/^.+\?/U','',$auth); + if ($mode=='url' && $query!=$auth ) { + parse_str($query,$param); //解析规则中的param + $intersect = array_intersect_assoc($REQUEST,$param); + $auth = preg_replace('/\?.*$/U','',$auth); + if ( in_array($auth,$name) && $intersect==$param ) { //如果节点相符且url参数满足 + $list[] = $auth ; + } + }else if (in_array($auth , $name)){ + $list[] = $auth ; + } + } + if ($relation == 'or' and !empty($list)) { + return true; + } + $diff = array_diff($name, $list); + if ($relation == 'and' and empty($diff)) { + return true; + } + return false; + } + + /** + * 根据用户id获取用户组,返回值为数组 + * @param uid int 用户id + * @return array 用户所属的用户组 array( + * array('uid'=>'用户id','group_id'=>'用户组id','title'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'), + * ...) + */ + public function getGroups($uid) { + static $groups = array(); + if (isset($groups[$uid])) + return $groups[$uid]; + $user_groups = M() + ->table($this->_config['AUTH_GROUP_ACCESS'] . ' a') + ->where("a.uid='$uid' and g.status='1'") + ->join($this->_config['AUTH_GROUP']." g on a.group_id=g.id") + ->field('uid,group_id,title,rules')->select(); + $groups[$uid]=$user_groups?:array(); + return $groups[$uid]; + } + + /** + * 获得权限列表 + * @param integer $uid 用户id + * @param integer $type + */ + protected function getAuthList($uid,$type) { + static $_authList = array(); //保存用户验证通过的权限列表 + $t = implode(',',(array)$type); + if (isset($_authList[$uid.$t])) { + return $_authList[$uid.$t]; + } + if( $this->_config['AUTH_TYPE']==2 && isset($_SESSION['_AUTH_LIST_'.$uid.$t])){ + return $_SESSION['_AUTH_LIST_'.$uid.$t]; + } + + //读取用户所属用户组 + $groups = $this->getGroups($uid); + $ids = array();//保存用户所属用户组设置的所有权限规则id + foreach ($groups as $g) { + $ids = array_merge($ids, explode(',', trim($g['rules'], ','))); + } + $ids = array_unique($ids); + if (empty($ids)) { + $_authList[$uid.$t] = array(); + return array(); + } + + $map=array( + 'id'=>array('in',$ids), + 'type'=>$type, + 'status'=>1, + ); + //读取用户组所有权限规则 + $rules = M()->table($this->_config['AUTH_RULE'])->where($map)->field('condition,name')->select(); + + //循环规则,判断结果。 + $authList = array(); // + foreach ($rules as $rule) { + if (!empty($rule['condition'])) { //根据condition进行验证 + $user = $this->getUserInfo($uid);//获取用户信息,一维数组 + + $command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']); + //dump($command);//debug + @(eval('$condition=(' . $command . ');')); + if ($condition) { + $authList[] = strtolower($rule['name']); + } + } else { + //只要存在就记录 + $authList[] = strtolower($rule['name']); + } + } + $_authList[$uid.$t] = $authList; + if($this->_config['AUTH_TYPE']==2){ + //规则列表结果保存到session + $_SESSION['_AUTH_LIST_'.$uid.$t]=$authList; + } + return array_unique($authList); + } + + /** + * 获得用户资料,根据自己的情况读取数据库 + */ + protected function getUserInfo($uid) { + static $userinfo=array(); + if(!isset($userinfo[$uid])){ + $userinfo[$uid]=M()->where(array('uid'=>$uid))->table($this->_config['AUTH_USER'])->find(); + } + return $userinfo[$uid]; + } + +} diff --git a/ThinkPHP/Library/Think/Behavior.class.php b/ThinkPHP/Library/Think/Behavior.class.php new file mode 100644 index 0000000..a467b9d --- /dev/null +++ b/ThinkPHP/Library/Think/Behavior.class.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP Behavior基础类 + */ +abstract class Behavior { + /** + * 执行行为 run方法是Behavior唯一的接口 + * @access public + * @param mixed $params 行为参数 + * @return void + */ + abstract public function run(&$params); + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Build.class.php b/ThinkPHP/Library/Think/Build.class.php new file mode 100644 index 0000000..6528107 --- /dev/null +++ b/ThinkPHP/Library/Think/Build.class.php @@ -0,0 +1,165 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * 用于ThinkPHP的自动生成 + */ +class Build { + + static protected $controller = 'show(\'

:)

欢迎使用 ThinkPHP


版本 V{$Think.version}
\',\'utf-8\'); + } +}'; + + static protected $model = ''配置值'\n);":''); + // 写入模块配置文件 + if(!is_file(APP_PATH.$module.'/Conf/config'.CONF_EXT)) + file_put_contents(APP_PATH.$module.'/Conf/config'.CONF_EXT,'.php' == CONF_EXT ? "'配置值'\n);":''); + // 生成模块的测试控制器 + if(defined('BUILD_CONTROLLER_LIST')){ + // 自动生成的控制器列表(注意大小写) + $list = explode(',',BUILD_CONTROLLER_LIST); + foreach($list as $controller){ + self::buildController($module,$controller); + } + }else{ + // 生成默认的控制器 + self::buildController($module); + } + // 生成模块的模型 + if(defined('BUILD_MODEL_LIST')){ + // 自动生成的控制器列表(注意大小写) + $list = explode(',',BUILD_MODEL_LIST); + foreach($list as $model){ + self::buildModel($module,$model); + } + } + }else{ + header('Content-Type:text/html; charset=utf-8'); + exit('应用目录['.APP_PATH.']不可写,目录无法自动生成!
请手动生成项目目录~'); + } + } + + // 检查缓存目录(Runtime) 如果不存在则自动创建 + static public function buildRuntime() { + if(!is_dir(RUNTIME_PATH)) { + mkdir(RUNTIME_PATH); + }elseif(!is_writeable(RUNTIME_PATH)) { + header('Content-Type:text/html; charset=utf-8'); + exit('目录 [ '.RUNTIME_PATH.' ] 不可写!'); + } + mkdir(CACHE_PATH); // 模板缓存目录 + if(!is_dir(LOG_PATH)) mkdir(LOG_PATH); // 日志目录 + if(!is_dir(TEMP_PATH)) mkdir(TEMP_PATH); // 数据缓存目录 + if(!is_dir(DATA_PATH)) mkdir(DATA_PATH); // 数据文件目录 + return true; + } + + // 创建控制器类 + static public function buildController($module,$controller='Index') { + $file = APP_PATH.$module.'/Controller/'.$controller.'Controller'.EXT; + if(!is_file($file)){ + $content = str_replace(array('[MODULE]','[CONTROLLER]'),array($module,$controller),self::$controller); + if(!C('APP_USE_NAMESPACE')){ + $content = preg_replace('/namespace\s(.*?);/','',$content,1); + } + $dir = dirname($file); + if(!is_dir($dir)){ + mkdir($dir, 0755, true); + } + file_put_contents($file,$content); + } + } + + // 创建模型类 + static public function buildModel($module,$model) { + $file = APP_PATH.$module.'/Model/'.$model.'Model'.EXT; + if(!is_file($file)){ + $content = str_replace(array('[MODULE]','[MODEL]'),array($module,$model),self::$model); + if(!C('APP_USE_NAMESPACE')){ + $content = preg_replace('/namespace\s(.*?);/','',$content,1); + } + $dir = dirname($file); + if(!is_dir($dir)){ + mkdir($dir, 0755, true); + } + file_put_contents($file,$content); + } + } + + // 生成目录安全文件 + static public function buildDirSecure($dirs=array()) { + // 目录安全写入(默认开启) + defined('BUILD_DIR_SECURE') or define('BUILD_DIR_SECURE', true); + if(BUILD_DIR_SECURE) { + defined('DIR_SECURE_FILENAME') or define('DIR_SECURE_FILENAME', 'index.html'); + defined('DIR_SECURE_CONTENT') or define('DIR_SECURE_CONTENT', ' '); + // 自动写入目录安全文件 + $content = DIR_SECURE_CONTENT; + $files = explode(',', DIR_SECURE_FILENAME); + foreach ($files as $filename){ + foreach ($dirs as $dir) + file_put_contents($dir.$filename,$content); + } + } + } +} diff --git a/ThinkPHP/Library/Think/Cache.class.php b/ThinkPHP/Library/Think/Cache.class.php new file mode 100644 index 0000000..b098261 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache.class.php @@ -0,0 +1,127 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * 缓存管理类 + */ +class Cache { + + /** + * 操作句柄 + * @var string + * @access protected + */ + protected $handler ; + + /** + * 缓存连接参数 + * @var integer + * @access protected + */ + protected $options = array(); + + /** + * 连接缓存 + * @access public + * @param string $type 缓存类型 + * @param array $options 配置数组 + * @return object + */ + public function connect($type='',$options=array()) { + if(empty($type)) $type = C('DATA_CACHE_TYPE'); + $class = strpos($type,'\\')? $type : 'Think\\Cache\\Driver\\'.ucwords(strtolower($type)); + if(class_exists($class)) + $cache = new $class($options); + else + E(L('_CACHE_TYPE_INVALID_').':'.$type); + return $cache; + } + + /** + * 取得缓存类实例 + * @static + * @access public + * @return mixed + */ + static function getInstance($type='',$options=array()) { + static $_instance = array(); + $guid = $type.to_guid_string($options); + if(!isset($_instance[$guid])){ + $obj = new Cache(); + $_instance[$guid] = $obj->connect($type,$options); + } + return $_instance[$guid]; + } + + public function __get($name) { + return $this->get($name); + } + + public function __set($name,$value) { + return $this->set($name,$value); + } + + public function __unset($name) { + $this->rm($name); + } + public function setOptions($name,$value) { + $this->options[$name] = $value; + } + + public function getOptions($name) { + return $this->options[$name]; + } + + /** + * 队列缓存 + * @access protected + * @param string $key 队列名 + * @return mixed + */ + // + protected function queue($key) { + static $_handler = array( + 'file' => array('F','F'), + 'xcache'=> array('xcache_get','xcache_set'), + 'apc' => array('apc_fetch','apc_store'), + ); + $queue = isset($this->options['queue'])?$this->options['queue']:'file'; + $fun = isset($_handler[$queue])?$_handler[$queue]:$_handler['file']; + $queue_name = isset($this->options['queue_name'])?$this->options['queue_name']:'think_queue'; + $value = $fun[0]($queue_name); + if(!$value) { + $value = array(); + } + // 进列 + if(false===array_search($key, $value)) array_push($value,$key); + if(count($value) > $this->options['length']) { + // 出列 + $key = array_shift($value); + // 删除缓存 + $this->rm($key); + if(APP_DEBUG){ + //调试模式下,记录出列次数 + N($queue_name.'_out_times',1); + } + } + return $fun[1]($queue_name,$value); + } + + public function __call($method,$args){ + //调用缓存类型自己的方法 + if(method_exists($this->handler, $method)){ + return call_user_func_array(array($this->handler,$method), $args); + }else{ + E(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_')); + return; + } + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Cache/Driver/Apachenote.class.php b/ThinkPHP/Library/Think/Cache/Driver/Apachenote.class.php new file mode 100644 index 0000000..4ad3fd7 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Apachenote.class.php @@ -0,0 +1,124 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * Apachenote缓存驱动 + */ +class Apachenote extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if(!empty($options)) { + $this->options = $options; + } + if(empty($options)) { + $options = array ( + 'host' => '127.0.0.1', + 'port' => 1042, + 'timeout' => 10, + ); + } + $this->options = $options; + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + $this->handler = null; + $this->open(); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $this->open(); + $name = $this->options['prefix'].$name; + $s = 'F' . pack('N', strlen($name)) . $name; + fwrite($this->handler, $s); + + for ($data = ''; !feof($this->handler);) { + $data .= fread($this->handler, 4096); + } + N('cache_read',1); + $this->close(); + return $data === '' ? '' : unserialize($data); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @return boolean + */ + public function set($name, $value) { + N('cache_write',1); + $this->open(); + $value = serialize($value); + $name = $this->options['prefix'].$name; + $s = 'S' . pack('NN', strlen($name), strlen($value)) . $name . $value; + + fwrite($this->handler, $s); + $ret = fgets($this->handler); + $this->close(); + if($ret === "OK\n") { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + $this->open(); + $name = $this->options['prefix'].$name; + $s = 'D' . pack('N', strlen($name)) . $name; + fwrite($this->handler, $s); + $ret = fgets($this->handler); + $this->close(); + return $ret === "OK\n"; + } + + /** + * 关闭缓存 + * @access private + */ + private function close() { + fclose($this->handler); + $this->handler = false; + } + + /** + * 打开缓存 + * @access private + */ + private function open() { + if (!is_resource($this->handler)) { + $this->handler = fsockopen($this->options['host'], $this->options['port'], $_, $_, $this->options['timeout']); + } + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Cache/Driver/Apc.class.php b/ThinkPHP/Library/Think/Cache/Driver/Apc.class.php new file mode 100644 index 0000000..4ccb941 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Apc.class.php @@ -0,0 +1,86 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * Apc缓存驱动 + */ +class Apc extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if(!function_exists('apc_cache_info')) { + E(L('_NOT_SUPPORT_').':Apc'); + } + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + N('cache_read',1); + return apc_fetch($this->options['prefix'].$name); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) { + N('cache_write',1); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + if($result = apc_store($name, $value, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + } + return $result; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + return apc_delete($this->options['prefix'].$name); + } + + /** + * 清除缓存 + * @access public + * @return boolean + */ + public function clear() { + return apc_clear_cache(); + } + +} diff --git a/ThinkPHP/Library/Think/Cache/Driver/Db.class.php b/ThinkPHP/Library/Think/Cache/Driver/Db.class.php new file mode 100644 index 0000000..fc2f210 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Db.class.php @@ -0,0 +1,138 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * 数据库方式缓存驱动 + * CREATE TABLE think_cache ( + * cachekey varchar(255) NOT NULL, + * expire int(11) NOT NULL, + * data blob, + * datacrc int(32), + * UNIQUE KEY `cachekey` (`cachekey`) + * ); + */ +class Db extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if(empty($options)) { + $options = array ( + 'table' => C('DATA_CACHE_TABLE'), + ); + } + $this->options = $options; + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + $this->handler = \Think\Db::getInstance(); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $name = $this->options['prefix'].addslashes($name); + N('cache_read',1); + $result = $this->handler->query('SELECT `data`,`datacrc` FROM `'.$this->options['table'].'` WHERE `cachekey`=\''.$name.'\' AND (`expire` =0 OR `expire`>'.time().') LIMIT 0,1'); + if(false !== $result ) { + $result = $result[0]; + if(C('DATA_CACHE_CHECK')) {//开启数据校验 + if($result['datacrc'] != md5($result['data'])) {//校验错误 + return false; + } + } + $content = $result['data']; + if(C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + $content = unserialize($content); + return $content; + } + else { + return false; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value,$expire=null) { + $data = serialize($value); + $name = $this->options['prefix'].addslashes($name); + N('cache_write',1); + if( C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data,3); + } + if(C('DATA_CACHE_CHECK')) {//开启数据校验 + $crc = md5($data); + }else { + $crc = ''; + } + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $expire = ($expire==0)?0: (time()+$expire) ;//缓存有效期为0表示永久缓存 + $result = $this->handler->query('select `cachekey` from `'.$this->options['table'].'` where `cachekey`=\''.$name.'\' limit 0,1'); + if(!empty($result) ) { + //更新记录 + $result = $this->handler->execute('UPDATE '.$this->options['table'].' SET data=\''.$data.'\' ,datacrc=\''.$crc.'\',expire='.$expire.' WHERE `cachekey`=\''.$name.'\''); + }else { + //新增记录 + $result = $this->handler->execute('INSERT INTO '.$this->options['table'].' (`cachekey`,`data`,`datacrc`,`expire`) VALUES (\''.$name.'\',\''.$data.'\',\''.$crc.'\','.$expire.')'); + } + if($result) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + }else { + return false; + } + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + $name = $this->options['prefix'].addslashes($name); + return $this->handler->execute('DELETE FROM `'.$this->options['table'].'` WHERE `cachekey`=\''.$name.'\''); + } + + /** + * 清除缓存 + * @access public + * @return boolean + */ + public function clear() { + return $this->handler->execute('TRUNCATE TABLE `'.$this->options['table'].'`'); + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Cache/Driver/Eaccelerator.class.php b/ThinkPHP/Library/Think/Cache/Driver/Eaccelerator.class.php new file mode 100644 index 0000000..751ef24 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Eaccelerator.class.php @@ -0,0 +1,77 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * Eaccelerator缓存驱动 + */ +class Eaccelerator extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + N('cache_read',1); + return eaccelerator_get($this->options['prefix'].$name); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) { + N('cache_write',1); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + eaccelerator_lock($name); + if(eaccelerator_put($name, $value, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + return eaccelerator_rm($this->options['prefix'].$name); + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Cache/Driver/File.class.php b/ThinkPHP/Library/Think/Cache/Driver/File.class.php new file mode 100644 index 0000000..c5c64ef --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/File.class.php @@ -0,0 +1,181 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * 文件类型缓存类 + */ +class File extends Cache { + + /** + * 架构函数 + * @access public + */ + public function __construct($options=array()) { + if(!empty($options)) { + $this->options = $options; + } + $this->options['temp'] = !empty($options['temp'])? $options['temp'] : C('DATA_CACHE_PATH'); + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + if(substr($this->options['temp'], -1) != '/') $this->options['temp'] .= '/'; + $this->init(); + } + + /** + * 初始化检查 + * @access private + * @return boolean + */ + private function init() { + // 创建应用缓存目录 + if (!is_dir($this->options['temp'])) { + mkdir($this->options['temp']); + } + } + + /** + * 取得变量的存储文件名 + * @access private + * @param string $name 缓存变量名 + * @return string + */ + private function filename($name) { + $name = md5(C('DATA_CACHE_KEY').$name); + if(C('DATA_CACHE_SUBDIR')) { + // 使用子目录 + $dir =''; + for($i=0;$ioptions['temp'].$dir)) { + mkdir($this->options['temp'].$dir,0755,true); + } + $filename = $dir.$this->options['prefix'].$name.'.php'; + }else{ + $filename = $this->options['prefix'].$name.'.php'; + } + return $this->options['temp'].$filename; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $filename = $this->filename($name); + if (!is_file($filename)) { + return false; + } + N('cache_read',1); + $content = file_get_contents($filename); + if( false !== $content) { + $expire = (int)substr($content,8, 12); + if($expire != 0 && time() > filemtime($filename) + $expire) { + //缓存过期删除缓存文件 + unlink($filename); + return false; + } + if(C('DATA_CACHE_CHECK')) {//开启数据校验 + $check = substr($content,20, 32); + $content = substr($content,52, -3); + if($check != md5($content)) {//校验错误 + return false; + } + }else { + $content = substr($content,20, -3); + } + if(C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + $content = unserialize($content); + return $content; + } + else { + return false; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return boolean + */ + public function set($name,$value,$expire=null) { + N('cache_write',1); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $filename = $this->filename($name); + $data = serialize($value); + if( C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data,3); + } + if(C('DATA_CACHE_CHECK')) {//开启数据校验 + $check = md5($data); + }else { + $check = ''; + } + $data = ""; + $result = file_put_contents($filename,$data); + if($result) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + clearstatcache(); + return true; + }else { + return false; + } + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + return unlink($this->filename($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function clear() { + $path = $this->options['temp']; + $files = scandir($path); + if($files){ + foreach($files as $file){ + if ($file != '.' && $file != '..' && is_dir($path.$file) ){ + array_map( 'unlink', glob( $path.$file.'/*.*' ) ); + }elseif(is_file($path.$file)){ + unlink( $path . $file ); + } + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Cache/Driver/Memcache.class.php b/ThinkPHP/Library/Think/Cache/Driver/Memcache.class.php new file mode 100644 index 0000000..ff53ebe --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Memcache.class.php @@ -0,0 +1,103 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * Memcache缓存驱动 + */ +class Memcache extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + function __construct($options=array()) { + if ( !extension_loaded('memcache') ) { + E(L('_NOT_SUPPORT_').':memcache'); + } + + $options = array_merge(array ( + 'host' => C('MEMCACHE_HOST') ? : '127.0.0.1', + 'port' => C('MEMCACHE_PORT') ? : 11211, + 'timeout' => C('DATA_CACHE_TIMEOUT') ? : false, + 'persistent' => false, + ),$options); + + $this->options = $options; + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + $func = $options['persistent'] ? 'pconnect' : 'connect'; + $this->handler = new \Memcache; + $options['timeout'] === false ? + $this->handler->$func($options['host'], $options['port']) : + $this->handler->$func($options['host'], $options['port'], $options['timeout']); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + N('cache_read',1); + return $this->handler->get($this->options['prefix'].$name); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) { + N('cache_write',1); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + if($this->handler->set($name, $value, 0, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name, $ttl = false) { + $name = $this->options['prefix'].$name; + return $ttl === false ? + $this->handler->delete($name) : + $this->handler->delete($name, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return boolean + */ + public function clear() { + return $this->handler->flush(); + } +} diff --git a/ThinkPHP/Library/Think/Cache/Driver/Memcached.class.php b/ThinkPHP/Library/Think/Cache/Driver/Memcached.class.php new file mode 100644 index 0000000..929bdca --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Memcached.class.php @@ -0,0 +1,102 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Cache\Driver; + +use Memcached as MemcachedResource; +use Think\Cache; + +/** + * Memcached缓存驱动 + */ +class Memcached extends Cache { + + /** + * + * @param array $options + */ + public function __construct($options = array()) { + if ( !extension_loaded('memcached') ) { + E(L('_NOT_SUPPORT_').':memcached'); + } + + $options = array_merge(array( + 'servers' => C('MEMCACHED_SERVER') ? : null, + 'lib_options' => C('MEMCACHED_LIB') ? : null + ), $options); + + $this->options = $options; + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + + $this->handler = new MemcachedResource; + $options['servers'] && $this->handler->addServers($options['servers']); + $options['lib_options'] && $this->handler->setOptions($options['lib_options']); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + N('cache_read',1); + return $this->handler->get($this->options['prefix'].$name); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) { + N('cache_write',1); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + if($this->handler->set($name, $value, time() + $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name, $ttl = false) { + $name = $this->options['prefix'].$name; + return $ttl === false ? + $this->handler->delete($name) : + $this->handler->delete($name, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return boolean + */ + public function clear() { + return $this->handler->flush(); + } +} diff --git a/ThinkPHP/Library/Think/Cache/Driver/Memcachesae.class.php b/ThinkPHP/Library/Think/Cache/Driver/Memcachesae.class.php new file mode 100644 index 0000000..a1d1c56 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Memcachesae.class.php @@ -0,0 +1,144 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; + +defined('THINK_PATH') or exit(); +/** + * Memcache缓存驱动 + * @category Extend + * @package Extend + * @subpackage Driver.Cache + * @author liu21st + */ +class Memcachesae extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + function __construct($options=array()) { + $options = array_merge(array ( + 'host' => C('MEMCACHE_HOST') ? : '127.0.0.1', + 'port' => C('MEMCACHE_PORT') ? : 11211, + 'timeout' => C('DATA_CACHE_TIMEOUT') ? : false, + 'persistent' => false, + ),$options); + + $this->options = $options; + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + $this->handler = memcache_init();//[sae] 下实例化 + //[sae] 下不用链接 + $this->connected=true; + } + + /** + * 是否连接 + * @access private + * @return boolean + */ + private function isConnected() { + return $this->connected; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + N('cache_read',1); + return $this->handler->get($_SERVER['HTTP_APPVERSION'].'/'.$this->options['prefix'].$name); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) { + N('cache_write',1); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + if($this->handler->set($_SERVER['HTTP_APPVERSION'].'/'.$name, $value, 0, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name, $ttl = false) { + $name = $_SERVER['HTTP_APPVERSION'].'/'.$this->options['prefix'].$name; + return $ttl === false ? + $this->handler->delete($name) : + $this->handler->delete($name, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return boolean + */ + public function clear() { + return $this->handler->flush(); + } + + /** + * 队列缓存 + * @access protected + * @param string $key 队列名 + * @return mixed + */ + //[sae] 下重写queque队列缓存方法 + protected function queue($key) { + $queue_name=isset($this->options['queue_name'])?$this->options['queue_name']:'think_queue'; + $value = F($queue_name); + if(!$value) { + $value = array(); + } + // 进列 + if(false===array_search($key, $value)) array_push($value,$key); + if(count($value) > $this->options['length']) { + // 出列 + $key = array_shift($value); + // 删除缓存 + $this->rm($key); + if (APP_DEBUG) { + //调试模式下记录出队次数 + $counter = Think::instance('SaeCounter'); + if ($counter->exists($queue_name.'_out_times')) + $counter->incr($queue_name.'_out_times'); + else + $counter->create($queue_name.'_out_times', 1); + } + } + return F($queue_name,$value); + } + +} diff --git a/ThinkPHP/Library/Think/Cache/Driver/Redis.class.php b/ThinkPHP/Library/Think/Cache/Driver/Redis.class.php new file mode 100644 index 0000000..132cefb --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Redis.class.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); + +/** + * Redis缓存驱动 + * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis + */ +class Redis extends Cache { + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !extension_loaded('redis') ) { + E(L('_NOT_SUPPORT_').':redis'); + } + $options = array_merge(array ( + 'host' => C('REDIS_HOST') ? : '127.0.0.1', + 'port' => C('REDIS_PORT') ? : 6379, + 'timeout' => C('DATA_CACHE_TIMEOUT') ? : false, + 'persistent' => false, + ),$options); + + $this->options = $options; + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + $func = $options['persistent'] ? 'pconnect' : 'connect'; + $this->handler = new \Redis; + $options['timeout'] === false ? + $this->handler->$func($options['host'], $options['port']) : + $this->handler->$func($options['host'], $options['port'], $options['timeout']); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + N('cache_read',1); + $value = $this->handler->get($this->options['prefix'].$name); + $jsonData = json_decode( $value, true ); + return ($jsonData === NULL) ? $value : $jsonData; //检测是否为JSON数据 true 返回JSON解析数组, false返回源数据 + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) { + N('cache_write',1); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + //对数组/对象数据进行缓存处理,保证数据完整性 + $value = (is_object($value) || is_array($value)) ? json_encode($value) : $value; + if(is_int($expire) && $expire) { + $result = $this->handler->setex($name, $expire, $value); + }else{ + $result = $this->handler->set($name, $value); + } + if($result && $this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return $result; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + return $this->handler->delete($this->options['prefix'].$name); + } + + /** + * 清除缓存 + * @access public + * @return boolean + */ + public function clear() { + return $this->handler->flushDB(); + } + +} diff --git a/ThinkPHP/Library/Think/Cache/Driver/Shmop.class.php b/ThinkPHP/Library/Think/Cache/Driver/Shmop.class.php new file mode 100644 index 0000000..60927c3 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Shmop.class.php @@ -0,0 +1,186 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * Shmop缓存驱动 + */ +class Shmop extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !extension_loaded('shmop') ) { + E(L('_NOT_SUPPORT_').':shmop'); + } + if(!empty($options)){ + $options = array( + 'size' => C('SHARE_MEM_SIZE'), + 'temp' => TEMP_PATH, + 'project' => 's', + 'length' => 0, + ); + } + $this->options = $options; + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + $this->handler = $this->_ftok($this->options['project']); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name = false) { + N('cache_read',1); + $id = shmop_open($this->handler, 'c', 0600, 0); + if ($id !== false) { + $ret = unserialize(shmop_read($id, 0, shmop_size($id))); + shmop_close($id); + + if ($name === false) { + return $ret; + } + $name = $this->options['prefix'].$name; + if(isset($ret[$name])) { + $content = $ret[$name]; + if(C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + return $content; + }else { + return null; + } + }else { + return false; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @return boolean + */ + public function set($name, $value) { + N('cache_write',1); + $lh = $this->_lock(); + $val = $this->get(); + if (!is_array($val)) $val = array(); + if( C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) { + //数据压缩 + $value = gzcompress($value,3); + } + $name = $this->options['prefix'].$name; + $val[$name] = $value; + $val = serialize($val); + if($this->_write($val, $lh)) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + $lh = $this->_lock(); + $val = $this->get(); + if (!is_array($val)) $val = array(); + $name = $this->options['prefix'].$name; + unset($val[$name]); + $val = serialize($val); + return $this->_write($val, $lh); + } + + /** + * 生成IPC key + * @access private + * @param string $project 项目标识名 + * @return integer + */ + private function _ftok($project) { + if (function_exists('ftok')) return ftok(__FILE__, $project); + if(strtoupper(PHP_OS) == 'WINNT'){ + $s = stat(__FILE__); + return sprintf("%u", (($s['ino'] & 0xffff) | (($s['dev'] & 0xff) << 16) | + (($project & 0xff) << 24))); + }else { + $filename = __FILE__ . (string) $project; + for($key = array(); sizeof($key) < strlen($filename); $key[] = ord(substr($filename, sizeof($key), 1))); + return dechex(array_sum($key)); + } + } + + /** + * 写入操作 + * @access private + * @param string $name 缓存变量名 + * @return integer|boolean + */ + private function _write(&$val, &$lh) { + $id = shmop_open($this->handler, 'c', 0600, $this->options['size']); + if ($id) { + $ret = shmop_write($id, $val, 0) == strlen($val); + shmop_close($id); + $this->_unlock($lh); + return $ret; + } + $this->_unlock($lh); + return false; + } + + /** + * 共享锁定 + * @access private + * @param string $name 缓存变量名 + * @return boolean + */ + private function _lock() { + if (function_exists('sem_get')) { + $fp = sem_get($this->handler, 1, 0600, 1); + sem_acquire ($fp); + } else { + $fp = fopen($this->options['temp'].$this->options['prefix'].md5($this->handler), 'w'); + flock($fp, LOCK_EX); + } + return $fp; + } + + /** + * 解除共享锁定 + * @access private + * @param string $name 缓存变量名 + * @return boolean + */ + private function _unlock(&$fp) { + if (function_exists('sem_release')) { + sem_release($fp); + } else { + fclose($fp); + } + } +} diff --git a/ThinkPHP/Library/Think/Cache/Driver/Sqlite.class.php b/ThinkPHP/Library/Think/Cache/Driver/Sqlite.class.php new file mode 100644 index 0000000..33bd222 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Sqlite.class.php @@ -0,0 +1,119 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * Sqlite缓存驱动 + */ +class Sqlite extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !extension_loaded('sqlite') ) { + E(L('_NOT_SUPPORT_').':sqlite'); + } + if(empty($options)) { + $options = array ( + 'db' => ':memory:', + 'table' => 'sharedmemory', + ); + } + $this->options = $options; + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + $this->handler = $func($this->options['db']); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + N('cache_read',1); + $name = $this->options['prefix'].sqlite_escape_string($name); + $sql = 'SELECT value FROM '.$this->options['table'].' WHERE var=\''.$name.'\' AND (expire=0 OR expire >'.time().') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + if (sqlite_num_rows($result)) { + $content = sqlite_fetch_single($result); + if(C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + return unserialize($content); + } + return false; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value,$expire=null) { + N('cache_write',1); + $name = $this->options['prefix'].sqlite_escape_string($name); + $value = sqlite_escape_string(serialize($value)); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $expire = ($expire==0)?0: (time()+$expire) ;//缓存有效期为0表示永久缓存 + if( C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) { + //数据压缩 + $value = gzcompress($value,3); + } + $sql = 'REPLACE INTO '.$this->options['table'].' (var, value,expire) VALUES (\''.$name.'\', \''.$value.'\', \''.$expire.'\')'; + if(sqlite_query($this->handler, $sql)){ + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + $name = $this->options['prefix'].sqlite_escape_string($name); + $sql = 'DELETE FROM '.$this->options['table'].' WHERE var=\''.$name.'\''; + sqlite_query($this->handler, $sql); + return true; + } + + /** + * 清除缓存 + * @access public + * @return boolean + */ + public function clear() { + $sql = 'DELETE FROM '.$this->options['table']; + sqlite_query($this->handler, $sql); + return ; + } +} diff --git a/ThinkPHP/Library/Think/Cache/Driver/Wincache.class.php b/ThinkPHP/Library/Think/Cache/Driver/Wincache.class.php new file mode 100644 index 0000000..9d118e9 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Wincache.class.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * Wincache缓存驱动 + */ +class Wincache extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !function_exists('wincache_ucache_info') ) { + E(L('_NOT_SUPPORT_').':WinCache'); + } + $this->options['expire'] = isset($options['expire'])? $options['expire'] : C('DATA_CACHE_TIME'); + $this->options['prefix'] = isset($options['prefix'])? $options['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])? $options['length'] : 0; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + N('cache_read',1); + $name = $this->options['prefix'].$name; + return wincache_ucache_exists($name)? wincache_ucache_get($name) : false; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value,$expire=null) { + N('cache_write',1); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + if(wincache_ucache_set($name, $value, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + return wincache_ucache_delete($this->options['prefix'].$name); + } + + /** + * 清除缓存 + * @access public + * @return boolean + */ + public function clear() { + return wincache_ucache_clear(); + } + +} diff --git a/ThinkPHP/Library/Think/Cache/Driver/Xcache.class.php b/ThinkPHP/Library/Think/Cache/Driver/Xcache.class.php new file mode 100644 index 0000000..ccb1fdd --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Xcache.class.php @@ -0,0 +1,90 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +use Think\Cache; +defined('THINK_PATH') or exit(); +/** + * Xcache缓存驱动 + */ +class Xcache extends Cache { + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !function_exists('xcache_info') ) { + E(L('_NOT_SUPPORT_').':Xcache'); + } + $this->options['expire'] = isset($options['expire'])?$options['expire']:C('DATA_CACHE_TIME'); + $this->options['prefix'] = isset($options['prefix'])?$options['prefix']:C('DATA_CACHE_PREFIX'); + $this->options['length'] = isset($options['length'])?$options['length']:0; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + N('cache_read',1); + $name = $this->options['prefix'].$name; + if (xcache_isset($name)) { + return xcache_get($name); + } + return false; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value,$expire=null) { + N('cache_write',1); + if(is_null($expire)) { + $expire = $this->options['expire'] ; + } + $name = $this->options['prefix'].$name; + if(xcache_set($name, $value, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) { + return xcache_unset($this->options['prefix'].$name); + } + + /** + * 清除缓存 + * @access public + * @return boolean + */ + public function clear() { + return xcache_clear_cache(1, -1); + } +} diff --git a/ThinkPHP/Library/Think/Controller.class.php b/ThinkPHP/Library/Think/Controller.class.php new file mode 100644 index 0000000..ce0f2a1 --- /dev/null +++ b/ThinkPHP/Library/Think/Controller.class.php @@ -0,0 +1,307 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP 控制器基类 抽象类 + */ +abstract class Controller { + + /** + * 视图实例对象 + * @var view + * @access protected + */ + protected $view = null; + + /** + * 控制器参数 + * @var config + * @access protected + */ + protected $config = array(); + + /** + * 架构函数 取得模板对象实例 + * @access public + */ + public function __construct() { + Hook::listen('action_begin',$this->config); + //实例化视图类 + $this->view = Think::instance('Think\View'); + //控制器初始化 + if(method_exists($this,'_initialize')) + $this->_initialize(); + } + + /** + * 模板显示 调用内置的模板引擎显示方法, + * @access protected + * @param string $templateFile 指定要调用的模板文件 + * 默认为空 由系统自动定位模板文件 + * @param string $charset 输出编码 + * @param string $contentType 输出类型 + * @param string $content 输出内容 + * @param string $prefix 模板缓存前缀 + * @return void + */ + protected function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') { + $this->view->display($templateFile,$charset,$contentType,$content,$prefix); + } + + /** + * 输出内容文本可以包括Html 并支持内容解析 + * @access protected + * @param string $content 输出内容 + * @param string $charset 模板输出字符集 + * @param string $contentType 输出类型 + * @param string $prefix 模板缓存前缀 + * @return mixed + */ + protected function show($content,$charset='',$contentType='',$prefix='') { + $this->view->display('',$charset,$contentType,$content,$prefix); + } + + /** + * 获取输出页面内容 + * 调用内置的模板引擎fetch方法, + * @access protected + * @param string $templateFile 指定要调用的模板文件 + * 默认为空 由系统自动定位模板文件 + * @param string $content 模板输出内容 + * @param string $prefix 模板缓存前缀* + * @return string + */ + protected function fetch($templateFile='',$content='',$prefix='') { + return $this->view->fetch($templateFile,$content,$prefix); + } + + /** + * 创建静态页面 + * @access protected + * @htmlfile 生成的静态文件名称 + * @htmlpath 生成的静态文件路径 + * @param string $templateFile 指定要调用的模板文件 + * 默认为空 由系统自动定位模板文件 + * @return string + */ + protected function buildHtml($htmlfile='',$htmlpath='',$templateFile='') { + $content = $this->fetch($templateFile); + $htmlpath = !empty($htmlpath)?$htmlpath:HTML_PATH; + $htmlfile = $htmlpath.$htmlfile.C('HTML_FILE_SUFFIX'); + Storage::put($htmlfile,$content,'html'); + return $content; + } + + /** + * 模板主题设置 + * @access protected + * @param string $theme 模版主题 + * @return Action + */ + protected function theme($theme){ + $this->view->theme($theme); + return $this; + } + + /** + * 模板变量赋值 + * @access protected + * @param mixed $name 要显示的模板变量 + * @param mixed $value 变量的值 + * @return Action + */ + protected function assign($name,$value='') { + $this->view->assign($name,$value); + return $this; + } + + public function __set($name,$value) { + $this->assign($name,$value); + } + + /** + * 取得模板显示变量的值 + * @access protected + * @param string $name 模板显示变量 + * @return mixed + */ + public function get($name='') { + return $this->view->get($name); + } + + public function __get($name) { + return $this->get($name); + } + + /** + * 检测模板变量的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) { + return $this->get($name); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method,$args) { + if( 0 === strcasecmp($method,ACTION_NAME.C('ACTION_SUFFIX'))) { + if(method_exists($this,'_empty')) { + // 如果定义了_empty操作 则调用 + $this->_empty($method,$args); + }elseif(file_exists_case($this->view->parseTemplate())){ + // 检查是否存在默认模版 如果有直接输出模版 + $this->display(); + }else{ + E(L('_ERROR_ACTION_').':'.ACTION_NAME); + } + }else{ + E(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_')); + return; + } + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param string $message 错误信息 + * @param string $jumpUrl 页面跳转地址 + * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间 + * @return void + */ + protected function error($message='',$jumpUrl='',$ajax=false) { + $this->dispatchJump($message,0,$jumpUrl,$ajax); + } + + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param string $message 提示信息 + * @param string $jumpUrl 页面跳转地址 + * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间 + * @return void + */ + protected function success($message='',$jumpUrl='',$ajax=false) { + $this->dispatchJump($message,1,$jumpUrl,$ajax); + } + + /** + * Ajax方式返回数据到客户端 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type AJAX返回数据格式 + * @param int $json_option 传递给json_encode的option参数 + * @return void + */ + protected function ajaxReturn($data,$type='',$json_option=0) { + if(empty($type)) $type = C('DEFAULT_AJAX_RETURN'); + switch (strtoupper($type)){ + case 'JSON' : + // 返回JSON数据格式到客户端 包含状态信息 + header('Content-Type:application/json; charset=utf-8'); + exit(json_encode($data,$json_option)); + case 'XML' : + // 返回xml格式数据 + header('Content-Type:text/xml; charset=utf-8'); + exit(xml_encode($data)); + case 'JSONP': + // 返回JSON数据格式到客户端 包含状态信息 + header('Content-Type:application/json; charset=utf-8'); + $handler = isset($_GET[C('VAR_JSONP_HANDLER')]) ? $_GET[C('VAR_JSONP_HANDLER')] : C('DEFAULT_JSONP_HANDLER'); + exit($handler.'('.json_encode($data,$json_option).');'); + case 'EVAL' : + // 返回可执行的js脚本 + header('Content-Type:text/html; charset=utf-8'); + exit($data); + default : + // 用于扩展其他返回格式数据 + Hook::listen('ajax_return',$data); + } + } + + /** + * Action跳转(URL重定向) 支持指定模块和延时跳转 + * @access protected + * @param string $url 跳转的URL表达式 + * @param array $params 其它URL参数 + * @param integer $delay 延时跳转的时间 单位为秒 + * @param string $msg 跳转提示信息 + * @return void + */ + protected function redirect($url,$params=array(),$delay=0,$msg='') { + $url = U($url,$params); + redirect($url,$delay,$msg); + } + + /** + * 默认跳转操作 支持错误导向和正确跳转 + * 调用模板显示 默认为public目录下面的success页面 + * 提示页面为可配置 支持模板标签 + * @param string $message 提示信息 + * @param Boolean $status 状态 + * @param string $jumpUrl 页面跳转地址 + * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间 + * @access private + * @return void + */ + private function dispatchJump($message,$status=1,$jumpUrl='',$ajax=false) { + if(true === $ajax || IS_AJAX) {// AJAX提交 + $data = is_array($ajax)?$ajax:array(); + $data['info'] = $message; + $data['status'] = $status; + $data['url'] = $jumpUrl; + $this->ajaxReturn($data); + } + if(is_int($ajax)) $this->assign('waitSecond',$ajax); + if(!empty($jumpUrl)) $this->assign('jumpUrl',$jumpUrl); + // 提示标题 + $this->assign('msgTitle',$status? L('_OPERATION_SUCCESS_') : L('_OPERATION_FAIL_')); + //如果设置了关闭窗口,则提示完毕后自动关闭窗口 + if($this->get('closeWin')) $this->assign('jumpUrl','javascript:window.close();'); + $this->assign('status',$status); // 状态 + //保证输出不受静态缓存影响 + C('HTML_CACHE_ON',false); + if($status) { //发送成功信息 + $this->assign('message',$message);// 提示信息 + // 成功操作后默认停留1秒 + if(!isset($this->waitSecond)) $this->assign('waitSecond','1'); + // 默认操作成功自动返回操作前页面 + if(!isset($this->jumpUrl)) $this->assign("jumpUrl",$_SERVER["HTTP_REFERER"]); + $this->display(C('TMPL_ACTION_SUCCESS')); + }else{ + $this->assign('error',$message);// 提示信息 + //发生错误时候默认停留3秒 + if(!isset($this->waitSecond)) $this->assign('waitSecond','3'); + // 默认发生错误的话自动返回上页 + if(!isset($this->jumpUrl)) $this->assign('jumpUrl',"javascript:history.back(-1);"); + $this->display(C('TMPL_ACTION_ERROR')); + // 中止执行 避免出错后继续执行 + exit ; + } + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() { + // 执行后续操作 + Hook::listen('action_end'); + } +} +// 设置控制器别名 便于升级 +class_alias('Think\Controller','Think\Action'); diff --git a/ThinkPHP/Library/Think/Controller/HproseController.class.php b/ThinkPHP/Library/Think/Controller/HproseController.class.php new file mode 100644 index 0000000..88d3a1c --- /dev/null +++ b/ThinkPHP/Library/Think/Controller/HproseController.class.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- +namespace Think\Controller; +/** + * ThinkPHP Hprose控制器类 + */ +class HproseController { + + protected $allowMethodList = ''; + protected $crossDomain = false; + protected $P3P = false; + protected $get = true; + protected $debug = false; + + /** + * 架构函数 + * @access public + */ + public function __construct() { + //控制器初始化 + if(method_exists($this,'_initialize')) + $this->_initialize(); + //导入类库 + Vendor('Hprose.HproseHttpServer'); + //实例化HproseHttpServer + $server = new \HproseHttpServer(); + if($this->allowMethodList){ + $methods = $this->allowMethodList; + }else{ + $methods = get_class_methods($this); + $methods = array_diff($methods,array('__construct','__call','_initialize')); + } + $server->addMethods($methods,$this); + if(APP_DEBUG || $this->debug ) { + $server->setDebugEnabled(true); + } + // Hprose设置 + $server->setCrossDomainEnabled($this->crossDomain); + $server->setP3PEnabled($this->P3P); + $server->setGetEnabled($this->get); + // 启动server + $server->start(); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method,$args){} +} diff --git a/ThinkPHP/Library/Think/Controller/JsonRpcController.class.php b/ThinkPHP/Library/Think/Controller/JsonRpcController.class.php new file mode 100644 index 0000000..a73ca5c --- /dev/null +++ b/ThinkPHP/Library/Think/Controller/JsonRpcController.class.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- +namespace Think\Controller; +/** + * ThinkPHP JsonRPC控制器类 + */ +class JsonRpcController { + + /** + * 架构函数 + * @access public + */ + public function __construct() { + //控制器初始化 + if(method_exists($this,'_initialize')) + $this->_initialize(); + //导入类库 + Vendor('jsonRPC.jsonRPCServer'); + // 启动server + \jsonRPCServer::handle($this); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method,$args){} +} diff --git a/ThinkPHP/Library/Think/Controller/RestController.class.php b/ThinkPHP/Library/Think/Controller/RestController.class.php new file mode 100644 index 0000000..2a62662 --- /dev/null +++ b/ThinkPHP/Library/Think/Controller/RestController.class.php @@ -0,0 +1,234 @@ + +// +---------------------------------------------------------------------- +namespace Think\Controller; +use Think\Controller; +use Think\App; +/** + * ThinkPHP REST控制器类 + */ +class RestController extends Controller { + // 当前请求类型 + protected $_method = ''; + // 当前请求的资源类型 + protected $_type = ''; + // REST允许的请求类型列表 + protected $allowMethod = array('get','post','put','delete'); + // REST默认请求类型 + protected $defaultMethod = 'get'; + // REST允许请求的资源类型列表 + protected $allowType = array('html','xml','json','rss'); + // 默认的资源类型 + protected $defaultType = 'html'; + // REST允许输出的资源类型列表 + protected $allowOutputType= array( + 'xml' => 'application/xml', + 'json' => 'application/json', + 'html' => 'text/html', + ); + + /** + * 架构函数 + * @access public + */ + public function __construct() { + // 资源类型检测 + if(''==__EXT__) { // 自动检测资源类型 + $this->_type = $this->getAcceptType(); + }elseif(!in_array(__EXT__,$this->allowType)) { + // 资源类型非法 则用默认资源类型访问 + $this->_type = $this->defaultType; + }else{ + $this->_type = __EXT__ ; + } + + // 请求方式检测 + $method = strtolower(REQUEST_METHOD); + if(!in_array($method,$this->allowMethod)) { + // 请求方式非法 则用默认请求方法 + $method = $this->defaultMethod; + } + $this->_method = $method; + + parent::__construct(); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method,$args) { + if( 0 === strcasecmp($method,ACTION_NAME.C('ACTION_SUFFIX'))) { + if(method_exists($this,$method.'_'.$this->_method.'_'.$this->_type)) { // RESTFul方法支持 + $fun = $method.'_'.$this->_method.'_'.$this->_type; + App::invokeAction($this,$fun); + }elseif($this->_method == $this->defaultMethod && method_exists($this,$method.'_'.$this->_type) ){ + $fun = $method.'_'.$this->_type; + App::invokeAction($this,$fun); + }elseif($this->_type == $this->defaultType && method_exists($this,$method.'_'.$this->_method) ){ + $fun = $method.'_'.$this->_method; + App::invokeAction($this,$fun); + }elseif(method_exists($this,'_empty')) { + // 如果定义了_empty操作 则调用 + $this->_empty($method,$args); + }elseif(file_exists_case($this->view->parseTemplate())){ + // 检查是否存在默认模版 如果有直接输出模版 + $this->display(); + }else{ + E(L('_ERROR_ACTION_').':'.ACTION_NAME); + } + } + } + + /** + * 获取当前请求的Accept头信息 + * @return string + */ + protected function getAcceptType(){ + $type = array( + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'png' => 'image/png', + 'jpg' => 'image/jpg,image/jpeg,image/pjpeg', + 'gif' => 'image/gif', + 'csv' => 'text/csv', + 'html' => 'text/html,application/xhtml+xml,*/*' + ); + + foreach($type as $key=>$val){ + $array = explode(',',$val); + foreach($array as $k=>$v){ + if(stristr($_SERVER['HTTP_ACCEPT'], $v)) { + return $key; + } + } + } + return false; + } + + // 发送Http状态信息 + protected function sendHttpStatus($code) { + static $_status = array( + // Informational 1xx + 100 => 'Continue', + 101 => 'Switching Protocols', + // Success 2xx + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + // Redirection 3xx + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Moved Temporarily ', // 1.1 + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + // 306 is deprecated but reserved + 307 => 'Temporary Redirect', + // Client Error 4xx + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + // Server Error 5xx + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 509 => 'Bandwidth Limit Exceeded' + ); + if(isset($_status[$code])) { + header('HTTP/1.1 '.$code.' '.$_status[$code]); + // 确保FastCGI模式下正常 + header('Status:'.$code.' '.$_status[$code]); + } + } + + /** + * 编码数据 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type 返回类型 JSON XML + * @return string + */ + protected function encodeData($data,$type='') { + if(empty($data)) return ''; + if('json' == $type) { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data); + }elseif('xml' == $type){ + // 返回xml格式数据 + $data = xml_encode($data); + }elseif('php'==$type){ + $data = serialize($data); + }// 默认直接输出 + $this->setContentType($type); + //header('Content-Length: ' . strlen($data)); + return $data; + } + + /** + * 设置页面输出的CONTENT_TYPE和编码 + * @access public + * @param string $type content_type 类型对应的扩展名 + * @param string $charset 页面输出编码 + * @return void + */ + public function setContentType($type, $charset=''){ + if(headers_sent()) return; + if(empty($charset)) $charset = C('DEFAULT_CHARSET'); + $type = strtolower($type); + if(isset($this->allowOutputType[$type])) //过滤content_type + header('Content-Type: '.$this->allowOutputType[$type].'; charset='.$charset); + } + + /** + * 输出返回数据 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type 返回类型 JSON XML + * @param integer $code HTTP状态 + * @return void + */ + protected function response($data,$type='',$code=200) { + $this->sendHttpStatus($code); + exit($this->encodeData($data,strtolower($type))); + } +} diff --git a/ThinkPHP/Library/Think/Controller/RpcController.class.php b/ThinkPHP/Library/Think/Controller/RpcController.class.php new file mode 100644 index 0000000..737e737 --- /dev/null +++ b/ThinkPHP/Library/Think/Controller/RpcController.class.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- +namespace Think\Controller; +/** + * ThinkPHP RPC控制器类 + */ +class RpcController { + + protected $allowMethodList = ''; + protected $debug = false; + + /** + * 架构函数 + * @access public + */ + public function __construct() { + //控制器初始化 + if(method_exists($this,'_initialize')) + $this->_initialize(); + //导入类库 + Vendor('phpRPC.phprpc_server'); + //实例化phprpc + $server = new \PHPRPC_Server(); + if($this->allowMethodList){ + $methods = $this->allowMethodList; + }else{ + $methods = get_class_methods($this); + $methods = array_diff($methods,array('__construct','__call','_initialize')); + } + $server->add($methods,$this); + + if(APP_DEBUG || $this->debug ) { + $server->setDebugMode(true); + } + $server->setEnableGZIP(true); + $server->start(); + echo $server->comment(); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method,$args){} +} diff --git a/ThinkPHP/Library/Think/Controller/YarController.class.php b/ThinkPHP/Library/Think/Controller/YarController.class.php new file mode 100644 index 0000000..40245b4 --- /dev/null +++ b/ThinkPHP/Library/Think/Controller/YarController.class.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +namespace Think\Controller; +/** + * ThinkPHP Yar控制器类 + */ +class YarController { + + /** + * 架构函数 + * @access public + */ + public function __construct() { + //控制器初始化 + if(method_exists($this,'_initialize')) + $this->_initialize(); + //判断扩展是否存在 + if(!extension_loaded('yar')) + E(L('_NOT_SUPPORT_').':yar'); + //实例化Yar_Server + $server = new \Yar_Server($this); + // 启动server + $server->handle(); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method,$args){} +} diff --git a/ThinkPHP/Library/Think/Crypt.class.php b/ThinkPHP/Library/Think/Crypt.class.php new file mode 100644 index 0000000..ee7bafa --- /dev/null +++ b/ThinkPHP/Library/Think/Crypt.class.php @@ -0,0 +1,53 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * 加密解密类 + */ +class Crypt { + + private static $handler = ''; + + public static function init($type=''){ + $type = $type?:C('DATA_CRYPT_TYPE'); + $class = strpos($type,'\\')? $type: 'Think\\Crypt\\Driver\\'. ucwords(strtolower($type)); + self::$handler = $class; + } + + /** + * 加密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @param integer $expire 有效期(秒) 0 为永久有效 + * @return string + */ + public static function encrypt($data,$key,$expire=0){ + if(empty(self::$handler)){ + self::init(); + } + $class = self::$handler; + return $class::encrypt($data,$key,$expire); + } + + /** + * 解密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + public static function decrypt($data,$key){ + if(empty(self::$handler)){ + self::init(); + } + $class = self::$handler; + return $class::decrypt($data,$key); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Crypt/Driver/Base64.class.php b/ThinkPHP/Library/Think/Crypt/Driver/Base64.class.php new file mode 100644 index 0000000..44f6014 --- /dev/null +++ b/ThinkPHP/Library/Think/Crypt/Driver/Base64.class.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- +namespace Think\Crypt\Driver; +/** + * Base64 加密实现类 + */ +class Base64 { + + /** + * 加密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @param integer $expire 有效期(秒) + * @return string + */ + public static function encrypt($data,$key,$expire=0) { + $expire = sprintf('%010d', $expire ? $expire + time():0); + $key = md5($key); + $data = base64_encode($expire.$data); + $x=0; + $len = strlen($data); + $l = strlen($key); + for ($i=0;$i< $len;$i++) { + if ($x== $l) $x=0; + $char .=substr($key,$x,1); + $x++; + } + + for ($i=0;$i< $len;$i++) { + $str .=chr(ord(substr($data,$i,1))+(ord(substr($char,$i,1)))%256); + } + return $str; + } + + /** + * 解密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + public static function decrypt($data,$key) { + $key = md5($key); + $x=0; + $len = strlen($data); + $l = strlen($key); + for ($i=0;$i< $len;$i++) { + if ($x== $l) $x=0; + $char .=substr($key,$x,1); + $x++; + } + for ($i=0;$i< $len;$i++) { + if (ord(substr($data,$i,1)) 0 && $expire < time()) { + return ''; + } + $data = substr($data,10); + return $data; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Crypt/Driver/Crypt.class.php b/ThinkPHP/Library/Think/Crypt/Driver/Crypt.class.php new file mode 100644 index 0000000..382425f --- /dev/null +++ b/ThinkPHP/Library/Think/Crypt/Driver/Crypt.class.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- +namespace Think\Crypt\Driver; +/** + * Crypt 加密实现类 + * @category ORG + * @package ORG + * @subpackage Crypt + * @author liu21st + */ +class Crypt { + + /** + * 加密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @param integer $expire 有效期(秒) + * @return string + */ + public static function encrypt($str,$key,$expire=0){ + $expire = sprintf('%010d', $expire ? $expire + time():0); + $r = md5($key); + $c = 0; + $v = ""; + $str = $expire.$str; + $len = strlen($str); + $l = strlen($r); + for ($i=0;$i<$len;$i++){ + if ($c== $l) $c=0; + $v .= substr($r,$c,1) . + (substr($str,$i,1) ^ substr($r,$c,1)); + $c++; + } + return self::ed($v,$key); + } + + /** + * 解密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + public static function decrypt($str,$key) { + $str = self::ed($str,$key); + $v = ""; + $len = strlen($str); + for ($i=0;$i<$len;$i++){ + $md5 = substr($str,$i,1); + $i++; + $v .= (substr($str,$i,1) ^ $md5); + } + $data = $v; + $expire = substr($data,0,10); + if($expire > 0 && $expire < time()) { + return ''; + } + $data = substr($data,10); + return $data; + } + + + static private function ed($str,$key) { + $r = md5($key); + $c = 0; + $v = ''; + $len = strlen($str); + $l = strlen($r); + for ($i=0;$i<$len;$i++) { + if ($c==$l) $c=0; + $v .= substr($str,$i,1) ^ substr($r,$c,1); + $c++; + } + return $v; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Crypt/Driver/Des.class.php b/ThinkPHP/Library/Think/Crypt/Driver/Des.class.php new file mode 100644 index 0000000..db1bad6 --- /dev/null +++ b/ThinkPHP/Library/Think/Crypt/Driver/Des.class.php @@ -0,0 +1,241 @@ + +// +---------------------------------------------------------------------- +namespace Think\Crypt\Driver; +/** + * Des 加密实现类 + * Converted from JavaScript to PHP by Jim Gibbs, June 2004 Paul Tero, July 2001 + * Optimised for performance with large blocks by Michael Hayworth, November 2001 + * http://www.netdealing.com + */ + +class Des { + + /** + * 加密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @param integer $expire 有效期(秒) + * @return string + */ + public static function encrypt($str, $key,$expire=0) { + if ($str == "") { + return ""; + } + $expire = sprintf('%010d', $expire ? $expire + time():0); + $str = $expire.$str; + return self::_des($key,$str,1); + } + + /** + * 解密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + public static function decrypt($str, $key) { + if ($str == "") { + return ""; + } + $data = self::_des($key,$str,0); + $expire = substr($data,0,10); + if($expire > 0 && $expire < time()) { + return ''; + } + $data = substr($data,10); + return $data; + } + + /** + * Des算法 + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + private static function _des($key, $message, $encrypt, $mode=0, $iv=null) { + //declaring this locally speeds things up a bit + $spfunction1 = array (0x1010400,0,0x10000,0x1010404,0x1010004,0x10404,0x4,0x10000,0x400,0x1010400,0x1010404,0x400,0x1000404,0x1010004,0x1000000,0x4,0x404,0x1000400,0x1000400,0x10400,0x10400,0x1010000,0x1010000,0x1000404,0x10004,0x1000004,0x1000004,0x10004,0,0x404,0x10404,0x1000000,0x10000,0x1010404,0x4,0x1010000,0x1010400,0x1000000,0x1000000,0x400,0x1010004,0x10000,0x10400,0x1000004,0x400,0x4,0x1000404,0x10404,0x1010404,0x10004,0x1010000,0x1000404,0x1000004,0x404,0x10404,0x1010400,0x404,0x1000400,0x1000400,0,0x10004,0x10400,0,0x1010004); + $spfunction2 = array (-0x7fef7fe0,-0x7fff8000,0x8000,0x108020,0x100000,0x20,-0x7fefffe0,-0x7fff7fe0,-0x7fffffe0,-0x7fef7fe0,-0x7fef8000,-0x80000000,-0x7fff8000,0x100000,0x20,-0x7fefffe0,0x108000,0x100020,-0x7fff7fe0,0,-0x80000000,0x8000,0x108020,-0x7ff00000,0x100020,-0x7fffffe0,0,0x108000,0x8020,-0x7fef8000,-0x7ff00000,0x8020,0,0x108020,-0x7fefffe0,0x100000,-0x7fff7fe0,-0x7ff00000,-0x7fef8000,0x8000,-0x7ff00000,-0x7fff8000,0x20,-0x7fef7fe0,0x108020,0x20,0x8000,-0x80000000,0x8020,-0x7fef8000,0x100000,-0x7fffffe0,0x100020,-0x7fff7fe0,-0x7fffffe0,0x100020,0x108000,0,-0x7fff8000,0x8020,-0x80000000,-0x7fefffe0,-0x7fef7fe0,0x108000); + $spfunction3 = array (0x208,0x8020200,0,0x8020008,0x8000200,0,0x20208,0x8000200,0x20008,0x8000008,0x8000008,0x20000,0x8020208,0x20008,0x8020000,0x208,0x8000000,0x8,0x8020200,0x200,0x20200,0x8020000,0x8020008,0x20208,0x8000208,0x20200,0x20000,0x8000208,0x8,0x8020208,0x200,0x8000000,0x8020200,0x8000000,0x20008,0x208,0x20000,0x8020200,0x8000200,0,0x200,0x20008,0x8020208,0x8000200,0x8000008,0x200,0,0x8020008,0x8000208,0x20000,0x8000000,0x8020208,0x8,0x20208,0x20200,0x8000008,0x8020000,0x8000208,0x208,0x8020000,0x20208,0x8,0x8020008,0x20200); + $spfunction4 = array (0x802001,0x2081,0x2081,0x80,0x802080,0x800081,0x800001,0x2001,0,0x802000,0x802000,0x802081,0x81,0,0x800080,0x800001,0x1,0x2000,0x800000,0x802001,0x80,0x800000,0x2001,0x2080,0x800081,0x1,0x2080,0x800080,0x2000,0x802080,0x802081,0x81,0x800080,0x800001,0x802000,0x802081,0x81,0,0,0x802000,0x2080,0x800080,0x800081,0x1,0x802001,0x2081,0x2081,0x80,0x802081,0x81,0x1,0x2000,0x800001,0x2001,0x802080,0x800081,0x2001,0x2080,0x800000,0x802001,0x80,0x800000,0x2000,0x802080); + $spfunction5 = array (0x100,0x2080100,0x2080000,0x42000100,0x80000,0x100,0x40000000,0x2080000,0x40080100,0x80000,0x2000100,0x40080100,0x42000100,0x42080000,0x80100,0x40000000,0x2000000,0x40080000,0x40080000,0,0x40000100,0x42080100,0x42080100,0x2000100,0x42080000,0x40000100,0,0x42000000,0x2080100,0x2000000,0x42000000,0x80100,0x80000,0x42000100,0x100,0x2000000,0x40000000,0x2080000,0x42000100,0x40080100,0x2000100,0x40000000,0x42080000,0x2080100,0x40080100,0x100,0x2000000,0x42080000,0x42080100,0x80100,0x42000000,0x42080100,0x2080000,0,0x40080000,0x42000000,0x80100,0x2000100,0x40000100,0x80000,0,0x40080000,0x2080100,0x40000100); + $spfunction6 = array (0x20000010,0x20400000,0x4000,0x20404010,0x20400000,0x10,0x20404010,0x400000,0x20004000,0x404010,0x400000,0x20000010,0x400010,0x20004000,0x20000000,0x4010,0,0x400010,0x20004010,0x4000,0x404000,0x20004010,0x10,0x20400010,0x20400010,0,0x404010,0x20404000,0x4010,0x404000,0x20404000,0x20000000,0x20004000,0x10,0x20400010,0x404000,0x20404010,0x400000,0x4010,0x20000010,0x400000,0x20004000,0x20000000,0x4010,0x20000010,0x20404010,0x404000,0x20400000,0x404010,0x20404000,0,0x20400010,0x10,0x4000,0x20400000,0x404010,0x4000,0x400010,0x20004010,0,0x20404000,0x20000000,0x400010,0x20004010); + $spfunction7 = array (0x200000,0x4200002,0x4000802,0,0x800,0x4000802,0x200802,0x4200800,0x4200802,0x200000,0,0x4000002,0x2,0x4000000,0x4200002,0x802,0x4000800,0x200802,0x200002,0x4000800,0x4000002,0x4200000,0x4200800,0x200002,0x4200000,0x800,0x802,0x4200802,0x200800,0x2,0x4000000,0x200800,0x4000000,0x200800,0x200000,0x4000802,0x4000802,0x4200002,0x4200002,0x2,0x200002,0x4000000,0x4000800,0x200000,0x4200800,0x802,0x200802,0x4200800,0x802,0x4000002,0x4200802,0x4200000,0x200800,0,0x2,0x4200802,0,0x200802,0x4200000,0x800,0x4000002,0x4000800,0x800,0x200002); + $spfunction8 = array (0x10001040,0x1000,0x40000,0x10041040,0x10000000,0x10001040,0x40,0x10000000,0x40040,0x10040000,0x10041040,0x41000,0x10041000,0x41040,0x1000,0x40,0x10040000,0x10000040,0x10001000,0x1040,0x41000,0x40040,0x10040040,0x10041000,0x1040,0,0,0x10040040,0x10000040,0x10001000,0x41040,0x40000,0x41040,0x40000,0x10041000,0x1000,0x40,0x10040040,0x1000,0x41040,0x10001000,0x40,0x10000040,0x10040000,0x10040040,0x10000000,0x40000,0x10001040,0,0x10041040,0x40040,0x10000040,0x10040000,0x10001000,0x10001040,0,0x10041040,0x41000,0x41000,0x1040,0x1040,0x40040,0x10000000,0x10041000); + $masks = array (4294967295,2147483647,1073741823,536870911,268435455,134217727,67108863,33554431,16777215,8388607,4194303,2097151,1048575,524287,262143,131071,65535,32767,16383,8191,4095,2047,1023,511,255,127,63,31,15,7,3,1,0); + + //create the 16 or 48 subkeys we will need + $keys = self::_createKeys ($key); + $m=0; + $len = strlen($message); + $chunk = 0; + //set up the loops for single and triple des + $iterations = ((count($keys) == 32) ? 3 : 9); //single or triple des + if ($iterations == 3) {$looping = (($encrypt) ? array (0, 32, 2) : array (30, -2, -2));} + else {$looping = (($encrypt) ? array (0, 32, 2, 62, 30, -2, 64, 96, 2) : array (94, 62, -2, 32, 64, 2, 30, -2, -2));} + + $message .= (chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0)); //pad the message out with null bytes + //store the result here + $result = ""; + $tempresult = ""; + + if ($mode == 1) { //CBC mode + $cbcleft = (ord($iv{$m++}) << 24) | (ord($iv{$m++}) << 16) | (ord($iv{$m++}) << 8) | ord($iv{$m++}); + $cbcright = (ord($iv{$m++}) << 24) | (ord($iv{$m++}) << 16) | (ord($iv{$m++}) << 8) | ord($iv{$m++}); + $m=0; + } + + //loop through each 64 bit chunk of the message + while ($m < $len) { + $left = (ord($message{$m++}) << 24) | (ord($message{$m++}) << 16) | (ord($message{$m++}) << 8) | ord($message{$m++}); + $right = (ord($message{$m++}) << 24) | (ord($message{$m++}) << 16) | (ord($message{$m++}) << 8) | ord($message{$m++}); + + //for Cipher Block Chaining mode, xor the message with the previous result + if ($mode == 1) {if ($encrypt) {$left ^= $cbcleft; $right ^= $cbcright;} else {$cbcleft2 = $cbcleft; $cbcright2 = $cbcright; $cbcleft = $left; $cbcright = $right;}} + + //first each 64 but chunk of the message must be permuted according to IP + $temp = (($left >> 4 & $masks[4]) ^ $right) & 0x0f0f0f0f; $right ^= $temp; $left ^= ($temp << 4); + $temp = (($left >> 16 & $masks[16]) ^ $right) & 0x0000ffff; $right ^= $temp; $left ^= ($temp << 16); + $temp = (($right >> 2 & $masks[2]) ^ $left) & 0x33333333; $left ^= $temp; $right ^= ($temp << 2); + $temp = (($right >> 8 & $masks[8]) ^ $left) & 0x00ff00ff; $left ^= $temp; $right ^= ($temp << 8); + $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555; $right ^= $temp; $left ^= ($temp << 1); + + $left = (($left << 1) | ($left >> 31 & $masks[31])); + $right = (($right << 1) | ($right >> 31 & $masks[31])); + + //do this either 1 or 3 times for each chunk of the message + for ($j=0; $j<$iterations; $j+=3) { + $endloop = $looping[$j+1]; + $loopinc = $looping[$j+2]; + //now go through and perform the encryption or decryption + for ($i=$looping[$j]; $i!=$endloop; $i+=$loopinc) { //for efficiency + $right1 = $right ^ $keys[$i]; + $right2 = (($right >> 4 & $masks[4]) | ($right << 28)) ^ $keys[$i+1]; + //the result is attained by passing these bytes through the S selection functions + $temp = $left; + $left = $right; + $right = $temp ^ ($spfunction2[($right1 >> 24 & $masks[24]) & 0x3f] | $spfunction4[($right1 >> 16 & $masks[16]) & 0x3f] + | $spfunction6[($right1 >> 8 & $masks[8]) & 0x3f] | $spfunction8[$right1 & 0x3f] + | $spfunction1[($right2 >> 24 & $masks[24]) & 0x3f] | $spfunction3[($right2 >> 16 & $masks[16]) & 0x3f] + | $spfunction5[($right2 >> 8 & $masks[8]) & 0x3f] | $spfunction7[$right2 & 0x3f]); + } + $temp = $left; $left = $right; $right = $temp; //unreverse left and right + } //for either 1 or 3 iterations + + //move then each one bit to the right + $left = (($left >> 1 & $masks[1]) | ($left << 31)); + $right = (($right >> 1 & $masks[1]) | ($right << 31)); + + //now perform IP-1, which is IP in the opposite direction + $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555; $right ^= $temp; $left ^= ($temp << 1); + $temp = (($right >> 8 & $masks[8]) ^ $left) & 0x00ff00ff; $left ^= $temp; $right ^= ($temp << 8); + $temp = (($right >> 2 & $masks[2]) ^ $left) & 0x33333333; $left ^= $temp; $right ^= ($temp << 2); + $temp = (($left >> 16 & $masks[16]) ^ $right) & 0x0000ffff; $right ^= $temp; $left ^= ($temp << 16); + $temp = (($left >> 4 & $masks[4]) ^ $right) & 0x0f0f0f0f; $right ^= $temp; $left ^= ($temp << 4); + + //for Cipher Block Chaining mode, xor the message with the previous result + if ($mode == 1) {if ($encrypt) {$cbcleft = $left; $cbcright = $right;} else {$left ^= $cbcleft2; $right ^= $cbcright2;}} + $tempresult .= (chr($left>>24 & $masks[24]) . chr(($left>>16 & $masks[16]) & 0xff) . chr(($left>>8 & $masks[8]) & 0xff) . chr($left & 0xff) . chr($right>>24 & $masks[24]) . chr(($right>>16 & $masks[16]) & 0xff) . chr(($right>>8 & $masks[8]) & 0xff) . chr($right & 0xff)); + + $chunk += 8; + if ($chunk == 512) {$result .= $tempresult; $tempresult = ""; $chunk = 0;} + } //for every 8 characters, or 64 bits in the message + + //return the result as an array + return ($result . $tempresult); + } //end of des + + /** + * createKeys + * this takes as input a 64 bit key (even though only 56 bits are used) + * as an array of 2 integers, and returns 16 48 bit keys + * @param string $key 加密key + * @return string + */ + private static function _createKeys ($key) { + //declaring this locally speeds things up a bit + $pc2bytes0 = array (0,0x4,0x20000000,0x20000004,0x10000,0x10004,0x20010000,0x20010004,0x200,0x204,0x20000200,0x20000204,0x10200,0x10204,0x20010200,0x20010204); + $pc2bytes1 = array (0,0x1,0x100000,0x100001,0x4000000,0x4000001,0x4100000,0x4100001,0x100,0x101,0x100100,0x100101,0x4000100,0x4000101,0x4100100,0x4100101); + $pc2bytes2 = array (0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808,0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808); + $pc2bytes3 = array (0,0x200000,0x8000000,0x8200000,0x2000,0x202000,0x8002000,0x8202000,0x20000,0x220000,0x8020000,0x8220000,0x22000,0x222000,0x8022000,0x8222000); + $pc2bytes4 = array (0,0x40000,0x10,0x40010,0,0x40000,0x10,0x40010,0x1000,0x41000,0x1010,0x41010,0x1000,0x41000,0x1010,0x41010); + $pc2bytes5 = array (0,0x400,0x20,0x420,0,0x400,0x20,0x420,0x2000000,0x2000400,0x2000020,0x2000420,0x2000000,0x2000400,0x2000020,0x2000420); + $pc2bytes6 = array (0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002,0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002); + $pc2bytes7 = array (0,0x10000,0x800,0x10800,0x20000000,0x20010000,0x20000800,0x20010800,0x20000,0x30000,0x20800,0x30800,0x20020000,0x20030000,0x20020800,0x20030800); + $pc2bytes8 = array (0,0x40000,0,0x40000,0x2,0x40002,0x2,0x40002,0x2000000,0x2040000,0x2000000,0x2040000,0x2000002,0x2040002,0x2000002,0x2040002); + $pc2bytes9 = array (0,0x10000000,0x8,0x10000008,0,0x10000000,0x8,0x10000008,0x400,0x10000400,0x408,0x10000408,0x400,0x10000400,0x408,0x10000408); + $pc2bytes10 = array (0,0x20,0,0x20,0x100000,0x100020,0x100000,0x100020,0x2000,0x2020,0x2000,0x2020,0x102000,0x102020,0x102000,0x102020); + $pc2bytes11 = array (0,0x1000000,0x200,0x1000200,0x200000,0x1200000,0x200200,0x1200200,0x4000000,0x5000000,0x4000200,0x5000200,0x4200000,0x5200000,0x4200200,0x5200200); + $pc2bytes12 = array (0,0x1000,0x8000000,0x8001000,0x80000,0x81000,0x8080000,0x8081000,0x10,0x1010,0x8000010,0x8001010,0x80010,0x81010,0x8080010,0x8081010); + $pc2bytes13 = array (0,0x4,0x100,0x104,0,0x4,0x100,0x104,0x1,0x5,0x101,0x105,0x1,0x5,0x101,0x105); + $masks = array (4294967295,2147483647,1073741823,536870911,268435455,134217727,67108863,33554431,16777215,8388607,4194303,2097151,1048575,524287,262143,131071,65535,32767,16383,8191,4095,2047,1023,511,255,127,63,31,15,7,3,1,0); + + //how many iterations (1 for des, 3 for triple des) + $iterations = ((strlen($key) >= 24) ? 3 : 1); + //stores the return keys + $keys = array (); // size = 32 * iterations but you don't specify this in php + //now define the left shifts which need to be done + $shifts = array (0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0); + //other variables + $m=0; + $n=0; + + for ($j=0; $j<$iterations; $j++) { //either 1 or 3 iterations + $left = (ord($key{$m++}) << 24) | (ord($key{$m++}) << 16) | (ord($key{$m++}) << 8) | ord($key{$m++}); + $right = (ord($key{$m++}) << 24) | (ord($key{$m++}) << 16) | (ord($key{$m++}) << 8) | ord($key{$m++}); + + $temp = (($left >> 4 & $masks[4]) ^ $right) & 0x0f0f0f0f; $right ^= $temp; $left ^= ($temp << 4); + $temp = (($right >> 16 & $masks[16]) ^ $left) & 0x0000ffff; $left ^= $temp; $right ^= ($temp << -16); + $temp = (($left >> 2 & $masks[2]) ^ $right) & 0x33333333; $right ^= $temp; $left ^= ($temp << 2); + $temp = (($right >> 16 & $masks[16]) ^ $left) & 0x0000ffff; $left ^= $temp; $right ^= ($temp << -16); + $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555; $right ^= $temp; $left ^= ($temp << 1); + $temp = (($right >> 8 & $masks[8]) ^ $left) & 0x00ff00ff; $left ^= $temp; $right ^= ($temp << 8); + $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555; $right ^= $temp; $left ^= ($temp << 1); + + //the right side needs to be shifted and to get the last four bits of the left side + $temp = ($left << 8) | (($right >> 20 & $masks[20]) & 0x000000f0); + //left needs to be put upside down + $left = ($right << 24) | (($right << 8) & 0xff0000) | (($right >> 8 & $masks[8]) & 0xff00) | (($right >> 24 & $masks[24]) & 0xf0); + $right = $temp; + + //now go through and perform these shifts on the left and right keys + for ($i=0; $i < count($shifts); $i++) { + //shift the keys either one or two bits to the left + if ($shifts[$i] > 0) { + $left = (($left << 2) | ($left >> 26 & $masks[26])); + $right = (($right << 2) | ($right >> 26 & $masks[26])); + } else { + $left = (($left << 1) | ($left >> 27 & $masks[27])); + $right = (($right << 1) | ($right >> 27 & $masks[27])); + } + $left = $left & -0xf; + $right = $right & -0xf; + + //now apply PC-2, in such a way that E is easier when encrypting or decrypting + //this conversion will look like PC-2 except only the last 6 bits of each byte are used + //rather than 48 consecutive bits and the order of lines will be according to + //how the S selection functions will be applied: S2, S4, S6, S8, S1, S3, S5, S7 + $lefttemp = $pc2bytes0[$left >> 28 & $masks[28]] | $pc2bytes1[($left >> 24 & $masks[24]) & 0xf] + | $pc2bytes2[($left >> 20 & $masks[20]) & 0xf] | $pc2bytes3[($left >> 16 & $masks[16]) & 0xf] + | $pc2bytes4[($left >> 12 & $masks[12]) & 0xf] | $pc2bytes5[($left >> 8 & $masks[8]) & 0xf] + | $pc2bytes6[($left >> 4 & $masks[4]) & 0xf]; + $righttemp = $pc2bytes7[$right >> 28 & $masks[28]] | $pc2bytes8[($right >> 24 & $masks[24]) & 0xf] + | $pc2bytes9[($right >> 20 & $masks[20]) & 0xf] | $pc2bytes10[($right >> 16 & $masks[16]) & 0xf] + | $pc2bytes11[($right >> 12 & $masks[12]) & 0xf] | $pc2bytes12[($right >> 8 & $masks[8]) & 0xf] + | $pc2bytes13[($right >> 4 & $masks[4]) & 0xf]; + $temp = (($righttemp >> 16 & $masks[16]) ^ $lefttemp) & 0x0000ffff; + $keys[$n++] = $lefttemp ^ $temp; $keys[$n++] = $righttemp ^ ($temp << 16); + } + } //for each iterations + //return the keys we've created + return $keys; + } //end of des_createKeys + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Crypt/Driver/Think.class.php b/ThinkPHP/Library/Think/Crypt/Driver/Think.class.php new file mode 100644 index 0000000..f8ae9e1 --- /dev/null +++ b/ThinkPHP/Library/Think/Crypt/Driver/Think.class.php @@ -0,0 +1,86 @@ + +// +---------------------------------------------------------------------- +namespace Think\Crypt\Driver; +/** + * Base64 加密实现类 + */ +class Think { + + /** + * 加密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @param integer $expire 有效期(秒) + * @return string + */ + public static function encrypt($data,$key,$expire=0) { + $expire = sprintf('%010d', $expire ? $expire + time():0); + $key = md5($key); + $data = base64_encode($expire.$data); + $x = 0; + $len = strlen($data); + $l = strlen($key); + $char = $str = ''; + + for ($i = 0; $i < $len; $i++) { + if ($x == $l) $x = 0; + $char .= substr($key, $x, 1); + $x++; + } + + for ($i = 0; $i < $len; $i++) { + $str .= chr(ord(substr($data, $i, 1)) + (ord(substr($char, $i, 1)))%256); + } + return str_replace(array('+','/','='),array('-','_',''),base64_encode($str)); + } + + /** + * 解密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + public static function decrypt($data,$key) { + $key = md5($key); + $data = str_replace(array('-','_'),array('+','/'),$data); + $mod4 = strlen($data) % 4; + if ($mod4) { + $data .= substr('====', $mod4); + } + $data = base64_decode($data); + + $x = 0; + $len = strlen($data); + $l = strlen($key); + $char = $str = ''; + + for ($i = 0; $i < $len; $i++) { + if ($x == $l) $x = 0; + $char .= substr($key, $x, 1); + $x++; + } + + for ($i = 0; $i < $len; $i++) { + if (ord(substr($data, $i, 1)) 0 && $expire < time()) { + return ''; + } + $data = substr($data,10); + return $data; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Crypt/Driver/Xxtea.class.php b/ThinkPHP/Library/Think/Crypt/Driver/Xxtea.class.php new file mode 100644 index 0000000..3f506a1 --- /dev/null +++ b/ThinkPHP/Library/Think/Crypt/Driver/Xxtea.class.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- +namespace Think\Crypt\Driver; +/** + * Xxtea 加密实现类 + */ +class Xxtea { + + /** + * 加密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @param integer $expire 有效期(秒) + * @return string + */ + public static function encrypt($str, $key,$expire=0) { + $expire = sprintf('%010d', $expire ? $expire + time():0); + $str = $expire.$str; + $v = self::str2long($str, true); + $k = self::str2long($key, false); + $n = count($v) - 1; + + $z = $v[$n]; + $y = $v[0]; + $delta = 0x9E3779B9; + $q = floor(6 + 52 / ($n + 1)); + $sum = 0; + while (0 < $q--) { + $sum = self::int32($sum + $delta); + $e = $sum >> 2 & 3; + for ($p = 0; $p < $n; $p++) { + $y = $v[$p + 1]; + $mx = self::int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ self::int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z)); + $z = $v[$p] = self::int32($v[$p] + $mx); + } + $y = $v[0]; + $mx = self::int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ self::int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z)); + $z = $v[$n] = self::int32($v[$n] + $mx); + } + return self::long2str($v, false); + } + + /** + * 解密字符串 + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + public static function decrypt($str, $key) { + $v = self::str2long($str, false); + $k = self::str2long($key, false); + $n = count($v) - 1; + + $z = $v[$n]; + $y = $v[0]; + $delta = 0x9E3779B9; + $q = floor(6 + 52 / ($n + 1)); + $sum = self::int32($q * $delta); + while ($sum != 0) { + $e = $sum >> 2 & 3; + for ($p = $n; $p > 0; $p--) { + $z = $v[$p - 1]; + $mx = self::int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ self::int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z)); + $y = $v[$p] = self::int32($v[$p] - $mx); + } + $z = $v[$n]; + $mx = self::int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ self::int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z)); + $y = $v[0] = self::int32($v[0] - $mx); + $sum = self::int32($sum - $delta); + } + $data = self::long2str($v, true); + $expire = substr($data,0,10); + if($expire > 0 && $expire < time()) { + return ''; + } + $data = substr($data,10); + return $data; + } + + private static function long2str($v, $w) { + $len = count($v); + $s = array(); + for ($i = 0; $i < $len; $i++) { + $s[$i] = pack("V", $v[$i]); + } + if ($w) { + return substr(join('', $s), 0, $v[$len - 1]); + }else{ + return join('', $s); + } + } + + private static function str2long($s, $w) { + $v = unpack("V*", $s. str_repeat("\0", (4 - strlen($s) % 4) & 3)); + $v = array_values($v); + if ($w) { + $v[count($v)] = strlen($s); + } + return $v; + } + + private static function int32($n) { + while ($n >= 2147483648) $n -= 4294967296; + while ($n <= -2147483649) $n += 4294967296; + return (int)$n; + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Db.class.php b/ThinkPHP/Library/Think/Db.class.php new file mode 100644 index 0000000..5c46e2f --- /dev/null +++ b/ThinkPHP/Library/Think/Db.class.php @@ -0,0 +1,137 @@ + +// +---------------------------------------------------------------------- + +namespace Think; + +/** + * ThinkPHP 数据库中间层实现类 + */ +class Db { + + static private $instance = array(); // 数据库连接实例 + static private $_instance = null; // 当前数据库连接实例 + + /** + * 取得数据库类实例 + * @static + * @access public + * @param mixed $config 连接配置 + * @return Object 返回数据库驱动类 + */ + static public function getInstance($config=array()) { + $md5 = md5(serialize($config)); + if(!isset(self::$instance[$md5])) { + // 解析连接参数 支持数组和字符串 + $options = self::parseConfig($config); + // 兼容mysqli + if('mysqli' == $options['type']) $options['type'] = 'mysql'; + // 如果采用lite方式 仅支持原生SQL 包括query和execute方法 + $class = !empty($options['lite'])? 'Think\Db\Lite' : 'Think\\Db\\Driver\\'.ucwords(strtolower($options['type'])); + if(class_exists($class)){ + self::$instance[$md5] = new $class($options); + }else{ + // 类没有定义 + E(L('_NO_DB_DRIVER_').': ' . $class); + } + } + self::$_instance = self::$instance[$md5]; + return self::$_instance; + } + + /** + * 数据库连接参数解析 + * @static + * @access private + * @param mixed $config + * @return array + */ + static private function parseConfig($config){ + if(!empty($config)){ + if(is_string($config)) { + return self::parseDsn($config); + } + $config = array_change_key_case($config); + $config = array ( + 'type' => $config['db_type'], + 'username' => $config['db_user'], + 'password' => $config['db_pwd'], + 'hostname' => $config['db_host'], + 'hostport' => $config['db_port'], + 'database' => $config['db_name'], + 'dsn' => isset($config['db_dsn'])?$config['db_dsn']:null, + 'params' => isset($config['db_params'])?$config['db_params']:null, + 'charset' => isset($config['db_charset'])?$config['db_charset']:'utf8', + 'deploy' => isset($config['db_deploy_type'])?$config['db_deploy_type']:0, + 'rw_separate' => isset($config['db_rw_separate'])?$config['db_rw_separate']:false, + 'master_num' => isset($config['db_master_num'])?$config['db_master_num']:1, + 'slave_no' => isset($config['db_slave_no'])?$config['db_slave_no']:'', + 'debug' => isset($config['db_debug'])?$config['db_debug']:APP_DEBUG, + 'lite' => isset($config['db_lite'])?$config['db_lite']:false, + ); + }else { + $config = array ( + 'type' => C('DB_TYPE'), + 'username' => C('DB_USER'), + 'password' => C('DB_PWD'), + 'hostname' => C('DB_HOST'), + 'hostport' => C('DB_PORT'), + 'database' => C('DB_NAME'), + 'dsn' => C('DB_DSN'), + 'params' => C('DB_PARAMS'), + 'charset' => C('DB_CHARSET'), + 'deploy' => C('DB_DEPLOY_TYPE'), + 'rw_separate' => C('DB_RW_SEPARATE'), + 'master_num' => C('DB_MASTER_NUM'), + 'slave_no' => C('DB_SLAVE_NO'), + 'debug' => C('DB_DEBUG',null,APP_DEBUG), + 'lite' => C('DB_LITE'), + ); + } + return $config; + } + + /** + * DSN解析 + * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 + * @static + * @access private + * @param string $dsnStr + * @return array + */ + static private function parseDsn($dsnStr) { + if( empty($dsnStr) ){return false;} + $info = parse_url($dsnStr); + if(!$info) { + return false; + } + $dsn = array( + 'type' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => isset($info['path']) ? substr($info['path'],1) : '', + 'charset' => isset($info['fragment'])?$info['fragment']:'utf8', + ); + + if(isset($info['query'])) { + parse_str($info['query'],$dsn['params']); + }else{ + $dsn['params'] = array(); + } + return $dsn; + } + + // 调用驱动类的方法 + static public function __callStatic($method, $params){ + return call_user_func_array(array(self::$_instance, $method), $params); + } +} diff --git a/ThinkPHP/Library/Think/Db/Driver.class.php b/ThinkPHP/Library/Think/Db/Driver.class.php new file mode 100644 index 0000000..fb46beb --- /dev/null +++ b/ThinkPHP/Library/Think/Db/Driver.class.php @@ -0,0 +1,1149 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db; +use Think\Config; +use Think\Debug; +use Think\Log; +use PDO; + +abstract class Driver { + // PDO操作实例 + protected $PDOStatement = null; + // 当前操作所属的模型名 + protected $model = '_think_'; + // 当前SQL指令 + protected $queryStr = ''; + protected $modelSql = array(); + // 最后插入ID + protected $lastInsID = null; + // 返回或者影响记录数 + protected $numRows = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + // 数据库连接ID 支持多个连接 + protected $linkID = array(); + // 当前连接ID + protected $_linkID = null; + // 数据库连接参数配置 + protected $config = array( + 'type' => '', // 数据库类型 + 'hostname' => '127.0.0.1', // 服务器地址 + 'database' => '', // 数据库名 + 'username' => '', // 用户名 + 'password' => '', // 密码 + 'hostport' => '', // 端口 + 'dsn' => '', // + 'params' => array(), // 数据库连接参数 + 'charset' => 'utf8', // 数据库编码默认采用utf8 + 'prefix' => '', // 数据库表前缀 + 'debug' => false, // 数据库调试模式 + 'deploy' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'rw_separate' => false, // 数据库读写是否分离 主从式有效 + 'master_num' => 1, // 读写分离后 主服务器数量 + 'slave_no' => '', // 指定从服务器序号 + 'db_like_fields' => '', + ); + // 数据库表达式 + protected $exp = array('eq'=>'=','neq'=>'<>','gt'=>'>','egt'=>'>=','lt'=>'<','elt'=>'<=','notlike'=>'NOT LIKE','like'=>'LIKE','in'=>'IN','notin'=>'NOT IN','not in'=>'NOT IN','between'=>'BETWEEN','not between'=>'NOT BETWEEN','notbetween'=>'NOT BETWEEN'); + // 查询表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%LOCK%%COMMENT%'; + // 查询次数 + protected $queryTimes = 0; + // 执行次数 + protected $executeTimes = 0; + // PDO连接参数 + protected $options = array( + PDO::ATTR_CASE => PDO::CASE_LOWER, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ); + protected $bind = array(); // 参数绑定 + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + if(!empty($config)) { + $this->config = array_merge($this->config,$config); + if(is_array($this->config['params'])){ + $this->options = $this->config['params'] + $this->options; + } + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0,$autoConnection=false) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + try{ + if(empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + if(version_compare(PHP_VERSION,'5.3.6','<=')){ + // 禁用模拟预处理语句 + $this->options[PDO::ATTR_EMULATE_PREPARES] = false; + } + $this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$this->options); + }catch (\PDOException $e) { + if($autoConnection){ + trace($e->getMessage(),'','ERR'); + return $this->connect($autoConnection,$linkNum); + }elseif($config['debug']){ + E($e->getMessage()); + } + } + } + return $this->linkID[$linkNum]; + } + + /** + * 解析pdo连接的dsn信息 + * @access public + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config){} + + /** + * 释放查询结果 + * @access public + */ + public function free() { + $this->PDOStatement = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @param boolean $fetchSql 不执行只是获取SQL + * @return mixed + */ + public function query($str,$fetchSql=false) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + if(!empty($this->bind)){ + $that = $this; + $this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind)); + } + if($fetchSql){ + return $this->queryStr; + } + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + $this->queryTimes++; + N('db_query',1); // 兼容代码 + // 调试开始 + $this->debug(true); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement){ + $this->error(); + return false; + } + foreach ($this->bind as $key => $val) { + if(is_array($val)){ + $this->PDOStatement->bindValue($key, $val[0], $val[1]); + }else{ + $this->PDOStatement->bindValue($key, $val); + } + } + $this->bind = array(); + try{ + $result = $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false); + if ( false === $result ) { + $this->error(); + return false; + } else { + return $this->getResult(); + } + }catch (\PDOException $e) { + $this->error(); + return false; + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @param boolean $fetchSql 不执行只是获取SQL + * @return mixed + */ + public function execute($str,$fetchSql=false) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + if(!empty($this->bind)){ + $that = $this; + $this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind)); + } + if($fetchSql){ + return $this->queryStr; + } + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + $this->executeTimes++; + N('db_write',1); // 兼容代码 + // 记录开始执行时间 + $this->debug(true); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement) { + $this->error(); + return false; + } + foreach ($this->bind as $key => $val) { + if(is_array($val)){ + $this->PDOStatement->bindValue($key, $val[0], $val[1]); + }else{ + $this->PDOStatement->bindValue($key, $val); + } + } + $this->bind = array(); + try{ + $result = $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false); + if ( false === $result) { + $this->error(); + return false; + } else { + $this->numRows = $this->PDOStatement->rowCount(); + if(preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) { + $this->lastInsID = $this->_linkID->lastInsertId(); + } + return $this->numRows; + } + }catch (\PDOException $e) { + $this->error(); + return false; + } + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + $this->_linkID->beginTransaction(); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolean + */ + public function commit() { + if ($this->transTimes > 0) { + $result = $this->_linkID->commit(); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolean + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = $this->_linkID->rollback(); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getResult() { + //返回数据集 + $result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC); + $this->numRows = count( $result ); + return $result; + } + + /** + * 获得查询次数 + * @access public + * @param boolean $execute 是否包含所有查询 + * @return integer + */ + public function getQueryTimes($execute=false){ + return $execute?$this->queryTimes+$this->executeTimes:$this->queryTimes; + } + + /** + * 获得执行次数 + * @access public + * @return integer + */ + public function getExecuteTimes(){ + return $this->executeTimes; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error() { + if($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $this->error = $error[1].':'.$error[2]; + }else{ + $this->error = ''; + } + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + // 记录错误日志 + trace($this->error,'','ERR'); + if($this->config['debug']) {// 开启数据库调试模式 + E($this->error); + }else{ + return $this->error; + } + } + + /** + * 设置锁机制 + * @access protected + * @return string + */ + protected function parseLock($lock=false) { + return $lock? ' FOR UPDATE ' : ''; + } + + /** + * set分析 + * @access protected + * @param array $data + * @return string + */ + protected function parseSet($data) { + foreach ($data as $key=>$val){ + if(is_array($val) && 'exp' == $val[0]){ + $set[] = $this->parseKey($key).'='.$val[1]; + }elseif(is_null($val)){ + $set[] = $this->parseKey($key).'=NULL'; + }elseif(is_scalar($val)) {// 过滤非标量数据 + if(0===strpos($val,':') && in_array($val,array_keys($this->bind)) ){ + $set[] = $this->parseKey($key).'='.$this->escapeString($val); + }else{ + $name = count($this->bind); + $set[] = $this->parseKey($key).'=:'.$name; + $this->bindParam($name,$val); + } + } + } + return ' SET '.implode(',',$set); + } + + /** + * 参数绑定 + * @access protected + * @param string $name 绑定参数名 + * @param mixed $value 绑定值 + * @return void + */ + protected function bindParam($name,$value){ + $this->bind[':'.$name] = $value; + } + + /** + * 字段名分析 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(&$key) { + return $key; + } + + /** + * value分析 + * @access protected + * @param mixed $value + * @return string + */ + protected function parseValue($value) { + if(is_string($value)) { + $value = strpos($value,':') === 0 && in_array($value,array_keys($this->bind))? $this->escapeString($value) : '\''.$this->escapeString($value).'\''; + }elseif(isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp'){ + $value = $this->escapeString($value[1]); + }elseif(is_array($value)) { + $value = array_map(array($this, 'parseValue'),$value); + }elseif(is_bool($value)){ + $value = $value ? '1' : '0'; + }elseif(is_null($value)){ + $value = 'null'; + } + return $value; + } + + /** + * field分析 + * @access protected + * @param mixed $fields + * @return string + */ + protected function parseField($fields) { + if(is_string($fields) && '' !== $fields) { + $fields = explode(',',$fields); + } + if(is_array($fields)) { + // 完善数组方式传字段名的支持 + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = array(); + foreach ($fields as $key=>$field){ + if(!is_numeric($key)) + $array[] = $this->parseKey($key).' AS '.$this->parseKey($field); + else + $array[] = $this->parseKey($field); + } + $fieldsStr = implode(',', $array); + }else{ + $fieldsStr = '*'; + } + //TODO 如果是查询全部字段,并且是join的方式,那么就把要查的表加个别名,以免字段被覆盖 + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param mixed $table + * @return string + */ + protected function parseTable($tables) { + if(is_array($tables)) {// 支持别名定义 + $array = array(); + foreach ($tables as $table=>$alias){ + if(!is_numeric($table)) + $array[] = $this->parseKey($table).' '.$this->parseKey($alias); + else + $array[] = $this->parseKey($alias); + } + $tables = $array; + }elseif(is_string($tables)){ + $tables = explode(',',$tables); + array_walk($tables, array(&$this, 'parseKey')); + } + return implode(',',$tables); + } + + /** + * where分析 + * @access protected + * @param mixed $where + * @return string + */ + protected function parseWhere($where) { + $whereStr = ''; + if(is_string($where)) { + // 直接使用字符串条件 + $whereStr = $where; + }else{ // 使用数组表达式 + $operate = isset($where['_logic'])?strtoupper($where['_logic']):''; + if(in_array($operate,array('AND','OR','XOR'))){ + // 定义逻辑运算规则 例如 OR XOR AND NOT + $operate = ' '.$operate.' '; + unset($where['_logic']); + }else{ + // 默认进行 AND 运算 + $operate = ' AND '; + } + foreach ($where as $key=>$val){ + if(is_numeric($key)){ + $key = '_complex'; + } + if(0===strpos($key,'_')) { + // 解析特殊条件表达式 + $whereStr .= $this->parseThinkWhere($key,$val); + }else{ + // 查询字段的安全过滤 + // if(!preg_match('/^[A-Z_\|\&\-.a-z0-9\(\)\,]+$/',trim($key))){ + // E(L('_EXPRESS_ERROR_').':'.$key); + // } + // 多条件支持 + $multi = is_array($val) && isset($val['_multi']); + $key = trim($key); + if(strpos($key,'|')) { // 支持 name|title|nickname 方式定义查询字段 + $array = explode('|',$key); + $str = array(); + foreach ($array as $m=>$k){ + $v = $multi?$val[$m]:$val; + $str[] = $this->parseWhereItem($this->parseKey($k),$v); + } + $whereStr .= '( '.implode(' OR ',$str).' )'; + }elseif(strpos($key,'&')){ + $array = explode('&',$key); + $str = array(); + foreach ($array as $m=>$k){ + $v = $multi?$val[$m]:$val; + $str[] = '('.$this->parseWhereItem($this->parseKey($k),$v).')'; + } + $whereStr .= '( '.implode(' AND ',$str).' )'; + }else{ + $whereStr .= $this->parseWhereItem($this->parseKey($key),$val); + } + } + $whereStr .= $operate; + } + $whereStr = substr($whereStr,0,-strlen($operate)); + } + return empty($whereStr)?'':' WHERE '.$whereStr; + } + + // where子单元分析 + protected function parseWhereItem($key,$val) { + $whereStr = ''; + if(is_array($val)) { + if(is_string($val[0])) { + $exp = strtolower($val[0]); + if(preg_match('/^(eq|neq|gt|egt|lt|elt)$/',$exp)) { // 比较运算 + $whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]); + }elseif(preg_match('/^(notlike|like)$/',$exp)){// 模糊查找 + if(is_array($val[1])) { + $likeLogic = isset($val[2])?strtoupper($val[2]):'OR'; + if(in_array($likeLogic,array('AND','OR','XOR'))){ + $like = array(); + foreach ($val[1] as $item){ + $like[] = $key.' '.$this->exp[$exp].' '.$this->parseValue($item); + } + $whereStr .= '('.implode(' '.$likeLogic.' ',$like).')'; + } + }else{ + $whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]); + } + }elseif('bind' == $exp ){ // 使用表达式 + $whereStr .= $key.' = :'.$val[1]; + }elseif('exp' == $exp ){ // 使用表达式 + $whereStr .= $key.' '.$val[1]; + }elseif(preg_match('/^(notin|not in|in)$/',$exp)){ // IN 运算 + if(isset($val[2]) && 'exp'==$val[2]) { + $whereStr .= $key.' '.$this->exp[$exp].' '.$val[1]; + }else{ + if(is_string($val[1])) { + $val[1] = explode(',',$val[1]); + } + $zone = implode(',',$this->parseValue($val[1])); + $whereStr .= $key.' '.$this->exp[$exp].' ('.$zone.')'; + } + }elseif(preg_match('/^(notbetween|not between|between)$/',$exp)){ // BETWEEN运算 + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]); + }else{ + E(L('_EXPRESS_ERROR_').':'.$val[0]); + } + }else { + $count = count($val); + $rule = isset($val[$count-1]) ? (is_array($val[$count-1]) ? strtoupper($val[$count-1][0]) : strtoupper($val[$count-1]) ) : '' ; + if(in_array($rule,array('AND','OR','XOR'))) { + $count = $count -1; + }else{ + $rule = 'AND'; + } + for($i=0;$i<$count;$i++) { + $data = is_array($val[$i])?$val[$i][1]:$val[$i]; + if('exp'==strtolower($val[$i][0])) { + $whereStr .= $key.' '.$data.' '.$rule.' '; + }else{ + $whereStr .= $this->parseWhereItem($key,$val[$i]).' '.$rule.' '; + } + } + $whereStr = '( '.substr($whereStr,0,-4).' )'; + } + }else { + //对字符串类型字段采用模糊匹配 + $likeFields = $this->config['db_like_fields']; + if($likeFields && preg_match('/^('.$likeFields.')$/i',$key)) { + $whereStr .= $key.' LIKE '.$this->parseValue('%'.$val.'%'); + }else { + $whereStr .= $key.' = '.$this->parseValue($val); + } + } + return $whereStr; + } + + /** + * 特殊条件分析 + * @access protected + * @param string $key + * @param mixed $val + * @return string + */ + protected function parseThinkWhere($key,$val) { + $whereStr = ''; + switch($key) { + case '_string': + // 字符串模式查询条件 + $whereStr = $val; + break; + case '_complex': + // 复合查询条件 + $whereStr = substr($this->parseWhere($val),6); + break; + case '_query': + // 字符串模式查询条件 + parse_str($val,$where); + if(isset($where['_logic'])) { + $op = ' '.strtoupper($where['_logic']).' '; + unset($where['_logic']); + }else{ + $op = ' AND '; + } + $array = array(); + foreach ($where as $field=>$data) + $array[] = $this->parseKey($field).' = '.$this->parseValue($data); + $whereStr = implode($op,$array); + break; + } + return '( '.$whereStr.' )'; + } + + /** + * limit分析 + * @access protected + * @param mixed $lmit + * @return string + */ + protected function parseLimit($limit) { + return !empty($limit)? ' LIMIT '.$limit.' ':''; + } + + /** + * join分析 + * @access protected + * @param mixed $join + * @return string + */ + protected function parseJoin($join) { + $joinStr = ''; + if(!empty($join)) { + $joinStr = ' '.implode(' ',$join).' '; + } + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @return string + */ + protected function parseOrder($order) { + if(is_array($order)) { + $array = array(); + foreach ($order as $key=>$val){ + if(is_numeric($key)) { + $array[] = $this->parseKey($val); + }else{ + $array[] = $this->parseKey($key).' '.$val; + } + } + $order = implode(',',$array); + } + return !empty($order)? ' ORDER BY '.$order:''; + } + + /** + * group分析 + * @access protected + * @param mixed $group + * @return string + */ + protected function parseGroup($group) { + return !empty($group)? ' GROUP BY '.$group:''; + } + + /** + * having分析 + * @access protected + * @param string $having + * @return string + */ + protected function parseHaving($having) { + return !empty($having)? ' HAVING '.$having:''; + } + + /** + * comment分析 + * @access protected + * @param string $comment + * @return string + */ + protected function parseComment($comment) { + return !empty($comment)? ' /* '.$comment.' */':''; + } + + /** + * distinct分析 + * @access protected + * @param mixed $distinct + * @return string + */ + protected function parseDistinct($distinct) { + return !empty($distinct)? ' DISTINCT ' :''; + } + + /** + * union分析 + * @access protected + * @param mixed $union + * @return string + */ + protected function parseUnion($union) { + if(empty($union)) return ''; + if(isset($union['_all'])) { + $str = 'UNION ALL '; + unset($union['_all']); + }else{ + $str = 'UNION '; + } + foreach ($union as $u){ + $sql[] = $str.(is_array($u)?$this->buildSelectSql($u):$u); + } + return implode(' ',$sql); + } + + /** + * 参数绑定分析 + * @access protected + * @param array $bind + * @return array + */ + protected function parseBind($bind){ + $this->bind = array_merge($this->bind,$bind); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param mixed $index + * @return string + */ + protected function parseForce($index) { + if(empty($index)) return ''; + if(is_array($index)) $index = join(",", $index); + return sprintf(" FORCE INDEX ( %s ) ", $index); + } + + /** + * ON DUPLICATE KEY UPDATE 分析 + * @access protected + * @param mixed $duplicate + * @return string + */ + protected function parseDuplicate($duplicate){ + return ''; + } + + /** + * 插入记录 + * @access public + * @param mixed $data 数据 + * @param array $options 参数表达式 + * @param boolean $replace 是否replace + * @return false | integer + */ + public function insert($data,$options=array(),$replace=false) { + $values = $fields = array(); + $this->model = $options['model']; + $this->parseBind(!empty($options['bind'])?$options['bind']:array()); + foreach ($data as $key=>$val){ + if(is_array($val) && 'exp' == $val[0]){ + $fields[] = $this->parseKey($key); + $values[] = $val[1]; + }elseif(is_null($val)){ + $fields[] = $this->parseKey($key); + $values[] = 'NULL'; + }elseif(is_scalar($val)) { // 过滤非标量数据 + $fields[] = $this->parseKey($key); + if(0===strpos($val,':') && in_array($val,array_keys($this->bind))){ + $values[] = $this->parseValue($val); + }else{ + $name = count($this->bind); + $values[] = ':'.$name; + $this->bindParam($name,$val); + } + } + } + // 兼容数字传入方式 + $replace= (is_numeric($replace) && $replace>0)?true:$replace; + $sql = (true===$replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')'.$this->parseDuplicate($replace); + $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); + } + + + /** + * 批量插入记录 + * @access public + * @param mixed $dataSet 数据集 + * @param array $options 参数表达式 + * @param boolean $replace 是否replace + * @return false | integer + */ + public function insertAll($dataSet,$options=array(),$replace=false) { + $values = array(); + $this->model = $options['model']; + if(!is_array($dataSet[0])) return false; + $this->parseBind(!empty($options['bind'])?$options['bind']:array()); + $fields = array_map(array($this,'parseKey'),array_keys($dataSet[0])); + foreach ($dataSet as $data){ + $value = array(); + foreach ($data as $key=>$val){ + if(is_array($val) && 'exp' == $val[0]){ + $value[] = $val[1]; + }elseif(is_null($val)){ + $value[] = 'NULL'; + }elseif(is_scalar($val)){ + if(0===strpos($val,':') && in_array($val,array_keys($this->bind))){ + $value[] = $this->parseValue($val); + }else{ + $name = count($this->bind); + $value[] = ':'.$name; + $this->bindParam($name,$val); + } + } + } + $values[] = 'SELECT '.implode(',', $value); + } + $sql = 'INSERT INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') '.implode(' UNION ALL ',$values); + $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @param array $option 查询数据参数 + * @return false | integer + */ + public function selectInsert($fields,$table,$options=array()) { + $this->model = $options['model']; + $this->parseBind(!empty($options['bind'])?$options['bind']:array()); + if(is_string($fields)) $fields = explode(',',$fields); + array_walk($fields, array($this, 'parseKey')); + $sql = 'INSERT INTO '.$this->parseTable($table).' ('.implode(',', $fields).') '; + $sql .= $this->buildSelectSql($options); + return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return false | integer + */ + public function update($data,$options) { + $this->model = $options['model']; + $this->parseBind(!empty($options['bind'])?$options['bind']:array()); + $table = $this->parseTable($options['table']); + $sql = 'UPDATE ' . $table . $this->parseSet($data); + if(strpos($table,',')){// 多表更新支持JOIN操作 + $sql .= $this->parseJoin(!empty($options['join'])?$options['join']:''); + } + $sql .= $this->parseWhere(!empty($options['where'])?$options['where']:''); + if(!strpos($table,',')){ + // 单表更新支持order和lmit + $sql .= $this->parseOrder(!empty($options['order'])?$options['order']:'') + .$this->parseLimit(!empty($options['limit'])?$options['limit']:''); + } + $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); + } + + /** + * 删除记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function delete($options=array()) { + $this->model = $options['model']; + $this->parseBind(!empty($options['bind'])?$options['bind']:array()); + $table = $this->parseTable($options['table']); + $sql = 'DELETE FROM '.$table; + if(strpos($table,',')){// 多表删除支持USING和JOIN操作 + if(!empty($options['using'])){ + $sql .= ' USING '.$this->parseTable($options['using']).' '; + } + $sql .= $this->parseJoin(!empty($options['join'])?$options['join']:''); + } + $sql .= $this->parseWhere(!empty($options['where'])?$options['where']:''); + if(!strpos($table,',')){ + // 单表删除支持order和limit + $sql .= $this->parseOrder(!empty($options['order'])?$options['order']:'') + .$this->parseLimit(!empty($options['limit'])?$options['limit']:''); + } + $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); + } + + /** + * 查找记录 + * @access public + * @param array $options 表达式 + * @return mixed + */ + public function select($options=array()) { + $this->model = $options['model']; + $this->parseBind(!empty($options['bind'])?$options['bind']:array()); + $sql = $this->buildSelectSql($options); + $result = $this->query($sql,!empty($options['fetch_sql']) ? true : false); + return $result; + } + + /** + * 生成查询SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function buildSelectSql($options=array()) { + if(isset($options['page'])) { + // 根据页数计算limit + list($page,$listRows) = $options['page']; + $page = $page>0 ? $page : 1; + $listRows= $listRows>0 ? $listRows : (is_numeric($options['limit'])?$options['limit']:20); + $offset = $listRows*($page-1); + $options['limit'] = $offset.','.$listRows; + } + $sql = $this->parseSql($this->selectSql,$options); + return $sql; + } + + /** + * 替换SQL语句中表达式 + * @access public + * @param array $options 表达式 + * @return string + */ + public function parseSql($sql,$options=array()){ + $sql = str_replace( + array('%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%LOCK%','%COMMENT%','%FORCE%'), + array( + $this->parseTable($options['table']), + $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false), + $this->parseField(!empty($options['field'])?$options['field']:'*'), + $this->parseJoin(!empty($options['join'])?$options['join']:''), + $this->parseWhere(!empty($options['where'])?$options['where']:''), + $this->parseGroup(!empty($options['group'])?$options['group']:''), + $this->parseHaving(!empty($options['having'])?$options['having']:''), + $this->parseOrder(!empty($options['order'])?$options['order']:''), + $this->parseLimit(!empty($options['limit'])?$options['limit']:''), + $this->parseUnion(!empty($options['union'])?$options['union']:''), + $this->parseLock(isset($options['lock'])?$options['lock']:false), + $this->parseComment(!empty($options['comment'])?$options['comment']:''), + $this->parseForce(!empty($options['force'])?$options['force']:'') + ),$sql); + return $sql; + } + + /** + * 获取最近一次查询的sql语句 + * @param string $model 模型名 + * @access public + * @return string + */ + public function getLastSql($model='') { + return $model?$this->modelSql[$model]:$this->queryStr; + } + + /** + * 获取最近插入的ID + * @access public + * @return string + */ + public function getLastInsID() { + return $this->lastInsID; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() { + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL字符串 + * @return string + */ + public function escapeString($str) { + return addslashes($str); + } + + /** + * 设置当前操作模型 + * @access public + * @param string $model 模型名 + * @return void + */ + public function setModel($model){ + $this->model = $model; + } + + /** + * 数据库调试 记录当前SQL + * @access protected + * @param boolean $start 调试开始标记 true 开始 false 结束 + */ + protected function debug($start) { + if($this->config['debug']) {// 开启数据库调试模式 + if($start) { + G('queryStartTime'); + }else{ + $this->modelSql[$this->model] = $this->queryStr; + //$this->model = '_think_'; + // 记录操作结束时间 + G('queryEndTime'); + trace($this->queryStr.' [ RunTime:'.G('queryStartTime','queryEndTime').'s ]','','SQL'); + } + } + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 主服务器 + * @return void + */ + protected function initConnect($master=true) { + if(!empty($this->config['deploy'])) + // 采用分布式数据库 + $this->_linkID = $this->multiConnect($master); + else + // 默认单数据库 + if ( !$this->_linkID ) $this->_linkID = $this->connect(); + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return void + */ + protected function multiConnect($master=false) { + // 分布式数据库配置解析 + $_config['username'] = explode(',',$this->config['username']); + $_config['password'] = explode(',',$this->config['password']); + $_config['hostname'] = explode(',',$this->config['hostname']); + $_config['hostport'] = explode(',',$this->config['hostport']); + $_config['database'] = explode(',',$this->config['database']); + $_config['dsn'] = explode(',',$this->config['dsn']); + $_config['charset'] = explode(',',$this->config['charset']); + + $m = floor(mt_rand(0,$this->config['master_num']-1)); + // 数据库读写是否分离 + if($this->config['rw_separate']){ + // 主从式采用读写分离 + if($master) + // 主服务器写入 + $r = $m; + else{ + if(is_numeric($this->config['slave_no'])) {// 指定服务器读 + $r = $this->config['slave_no']; + }else{ + // 读操作连接从服务器 + $r = floor(mt_rand($this->config['master_num'],count($_config['hostname'])-1)); // 每次随机连接的数据库 + } + } + }else{ + // 读写操作不区分服务器 + $r = floor(mt_rand(0,count($_config['hostname'])-1)); // 每次随机连接的数据库 + } + + if($m != $r ){ + $db_master = array( + 'username' => isset($_config['username'][$m])?$_config['username'][$m]:$_config['username'][0], + 'password' => isset($_config['password'][$m])?$_config['password'][$m]:$_config['password'][0], + 'hostname' => isset($_config['hostname'][$m])?$_config['hostname'][$m]:$_config['hostname'][0], + 'hostport' => isset($_config['hostport'][$m])?$_config['hostport'][$m]:$_config['hostport'][0], + 'database' => isset($_config['database'][$m])?$_config['database'][$m]:$_config['database'][0], + 'dsn' => isset($_config['dsn'][$m])?$_config['dsn'][$m]:$_config['dsn'][0], + 'charset' => isset($_config['charset'][$m])?$_config['charset'][$m]:$_config['charset'][0], + ); + } + $db_config = array( + 'username' => isset($_config['username'][$r])?$_config['username'][$r]:$_config['username'][0], + 'password' => isset($_config['password'][$r])?$_config['password'][$r]:$_config['password'][0], + 'hostname' => isset($_config['hostname'][$r])?$_config['hostname'][$r]:$_config['hostname'][0], + 'hostport' => isset($_config['hostport'][$r])?$_config['hostport'][$r]:$_config['hostport'][0], + 'database' => isset($_config['database'][$r])?$_config['database'][$r]:$_config['database'][0], + 'dsn' => isset($_config['dsn'][$r])?$_config['dsn'][$r]:$_config['dsn'][0], + 'charset' => isset($_config['charset'][$r])?$_config['charset'][$r]:$_config['charset'][0], + ); + return $this->connect($db_config,$r,$r == $m ? false : $db_master); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() { + // 释放查询 + if ($this->PDOStatement){ + $this->free(); + } + // 关闭连接 + $this->close(); + } +} diff --git a/ThinkPHP/Library/Think/Db/Driver/Firebird.class.php b/ThinkPHP/Library/Think/Db/Driver/Firebird.class.php new file mode 100644 index 0000000..6cc281d --- /dev/null +++ b/ThinkPHP/Library/Think/Db/Driver/Firebird.class.php @@ -0,0 +1,151 @@ + +// +---------------------------------------------------------------------- +namespace Think\Db\Driver; +use Think\Db\Driver; + +/** + * Firebird数据库驱动 + */ +class Firebird extends Driver{ + protected $selectSql = 'SELECT %LIMIT% %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%'; + + /** + * 解析pdo连接的dsn信息 + * @access public + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config){ + $dsn = 'firebird:dbname='.$config['hostname'].'/'.($config['hostport']?:3050).':'.$config['database']; + return $dsn; + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @param boolean $fetchSql 不执行只是获取SQL + * @return mixed + */ + public function execute($str,$fetchSql=false) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + if(!empty($this->bind)){ + $that = $this; + $this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind)); + } + if($fetchSql){ + return $this->queryStr; + } + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + $this->executeTimes++; + N('db_write',1); // 兼容代码 + // 记录开始执行时间 + $this->debug(true); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement) { + E($this->error()); + } + foreach ($this->bind as $key => $val) { + if(is_array($val)){ + $this->PDOStatement->bindValue($key, $val[0], $val[1]); + }else{ + $this->PDOStatement->bindValue($key, $val); + } + } + $this->bind = array(); + $result = $this->PDOStatement->execute(); + $this->debug(false); + if ( false === $result) { + $this->error(); + return false; + } else { + $this->numRows = $this->PDOStatement->rowCount(); + return $this->numRows; + } + } + + /** + * 取得数据表的字段信息 + * @access public + */ + public function getFields($tableName) { + $this->initConnect(true); + list($tableName) = explode(' ', $tableName); + $sql='SELECT RF.RDB$FIELD_NAME AS FIELD,RF.RDB$DEFAULT_VALUE AS DEFAULT1,RF.RDB$NULL_FLAG AS NULL1,TRIM(T.RDB$TYPE_NAME) || \'(\' || F.RDB$FIELD_LENGTH || \')\' as TYPE FROM RDB$RELATION_FIELDS RF LEFT JOIN RDB$FIELDS F ON (F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE) LEFT JOIN RDB$TYPES T ON (T.RDB$TYPE = F.RDB$FIELD_TYPE) WHERE RDB$RELATION_NAME=UPPER(\''.$tableName.'\') AND T.RDB$FIELD_NAME = \'RDB$FIELD_TYPE\' ORDER By RDB$FIELD_POSITION'; + $result = $this->query($sql); + $info = array(); + if($result){ + foreach($result as $key => $val){ + $info[trim($val['field'])] = array( + 'name' => trim($val['field']), + 'type' => $val['type'], + 'notnull' => (bool) ($val['null1'] ==1), // 1表示不为Null + 'default' => $val['default1'], + 'primary' => false, + 'autoinc' => false, + ); + } + } + //获取主键 + $sql='select b.rdb$field_name as field_name from rdb$relation_constraints a join rdb$index_segments b on a.rdb$index_name=b.rdb$index_name where a.rdb$constraint_type=\'PRIMARY KEY\' and a.rdb$relation_name=UPPER(\''.$tableName.'\')'; + $rs_temp = $this->query($sql); + foreach($rs_temp as $row) { + $info[trim($row['field_name'])]['primary']= true; + } + return $info; + } + + /** + * 取得数据库的表信息 + * @access public + */ + public function getTables($dbName='') { + $sql='SELECT DISTINCT RDB$RELATION_NAME FROM RDB$RELATION_FIELDS WHERE RDB$SYSTEM_FLAG=0'; + $result = $this->query($sql); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = trim(current($val)); + } + return $info; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL指令 + * @return string + */ + public function escapeString($str) { + return str_replace("'", "''", $str); + } + + /** + * limit + * @access public + * @param $limit limit表达式 + * @return string + */ + public function parseLimit($limit) { + $limitStr = ''; + if(!empty($limit)) { + $limit = explode(',',$limit); + if(count($limit)>1) { + $limitStr = ' FIRST '.$limit[1].' SKIP '.$limit[0].' '; + }else{ + $limitStr = ' FIRST '.$limit[0].' '; + } + } + return $limitStr; + } +} diff --git a/ThinkPHP/Library/Think/Db/Driver/Mongo.class.php b/ThinkPHP/Library/Think/Db/Driver/Mongo.class.php new file mode 100644 index 0000000..a070b8c --- /dev/null +++ b/ThinkPHP/Library/Think/Db/Driver/Mongo.class.php @@ -0,0 +1,821 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db\Driver; + +/** + * Mongo数据库驱动 + */ +class Mongo extends Driver { + + protected $_mongo = null; // MongoDb Object + protected $_collection = null; // MongoCollection Object + protected $_dbName = ''; // dbName + protected $_collectionName = ''; // collectionName + protected $_cursor = null; // MongoCursor Object + protected $comparison = array('neq'=>'ne','ne'=>'ne','gt'=>'gt','egt'=>'gte','gte'=>'gte','lt'=>'lt','elt'=>'lte','lte'=>'lte','in'=>'in','not in'=>'nin','nin'=>'nin'); + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + if ( !class_exists('mongoClient') ) { + E(L('_NOT_SUPPORT_').':Mongo'); + } + if(!empty($config)) { + $this->config = array_merge($this->config,$config); + if(empty($this->config['params'])){ + $this->config['params'] = array(); + } + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + $host = 'mongodb://'.($config['username']?"{$config['username']}":'').($config['password']?":{$config['password']}@":'').$config['hostname'].($config['hostport']?":{$config['hostport']}":'').'/'.($config['database']?"{$config['database']}":''); + try{ + $this->linkID[$linkNum] = new \mongoClient( $host,$this->config['params']); + }catch (\MongoConnectionException $e){ + E($e->getmessage()); + } + } + return $this->linkID[$linkNum]; + } + + /** + * 切换当前操作的Db和Collection + * @access public + * @param string $collection collection + * @param string $db db + * @param boolean $master 是否主服务器 + * @return void + */ + public function switchCollection($collection,$db='',$master=true){ + // 当前没有连接 则首先进行数据库连接 + if ( !$this->_linkID ) $this->initConnect($master); + try{ + if(!empty($db)) { // 传人Db则切换数据库 + // 当前MongoDb对象 + $this->_dbName = $db; + $this->_mongo = $this->_linkID->selectDb($db); + } + // 当前MongoCollection对象 + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.getCollection('.$collection.')'; + } + if($this->_collectionName != $collection) { + $this->queryTimes++; + N('db_query',1); // 兼容代码 + $this->debug(true); + $this->_collection = $this->_mongo->selectCollection($collection); + $this->debug(false); + $this->_collectionName = $collection; // 记录当前Collection名称 + } + }catch (MongoException $e){ + E($e->getMessage()); + } + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + $this->_cursor = null; + } + + /** + * 执行命令 + * @access public + * @param array $command 指令 + * @return array + */ + public function command($command=array(), $options=array()) { + $cache = isset($options['cache'])?$options['cache']:false; + if($cache) { // 查询缓存检测 + $key = is_string($cache['key'])?$cache['key']:md5(serialize($command)); + $value = S($key,'','',$cache['type']); + if(false !== $value) { + return $value; + } + } + N('db_write',1); // 兼容代码 + $this->executeTimes++; + try{ + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.runCommand('; + $this->queryStr .= json_encode($command); + $this->queryStr .= ')'; + } + $this->debug(true); + $result = $this->_mongo->command($command); + $this->debug(false); + + if($cache && $result['ok']) { // 查询缓存写入 + S($key,$result,$cache['expire'],$cache['type']); + } + return $result; + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + } + + /** + * 执行语句 + * @access public + * @param string $code sql指令 + * @param array $args 参数 + * @return mixed + */ + public function execute($code,$args=array()) { + $this->executeTimes++; + N('db_write',1); // 兼容代码 + $this->debug(true); + $this->queryStr = 'execute:'.$code; + $result = $this->_mongo->execute($code,$args); + $this->debug(false); + if($result['ok']) { + return $result['retval']; + }else{ + E($result['errmsg']); + } + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + if($this->_linkID) { + $this->_linkID->close(); + $this->_linkID = null; + $this->_mongo = null; + $this->_collection = null; + $this->_cursor = null; + } + } + + /** + * 数据库错误信息 + * @access public + * @return string + */ + public function error() { + $this->error = $this->_mongo->lastError(); + trace($this->error,'','ERR'); + return $this->error; + } + + /** + * 插入记录 + * @access public + * @param mixed $data 数据 + * @param array $options 参数表达式 + * @param boolean $replace 是否replace + * @return false | integer + */ + public function insert($data,$options=array(),$replace=false) { + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $this->model = $options['model']; + $this->executeTimes++; + N('db_write',1); // 兼容代码 + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.insert('; + $this->queryStr .= $data?json_encode($data):'{}'; + $this->queryStr .= ')'; + } + try{ + $this->debug(true); + $result = $replace? $this->_collection->save($data): $this->_collection->insert($data); + $this->debug(false); + if($result) { + $_id = $data['_id']; + if(is_object($_id)) { + $_id = $_id->__toString(); + } + $this->lastInsID = $_id; + } + return $result; + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + } + + /** + * 插入多条记录 + * @access public + * @param array $dataList 数据 + * @param array $options 参数表达式 + * @return bool + */ + public function insertAll($dataList,$options=array()) { + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $this->model = $options['model']; + $this->executeTimes++; + N('db_write',1); // 兼容代码 + try{ + $this->debug(true); + $result = $this->_collection->batchInsert($dataList); + $this->debug(false); + return $result; + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + } + + /** + * 生成下一条记录ID 用于自增非MongoId主键 + * @access public + * @param string $pk 主键名 + * @return integer + */ + public function getMongoNextId($pk) { + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.find({},{'.$pk.':1}).sort({'.$pk.':-1}).limit(1)'; + } + try{ + $this->debug(true); + $result = $this->_collection->find(array(),array($pk=>1))->sort(array($pk=>-1))->limit(1); + $this->debug(false); + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + $data = $result->getNext(); + return isset($data[$pk])?$data[$pk]+1:1; + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return bool + */ + public function update($data,$options) { + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $this->executeTimes++; + N('db_write',1); // 兼容代码 + $this->model = $options['model']; + $query = $this->parseWhere(isset($options['where'])?$options['where']:array()); + $set = $this->parseSet($data); + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.update('; + $this->queryStr .= $query?json_encode($query):'{}'; + $this->queryStr .= ','.json_encode($set).')'; + } + try{ + $this->debug(true); + if(isset($options['limit']) && $options['limit'] == 1) { + $multiple = array("multiple" => false); + }else{ + $multiple = array("multiple" => true); + } + $result = $this->_collection->update($query,$set,$multiple); + $this->debug(false); + return $result; + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + } + + /** + * 删除记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function delete($options=array()) { + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $query = $this->parseWhere(isset($options['where'])?$options['where']:array()); + $this->model = $options['model']; + $this->executeTimes++; + N('db_write',1); // 兼容代码 + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.remove('.json_encode($query).')'; + } + try{ + $this->debug(true); + $result = $this->_collection->remove($query); + $this->debug(false); + return $result; + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + } + + /** + * 清空记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function clear($options=array()){ + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $this->model = $options['model']; + $this->executeTimes++; + N('db_write',1); // 兼容代码 + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.remove({})'; + } + try{ + $this->debug(true); + $result = $this->_collection->drop(); + $this->debug(false); + return $result; + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + } + + /** + * 查找记录 + * @access public + * @param array $options 表达式 + * @return iterator + */ + public function select($options=array()) { + if(isset($options['table'])) { + $this->switchCollection($options['table'],'',false); + } + $this->model = $options['model']; + $this->queryTimes++; + N('db_query',1); // 兼容代码 + $query = $this->parseWhere(isset($options['where'])?$options['where']:array()); + $field = $this->parseField(isset($options['field'])?$options['field']:array()); + try{ + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.find('; + $this->queryStr .= $query? json_encode($query):'{}'; + if(is_array($field) && count($field)) { + foreach ($field as $f=>$v) + $_field_array[$f] = $v ? 1 : 0; + + $this->queryStr .= $field? ', '.json_encode($_field_array):', {}'; + } + $this->queryStr .= ')'; + } + $this->debug(true); + $_cursor = $this->_collection->find($query,$field); + if(!empty($options['order'])) { + $order = $this->parseOrder($options['order']); + if($this->config['debug']) { + $this->queryStr .= '.sort('.json_encode($order).')'; + } + $_cursor = $_cursor->sort($order); + } + if(isset($options['page'])) { // 根据页数计算limit + list($page,$length) = $options['page']; + $page = $page>0 ? $page : 1; + $length = $length>0 ? $length : (is_numeric($options['limit'])?$options['limit']:20); + $offset = $length*((int)$page-1); + $options['limit'] = $offset.','.$length; + } + if(isset($options['limit'])) { + list($offset,$length) = $this->parseLimit($options['limit']); + if(!empty($offset)) { + if($this->config['debug']) { + $this->queryStr .= '.skip('.intval($offset).')'; + } + $_cursor = $_cursor->skip(intval($offset)); + } + if($this->config['debug']) { + $this->queryStr .= '.limit('.intval($length).')'; + } + $_cursor = $_cursor->limit(intval($length)); + } + $this->debug(false); + $this->_cursor = $_cursor; + $resultSet = iterator_to_array($_cursor); + return $resultSet; + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + } + + /** + * 查找某个记录 + * @access public + * @param array $options 表达式 + * @return array + */ + public function find($options=array()){ + $options['limit'] = 1; + $find = $this->select($options); + return array_shift($find); + } + + /** + * 统计记录数 + * @access public + * @param array $options 表达式 + * @return iterator + */ + public function count($options=array()){ + if(isset($options['table'])) { + $this->switchCollection($options['table'],'',false); + } + $this->model = $options['model']; + $this->queryTimes++; + N('db_query',1); // 兼容代码 + $query = $this->parseWhere(isset($options['where'])?$options['where']:array()); + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName; + $this->queryStr .= $query?'.find('.json_encode($query).')':''; + $this->queryStr .= '.count()'; + } + try{ + $this->debug(true); + $count = $this->_collection->count($query); + $this->debug(false); + return $count; + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + } + + public function group($keys,$initial,$reduce,$options=array()){ + if(isset($options['table']) && $this->_collectionName != $options['table']) { + $this->switchCollection($options['table'],'',false); + } + + $cache = isset($options['cache'])?$options['cache']:false; + if($cache) { + $key = is_string($cache['key'])?$cache['key']:md5(serialize($options)); + $value = S($key,'','',$cache['type']); + if(false !== $value) { + return $value; + } + } + + $this->model = $options['model']; + $this->queryTimes++; + N('db_query',1); // 兼容代码 + $query = $this->parseWhere(isset($options['where'])?$options['where']:array()); + + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.group({key:'.json_encode($keys).',cond:'. + json_encode($options['condition']) . ',reduce:' . + json_encode($reduce).',initial:'. + json_encode($initial).'})'; + } + try{ + $this->debug(true); + $option = array('condition'=>$options['condition'], 'finalize'=>$options['finalize'], 'maxTimeMS'=>$options['maxTimeMS']); + $group = $this->_collection->group($keys,$initial,$reduce,$options); + $this->debug(false); + + if($cache && $group['ok']) + S($key,$group,$cache['expire'],$cache['type']); + + return $group; + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($collection=''){ + if(!empty($collection) && $collection != $this->_collectionName) { + $this->switchCollection($collection,'',false); + } + $this->queryTimes++; + N('db_query',1); // 兼容代码 + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.findOne()'; + } + try{ + $this->debug(true); + $result = $this->_collection->findOne(); + $this->debug(false); + } catch (\MongoCursorException $e) { + E($e->getMessage()); + } + if($result) { // 存在数据则分析字段 + $info = array(); + foreach ($result as $key=>$val){ + $info[$key] = array( + 'name' => $key, + 'type' => getType($val), + ); + } + return $info; + } + // 暂时没有数据 返回false + return false; + } + + /** + * 取得当前数据库的collection信息 + * @access public + */ + public function getTables(){ + if($this->config['debug']) { + $this->queryStr = $this->_dbName.'.getCollenctionNames()'; + } + $this->queryTimes++; + N('db_query',1); // 兼容代码 + $this->debug(true); + $list = $this->_mongo->listCollections(); + $this->debug(false); + $info = array(); + foreach ($list as $collection){ + $info[] = $collection->getName(); + } + return $info; + } + + /** + * 取得当前数据库的对象 + * @access public + * @return object mongoClient + */ + public function getDB(){ + return $this->_mongo; + } + + /** + * 取得当前集合的对象 + * @access public + * @return object MongoCollection + */ + public function getCollection(){ + return $this->_collection; + } + + /** + * set分析 + * @access protected + * @param array $data + * @return string + */ + protected function parseSet($data) { + $result = array(); + foreach ($data as $key=>$val){ + if(is_array($val)) { + switch($val[0]) { + case 'inc': + $result['$inc'][$key] = (int)$val[1]; + break; + case 'set': + case 'unset': + case 'push': + case 'pushall': + case 'addtoset': + case 'pop': + case 'pull': + case 'pullall': + $result['$'.$val[0]][$key] = $val[1]; + break; + default: + $result['$set'][$key] = $val; + } + }else{ + $result['$set'][$key] = $val; + } + } + return $result; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @return array + */ + protected function parseOrder($order) { + if(is_string($order)) { + $array = explode(',',$order); + $order = array(); + foreach ($array as $key=>$val){ + $arr = explode(' ',trim($val)); + if(isset($arr[1])) { + $arr[1] = $arr[1]=='asc'?1:-1; + }else{ + $arr[1] = 1; + } + $order[$arr[0]] = $arr[1]; + } + } + return $order; + } + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return array + */ + protected function parseLimit($limit) { + if(strpos($limit,',')) { + $array = explode(',',$limit); + }else{ + $array = array(0,$limit); + } + return $array; + } + + /** + * field分析 + * @access protected + * @param mixed $fields + * @return array + */ + public function parseField($fields){ + if(empty($fields)) { + $fields = array(); + } + if(is_string($fields)) { + $_fields = explode(',',$fields); + $fields = array(); + foreach ($_fields as $f) + $fields[$f] = true; + }elseif(is_array($fields)) { + $_fields = $fields; + $fields = array(); + foreach ($_fields as $f=>$v) { + if(is_numeric($f)) + $fields[$v] = true; + else + $fields[$f] = $v ? true : false; + } + } + return $fields; + } + + /** + * where分析 + * @access protected + * @param mixed $where + * @return array + */ + public function parseWhere($where){ + $query = array(); + $return = array(); + $_logic = '$and'; + if(isset($where['_logic'])){ + $where['_logic'] = strtolower($where['_logic']); + $_logic = in_array($where['_logic'], array('or','xor','nor', 'and'))?'$'.$where['_logic']:$_logic; + unset($where['_logic']); + } + foreach ($where as $key=>$val){ + if('_id' != $key && 0===strpos($key,'_')) { + // 解析特殊条件表达式 + $parse = $this->parseThinkWhere($key,$val); + $query = array_merge($query,$parse); + }else{ + // 查询字段的安全过滤 + if(!preg_match('/^[A-Z_\|\&\-.a-z0-9]+$/',trim($key))){ + E(L('_ERROR_QUERY_').':'.$key); + } + $key = trim($key); + if(strpos($key,'|')) { + $array = explode('|',$key); + $str = array(); + foreach ($array as $k){ + $str[] = $this->parseWhereItem($k,$val); + } + $query['$or'] = $str; + }elseif(strpos($key,'&')){ + $array = explode('&',$key); + $str = array(); + foreach ($array as $k){ + $str[] = $this->parseWhereItem($k,$val); + } + $query = array_merge($query,$str); + }else{ + $str = $this->parseWhereItem($key,$val); + $query = array_merge($query,$str); + } + } + } + if($_logic == '$and') + return $query; + + foreach($query as $key=>$val) + $return[$_logic][] = array($key=>$val); + + return $return; + } + + /** + * 特殊条件分析 + * @access protected + * @param string $key + * @param mixed $val + * @return string + */ + protected function parseThinkWhere($key,$val) { + $query = array(); + $_logic = array('or','xor','nor', 'and'); + + switch($key) { + case '_query': // 字符串模式查询条件 + parse_str($val,$query); + if(isset($query['_logic']) && strtolower($query['_logic']) == 'or' ) { + unset($query['_logic']); + $query['$or'] = $query; + } + break; + case '_complex': // 子查询模式查询条件 + $__logic = strtolower($val['_logic']); + if(isset($val['_logic']) && in_array($__logic, $_logic) ) { + unset($val['_logic']); + $query['$'.$__logic] = $val; + } + break; + case '_string':// MongoCode查询 + $query['$where'] = new \MongoCode($val); + break; + } + //兼容 MongoClient OR条件查询方法 + if(isset($query['$or']) && !is_array(current($query['$or']))) { + $val = array(); + foreach ($query['$or'] as $k=>$v) + $val[] = array($k=>$v); + $query['$or'] = $val; + } + return $query; + } + + /** + * where子单元分析 + * @access protected + * @param string $key + * @param mixed $val + * @return array + */ + protected function parseWhereItem($key,$val) { + $query = array(); + if(is_array($val)) { + if(is_string($val[0])) { + $con = strtolower($val[0]); + if(in_array($con,array('neq','ne','gt','egt','gte','lt','lte','elt'))) { // 比较运算 + $k = '$'.$this->comparison[$con]; + $query[$key] = array($k=>$val[1]); + }elseif('like'== $con){ // 模糊查询 采用正则方式 + $query[$key] = new \MongoRegex("/".$val[1]."/"); + }elseif('mod'==$con){ // mod 查询 + $query[$key] = array('$mod'=>$val[1]); + }elseif('regex'==$con){ // 正则查询 + $query[$key] = new \MongoRegex($val[1]); + }elseif(in_array($con,array('in','nin','not in'))){ // IN NIN 运算 + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $k = '$'.$this->comparison[$con]; + $query[$key] = array($k=>$data); + }elseif('all'==$con){ // 满足所有指定条件 + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $query[$key] = array('$all'=>$data); + }elseif('between'==$con){ // BETWEEN运算 + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $query[$key] = array('$gte'=>$data[0],'$lte'=>$data[1]); + }elseif('not between'==$con){ + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $query[$key] = array('$lt'=>$data[0],'$gt'=>$data[1]); + }elseif('exp'==$con){ // 表达式查询 + $query['$where'] = new \MongoCode($val[1]); + }elseif('exists'==$con){ // 字段是否存在 + $query[$key] = array('$exists'=>(bool)$val[1]); + }elseif('size'==$con){ // 限制属性大小 + $query[$key] = array('$size'=>intval($val[1])); + }elseif('type'==$con){ // 限制字段类型 1 浮点型 2 字符型 3 对象或者MongoDBRef 5 MongoBinData 7 MongoId 8 布尔型 9 MongoDate 10 NULL 15 MongoCode 16 32位整型 17 MongoTimestamp 18 MongoInt64 如果是数组的话判断元素的类型 + $query[$key] = array('$type'=>intval($val[1])); + }else{ + $query[$key] = $val; + } + return $query; + } + } + $query[$key] = $val; + return $query; + } +} diff --git a/ThinkPHP/Library/Think/Db/Driver/Mysql.class.php b/ThinkPHP/Library/Think/Db/Driver/Mysql.class.php new file mode 100644 index 0000000..ee0a338 --- /dev/null +++ b/ThinkPHP/Library/Think/Db/Driver/Mysql.class.php @@ -0,0 +1,235 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db\Driver; + +/** + * mysql数据库驱动 + */ +class Mysql extends Driver{ + + /** + * 解析pdo连接的dsn信息 + * @access public + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config){ + $dsn = 'mysql:dbname='.$config['database'].';host='.$config['hostname']; + if(!empty($config['hostport'])) { + $dsn .= ';port='.$config['hostport']; + }elseif(!empty($config['socket'])){ + $dsn .= ';unix_socket='.$config['socket']; + } + + if(!empty($config['charset'])){ + //为兼容各版本PHP,用两种方式设置编码 + $this->options[\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$config['charset']; + $dsn .= ';charset='.$config['charset']; + } + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + */ + public function getFields($tableName) { + $this->initConnect(true); + list($tableName) = explode(' ', $tableName); + if(strpos($tableName,'.')){ + list($dbName,$tableName) = explode('.',$tableName); + $sql = 'SHOW COLUMNS FROM `'.$dbName.'`.`'.$tableName.'`'; + }else{ + $sql = 'SHOW COLUMNS FROM `'.$tableName.'`'; + } + + $result = $this->query($sql); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + if(\PDO::CASE_LOWER != $this->_linkID->getAttribute(\PDO::ATTR_CASE)){ + $val = array_change_key_case ( $val , CASE_LOWER ); + } + $info[$val['field']] = array( + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ($val['null'] === ''), // not null is empty, null is yes + 'default' => $val['default'], + 'primary' => (strtolower($val['key']) == 'pri'), + 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + ); + } + } + return $info; + } + + /** + * 取得数据库的表信息 + * @access public + */ + public function getTables($dbName='') { + $sql = !empty($dbName)?'SHOW TABLES FROM '.$dbName:'SHOW TABLES '; + $result = $this->query($sql); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * 字段和表名处理 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(&$key) { + $key = trim($key); + if(!is_numeric($key) && !preg_match('/[,\'\"\*\(\)`.\s]/',$key)) { + $key = '`'.$key.'`'; + } + return $key; + } + + /** + * 批量插入记录 + * @access public + * @param mixed $dataSet 数据集 + * @param array $options 参数表达式 + * @param boolean $replace 是否replace + * @return false | integer + */ + public function insertAll($dataSet,$options=array(),$replace=false) { + $values = array(); + $this->model = $options['model']; + if(!is_array($dataSet[0])) return false; + $this->parseBind(!empty($options['bind'])?$options['bind']:array()); + $fields = array_map(array($this,'parseKey'),array_keys($dataSet[0])); + foreach ($dataSet as $data){ + $value = array(); + foreach ($data as $key=>$val){ + if(is_array($val) && 'exp' == $val[0]){ + $value[] = $val[1]; + }elseif(is_null($val)){ + $value[] = 'NULL'; + }elseif(is_scalar($val)){ + if(0===strpos($val,':') && in_array($val,array_keys($this->bind))){ + $value[] = $this->parseValue($val); + }else{ + $name = count($this->bind); + $value[] = ':'.$name; + $this->bindParam($name,$val); + } + } + } + $values[] = '('.implode(',', $value).')'; + } + // 兼容数字传入方式 + $replace= (is_numeric($replace) && $replace>0)?true:$replace; + $sql = (true===$replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES '.implode(',',$values).$this->parseDuplicate($replace); + $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); + } + + /** + * ON DUPLICATE KEY UPDATE 分析 + * @access protected + * @param mixed $duplicate + * @return string + */ + protected function parseDuplicate($duplicate){ + // 布尔值或空则返回空字符串 + if(is_bool($duplicate) || empty($duplicate)) return ''; + + if(is_string($duplicate)){ + // field1,field2 转数组 + $duplicate = explode(',', $duplicate); + }elseif(is_object($duplicate)){ + // 对象转数组 + $duplicate = get_class_vars($duplicate); + } + $updates = array(); + foreach((array) $duplicate as $key=>$val){ + if(is_numeric($key)){ // array('field1', 'field2', 'field3') 解析为 ON DUPLICATE KEY UPDATE field1=VALUES(field1), field2=VALUES(field2), field3=VALUES(field3) + $updates[] = $this->parseKey($val)."=VALUES(".$this->parseKey($val).")"; + }else{ + if(is_scalar($val)) // 兼容标量传值方式 + $val = array('value', $val); + if(!isset($val[1])) continue; + switch($val[0]){ + case 'exp': // 表达式 + $updates[] = $this->parseKey($key)."=($val[1])"; + break; + case 'value': // 值 + default: + $name = count($this->bind); + $updates[] = $this->parseKey($key)."=:".$name; + $this->bindParam($name, $val[1]); + break; + } + } + } + if(empty($updates)) return ''; + return " ON DUPLICATE KEY UPDATE ".join(', ', $updates); + } + + + + /** + * 执行存储过程查询 返回多个数据集 + * @access public + * @param string $str sql指令 + * @param boolean $fetchSql 不执行只是获取SQL + * @return mixed + */ + public function procedure($str,$fetchSql=false) { + $this->initConnect(false); + $this->_linkID->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_WARNING); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + if($fetchSql){ + return $this->queryStr; + } + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + $this->queryTimes++; + N('db_query',1); // 兼容代码 + // 调试开始 + $this->debug(true); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement){ + $this->error(); + return false; + } + try{ + $result = $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false); + do + { + $result = $this->PDOStatement->fetchAll(\PDO::FETCH_ASSOC); + if ($result) + { + $resultArr[] = $result; + } + } + while ($this->PDOStatement->nextRowset()); + $this->_linkID->setAttribute(\PDO::ATTR_ERRMODE, $this->options[\PDO::ATTR_ERRMODE]); + return $resultArr; + }catch (\PDOException $e) { + $this->error(); + $this->_linkID->setAttribute(\PDO::ATTR_ERRMODE, $this->options[\PDO::ATTR_ERRMODE]); + return false; + } + } +} diff --git a/ThinkPHP/Library/Think/Db/Driver/Oracle.class.php b/ThinkPHP/Library/Think/Db/Driver/Oracle.class.php new file mode 100644 index 0000000..6bf5477 --- /dev/null +++ b/ThinkPHP/Library/Think/Db/Driver/Oracle.class.php @@ -0,0 +1,168 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db\Driver; + +/** + * Oracle数据库驱动 + */ +class Oracle extends Driver{ + + private $table = ''; + protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%'; + + /** + * 解析pdo连接的dsn信息 + * @access public + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config){ + $dsn = 'oci:dbname=//'.$config['hostname'].($config['hostport']?':'.$config['hostport']:'').'/'.$config['database']; + if(!empty($config['charset'])) { + $dsn .= ';charset='.$config['charset']; + } + return $dsn; + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @param boolean $fetchSql 不执行只是获取SQL + * @return integer + */ + public function execute($str,$fetchSql=false) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + if(!empty($this->bind)){ + $that = $this; + $this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind)); + } + if($fetchSql){ + return $this->queryStr; + } + $flag = false; + if(preg_match("/^\s*(INSERT\s+INTO)\s+(\w+)\s+/i", $str, $match)) { + $this->table = C("DB_SEQUENCE_PREFIX").str_ireplace(C("DB_PREFIX"), "", $match[2]); + $flag = (boolean)$this->query("SELECT * FROM user_sequences WHERE sequence_name='" . strtoupper($this->table) . "'"); + } + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + $this->executeTimes++; + N('db_write',1); // 兼容代码 + // 记录开始执行时间 + $this->debug(true); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement) { + $this->error(); + return false; + } + foreach ($this->bind as $key => $val) { + if(is_array($val)){ + $this->PDOStatement->bindValue($key, $val[0], $val[1]); + }else{ + $this->PDOStatement->bindValue($key, $val); + } + } + $this->bind = array(); + $result = $this->PDOStatement->execute(); + $this->debug(false); + if ( false === $result) { + $this->error(); + return false; + } else { + $this->numRows = $this->PDOStatement->rowCount(); + if($flag || preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) { + $this->lastInsID = $this->_linkID->lastInsertId(); + } + return $this->numRows; + } + } + + /** + * 取得数据表的字段信息 + * @access public + */ + public function getFields($tableName) { + list($tableName) = explode(' ', $tableName); + $result = $this->query("select a.column_name,data_type,decode(nullable,'Y',0,1) notnull,data_default,decode(a.column_name,b.column_name,1,0) pk " + ."from user_tab_columns a,(select column_name from user_constraints c,user_cons_columns col " + ."where c.constraint_name=col.constraint_name and c.constraint_type='P'and c.table_name='".strtoupper($tableName) + ."') b where table_name='".strtoupper($tableName)."' and a.column_name=b.column_name(+)"); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[strtolower($val['column_name'])] = array( + 'name' => strtolower($val['column_name']), + 'type' => strtolower($val['data_type']), + 'notnull' => $val['notnull'], + 'default' => $val['data_default'], + 'primary' => $val['pk'], + 'autoinc' => $val['pk'], + ); + } + } + return $info; + } + + /** + * 取得数据库的表信息(暂时实现取得用户表信息) + * @access public + */ + public function getTables($dbName='') { + $result = $this->query("select table_name from user_tables"); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL指令 + * @return string + */ + public function escapeString($str) { + return str_ireplace("'", "''", $str); + } + + /** + * limit + * @access public + * @return string + */ + public function parseLimit($limit) { + $limitStr = ''; + if(!empty($limit)) { + $limit = explode(',',$limit); + if(count($limit)>1) + $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0]+$limit[1]) . ")"; + else + $limitStr = "(numrow>0 AND numrow<=".$limit[0].")"; + } + return $limitStr?' WHERE '.$limitStr:''; + } + + /** + * 设置锁机制 + * @access protected + * @return string + */ + protected function parseLock($lock=false) { + if(!$lock) return ''; + return ' FOR UPDATE NOWAIT '; + } +} diff --git a/ThinkPHP/Library/Think/Db/Driver/Pgsql.class.php b/ThinkPHP/Library/Think/Db/Driver/Pgsql.class.php new file mode 100644 index 0000000..e1223a4 --- /dev/null +++ b/ThinkPHP/Library/Think/Db/Driver/Pgsql.class.php @@ -0,0 +1,91 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db\Driver; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Driver{ + + /** + * 解析pdo连接的dsn信息 + * @access public + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config){ + $dsn = 'pgsql:dbname='.$config['database'].';host='.$config['hostname']; + if(!empty($config['hostport'])) { + $dsn .= ';port='.$config['hostport']; + } + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($tableName) { + list($tableName) = explode(' ', $tableName); + $result = $this->query('select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg('.$tableName.');'); + $info = array(); + if($result){ + foreach ($result as $key => $val) { + $info[$val['field']] = array( + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ($val['null'] === ''), // not null is empty, null is yes + 'default' => $val['default'], + 'primary' => (strtolower($val['key']) == 'pri'), + 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + ); + } + } + return $info; + } + + /** + * 取得数据库的表信息 + * @access public + * @return array + */ + public function getTables($dbName='') { + $result = $this->query("select tablename as Tables_in_test from pg_tables where schemaname ='public'"); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * limit分析 + * @access protected + * @param mixed $lmit + * @return string + */ + public function parseLimit($limit) { + $limitStr = ''; + if(!empty($limit)) { + $limit = explode(',',$limit); + if(count($limit)>1) { + $limitStr .= ' LIMIT '.$limit[1].' OFFSET '.$limit[0].' '; + }else{ + $limitStr .= ' LIMIT '.$limit[0].' '; + } + } + return $limitStr; + } + +} diff --git a/ThinkPHP/Library/Think/Db/Driver/Sqlite.class.php b/ThinkPHP/Library/Think/Db/Driver/Sqlite.class.php new file mode 100644 index 0000000..d7fd641 --- /dev/null +++ b/ThinkPHP/Library/Think/Db/Driver/Sqlite.class.php @@ -0,0 +1,98 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db\Driver; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Driver { + + /** + * 解析pdo连接的dsn信息 + * @access public + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config){ + $dsn = 'sqlite:'.$config['database']; + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($tableName) { + list($tableName) = explode(' ', $tableName); + $result = $this->query('PRAGMA table_info( '.$tableName.' )'); + $info = array(); + if($result){ + foreach ($result as $key => $val) { + $info[$val['field']] = array( + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ($val['null'] === ''), // not null is empty, null is yes + 'default' => $val['default'], + 'primary' => (strtolower($val['dey']) == 'pri'), + 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + ); + } + } + return $info; + } + + /** + * 取得数据库的表信息 + * @access public + * @return array + */ + public function getTables($dbName='') { + $result = $this->query("SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL指令 + * @return string + */ + public function escapeString($str) { + return str_ireplace("'", "''", $str); + } + + /** + * limit + * @access public + * @return string + */ + public function parseLimit($limit) { + $limitStr = ''; + if(!empty($limit)) { + $limit = explode(',',$limit); + if(count($limit)>1) { + $limitStr .= ' LIMIT '.$limit[1].' OFFSET '.$limit[0].' '; + }else{ + $limitStr .= ' LIMIT '.$limit[0].' '; + } + } + return $limitStr; + } +} diff --git a/ThinkPHP/Library/Think/Db/Driver/Sqlsrv.class.php b/ThinkPHP/Library/Think/Db/Driver/Sqlsrv.class.php new file mode 100644 index 0000000..9df1c92 --- /dev/null +++ b/ThinkPHP/Library/Think/Db/Driver/Sqlsrv.class.php @@ -0,0 +1,166 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db\Driver; +use PDO; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Driver{ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING% %UNION%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + // PDO连接参数 + protected $options = array( + PDO::ATTR_CASE => PDO::CASE_LOWER, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::SQLSRV_ATTR_ENCODING => PDO::SQLSRV_ENCODING_UTF8, + ); + + /** + * 解析pdo连接的dsn信息 + * @access public + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config){ + $dsn = 'sqlsrv:Database='.$config['database'].';Server='.$config['hostname']; + if(!empty($config['hostport'])) { + $dsn .= ','.$config['hostport']; + } + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($tableName) { + list($tableName) = explode(' ', $tableName); + $result = $this->query("SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[$val['column_name']] = array( + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ($val['is_nullable'] === ''), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ); + } + } + return $info; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getTables($dbName='') { + $result = $this->query("SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @return string + */ + protected function parseOrder($order) { + return !empty($order)? ' ORDER BY '.$order:' ORDER BY rand()'; + } + + /** + * 字段名分析 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(&$key) { + $key = trim($key); + if(!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/',$key)) { + $key = '['.$key.']'; + } + return $key; + } + + /** + * limit + * @access public + * @param mixed $limit + * @return string + */ + public function parseLimit($limit) { + if(empty($limit)) return ''; + $limit = explode(',',$limit); + if(count($limit)>1) + $limitStr = '(T1.ROW_NUMBER BETWEEN '.$limit[0].' + 1 AND '.$limit[0].' + '.$limit[1].')'; + else + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND '.$limit[0].")"; + return 'WHERE '.$limitStr; + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return false | integer + */ + public function update($data,$options) { + $this->model = $options['model']; + $this->parseBind(!empty($options['bind'])?$options['bind']:array()); + $sql = 'UPDATE ' + .$this->parseTable($options['table']) + .$this->parseSet($data) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); + } + + /** + * 删除记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function delete($options=array()) { + $this->model = $options['model']; + $this->parseBind(!empty($options['bind'])?$options['bind']:array()); + $sql = 'DELETE FROM ' + .$this->parseTable($options['table']) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Db/Lite.class.php b/ThinkPHP/Library/Think/Db/Lite.class.php new file mode 100644 index 0000000..577c44b --- /dev/null +++ b/ThinkPHP/Library/Think/Db/Lite.class.php @@ -0,0 +1,466 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db; +use Think\Config; +use Think\Debug; +use Think\Log; +use PDO; + +class Lite { + // PDO操作实例 + protected $PDOStatement = null; + // 当前操作所属的模型名 + protected $model = '_think_'; + // 当前SQL指令 + protected $queryStr = ''; + protected $modelSql = array(); + // 最后插入ID + protected $lastInsID = null; + // 返回或者影响记录数 + protected $numRows = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + // 数据库连接ID 支持多个连接 + protected $linkID = array(); + // 当前连接ID + protected $_linkID = null; + // 数据库连接参数配置 + protected $config = array( + 'type' => '', // 数据库类型 + 'hostname' => '127.0.0.1', // 服务器地址 + 'database' => '', // 数据库名 + 'username' => '', // 用户名 + 'password' => '', // 密码 + 'hostport' => '', // 端口 + 'dsn' => '', // + 'params' => array(), // 数据库连接参数 + 'charset' => 'utf8', // 数据库编码默认采用utf8 + 'prefix' => '', // 数据库表前缀 + 'debug' => false, // 数据库调试模式 + 'deploy' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'rw_separate' => false, // 数据库读写是否分离 主从式有效 + 'master_num' => 1, // 读写分离后 主服务器数量 + 'slave_no' => '', // 指定从服务器序号 + ); + // 数据库表达式 + protected $comparison = array('eq'=>'=','neq'=>'<>','gt'=>'>','egt'=>'>=','lt'=>'<','elt'=>'<=','notlike'=>'NOT LIKE','like'=>'LIKE','in'=>'IN','notin'=>'NOT IN'); + // 查询表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%COMMENT%'; + // 查询次数 + protected $queryTimes = 0; + // 执行次数 + protected $executeTimes = 0; + // PDO连接参数 + protected $options = array( + PDO::ATTR_CASE => PDO::CASE_LOWER, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ); + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + if(!empty($config)) { + $this->config = array_merge($this->config,$config); + if(is_array($this->config['params'])){ + $this->options += $this->config['params']; + } + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + try{ + if(empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + if(version_compare(PHP_VERSION,'5.3.6','<=')){ //禁用模拟预处理语句 + $this->options[PDO::ATTR_EMULATE_PREPARES] = false; + } + $this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$this->options); + }catch (\PDOException $e) { + E($e->getMessage()); + } + } + return $this->linkID[$linkNum]; + } + + /** + * 解析pdo连接的dsn信息 + * @access public + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config){} + + /** + * 释放查询结果 + * @access public + */ + public function free() { + $this->PDOStatement = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @param array $bind 参数绑定 + * @return mixed + */ + public function query($str,$bind=array()) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + if(!empty($bind)){ + $that = $this; + $this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$bind)); + } + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + $this->queryTimes++; + N('db_query',1); // 兼容代码 + // 调试开始 + $this->debug(true); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement) + E($this->error()); + foreach ($bind as $key => $val) { + if(is_array($val)){ + $this->PDOStatement->bindValue($key, $val[0], $val[1]); + }else{ + $this->PDOStatement->bindValue($key, $val); + } + } + $result = $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false); + if ( false === $result ) { + $this->error(); + return false; + } else { + return $this->getResult(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @param array $bind 参数绑定 + * @return integer + */ + public function execute($str,$bind=array()) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + if(!empty($bind)){ + $that = $this; + $this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$bind)); + } + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + $this->executeTimes++; + N('db_write',1); // 兼容代码 + // 记录开始执行时间 + $this->debug(true); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement) { + E($this->error()); + } + foreach ($bind as $key => $val) { + if(is_array($val)){ + $this->PDOStatement->bindValue($key, $val[0], $val[1]); + }else{ + $this->PDOStatement->bindValue($key, $val); + } + } + $result = $this->PDOStatement->execute(); + $this->debug(false); + if ( false === $result) { + $this->error(); + return false; + } else { + $this->numRows = $this->PDOStatement->rowCount(); + if(preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) { + $this->lastInsID = $this->_linkID->lastInsertId(); + } + return $this->numRows; + } + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + $this->_linkID->beginTransaction(); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolean + */ + public function commit() { + if ($this->transTimes > 0) { + $result = $this->_linkID->commit(); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolean + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = $this->_linkID->rollback(); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getResult() { + //返回数据集 + $result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC); + $this->numRows = count( $result ); + return $result; + } + + /** + * 获得查询次数 + * @access public + * @param boolean $execute 是否包含所有查询 + * @return integer + */ + public function getQueryTimes($execute=false){ + return $execute?$this->queryTimes+$this->executeTimes:$this->queryTimes; + } + + /** + * 获得执行次数 + * @access public + * @return integer + */ + public function getExecuteTimes(){ + return $this->executeTimes; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error() { + if($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $this->error = $error[1].':'.$error[2]; + }else{ + $this->error = ''; + } + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + // 记录错误日志 + trace($this->error,'','ERR'); + if($this->config['debug']) {// 开启数据库调试模式 + E($this->error); + }else{ + return $this->error; + } + } + + /** + * 获取最近一次查询的sql语句 + * @param string $model 模型名 + * @access public + * @return string + */ + public function getLastSql($model='') { + return $model?$this->modelSql[$model]:$this->queryStr; + } + + /** + * 获取最近插入的ID + * @access public + * @return string + */ + public function getLastInsID() { + return $this->lastInsID; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() { + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL字符串 + * @return string + */ + public function escapeString($str) { + return addslashes($str); + } + + /** + * 设置当前操作模型 + * @access public + * @param string $model 模型名 + * @return void + */ + public function setModel($model){ + $this->model = $model; + } + + /** + * 数据库调试 记录当前SQL + * @access protected + * @param boolean $start 调试开始标记 true 开始 false 结束 + */ + protected function debug($start) { + if($this->config['debug']) {// 开启数据库调试模式 + if($start) { + G('queryStartTime'); + }else{ + $this->modelSql[$this->model] = $this->queryStr; + //$this->model = '_think_'; + // 记录操作结束时间 + G('queryEndTime'); + trace($this->queryStr.' [ RunTime:'.G('queryStartTime','queryEndTime').'s ]','','SQL'); + } + } + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 主服务器 + * @return void + */ + protected function initConnect($master=true) { + if(!empty($this->config['deploy'])) + // 采用分布式数据库 + $this->_linkID = $this->multiConnect($master); + else + // 默认单数据库 + if ( !$this->_linkID ) $this->_linkID = $this->connect(); + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return void + */ + protected function multiConnect($master=false) { + // 分布式数据库配置解析 + $_config['username'] = explode(',',$this->config['username']); + $_config['password'] = explode(',',$this->config['password']); + $_config['hostname'] = explode(',',$this->config['hostname']); + $_config['hostport'] = explode(',',$this->config['hostport']); + $_config['database'] = explode(',',$this->config['database']); + $_config['dsn'] = explode(',',$this->config['dsn']); + $_config['charset'] = explode(',',$this->config['charset']); + + // 数据库读写是否分离 + if($this->config['rw_separate']){ + // 主从式采用读写分离 + if($master) + // 主服务器写入 + $r = floor(mt_rand(0,$this->config['master_num']-1)); + else{ + if(is_numeric($this->config['slave_no'])) {// 指定服务器读 + $r = $this->config['slave_no']; + }else{ + // 读操作连接从服务器 + $r = floor(mt_rand($this->config['master_num'],count($_config['hostname'])-1)); // 每次随机连接的数据库 + } + } + }else{ + // 读写操作不区分服务器 + $r = floor(mt_rand(0,count($_config['hostname'])-1)); // 每次随机连接的数据库 + } + $db_config = array( + 'username' => isset($_config['username'][$r])?$_config['username'][$r]:$_config['username'][0], + 'password' => isset($_config['password'][$r])?$_config['password'][$r]:$_config['password'][0], + 'hostname' => isset($_config['hostname'][$r])?$_config['hostname'][$r]:$_config['hostname'][0], + 'hostport' => isset($_config['hostport'][$r])?$_config['hostport'][$r]:$_config['hostport'][0], + 'database' => isset($_config['database'][$r])?$_config['database'][$r]:$_config['database'][0], + 'dsn' => isset($_config['dsn'][$r])?$_config['dsn'][$r]:$_config['dsn'][0], + 'charset' => isset($_config['charset'][$r])?$_config['charset'][$r]:$_config['charset'][0], + ); + return $this->connect($db_config,$r); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() { + // 释放查询 + if ($this->PDOStatement){ + $this->free(); + } + // 关闭连接 + $this->close(); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Dispatcher.class.php b/ThinkPHP/Library/Think/Dispatcher.class.php new file mode 100644 index 0000000..0065cfb --- /dev/null +++ b/ThinkPHP/Library/Think/Dispatcher.class.php @@ -0,0 +1,339 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP内置的Dispatcher类 + * 完成URL解析、路由和调度 + */ +class Dispatcher { + + /** + * URL映射到控制器 + * @access public + * @return void + */ + static public function dispatch() { + $varPath = C('VAR_PATHINFO'); + $varAddon = C('VAR_ADDON'); + $varModule = C('VAR_MODULE'); + $varController = C('VAR_CONTROLLER'); + $varAction = C('VAR_ACTION'); + $urlCase = C('URL_CASE_INSENSITIVE'); + if(isset($_GET[$varPath])) { // 判断URL里面是否有兼容模式参数 + $_SERVER['PATH_INFO'] = $_GET[$varPath]; + unset($_GET[$varPath]); + }elseif(IS_CLI){ // CLI模式下 index.php module/controller/action/params/... + $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } + + // 开启子域名部署 + if(C('APP_SUB_DOMAIN_DEPLOY')) { + $rules = C('APP_SUB_DOMAIN_RULES'); + if(isset($rules[$_SERVER['HTTP_HOST']])) { // 完整域名或者IP配置 + define('APP_DOMAIN',$_SERVER['HTTP_HOST']); // 当前完整域名 + $rule = $rules[APP_DOMAIN]; + }else{ + if(strpos(C('APP_DOMAIN_SUFFIX'),'.')){ // com.cn net.cn + $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -3); + }else{ + $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -2); + } + if(!empty($domain)) { + $subDomain = implode('.', $domain); + define('SUB_DOMAIN',$subDomain); // 当前完整子域名 + $domain2 = array_pop($domain); // 二级域名 + if($domain) { // 存在三级域名 + $domain3 = array_pop($domain); + } + if(isset($rules[$subDomain])) { // 子域名 + $rule = $rules[$subDomain]; + }elseif(isset($rules['*.' . $domain2]) && !empty($domain3)){ // 泛三级域名 + $rule = $rules['*.' . $domain2]; + $panDomain = $domain3; + }elseif(isset($rules['*']) && !empty($domain2) && 'www' != $domain2 ){ // 泛二级域名 + $rule = $rules['*']; + $panDomain = $domain2; + } + } + } + + if(!empty($rule)) { + // 子域名部署规则 '子域名'=>array('模块名[/控制器名]','var1=a&var2=b'); + if(is_array($rule)){ + list($rule,$vars) = $rule; + } + $array = explode('/',$rule); + // 模块绑定 + define('BIND_MODULE',array_shift($array)); + // 控制器绑定 + if(!empty($array)) { + $controller = array_shift($array); + if($controller){ + define('BIND_CONTROLLER',$controller); + } + } + if(isset($vars)) { // 传入参数 + parse_str($vars,$parms); + if(isset($panDomain)){ + $pos = array_search('*', $parms); + if(false !== $pos) { + // 泛域名作为参数 + $parms[$pos] = $panDomain; + } + } + $_GET = array_merge($_GET,$parms); + } + } + } + // 分析PATHINFO信息 + if(!isset($_SERVER['PATH_INFO'])) { + $types = explode(',',C('URL_PATHINFO_FETCH')); + foreach ($types as $type){ + if(0===strpos($type,':')) {// 支持函数判断 + $_SERVER['PATH_INFO'] = call_user_func(substr($type,1)); + break; + }elseif(!empty($_SERVER[$type])) { + $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type],$_SERVER['SCRIPT_NAME']))? + substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + break; + } + } + } + + $depr = C('URL_PATHINFO_DEPR'); + define('MODULE_PATHINFO_DEPR', $depr); + + if(empty($_SERVER['PATH_INFO'])) { + $_SERVER['PATH_INFO'] = ''; + define('__INFO__',''); + define('__EXT__',''); + }else{ + define('__INFO__',trim($_SERVER['PATH_INFO'],'/')); + // URL后缀 + define('__EXT__', strtolower(pathinfo($_SERVER['PATH_INFO'],PATHINFO_EXTENSION))); + $_SERVER['PATH_INFO'] = __INFO__; + if(!defined('BIND_MODULE') && (!C('URL_ROUTER_ON') || !Route::check())){ + if (__INFO__ && C('MULTI_MODULE')){ // 获取模块名 + $paths = explode($depr,__INFO__,2); + $allowList = C('MODULE_ALLOW_LIST'); // 允许的模块列表 + $module = preg_replace('/\.' . __EXT__ . '$/i', '',$paths[0]); + if( empty($allowList) || (is_array($allowList) && in_array_case($module, $allowList))){ + $_GET[$varModule] = $module; + $_SERVER['PATH_INFO'] = isset($paths[1])?$paths[1]:''; + } + } + } + } + + // URL常量 + define('__SELF__',strip_tags($_SERVER[C('URL_REQUEST_URI')])); + + // 获取模块名称 + define('MODULE_NAME', defined('BIND_MODULE')? BIND_MODULE : self::getModule($varModule)); + + // 检测模块是否存在 + if( MODULE_NAME && (defined('BIND_MODULE') || !in_array_case(MODULE_NAME,C('MODULE_DENY_LIST')) ) && is_dir(APP_PATH.MODULE_NAME)){ + // 定义当前模块路径 + define('MODULE_PATH', APP_PATH.MODULE_NAME.'/'); + // 定义当前模块的模版缓存路径 + C('CACHE_PATH',CACHE_PATH.MODULE_NAME.'/'); + // 定义当前模块的日志目录 + C('LOG_PATH', realpath(LOG_PATH).'/'.MODULE_NAME.'/'); + + // 模块检测 + Hook::listen('module_check'); + + // 加载模块配置文件 + if(is_file(MODULE_PATH.'Conf/config'.CONF_EXT)) + C(load_config(MODULE_PATH.'Conf/config'.CONF_EXT)); + // 加载应用模式对应的配置文件 + if('common' != APP_MODE && is_file(MODULE_PATH.'Conf/config_'.APP_MODE.CONF_EXT)) + C(load_config(MODULE_PATH.'Conf/config_'.APP_MODE.CONF_EXT)); + // 当前应用状态对应的配置文件 + if(APP_STATUS && is_file(MODULE_PATH.'Conf/'.APP_STATUS.CONF_EXT)) + C(load_config(MODULE_PATH.'Conf/'.APP_STATUS.CONF_EXT)); + + // 加载模块别名定义 + if(is_file(MODULE_PATH.'Conf/alias.php')) + Think::addMap(include MODULE_PATH.'Conf/alias.php'); + // 加载模块tags文件定义 + if(is_file(MODULE_PATH.'Conf/tags.php')) + Hook::import(include MODULE_PATH.'Conf/tags.php'); + // 加载模块函数文件 + if(is_file(MODULE_PATH.'Common/function.php')) + include MODULE_PATH.'Common/function.php'; + + $urlCase = C('URL_CASE_INSENSITIVE'); + // 加载模块的扩展配置文件 + load_ext_file(MODULE_PATH); + }else{ + E(L('_MODULE_NOT_EXIST_').':'.MODULE_NAME); + } + + if(!defined('__APP__')){ + $urlMode = C('URL_MODEL'); + if($urlMode == URL_COMPAT ){// 兼容模式判断 + define('PHP_FILE',_PHP_FILE_.'?'.$varPath.'='); + }elseif($urlMode == URL_REWRITE ) { + $url = dirname(_PHP_FILE_); + if($url == '/' || $url == '\\') + $url = ''; + define('PHP_FILE',$url); + }else { + define('PHP_FILE',_PHP_FILE_); + } + // 当前应用地址 + define('__APP__',strip_tags(PHP_FILE)); + } + // 模块URL地址 + $moduleName = defined('MODULE_ALIAS')? MODULE_ALIAS : MODULE_NAME; + define('__MODULE__',(defined('BIND_MODULE') || !C('MULTI_MODULE'))? __APP__ : __APP__.'/'.($urlCase ? strtolower($moduleName) : $moduleName)); + + if('' != $_SERVER['PATH_INFO'] && (!C('URL_ROUTER_ON') || !Route::check()) ){ // 检测路由规则 如果没有则按默认规则调度URL + Hook::listen('path_info'); + // 检查禁止访问的URL后缀 + if(C('URL_DENY_SUFFIX') && preg_match('/\.('.trim(C('URL_DENY_SUFFIX'),'.').')$/i', $_SERVER['PATH_INFO'])){ + send_http_status(404); + exit; + } + + // 去除URL后缀 + $_SERVER['PATH_INFO'] = preg_replace(C('URL_HTML_SUFFIX')? '/\.('.trim(C('URL_HTML_SUFFIX'),'.').')$/i' : '/\.'.__EXT__.'$/i', '', $_SERVER['PATH_INFO']); + + $depr = C('URL_PATHINFO_DEPR'); + $paths = explode($depr,trim($_SERVER['PATH_INFO'],$depr)); + + if(!defined('BIND_CONTROLLER')) {// 获取控制器 + if(C('CONTROLLER_LEVEL')>1){// 控制器层次 + $_GET[$varController] = implode('/',array_slice($paths,0,C('CONTROLLER_LEVEL'))); + $paths = array_slice($paths, C('CONTROLLER_LEVEL')); + }else{ + $_GET[$varController] = array_shift($paths); + } + } + // 获取操作 + if(!defined('BIND_ACTION')){ + $_GET[$varAction] = array_shift($paths); + } + // 解析剩余的URL参数 + $var = array(); + if(C('URL_PARAMS_BIND') && 1 == C('URL_PARAMS_BIND_TYPE')){ + // URL参数按顺序绑定变量 + $var = $paths; + }else{ + preg_replace_callback('/(\w+)\/([^\/]+)/', function($match) use(&$var){$var[$match[1]]=strip_tags($match[2]);}, implode('/',$paths)); + } + $_GET = array_merge($var,$_GET); + } + // 获取控制器的命名空间(路径) + define('CONTROLLER_PATH', self::getSpace($varAddon,$urlCase)); + // 获取控制器和操作名 + define('CONTROLLER_NAME', defined('BIND_CONTROLLER')? BIND_CONTROLLER : self::getController($varController,$urlCase)); + define('ACTION_NAME', defined('BIND_ACTION')? BIND_ACTION : self::getAction($varAction,$urlCase)); + + // 当前控制器的UR地址 + $controllerName = defined('CONTROLLER_ALIAS')? CONTROLLER_ALIAS : CONTROLLER_NAME; + define('__CONTROLLER__',__MODULE__.$depr.(defined('BIND_CONTROLLER')? '': ( $urlCase ? parse_name($controllerName) : $controllerName )) ); + + // 当前操作的URL地址 + define('__ACTION__',__CONTROLLER__.$depr.(defined('ACTION_ALIAS')?ACTION_ALIAS:ACTION_NAME)); + + //保证$_REQUEST正常取值 + $_REQUEST = array_merge($_POST,$_GET,$_COOKIE); // -- 加了$_COOKIE. 保证哦.. + } + + /** + * 获得控制器的命名空间路径 便于插件机制访问 + */ + static private function getSpace($var,$urlCase) { + $space = !empty($_GET[$var])?strip_tags($_GET[$var]):''; + unset($_GET[$var]); + return $space; + } + + /** + * 获得实际的控制器名称 + */ + static private function getController($var,$urlCase) { + $controller = (!empty($_GET[$var])? $_GET[$var]:C('DEFAULT_CONTROLLER')); + unset($_GET[$var]); + if($maps = C('URL_CONTROLLER_MAP')) { + if(isset($maps[strtolower($controller)])) { + // 记录当前别名 + define('CONTROLLER_ALIAS',strtolower($controller)); + // 获取实际的控制器名 + return ucfirst($maps[CONTROLLER_ALIAS]); + }elseif(array_search(strtolower($controller),$maps)){ + // 禁止访问原始控制器 + return ''; + } + } + if($urlCase) { + // URL地址不区分大小写 + // 智能识别方式 user_type 识别到 UserTypeController 控制器 + $controller = parse_name($controller,1); + } + return strip_tags(ucfirst($controller)); + } + + /** + * 获得实际的操作名称 + */ + static private function getAction($var,$urlCase) { + $action = !empty($_POST[$var]) ? + $_POST[$var] : + (!empty($_GET[$var])?$_GET[$var]:C('DEFAULT_ACTION')); + unset($_POST[$var],$_GET[$var]); + if($maps = C('URL_ACTION_MAP')) { + if(isset($maps[strtolower(CONTROLLER_NAME)])) { + $maps = $maps[strtolower(CONTROLLER_NAME)]; + if(isset($maps[strtolower($action)])) { + // 记录当前别名 + define('ACTION_ALIAS',strtolower($action)); + // 获取实际的操作名 + if(is_array($maps[ACTION_ALIAS])){ + parse_str($maps[ACTION_ALIAS][1],$vars); + $_GET = array_merge($_GET,$vars); + return $maps[ACTION_ALIAS][0]; + }else{ + return $maps[ACTION_ALIAS]; + } + + }elseif(array_search(strtolower($action),$maps)){ + // 禁止访问原始操作 + return ''; + } + } + } + return strip_tags( $urlCase? strtolower($action) : $action ); + } + + /** + * 获得实际的模块名称 + */ + static private function getModule($var) { + $module = (!empty($_GET[$var])?$_GET[$var]:C('DEFAULT_MODULE')); + unset($_GET[$var]); + if($maps = C('URL_MODULE_MAP')) { + if(isset($maps[strtolower($module)])) { + // 记录当前别名 + define('MODULE_ALIAS',strtolower($module)); + // 获取实际的模块名 + return ucfirst($maps[MODULE_ALIAS]); + }elseif(array_search(strtolower($module),$maps)){ + // 禁止访问原始模块 + return ''; + } + } + return strip_tags(ucfirst($module)); + } + +} diff --git a/ThinkPHP/Library/Think/Exception.class.php b/ThinkPHP/Library/Think/Exception.class.php new file mode 100644 index 0000000..7b4a918 --- /dev/null +++ b/ThinkPHP/Library/Think/Exception.class.php @@ -0,0 +1,16 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP系统异常基类 + */ +class Exception extends \Exception { +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Hook.class.php b/ThinkPHP/Library/Think/Hook.class.php new file mode 100644 index 0000000..51176c7 --- /dev/null +++ b/ThinkPHP/Library/Think/Hook.class.php @@ -0,0 +1,121 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP系统钩子实现 + */ +class Hook { + + static private $tags = array(); + + /** + * 动态添加插件到某个标签 + * @param string $tag 标签名称 + * @param mixed $name 插件名称 + * @return void + */ + static public function add($tag,$name) { + if(!isset(self::$tags[$tag])){ + self::$tags[$tag] = array(); + } + if(is_array($name)){ + self::$tags[$tag] = array_merge(self::$tags[$tag],$name); + }else{ + self::$tags[$tag][] = $name; + } + } + + /** + * 批量导入插件 + * @param array $data 插件信息 + * @param boolean $recursive 是否递归合并 + * @return void + */ + static public function import($data,$recursive=true) { + if(!$recursive){ // 覆盖导入 + self::$tags = array_merge(self::$tags,$data); + }else{ // 合并导入 + foreach ($data as $tag=>$val){ + if(!isset(self::$tags[$tag])) + self::$tags[$tag] = array(); + if(!empty($val['_overlay'])){ + // 可以针对某个标签指定覆盖模式 + unset($val['_overlay']); + self::$tags[$tag] = $val; + }else{ + // 合并模式 + self::$tags[$tag] = array_merge(self::$tags[$tag],$val); + } + } + } + } + + /** + * 获取插件信息 + * @param string $tag 插件位置 留空获取全部 + * @return array + */ + static public function get($tag='') { + if(empty($tag)){ + // 获取全部的插件信息 + return self::$tags; + }else{ + return self::$tags[$tag]; + } + } + + /** + * 监听标签的插件 + * @param string $tag 标签名称 + * @param mixed $params 传入参数 + * @return void + */ + static public function listen($tag, &$params=NULL) { + if(isset(self::$tags[$tag])) { + if(APP_DEBUG) { + G($tag.'Start'); + trace('[ '.$tag.' ] --START--','','INFO'); + } + foreach (self::$tags[$tag] as $name) { + APP_DEBUG && G($name.'_start'); + $result = self::exec($name, $tag,$params); + if(APP_DEBUG){ + G($name.'_end'); + trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO'); + } + if(false === $result) { + // 如果返回false 则中断插件执行 + return ; + } + } + if(APP_DEBUG) { // 记录行为的执行日志 + trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO'); + } + } + return; + } + + /** + * 执行某个插件 + * @param string $name 插件名称 + * @param string $tag 方法名(标签名) + * @param Mixed $params 传入的参数 + * @return void + */ + static public function exec($name, $tag,&$params=NULL) { + if('Behavior' == substr($name,-8) ){ + // 行为扩展必须用run入口方法 + $tag = 'run'; + } + $addon = new $name(); + return $addon->$tag($params); + } +} diff --git a/ThinkPHP/Library/Think/Image.class.php b/ThinkPHP/Library/Think/Image.class.php new file mode 100644 index 0000000..178365e --- /dev/null +++ b/ThinkPHP/Library/Think/Image.class.php @@ -0,0 +1,191 @@ + +// +---------------------------------------------------------------------- +// | ThinkImage.class.php 2013-03-05 +// +---------------------------------------------------------------------- + +namespace Think; + +/** + * 图片处理驱动类,可配置图片处理库 + * 目前支持GD库和imagick + * @author 麦当苗儿 + */ +class Image{ + /* 驱动相关常量定义 */ + const IMAGE_GD = 1; //常量,标识GD库类型 + const IMAGE_IMAGICK = 2; //常量,标识imagick库类型 + + /* 缩略图相关常量定义 */ + const IMAGE_THUMB_SCALE = 1 ; //常量,标识缩略图等比例缩放类型 + const IMAGE_THUMB_FILLED = 2 ; //常量,标识缩略图缩放后填充类型 + const IMAGE_THUMB_CENTER = 3 ; //常量,标识缩略图居中裁剪类型 + const IMAGE_THUMB_NORTHWEST = 4 ; //常量,标识缩略图左上角裁剪类型 + const IMAGE_THUMB_SOUTHEAST = 5 ; //常量,标识缩略图右下角裁剪类型 + const IMAGE_THUMB_FIXED = 6 ; //常量,标识缩略图固定尺寸缩放类型 + + /* 水印相关常量定义 */ + const IMAGE_WATER_NORTHWEST = 1 ; //常量,标识左上角水印 + const IMAGE_WATER_NORTH = 2 ; //常量,标识上居中水印 + const IMAGE_WATER_NORTHEAST = 3 ; //常量,标识右上角水印 + const IMAGE_WATER_WEST = 4 ; //常量,标识左居中水印 + const IMAGE_WATER_CENTER = 5 ; //常量,标识居中水印 + const IMAGE_WATER_EAST = 6 ; //常量,标识右居中水印 + const IMAGE_WATER_SOUTHWEST = 7 ; //常量,标识左下角水印 + const IMAGE_WATER_SOUTH = 8 ; //常量,标识下居中水印 + const IMAGE_WATER_SOUTHEAST = 9 ; //常量,标识右下角水印 + + /** + * 图片资源 + * @var resource + */ + private $img; + + /** + * 构造方法,用于实例化一个图片处理对象 + * @param string $type 要使用的类库,默认使用GD库 + */ + public function __construct($type = self::IMAGE_GD, $imgname = null){ + /* 判断调用库的类型 */ + switch ($type) { + case self::IMAGE_GD: + $class = 'Gd'; + break; + case self::IMAGE_IMAGICK: + $class = 'Imagick'; + break; + default: + E('不支持的图片处理库类型'); + } + + /* 引入处理库,实例化图片处理对象 */ + $class = "Think\\Image\\Driver\\{$class}"; + $this->img = new $class($imgname); + } + + /** + * 打开一幅图像 + * @param string $imgname 图片路径 + * @return Object 当前图片处理库对象 + */ + public function open($imgname){ + $this->img->open($imgname); + return $this; + } + + /** + * 保存图片 + * @param string $imgname 图片保存名称 + * @param string $type 图片类型 + * @param integer $quality 图像质量 + * @param boolean $interlace 是否对JPEG类型图片设置隔行扫描 + * @return Object 当前图片处理库对象 + */ + public function save($imgname, $type = null, $quality=80,$interlace = true){ + $this->img->save($imgname, $type, $quality,$interlace); + return $this; + } + + /** + * 返回图片宽度 + * @return integer 图片宽度 + */ + public function width(){ + return $this->img->width(); + } + + /** + * 返回图片高度 + * @return integer 图片高度 + */ + public function height(){ + return $this->img->height(); + } + + /** + * 返回图像类型 + * @return string 图片类型 + */ + public function type(){ + return $this->img->type(); + } + + /** + * 返回图像MIME类型 + * @return string 图像MIME类型 + */ + public function mime(){ + return $this->img->mime(); + } + + /** + * 返回图像尺寸数组 0 - 图片宽度,1 - 图片高度 + * @return array 图片尺寸 + */ + public function size(){ + return $this->img->size(); + } + + /** + * 裁剪图片 + * @param integer $w 裁剪区域宽度 + * @param integer $h 裁剪区域高度 + * @param integer $x 裁剪区域x坐标 + * @param integer $y 裁剪区域y坐标 + * @param integer $width 图片保存宽度 + * @param integer $height 图片保存高度 + * @return Object 当前图片处理库对象 + */ + public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null){ + $this->img->crop($w, $h, $x, $y, $width, $height); + return $this; + } + + /** + * 生成缩略图 + * @param integer $width 缩略图最大宽度 + * @param integer $height 缩略图最大高度 + * @param integer $type 缩略图裁剪类型 + * @return Object 当前图片处理库对象 + */ + public function thumb($width, $height, $type = self::IMAGE_THUMB_SCALE){ + $this->img->thumb($width, $height, $type); + return $this; + } + + /** + * 添加水印 + * @param string $source 水印图片路径 + * @param integer $locate 水印位置 + * @param integer $alpha 水印透明度 + * @return Object 当前图片处理库对象 + */ + public function water($source, $locate = self::IMAGE_WATER_SOUTHEAST,$alpha=80){ + $this->img->water($source, $locate,$alpha); + return $this; + } + + /** + * 图像添加文字 + * @param string $text 添加的文字 + * @param string $font 字体路径 + * @param integer $size 字号 + * @param string $color 文字颜色 + * @param integer $locate 文字写入位置 + * @param integer $offset 文字相对当前位置的偏移量 + * @param integer $angle 文字倾斜角度 + * @return Object 当前图片处理库对象 + */ + public function text($text, $font, $size, $color = '#00000000', + $locate = self::IMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0){ + $this->img->text($text, $font, $size, $color, $locate, $offset, $angle); + return $this; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Image/Driver/GIF.class.php b/ThinkPHP/Library/Think/Image/Driver/GIF.class.php new file mode 100644 index 0000000..359d2ae --- /dev/null +++ b/ThinkPHP/Library/Think/Image/Driver/GIF.class.php @@ -0,0 +1,567 @@ + +// +---------------------------------------------------------------------- +// | GIF.class.php 2013-03-09 +// +---------------------------------------------------------------------- +namespace Think\Image\Driver; +class GIF{ + /** + * GIF帧列表 + * @var array + */ + private $frames = array(); + + /** + * 每帧等待时间列表 + * @var array + */ + private $delays = array(); + + /** + * 构造方法,用于解码GIF图片 + * @param string $src GIF图片数据 + * @param string $mod 图片数据类型 + */ + public function __construct($src = null, $mod = 'url') { + if(!is_null($src)){ + if('url' == $mod && is_file($src)){ + $src = file_get_contents($src); + } + + /* 解码GIF图片 */ + try{ + $de = new GIFDecoder($src); + $this->frames = $de->GIFGetFrames(); + $this->delays = $de->GIFGetDelays(); + } catch(\Exception $e){ + E("解码GIF图片出错"); + } + } + } + + /** + * 设置或获取当前帧的数据 + * @param string $stream 二进制数据流 + * @return boolean 获取到的数据 + */ + public function image($stream = null){ + if(is_null($stream)){ + $current = current($this->frames); + return false === $current ? reset($this->frames) : $current; + } else { + $this->frames[key($this->frames)] = $stream; + } + } + + /** + * 将当前帧移动到下一帧 + * @return string 当前帧数据 + */ + public function nextImage(){ + return next($this->frames); + } + + /** + * 编码并保存当前GIF图片 + * @param string $gifname 图片名称 + */ + public function save($gifname){ + $gif = new GIFEncoder($this->frames, $this->delays, 0, 2, 0, 0, 0, 'bin'); + file_put_contents($gifname, $gif->GetAnimation()); + } + +} + + +/* +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: GIFEncoder Version 2.0 by László Zsidi, http://gifs.hu +:: +:: This class is a rewritten 'GifMerge.class.php' version. +:: +:: Modification: +:: - Simplified and easy code, +:: - Ultra fast encoding, +:: - Built-in errors, +:: - Stable working +:: +:: +:: Updated at 2007. 02. 13. '00.05.AM' +:: +:: +:: +:: Try on-line GIFBuilder Form demo based on GIFEncoder. +:: +:: http://gifs.hu/phpclasses/demos/GifBuilder/ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +*/ + +Class GIFEncoder { + private $GIF = "GIF89a"; /* GIF header 6 bytes */ + private $VER = "GIFEncoder V2.05"; /* Encoder version */ + + private $BUF = Array ( ); + private $LOP = 0; + private $DIS = 2; + private $COL = -1; + private $IMG = -1; + + private $ERR = Array ( + 'ERR00' => "Does not supported function for only one image!", + 'ERR01' => "Source is not a GIF image!", + 'ERR02' => "Unintelligible flag ", + 'ERR03' => "Does not make animation from animated GIF source", + ); + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFEncoder... + :: + */ + public function __construct($GIF_src, $GIF_dly, $GIF_lop, $GIF_dis,$GIF_red, $GIF_grn, $GIF_blu, $GIF_mod) { + if ( ! is_array ( $GIF_src ) && ! is_array ( $GIF_dly ) ) { + printf ( "%s: %s", $this->VER, $this->ERR [ 'ERR00' ] ); + exit ( 0 ); + } + $this->LOP = ( $GIF_lop > -1 ) ? $GIF_lop : 0; + $this->DIS = ( $GIF_dis > -1 ) ? ( ( $GIF_dis < 3 ) ? $GIF_dis : 3 ) : 2; + $this->COL = ( $GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1 ) ? + ( $GIF_red | ( $GIF_grn << 8 ) | ( $GIF_blu << 16 ) ) : -1; + + for ( $i = 0; $i < count ( $GIF_src ); $i++ ) { + if ( strToLower ( $GIF_mod ) == "url" ) { + $this->BUF [ ] = fread ( fopen ( $GIF_src [ $i ], "rb" ), filesize ( $GIF_src [ $i ] ) ); + } + else if ( strToLower ( $GIF_mod ) == "bin" ) { + $this->BUF [ ] = $GIF_src [ $i ]; + } + else { + printf ( "%s: %s ( %s )!", $this->VER, $this->ERR [ 'ERR02' ], $GIF_mod ); + exit ( 0 ); + } + if ( substr ( $this->BUF [ $i ], 0, 6 ) != "GIF87a" && substr ( $this->BUF [ $i ], 0, 6 ) != "GIF89a" ) { + printf ( "%s: %d %s", $this->VER, $i, $this->ERR [ 'ERR01' ] ); + exit ( 0 ); + } + for ( $j = ( 13 + 3 * ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ) ), $k = TRUE; $k; $j++ ) { + switch ( $this->BUF [ $i ] { $j } ) { + case "!": + if ( ( substr ( $this->BUF [ $i ], ( $j + 3 ), 8 ) ) == "NETSCAPE" ) { + printf ( "%s: %s ( %s source )!", $this->VER, $this->ERR [ 'ERR03' ], ( $i + 1 ) ); + exit ( 0 ); + } + break; + case ";": + $k = FALSE; + break; + } + } + } + $this->GIFAddHeader ( ); + for ( $i = 0; $i < count ( $this->BUF ); $i++ ) { + $this->GIFAddFrames ( $i, $GIF_dly [ $i ] ); + } + $this->GIFAddFooter ( ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddHeader... + :: + */ + private function GIFAddHeader ( ) { + $cmap = 0; + + if ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x80 ) { + $cmap = 3 * ( 2 << ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ) ); + + $this->GIF .= substr ( $this->BUF [ 0 ], 6, 7 ); + $this->GIF .= substr ( $this->BUF [ 0 ], 13, $cmap ); + $this->GIF .= "!\377\13NETSCAPE2.0\3\1" . $this->GIFWord ( $this->LOP ) . "\0"; + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddFrames... + :: + */ + private function GIFAddFrames ( $i, $d ) { + + $Locals_str = 13 + 3 * ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ); + + $Locals_end = strlen ( $this->BUF [ $i ] ) - $Locals_str - 1; + $Locals_tmp = substr ( $this->BUF [ $i ], $Locals_str, $Locals_end ); + + $Global_len = 2 << ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ); + $Locals_len = 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ); + + $Global_rgb = substr ( $this->BUF [ 0 ], 13, + 3 * ( 2 << ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ) ) ); + $Locals_rgb = substr ( $this->BUF [ $i ], 13, + 3 * ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ) ); + + $Locals_ext = "!\xF9\x04" . chr ( ( $this->DIS << 2 ) + 0 ) . + chr ( ( $d >> 0 ) & 0xFF ) . chr ( ( $d >> 8 ) & 0xFF ) . "\x0\x0"; + + if ( $this->COL > -1 && ord ( $this->BUF [ $i ] { 10 } ) & 0x80 ) { + for ( $j = 0; $j < ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ); $j++ ) { + if ( + ord ( $Locals_rgb { 3 * $j + 0 } ) == ( ( $this->COL >> 16 ) & 0xFF ) && + ord ( $Locals_rgb { 3 * $j + 1 } ) == ( ( $this->COL >> 8 ) & 0xFF ) && + ord ( $Locals_rgb { 3 * $j + 2 } ) == ( ( $this->COL >> 0 ) & 0xFF ) + ) { + $Locals_ext = "!\xF9\x04" . chr ( ( $this->DIS << 2 ) + 1 ) . + chr ( ( $d >> 0 ) & 0xFF ) . chr ( ( $d >> 8 ) & 0xFF ) . chr ( $j ) . "\x0"; + break; + } + } + } + switch ( $Locals_tmp { 0 } ) { + case "!": + $Locals_img = substr ( $Locals_tmp, 8, 10 ); + $Locals_tmp = substr ( $Locals_tmp, 18, strlen ( $Locals_tmp ) - 18 ); + break; + case ",": + $Locals_img = substr ( $Locals_tmp, 0, 10 ); + $Locals_tmp = substr ( $Locals_tmp, 10, strlen ( $Locals_tmp ) - 10 ); + break; + } + if ( ord ( $this->BUF [ $i ] { 10 } ) & 0x80 && $this->IMG > -1 ) { + if ( $Global_len == $Locals_len ) { + if ( $this->GIFBlockCompare ( $Global_rgb, $Locals_rgb, $Global_len ) ) { + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_tmp ); + } + else { + $byte = ord ( $Locals_img { 9 } ); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ); + $Locals_img { 9 } = chr ( $byte ); + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp ); + } + } + else { + $byte = ord ( $Locals_img { 9 } ); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ); + $Locals_img { 9 } = chr ( $byte ); + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp ); + } + } + else { + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_tmp ); + } + $this->IMG = 1; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddFooter... + :: + */ + private function GIFAddFooter ( ) { + $this->GIF .= ";"; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFBlockCompare... + :: + */ + private function GIFBlockCompare ( $GlobalBlock, $LocalBlock, $Len ) { + + for ( $i = 0; $i < $Len; $i++ ) { + if ( + $GlobalBlock { 3 * $i + 0 } != $LocalBlock { 3 * $i + 0 } || + $GlobalBlock { 3 * $i + 1 } != $LocalBlock { 3 * $i + 1 } || + $GlobalBlock { 3 * $i + 2 } != $LocalBlock { 3 * $i + 2 } + ) { + return ( 0 ); + } + } + + return ( 1 ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFWord... + :: + */ + private function GIFWord ( $int ) { + + return ( chr ( $int & 0xFF ) . chr ( ( $int >> 8 ) & 0xFF ) ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GetAnimation... + :: + */ + public function GetAnimation ( ) { + return ( $this->GIF ); + } +} + + +/* +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: GIFDecoder Version 2.0 by László Zsidi, http://gifs.hu +:: +:: Created at 2007. 02. 01. '07.47.AM' +:: +:: +:: +:: +:: Try on-line GIFBuilder Form demo based on GIFDecoder. +:: +:: http://gifs.hu/phpclasses/demos/GifBuilder/ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +*/ + +Class GIFDecoder { + private $GIF_buffer = Array ( ); + private $GIF_arrays = Array ( ); + private $GIF_delays = Array ( ); + private $GIF_stream = ""; + private $GIF_string = ""; + private $GIF_bfseek = 0; + + private $GIF_screen = Array ( ); + private $GIF_global = Array ( ); + private $GIF_sorted; + private $GIF_colorS; + private $GIF_colorC; + private $GIF_colorF; + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFDecoder ( $GIF_pointer ) + :: + */ + public function __construct ( $GIF_pointer ) { + $this->GIF_stream = $GIF_pointer; + + $this->GIFGetByte ( 6 ); // GIF89a + $this->GIFGetByte ( 7 ); // Logical Screen Descriptor + + $this->GIF_screen = $this->GIF_buffer; + $this->GIF_colorF = $this->GIF_buffer [ 4 ] & 0x80 ? 1 : 0; + $this->GIF_sorted = $this->GIF_buffer [ 4 ] & 0x08 ? 1 : 0; + $this->GIF_colorC = $this->GIF_buffer [ 4 ] & 0x07; + $this->GIF_colorS = 2 << $this->GIF_colorC; + + if ( $this->GIF_colorF == 1 ) { + $this->GIFGetByte ( 3 * $this->GIF_colorS ); + $this->GIF_global = $this->GIF_buffer; + } + /* + * + * 05.06.2007. + * Made a little modification + * + * + - for ( $cycle = 1; $cycle; ) { + + if ( GIFDecoder::GIFGetByte ( 1 ) ) { + - switch ( $this->GIF_buffer [ 0 ] ) { + - case 0x21: + - GIFDecoder::GIFReadExtensions ( ); + - break; + - case 0x2C: + - GIFDecoder::GIFReadDescriptor ( ); + - break; + - case 0x3B: + - $cycle = 0; + - break; + - } + - } + + else { + + $cycle = 0; + + } + - } + */ + for ( $cycle = 1; $cycle; ) { + if ( $this->GIFGetByte ( 1 ) ) { + switch ( $this->GIF_buffer [ 0 ] ) { + case 0x21: + $this->GIFReadExtensions ( ); + break; + case 0x2C: + $this->GIFReadDescriptor ( ); + break; + case 0x3B: + $cycle = 0; + break; + } + } + else { + $cycle = 0; + } + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFReadExtension ( ) + :: + */ + private function GIFReadExtensions ( ) { + $this->GIFGetByte ( 1 ); + for ( ; ; ) { + $this->GIFGetByte ( 1 ); + if ( ( $u = $this->GIF_buffer [ 0 ] ) == 0x00 ) { + break; + } + $this->GIFGetByte ( $u ); + /* + * 07.05.2007. + * Implemented a new line for a new function + * to determine the originaly delays between + * frames. + * + */ + if ( $u == 4 ) { + $this->GIF_delays [ ] = ( $this->GIF_buffer [ 1 ] | $this->GIF_buffer [ 2 ] << 8 ); + } + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFReadExtension ( ) + :: + */ + private function GIFReadDescriptor ( ) { + $GIF_screen = Array ( ); + + $this->GIFGetByte ( 9 ); + $GIF_screen = $this->GIF_buffer; + $GIF_colorF = $this->GIF_buffer [ 8 ] & 0x80 ? 1 : 0; + if ( $GIF_colorF ) { + $GIF_code = $this->GIF_buffer [ 8 ] & 0x07; + $GIF_sort = $this->GIF_buffer [ 8 ] & 0x20 ? 1 : 0; + } + else { + $GIF_code = $this->GIF_colorC; + $GIF_sort = $this->GIF_sorted; + } + $GIF_size = 2 << $GIF_code; + $this->GIF_screen [ 4 ] &= 0x70; + $this->GIF_screen [ 4 ] |= 0x80; + $this->GIF_screen [ 4 ] |= $GIF_code; + if ( $GIF_sort ) { + $this->GIF_screen [ 4 ] |= 0x08; + } + $this->GIF_string = "GIF87a"; + $this->GIFPutByte ( $this->GIF_screen ); + if ( $GIF_colorF == 1 ) { + $this->GIFGetByte ( 3 * $GIF_size ); + $this->GIFPutByte ( $this->GIF_buffer ); + } + else { + $this->GIFPutByte ( $this->GIF_global ); + } + $this->GIF_string .= chr ( 0x2C ); + $GIF_screen [ 8 ] &= 0x40; + $this->GIFPutByte ( $GIF_screen ); + $this->GIFGetByte ( 1 ); + $this->GIFPutByte ( $this->GIF_buffer ); + for ( ; ; ) { + $this->GIFGetByte ( 1 ); + $this->GIFPutByte ( $this->GIF_buffer ); + if ( ( $u = $this->GIF_buffer [ 0 ] ) == 0x00 ) { + break; + } + $this->GIFGetByte ( $u ); + $this->GIFPutByte ( $this->GIF_buffer ); + } + $this->GIF_string .= chr ( 0x3B ); + /* + Add frames into $GIF_stream array... + */ + $this->GIF_arrays [ ] = $this->GIF_string; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFGetByte ( $len ) + :: + */ + + /* + * + * 05.06.2007. + * Made a little modification + * + * + - function GIFGetByte ( $len ) { + - $this->GIF_buffer = Array ( ); + - + - for ( $i = 0; $i < $len; $i++ ) { + + if ( $this->GIF_bfseek > strlen ( $this->GIF_stream ) ) { + + return 0; + + } + - $this->GIF_buffer [ ] = ord ( $this->GIF_stream { $this->GIF_bfseek++ } ); + - } + + return 1; + - } + */ + private function GIFGetByte ( $len ) { + $this->GIF_buffer = Array ( ); + + for ( $i = 0; $i < $len; $i++ ) { + if ( $this->GIF_bfseek > strlen ( $this->GIF_stream ) ) { + return 0; + } + $this->GIF_buffer [ ] = ord ( $this->GIF_stream { $this->GIF_bfseek++ } ); + } + return 1; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFPutByte ( $bytes ) + :: + */ + private function GIFPutByte ( $bytes ) { + for ( $i = 0; $i < count ( $bytes ); $i++ ) { + $this->GIF_string .= chr ( $bytes [ $i ] ); + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: PUBLIC FUNCTIONS + :: + :: + :: GIFGetFrames ( ) + :: + */ + public function GIFGetFrames ( ) { + return ( $this->GIF_arrays ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFGetDelays ( ) + :: + */ + public function GIFGetDelays ( ) { + return ( $this->GIF_delays ); + } +} diff --git a/ThinkPHP/Library/Think/Image/Driver/Gd.class.php b/ThinkPHP/Library/Think/Image/Driver/Gd.class.php new file mode 100644 index 0000000..0df7554 --- /dev/null +++ b/ThinkPHP/Library/Think/Image/Driver/Gd.class.php @@ -0,0 +1,542 @@ + +// +---------------------------------------------------------------------- +// | ImageGd.class.php 2013-03-05 +// +---------------------------------------------------------------------- +namespace Think\Image\Driver; +use Think\Image; +class Gd{ + /** + * 图像资源对象 + * @var resource + */ + private $img; + + /** + * 图像信息,包括width,height,type,mime,size + * @var array + */ + private $info; + + /** + * 构造方法,可用于打开一张图像 + * @param string $imgname 图像路径 + */ + public function __construct($imgname = null) { + $imgname && $this->open($imgname); + } + + /** + * 打开一张图像 + * @param string $imgname 图像路径 + */ + public function open($imgname){ + //检测图像文件 + if(!is_file($imgname)) E('不存在的图像文件'); + + //获取图像信息 + $info = getimagesize($imgname); + + //检测图像合法性 + if(false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))){ + E('非法图像文件'); + } + + //设置图像信息 + $this->info = array( + 'width' => $info[0], + 'height' => $info[1], + 'type' => image_type_to_extension($info[2], false), + 'mime' => $info['mime'], + ); + + //销毁已存在的图像 + empty($this->img) || imagedestroy($this->img); + + //打开图像 + if('gif' == $this->info['type']){ + $class = 'Think\\Image\\Driver\\GIF'; + $this->gif = new $class($imgname); + $this->img = imagecreatefromstring($this->gif->image()); + } else { + $fun = "imagecreatefrom{$this->info['type']}"; + $this->img = $fun($imgname); + } + } + + /** + * 保存图像 + * @param string $imgname 图像保存名称 + * @param string $type 图像类型 + * @param integer $quality 图像质量 + * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描 + */ + public function save($imgname, $type = null, $quality=80,$interlace = true){ + if(empty($this->img)) E('没有可以被保存的图像资源'); + + //自动获取图像类型 + if(is_null($type)){ + $type = $this->info['type']; + } else { + $type = strtolower($type); + } + //保存图像 + if('jpeg' == $type || 'jpg' == $type){ + //JPEG图像设置隔行扫描 + imageinterlace($this->img, $interlace); + imagejpeg($this->img, $imgname,$quality); + }elseif('gif' == $type && !empty($this->gif)){ + $this->gif->save($imgname); + }else{ + $fun = 'image'.$type; + $fun($this->img, $imgname); + } + } + + /** + * 返回图像宽度 + * @return integer 图像宽度 + */ + public function width(){ + if(empty($this->img)) E('没有指定图像资源'); + return $this->info['width']; + } + + /** + * 返回图像高度 + * @return integer 图像高度 + */ + public function height(){ + if(empty($this->img)) E('没有指定图像资源'); + return $this->info['height']; + } + + /** + * 返回图像类型 + * @return string 图像类型 + */ + public function type(){ + if(empty($this->img)) E('没有指定图像资源'); + return $this->info['type']; + } + + /** + * 返回图像MIME类型 + * @return string 图像MIME类型 + */ + public function mime(){ + if(empty($this->img)) E('没有指定图像资源'); + return $this->info['mime']; + } + + /** + * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度 + * @return array 图像尺寸 + */ + public function size(){ + if(empty($this->img)) E('没有指定图像资源'); + return array($this->info['width'], $this->info['height']); + } + + /** + * 裁剪图像 + * @param integer $w 裁剪区域宽度 + * @param integer $h 裁剪区域高度 + * @param integer $x 裁剪区域x坐标 + * @param integer $y 裁剪区域y坐标 + * @param integer $width 图像保存宽度 + * @param integer $height 图像保存高度 + */ + public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null){ + if(empty($this->img)) E('没有可以被裁剪的图像资源'); + + //设置保存尺寸 + empty($width) && $width = $w; + empty($height) && $height = $h; + + do { + //创建新图像 + $img = imagecreatetruecolor($width, $height); + // 调整默认颜色 + $color = imagecolorallocate($img, 255, 255, 255); + imagefill($img, 0, 0, $color); + + //裁剪 + imagecopyresampled($img, $this->img, 0, 0, $x, $y, $width, $height, $w, $h); + imagedestroy($this->img); //销毁原图 + + //设置新图像 + $this->img = $img; + } while(!empty($this->gif) && $this->gifNext()); + + $this->info['width'] = $width; + $this->info['height'] = $height; + } + + /** + * 生成缩略图 + * @param integer $width 缩略图最大宽度 + * @param integer $height 缩略图最大高度 + * @param integer $type 缩略图裁剪类型 + */ + public function thumb($width, $height, $type = Image::IMAGE_THUMB_SCALE){ + if(empty($this->img)) E('没有可以被缩略的图像资源'); + + //原图宽度和高度 + $w = $this->info['width']; + $h = $this->info['height']; + + /* 计算缩略图生成的必要参数 */ + switch ($type) { + /* 等比例缩放 */ + case Image::IMAGE_THUMB_SCALE: + //原图尺寸小于缩略图尺寸则不进行缩略 + if($w < $width && $h < $height) return; + + //计算缩放比例 + $scale = min($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $width = $w * $scale; + $height = $h * $scale; + break; + + /* 居中裁剪 */ + case Image::IMAGE_THUMB_CENTER: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = ($this->info['width'] - $w)/2; + $y = ($this->info['height'] - $h)/2; + break; + + /* 左上角裁剪 */ + case Image::IMAGE_THUMB_NORTHWEST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $w = $width/$scale; + $h = $height/$scale; + break; + + /* 右下角裁剪 */ + case Image::IMAGE_THUMB_SOUTHEAST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = $this->info['width'] - $w; + $y = $this->info['height'] - $h; + break; + + /* 填充 */ + case Image::IMAGE_THUMB_FILLED: + //计算缩放比例 + if($w < $width && $h < $height){ + $scale = 1; + } else { + $scale = min($width/$w, $height/$h); + } + + //设置缩略图的坐标及宽度和高度 + $neww = $w * $scale; + $newh = $h * $scale; + $posx = ($width - $w * $scale)/2; + $posy = ($height - $h * $scale)/2; + + do{ + //创建新图像 + $img = imagecreatetruecolor($width, $height); + // 调整默认颜色 + $color = imagecolorallocate($img, 255, 255, 255); + imagefill($img, 0, 0, $color); + + //裁剪 + imagecopyresampled($img, $this->img, $posx, $posy, $x, $y, $neww, $newh, $w, $h); + imagedestroy($this->img); //销毁原图 + $this->img = $img; + } while(!empty($this->gif) && $this->gifNext()); + + $this->info['width'] = $width; + $this->info['height'] = $height; + return; + + /* 固定 */ + case Image::IMAGE_THUMB_FIXED: + $x = $y = 0; + break; + + default: + E('不支持的缩略图裁剪类型'); + } + + /* 裁剪图像 */ + $this->crop($w, $h, $x, $y, $width, $height); + } + + /** + * 添加水印 + * @param string $source 水印图片路径 + * @param integer $locate 水印位置 + * @param integer $alpha 水印透明度 + */ + public function water($source, $locate = Image::IMAGE_WATER_SOUTHEAST,$alpha=80){ + //资源检测 + if(empty($this->img)) E('没有可以被添加水印的图像资源'); + if(!is_file($source)) E('水印图像不存在'); + + //获取水印图像信息 + $info = getimagesize($source); + if(false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))){ + E('非法水印文件'); + } + + //创建水印图像资源 + $fun = 'imagecreatefrom' . image_type_to_extension($info[2], false); + $water = $fun($source); + + //设定水印图像的混色模式 + imagealphablending($water, true); + + /* 设定水印位置 */ + switch ($locate) { + /* 右下角水印 */ + case Image::IMAGE_WATER_SOUTHEAST: + $x = $this->info['width'] - $info[0]; + $y = $this->info['height'] - $info[1]; + break; + + /* 左下角水印 */ + case Image::IMAGE_WATER_SOUTHWEST: + $x = 0; + $y = $this->info['height'] - $info[1]; + break; + + /* 左上角水印 */ + case Image::IMAGE_WATER_NORTHWEST: + $x = $y = 0; + break; + + /* 右上角水印 */ + case Image::IMAGE_WATER_NORTHEAST: + $x = $this->info['width'] - $info[0]; + $y = 0; + break; + + /* 居中水印 */ + case Image::IMAGE_WATER_CENTER: + $x = ($this->info['width'] - $info[0])/2; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 下居中水印 */ + case Image::IMAGE_WATER_SOUTH: + $x = ($this->info['width'] - $info[0])/2; + $y = $this->info['height'] - $info[1]; + break; + + /* 右居中水印 */ + case Image::IMAGE_WATER_EAST: + $x = $this->info['width'] - $info[0]; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 上居中水印 */ + case Image::IMAGE_WATER_NORTH: + $x = ($this->info['width'] - $info[0])/2; + $y = 0; + break; + + /* 左居中水印 */ + case Image::IMAGE_WATER_WEST: + $x = 0; + $y = ($this->info['height'] - $info[1])/2; + break; + + default: + /* 自定义水印坐标 */ + if(is_array($locate)){ + list($x, $y) = $locate; + } else { + E('不支持的水印位置类型'); + } + } + + do{ + //添加水印 + $src = imagecreatetruecolor($info[0], $info[1]); + // 调整默认颜色 + $color = imagecolorallocate($src, 255, 255, 255); + imagefill($src, 0, 0, $color); + + imagecopy($src, $this->img, 0, 0, $x, $y, $info[0], $info[1]); + imagecopy($src, $water, 0, 0, 0, 0, $info[0], $info[1]); + imagecopymerge($this->img, $src, $x, $y, 0, 0, $info[0], $info[1], $alpha); + + //销毁零时图片资源 + imagedestroy($src); + } while(!empty($this->gif) && $this->gifNext()); + + //销毁水印资源 + imagedestroy($water); + } + + /** + * 图像添加文字 + * @param string $text 添加的文字 + * @param string $font 字体路径 + * @param integer $size 字号 + * @param string $color 文字颜色 + * @param integer $locate 文字写入位置 + * @param integer $offset 文字相对当前位置的偏移量 + * @param integer $angle 文字倾斜角度 + */ + public function text($text, $font, $size, $color = '#00000000', + $locate = Image::IMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0){ + //资源检测 + if(empty($this->img)) E('没有可以被写入文字的图像资源'); + if(!is_file($font)) E("不存在的字体文件:{$font}"); + + //获取文字信息 + $info = imagettfbbox($size, $angle, $font, $text); + $minx = min($info[0], $info[2], $info[4], $info[6]); + $maxx = max($info[0], $info[2], $info[4], $info[6]); + $miny = min($info[1], $info[3], $info[5], $info[7]); + $maxy = max($info[1], $info[3], $info[5], $info[7]); + + /* 计算文字初始坐标和尺寸 */ + $x = $minx; + $y = abs($miny); + $w = $maxx - $minx; + $h = $maxy - $miny; + + /* 设定文字位置 */ + switch ($locate) { + /* 右下角文字 */ + case Image::IMAGE_WATER_SOUTHEAST: + $x += $this->info['width'] - $w; + $y += $this->info['height'] - $h; + break; + + /* 左下角文字 */ + case Image::IMAGE_WATER_SOUTHWEST: + $y += $this->info['height'] - $h; + break; + + /* 左上角文字 */ + case Image::IMAGE_WATER_NORTHWEST: + // 起始坐标即为左上角坐标,无需调整 + break; + + /* 右上角文字 */ + case Image::IMAGE_WATER_NORTHEAST: + $x += $this->info['width'] - $w; + break; + + /* 居中文字 */ + case Image::IMAGE_WATER_CENTER: + $x += ($this->info['width'] - $w)/2; + $y += ($this->info['height'] - $h)/2; + break; + + /* 下居中文字 */ + case Image::IMAGE_WATER_SOUTH: + $x += ($this->info['width'] - $w)/2; + $y += $this->info['height'] - $h; + break; + + /* 右居中文字 */ + case Image::IMAGE_WATER_EAST: + $x += $this->info['width'] - $w; + $y += ($this->info['height'] - $h)/2; + break; + + /* 上居中文字 */ + case Image::IMAGE_WATER_NORTH: + $x += ($this->info['width'] - $w)/2; + break; + + /* 左居中文字 */ + case Image::IMAGE_WATER_WEST: + $y += ($this->info['height'] - $h)/2; + break; + + default: + /* 自定义文字坐标 */ + if(is_array($locate)){ + list($posx, $posy) = $locate; + $x += $posx; + $y += $posy; + } else { + E('不支持的文字位置类型'); + } + } + + /* 设置偏移量 */ + if(is_array($offset)){ + $offset = array_map('intval', $offset); + list($ox, $oy) = $offset; + } else{ + $offset = intval($offset); + $ox = $oy = $offset; + } + + /* 设置颜色 */ + if(is_string($color) && 0 === strpos($color, '#')){ + $color = str_split(substr($color, 1), 2); + $color = array_map('hexdec', $color); + if(empty($color[3]) || $color[3] > 127){ + $color[3] = 0; + } + } elseif (!is_array($color)) { + E('错误的颜色值'); + } + + do{ + /* 写入文字 */ + $col = imagecolorallocatealpha($this->img, $color[0], $color[1], $color[2], $color[3]); + imagettftext($this->img, $size, $angle, $x + $ox, $y + $oy, $col, $font, $text); + } while(!empty($this->gif) && $this->gifNext()); + } + + /* 切换到GIF的下一帧并保存当前帧,内部使用 */ + private function gifNext(){ + ob_start(); + ob_implicit_flush(0); + imagegif($this->img); + $img = ob_get_clean(); + + $this->gif->image($img); + $next = $this->gif->nextImage(); + + if($next){ + $this->img = imagecreatefromstring($next); + return $next; + } else { + $this->img = imagecreatefromstring($this->gif->image()); + return false; + } + } + + /** + * 析构方法,用于销毁图像资源 + */ + public function __destruct() { + empty($this->img) || imagedestroy($this->img); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Image/Driver/Imagick.class.php b/ThinkPHP/Library/Think/Image/Driver/Imagick.class.php new file mode 100644 index 0000000..df952a6 --- /dev/null +++ b/ThinkPHP/Library/Think/Image/Driver/Imagick.class.php @@ -0,0 +1,593 @@ + +// +---------------------------------------------------------------------- +// | ImageImagick.class.php 2013-03-06 +// +---------------------------------------------------------------------- +namespace Think\Image\Driver; +use Think\Image; +class Imagick{ + /** + * 图像资源对象 + * @var resource + */ + private $img; + + /** + * 图像信息,包括width,height,type,mime,size + * @var array + */ + private $info; + + /** + * 构造方法,可用于打开一张图像 + * @param string $imgname 图像路径 + */ + public function __construct($imgname = null) { + $imgname && $this->open($imgname); + } + + /** + * 打开一张图像 + * @param string $imgname 图像路径 + */ + public function open($imgname){ + //检测图像文件 + if(!is_file($imgname)) E('不存在的图像文件'); + + //销毁已存在的图像 + empty($this->img) || $this->img->destroy(); + + //载入图像 + $this->img = new \Imagick(realpath($imgname)); + + //设置图像信息 + $this->info = array( + 'width' => $this->img->getImageWidth(), + 'height' => $this->img->getImageHeight(), + 'type' => strtolower($this->img->getImageFormat()), + 'mime' => $this->img->getImageMimeType(), + ); + } + + /** + * 保存图像 + * @param string $imgname 图像保存名称 + * @param string $type 图像类型 + * @param integer $quality JPEG图像质量 + * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描 + */ + public function save($imgname, $type = null, $quality=80,$interlace = true){ + if(empty($this->img)) E('没有可以被保存的图像资源'); + + //设置图片类型 + if(is_null($type)){ + $type = $this->info['type']; + } else { + $type = strtolower($type); + $this->img->setImageFormat($type); + } + + //JPEG图像设置隔行扫描 + if('jpeg' == $type || 'jpg' == $type){ + $this->img->setImageInterlaceScheme(1); + } + + // 设置图像质量 + $this->img->setImageCompressionQuality($quality); + + //去除图像配置信息 + $this->img->stripImage(); + + //保存图像 + $imgname = realpath(dirname($imgname)) . '/' . basename($imgname); //强制绝对路径 + if ('gif' == $type) { + $this->img->writeImages($imgname, true); + } else { + $this->img->writeImage($imgname); + } + } + + /** + * 返回图像宽度 + * @return integer 图像宽度 + */ + public function width(){ + if(empty($this->img)) E('没有指定图像资源'); + return $this->info['width']; + } + + /** + * 返回图像高度 + * @return integer 图像高度 + */ + public function height(){ + if(empty($this->img)) E('没有指定图像资源'); + return $this->info['height']; + } + + /** + * 返回图像类型 + * @return string 图像类型 + */ + public function type(){ + if(empty($this->img)) E('没有指定图像资源'); + return $this->info['type']; + } + + /** + * 返回图像MIME类型 + * @return string 图像MIME类型 + */ + public function mime(){ + if(empty($this->img)) E('没有指定图像资源'); + return $this->info['mime']; + } + + /** + * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度 + * @return array 图像尺寸 + */ + public function size(){ + if(empty($this->img)) E('没有指定图像资源'); + return array($this->info['width'], $this->info['height']); + } + + /** + * 裁剪图像 + * @param integer $w 裁剪区域宽度 + * @param integer $h 裁剪区域高度 + * @param integer $x 裁剪区域x坐标 + * @param integer $y 裁剪区域y坐标 + * @param integer $width 图像保存宽度 + * @param integer $height 图像保存高度 + */ + public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null){ + if(empty($this->img)) E('没有可以被裁剪的图像资源'); + + //设置保存尺寸 + empty($width) && $width = $w; + empty($height) && $height = $h; + + //裁剪图片 + if('gif' == $this->info['type']){ + $img = $this->img->coalesceImages(); + $this->img->destroy(); //销毁原图 + + //循环裁剪每一帧 + do { + $this->_crop($w, $h, $x, $y, $width, $height, $img); + } while ($img->nextImage()); + + //压缩图片 + $this->img = $img->deconstructImages(); + $img->destroy(); //销毁零时图片 + } else { + $this->_crop($w, $h, $x, $y, $width, $height); + } + } + + /* 裁剪图片,内部调用 */ + private function _crop($w, $h, $x, $y, $width, $height, $img = null){ + is_null($img) && $img = $this->img; + + //裁剪 + $info = $this->info; + if($x != 0 || $y != 0 || $w != $info['width'] || $h != $info['height']){ + $img->cropImage($w, $h, $x, $y); + $img->setImagePage($w, $h, 0, 0); //调整画布和图片一致 + } + + //调整大小 + if($w != $width || $h != $height){ + $img->sampleImage($width, $height); + } + + //设置缓存尺寸 + $this->info['width'] = $width; + $this->info['height'] = $height; + } + + /** + * 生成缩略图 + * @param integer $width 缩略图最大宽度 + * @param integer $height 缩略图最大高度 + * @param integer $type 缩略图裁剪类型 + */ + public function thumb($width, $height, $type = Image::IMAGE_THUMB_SCALE){ + if(empty($this->img)) E('没有可以被缩略的图像资源'); + + //原图宽度和高度 + $w = $this->info['width']; + $h = $this->info['height']; + + /* 计算缩略图生成的必要参数 */ + switch ($type) { + /* 等比例缩放 */ + case Image::IMAGE_THUMB_SCALE: + //原图尺寸小于缩略图尺寸则不进行缩略 + if($w < $width && $h < $height) return; + + //计算缩放比例 + $scale = min($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $width = $w * $scale; + $height = $h * $scale; + break; + + /* 居中裁剪 */ + case Image::IMAGE_THUMB_CENTER: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = ($this->info['width'] - $w)/2; + $y = ($this->info['height'] - $h)/2; + break; + + /* 左上角裁剪 */ + case Image::IMAGE_THUMB_NORTHWEST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $w = $width/$scale; + $h = $height/$scale; + break; + + /* 右下角裁剪 */ + case Image::IMAGE_THUMB_SOUTHEAST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = $this->info['width'] - $w; + $y = $this->info['height'] - $h; + break; + + /* 填充 */ + case Image::IMAGE_THUMB_FILLED: + //计算缩放比例 + if($w < $width && $h < $height){ + $scale = 1; + } else { + $scale = min($width/$w, $height/$h); + } + + //设置缩略图的坐标及宽度和高度 + $neww = $w * $scale; + $newh = $h * $scale; + $posx = ($width - $w * $scale)/2; + $posy = ($height - $h * $scale)/2; + + //创建一张新图像 + $newimg = new \Imagick(); + $newimg->newImage($width, $height, 'white', $this->info['type']); + + + if('gif' == $this->info['type']){ + $imgs = $this->img->coalesceImages(); + $img = new \Imagick(); + $this->img->destroy(); //销毁原图 + + //循环填充每一帧 + do { + //填充图像 + $image = $this->_fill($newimg, $posx, $posy, $neww, $newh, $imgs); + + $img->addImage($image); + $img->setImageDelay($imgs->getImageDelay()); + $img->setImagePage($width, $height, 0, 0); + + $image->destroy(); //销毁零时图片 + + } while ($imgs->nextImage()); + + //压缩图片 + $this->img->destroy(); + $this->img = $img->deconstructImages(); + $imgs->destroy(); //销毁零时图片 + $img->destroy(); //销毁零时图片 + + } else { + //填充图像 + $img = $this->_fill($newimg, $posx, $posy, $neww, $newh); + //销毁原图 + $this->img->destroy(); + $this->img = $img; + } + + //设置新图像属性 + $this->info['width'] = $width; + $this->info['height'] = $height; + return; + + /* 固定 */ + case Image::IMAGE_THUMB_FIXED: + $x = $y = 0; + break; + + default: + E('不支持的缩略图裁剪类型'); + } + + /* 裁剪图像 */ + $this->crop($w, $h, $x, $y, $width, $height); + } + + /* 填充指定图像,内部使用 */ + private function _fill($newimg, $posx, $posy, $neww, $newh, $img = null){ + is_null($img) && $img = $this->img; + + /* 将指定图片绘入空白图片 */ + $draw = new \ImagickDraw(); + $draw->composite($img->getImageCompose(), $posx, $posy, $neww, $newh, $img); + $image = $newimg->clone(); + $image->drawImage($draw); + $draw->destroy(); + + return $image; + } + + /** + * 添加水印 + * @param string $source 水印图片路径 + * @param integer $locate 水印位置 + * @param integer $alpha 水印透明度 + */ + public function water($source, $locate = Image::IMAGE_WATER_SOUTHEAST,$alpha=80){ + //资源检测 + if(empty($this->img)) E('没有可以被添加水印的图像资源'); + if(!is_file($source)) E('水印图像不存在'); + + //创建水印图像资源 + $water = new \Imagick(realpath($source)); + $info = array($water->getImageWidth(), $water->getImageHeight()); + + /* 设定水印位置 */ + switch ($locate) { + /* 右下角水印 */ + case Image::IMAGE_WATER_SOUTHEAST: + $x = $this->info['width'] - $info[0]; + $y = $this->info['height'] - $info[1]; + break; + + /* 左下角水印 */ + case Image::IMAGE_WATER_SOUTHWEST: + $x = 0; + $y = $this->info['height'] - $info[1]; + break; + + /* 左上角水印 */ + case Image::IMAGE_WATER_NORTHWEST: + $x = $y = 0; + break; + + /* 右上角水印 */ + case Image::IMAGE_WATER_NORTHEAST: + $x = $this->info['width'] - $info[0]; + $y = 0; + break; + + /* 居中水印 */ + case Image::IMAGE_WATER_CENTER: + $x = ($this->info['width'] - $info[0])/2; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 下居中水印 */ + case Image::IMAGE_WATER_SOUTH: + $x = ($this->info['width'] - $info[0])/2; + $y = $this->info['height'] - $info[1]; + break; + + /* 右居中水印 */ + case Image::IMAGE_WATER_EAST: + $x = $this->info['width'] - $info[0]; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 上居中水印 */ + case Image::IMAGE_WATER_NORTH: + $x = ($this->info['width'] - $info[0])/2; + $y = 0; + break; + + /* 左居中水印 */ + case Image::IMAGE_WATER_WEST: + $x = 0; + $y = ($this->info['height'] - $info[1])/2; + break; + + default: + /* 自定义水印坐标 */ + if(is_array($locate)){ + list($x, $y) = $locate; + } else { + E('不支持的水印位置类型'); + } + } + + //创建绘图资源 + $draw = new \ImagickDraw(); + $draw->composite($water->getImageCompose(), $x, $y, $info[0], $info[1], $water); + + if('gif' == $this->info['type']){ + $img = $this->img->coalesceImages(); + $this->img->destroy(); //销毁原图 + + do{ + //添加水印 + $img->drawImage($draw); + } while ($img->nextImage()); + + //压缩图片 + $this->img = $img->deconstructImages(); + $img->destroy(); //销毁零时图片 + + } else { + //添加水印 + $this->img->drawImage($draw); + } + + //销毁水印资源 + $draw->destroy(); + $water->destroy(); + } + + /** + * 图像添加文字 + * @param string $text 添加的文字 + * @param string $font 字体路径 + * @param integer $size 字号 + * @param string $color 文字颜色 + * @param integer $locate 文字写入位置 + * @param integer $offset 文字相对当前位置的偏移量 + * @param integer $angle 文字倾斜角度 + */ + public function text($text, $font, $size, $color = '#00000000', + $locate = Image::IMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0){ + //资源检测 + if(empty($this->img)) E('没有可以被写入文字的图像资源'); + if(!is_file($font)) E("不存在的字体文件:{$font}"); + + //获取颜色和透明度 + if(is_array($color)){ + $color = array_map('dechex', $color); + foreach ($color as &$value) { + $value = str_pad($value, 2, '0', STR_PAD_LEFT); + } + $color = '#' . implode('', $color); + } elseif(!is_string($color) || 0 !== strpos($color, '#')) { + E('错误的颜色值'); + } + $col = substr($color, 0, 7); + $alp = strlen($color) == 9 ? substr($color, -2) : 0; + + + //获取文字信息 + $draw = new \ImagickDraw(); + $draw->setFont(realpath($font)); + $draw->setFontSize($size); + $draw->setFillColor($col); + $draw->setFillAlpha(1-hexdec($alp)/127); + $draw->setTextAntialias(true); + $draw->setStrokeAntialias(true); + + $metrics = $this->img->queryFontMetrics($draw, $text); + + /* 计算文字初始坐标和尺寸 */ + $x = 0; + $y = $metrics['ascender']; + $w = $metrics['textWidth']; + $h = $metrics['textHeight']; + + /* 设定文字位置 */ + switch ($locate) { + /* 右下角文字 */ + case Image::IMAGE_WATER_SOUTHEAST: + $x += $this->info['width'] - $w; + $y += $this->info['height'] - $h; + break; + + /* 左下角文字 */ + case Image::IMAGE_WATER_SOUTHWEST: + $y += $this->info['height'] - $h; + break; + + /* 左上角文字 */ + case Image::IMAGE_WATER_NORTHWEST: + // 起始坐标即为左上角坐标,无需调整 + break; + + /* 右上角文字 */ + case Image::IMAGE_WATER_NORTHEAST: + $x += $this->info['width'] - $w; + break; + + /* 居中文字 */ + case Image::IMAGE_WATER_CENTER: + $x += ($this->info['width'] - $w)/2; + $y += ($this->info['height'] - $h)/2; + break; + + /* 下居中文字 */ + case Image::IMAGE_WATER_SOUTH: + $x += ($this->info['width'] - $w)/2; + $y += $this->info['height'] - $h; + break; + + /* 右居中文字 */ + case Image::IMAGE_WATER_EAST: + $x += $this->info['width'] - $w; + $y += ($this->info['height'] - $h)/2; + break; + + /* 上居中文字 */ + case Image::IMAGE_WATER_NORTH: + $x += ($this->info['width'] - $w)/2; + break; + + /* 左居中文字 */ + case Image::IMAGE_WATER_WEST: + $y += ($this->info['height'] - $h)/2; + break; + + default: + /* 自定义文字坐标 */ + if(is_array($locate)){ + list($posx, $posy) = $locate; + $x += $posx; + $y += $posy; + } else { + E('不支持的文字位置类型'); + } + } + + /* 设置偏移量 */ + if(is_array($offset)){ + $offset = array_map('intval', $offset); + list($ox, $oy) = $offset; + } else{ + $offset = intval($offset); + $ox = $oy = $offset; + } + + /* 写入文字 */ + if('gif' == $this->info['type']){ + $img = $this->img->coalesceImages(); + $this->img->destroy(); //销毁原图 + do{ + $img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text); + } while ($img->nextImage()); + + //压缩图片 + $this->img = $img->deconstructImages(); + $img->destroy(); //销毁零时图片 + + } else { + $this->img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text); + } + $draw->destroy(); + } + + /** + * 析构方法,用于销毁图像资源 + */ + public function __destruct() { + empty($this->img) || $this->img->destroy(); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Log.class.php b/ThinkPHP/Library/Think/Log.class.php new file mode 100644 index 0000000..09b1155 --- /dev/null +++ b/ThinkPHP/Library/Think/Log.class.php @@ -0,0 +1,104 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * 日志处理类 + */ +class Log { + + // 日志级别 从上到下,由低到高 + const EMERG = 'EMERG'; // 严重错误: 导致系统崩溃无法使用 + const ALERT = 'ALERT'; // 警戒性错误: 必须被立即修改的错误 + const CRIT = 'CRIT'; // 临界值错误: 超过临界值的错误,例如一天24小时,而输入的是25小时这样 + const ERR = 'ERR'; // 一般错误: 一般性错误 + const WARN = 'WARN'; // 警告性错误: 需要发出警告的错误 + const NOTICE = 'NOTIC'; // 通知: 程序可以运行但是还不够完美的错误 + const INFO = 'INFO'; // 信息: 程序输出信息 + const DEBUG = 'DEBUG'; // 调试: 调试信息 + const SQL = 'SQL'; // SQL:SQL语句 注意只在调试模式开启时有效 + + // 日志信息 + static protected $log = array(); + + // 日志存储 + static protected $storage = null; + + // 日志初始化 + static public function init($config=array()){ + $type = isset($config['type']) ? $config['type'] : 'File'; + $class = strpos($type,'\\')? $type: 'Think\\Log\\Driver\\'. ucwords(strtolower($type)); + unset($config['type']); + self::$storage = new $class($config); + } + + /** + * 记录日志 并且会过滤未经设置的级别 + * @static + * @access public + * @param string $message 日志信息 + * @param string $level 日志级别 + * @param boolean $record 是否强制记录 + * @return void + */ + static function record($message,$level=self::ERR,$record=false) { + if($record || false !== strpos(C('LOG_LEVEL'),$level)) { + self::$log[] = "{$level}: {$message}\r\n"; + } + } + + /** + * 日志保存 + * @static + * @access public + * @param integer $type 日志记录方式 + * @param string $destination 写入目标 + * @return void + */ + static function save($type='',$destination='') { + if(empty(self::$log)) return ; + + if(empty($destination)){ + $destination = C('LOG_PATH').date('y_m_d').'.log'; + } + if(!self::$storage){ + $type = $type ? : C('LOG_TYPE'); + $class = 'Think\\Log\\Driver\\'. ucwords($type); + self::$storage = new $class(); + } + $message = implode('',self::$log); + self::$storage->write($message,$destination); + // 保存后清空日志缓存 + self::$log = array(); + } + + /** + * 日志直接写入 + * @static + * @access public + * @param string $message 日志信息 + * @param string $level 日志级别 + * @param integer $type 日志记录方式 + * @param string $destination 写入目标 + * @return void + */ + static function write($message,$level=self::ERR,$type='',$destination='') { + if(!self::$storage){ + $type = $type ? : C('LOG_TYPE'); + $class = 'Think\\Log\\Driver\\'. ucwords($type); + $config['log_path'] = C('LOG_PATH'); + self::$storage = new $class($config); + } + if(empty($destination)){ + $destination = C('LOG_PATH').date('y_m_d').'.log'; + } + self::$storage->write("{$level}: {$message}", $destination); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Log/Driver/File.class.php b/ThinkPHP/Library/Think/Log/Driver/File.class.php new file mode 100644 index 0000000..290746e --- /dev/null +++ b/ThinkPHP/Library/Think/Log/Driver/File.class.php @@ -0,0 +1,50 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Log\Driver; + +class File { + + protected $config = array( + 'log_time_format' => ' c ', + 'log_file_size' => 2097152, + 'log_path' => '', + ); + + // 实例化并传入参数 + public function __construct($config=array()){ + $this->config = array_merge($this->config,$config); + } + + /** + * 日志写入接口 + * @access public + * @param string $log 日志信息 + * @param string $destination 写入目标 + * @return void + */ + public function write($log,$destination='') { + $now = date($this->config['log_time_format']); + if(empty($destination)){ + $destination = $this->config['log_path'].date('y_m_d').'.log'; + } + // 自动创建日志目录 + $log_dir = dirname($destination); + if (!is_dir($log_dir)) { + mkdir($log_dir, 0755, true); + } + //检测日志文件大小,超过配置大小则备份日志文件重新生成 + if(is_file($destination) && floor($this->config['log_file_size']) <= filesize($destination) ){ + rename($destination,dirname($destination).'/'.time().'-'.basename($destination)); + } + error_log("[{$now}] ".$_SERVER['REMOTE_ADDR'].' '.$_SERVER['REQUEST_URI']."\r\n{$log}\r\n", 3,$destination); + } +} diff --git a/ThinkPHP/Library/Think/Log/Driver/Sae.class.php b/ThinkPHP/Library/Think/Log/Driver/Sae.class.php new file mode 100644 index 0000000..accbcae --- /dev/null +++ b/ThinkPHP/Library/Think/Log/Driver/Sae.class.php @@ -0,0 +1,49 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Log\Driver; + +class Sae { + + protected $config = array( + 'log_time_format' => ' c ', + ); + + // 实例化并传入参数 + public function __construct($config=array()){ + $this->config = array_merge($this->config,$config); + } + + /** + * 日志写入接口 + * @access public + * @param string $log 日志信息 + * @param string $destination 写入目标 + * @return void + */ + public function write($log,$destination='') { + static $is_debug=null; + $now = date($this->config['log_time_format']); + $logstr="[{$now}] ".$_SERVER['REMOTE_ADDR'].' '.$_SERVER['REQUEST_URI']."\r\n{$log}\r\n"; + if(is_null($is_debug)){ + preg_replace('@(\w+)\=([^;]*)@e', '$appSettings[\'\\1\']="\\2";', $_SERVER['HTTP_APPCOOKIE']); + $is_debug = in_array($_SERVER['HTTP_APPVERSION'], explode(',', $appSettings['debug'])) ? true : false; + } + if($is_debug){ + sae_set_display_errors(false);//记录日志不将日志打印出来 + } + sae_debug($logstr); + if($is_debug){ + sae_set_display_errors(true); + } + + } +} diff --git a/ThinkPHP/Library/Think/Model.class.php b/ThinkPHP/Library/Think/Model.class.php new file mode 100644 index 0000000..e56474b --- /dev/null +++ b/ThinkPHP/Library/Think/Model.class.php @@ -0,0 +1,1910 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP Model模型类 + * 实现了ORM和ActiveRecords模式 + */ +class Model { + // 操作状态 + const MODEL_INSERT = 1; // 插入模型数据 + const MODEL_UPDATE = 2; // 更新模型数据 + const MODEL_BOTH = 3; // 包含上面两种方式 + const MUST_VALIDATE = 1; // 必须验证 + const EXISTS_VALIDATE = 0; // 表单存在字段则验证 + const VALUE_VALIDATE = 2; // 表单值不为空则验证 + + // 当前数据库操作对象 + protected $db = null; + // 数据库对象池 + private $_db = array(); + // 主键名称 + protected $pk = 'id'; + // 主键是否自动增长 + protected $autoinc = false; + // 数据表前缀 + protected $tablePrefix = null; + // 模型名称 + protected $name = ''; + // 数据库名称 + protected $dbName = ''; + //数据库配置 + protected $connection = ''; + // 数据表名(不包含表前缀) + protected $tableName = ''; + // 实际数据表名(包含表前缀) + protected $trueTableName = ''; + // 最近错误信息 + protected $error = ''; + // 字段信息 + protected $fields = array(); + // 数据信息 + protected $data = array(); + // 查询表达式参数 + protected $options = array(); + protected $_validate = array(); // 自动验证定义 + protected $_auto = array(); // 自动完成定义 + protected $_map = array(); // 字段映射定义 + protected $_scope = array(); // 命名范围定义 + // 是否自动检测数据表字段信息 + protected $autoCheckFields = true; + // 是否批处理验证 + protected $patchValidate = false; + // 链操作方法列表 + protected $methods = array('strict','order','alias','having','group','lock','distinct','auto','filter','validate','result','token','index','force'); + + /** + * 架构函数 + * 取得DB类的实例对象 字段检查 + * @access public + * @param string $name 模型名称 + * @param string $tablePrefix 表前缀 + * @param mixed $connection 数据库连接信息 + */ + public function __construct($name='',$tablePrefix='',$connection='') { + // 模型初始化 + $this->_initialize(); + // 获取模型名称 + if(!empty($name)) { + if(strpos($name,'.')) { // 支持 数据库名.模型名的 定义 + list($this->dbName,$this->name) = explode('.',$name); + }else{ + $this->name = $name; + } + }elseif(empty($this->name)){ + $this->name = $this->getModelName(); + } + // 设置表前缀 + if(is_null($tablePrefix)) {// 前缀为Null表示没有前缀 + $this->tablePrefix = ''; + }elseif('' != $tablePrefix) { + $this->tablePrefix = $tablePrefix; + }elseif(!isset($this->tablePrefix)){ + $this->tablePrefix = C('DB_PREFIX'); + } + + // 数据库初始化操作 + // 获取数据库操作对象 + // 当前模型有独立的数据库连接信息 + $this->db(0,empty($this->connection)?$connection:$this->connection,true); + } + + /** + * 自动检测数据表信息 + * @access protected + * @return void + */ + protected function _checkTableInfo() { + // 如果不是Model类 自动记录数据表信息 + // 只在第一次执行记录 + if(empty($this->fields)) { + // 如果数据表字段没有定义则自动获取 + if(C('DB_FIELDS_CACHE')) { + $db = $this->dbName?:C('DB_NAME'); + $fields = F('_fields/'.strtolower($db.'.'.$this->tablePrefix.$this->name)); + if($fields) { + $this->fields = $fields; + if(!empty($fields['_pk'])){ + $this->pk = $fields['_pk']; + } + return ; + } + } + // 每次都会读取数据表信息 + $this->flush(); + } + } + + /** + * 获取字段信息并缓存 + * @access public + * @return void + */ + public function flush() { + // 缓存不存在则查询数据表信息 + $this->db->setModel($this->name); + $fields = $this->db->getFields($this->getTableName()); + if(!$fields) { // 无法获取字段信息 + return false; + } + $this->fields = array_keys($fields); + unset($this->fields['_pk']); + foreach ($fields as $key=>$val){ + // 记录字段类型 + $type[$key] = $val['type']; + if($val['primary']) { + // 增加复合主键支持 + if (isset($this->fields['_pk']) && $this->fields['_pk'] != null) { + if (is_string($this->fields['_pk'])) { + $this->pk = array($this->fields['_pk']); + $this->fields['_pk'] = $this->pk; + } + $this->pk[] = $key; + $this->fields['_pk'][] = $key; + } else { + $this->pk = $key; + $this->fields['_pk'] = $key; + } + if($val['autoinc']) $this->autoinc = true; + } + } + // 记录字段类型信息 + $this->fields['_type'] = $type; + + // 2008-3-7 增加缓存开关控制 + if(C('DB_FIELDS_CACHE')){ + // 永久缓存数据表信息 + $db = $this->dbName?:C('DB_NAME'); + F('_fields/'.strtolower($db.'.'.$this->tablePrefix.$this->name),$this->fields); + } + } + + /** + * 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name,$value) { + // 设置数据对象属性 + $this->data[$name] = $value; + } + + /** + * 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) { + return isset($this->data[$name])?$this->data[$name]:null; + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) { + return isset($this->data[$name]); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) { + unset($this->data[$name]); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + */ + public function __call($method,$args) { + if(in_array(strtolower($method),$this->methods,true)) { + // 连贯操作的实现 + $this->options[strtolower($method)] = $args[0]; + return $this; + }elseif(in_array(strtolower($method),array('count','sum','min','max','avg'),true)){ + // 统计查询的实现 + $field = isset($args[0])?$args[0]:'*'; + return $this->getField(strtoupper($method).'('.$field.') AS tp_'.$method); + }elseif(strtolower(substr($method,0,5))=='getby') { + // 根据某个字段获取记录 + $field = parse_name(substr($method,5)); + $where[$field] = $args[0]; + return $this->where($where)->find(); + }elseif(strtolower(substr($method,0,10))=='getfieldby') { + // 根据某个字段获取记录的某个值 + $name = parse_name(substr($method,10)); + $where[$name] =$args[0]; + return $this->where($where)->getField($args[1]); + }elseif(isset($this->_scope[$method])){// 命名范围的单独调用支持 + return $this->scope($method,$args[0]); + }else{ + E(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_')); + return; + } + } + // 回调方法 初始化模型 + protected function _initialize() {} + + /** + * 对保存到数据库的数据进行处理 + * @access protected + * @param mixed $data 要操作的数据 + * @return boolean + */ + protected function _facade($data) { + + // 检查数据字段合法性 + if(!empty($this->fields)) { + if(!empty($this->options['field'])) { + $fields = $this->options['field']; + unset($this->options['field']); + if(is_string($fields)) { + $fields = explode(',',$fields); + } + }else{ + $fields = $this->fields; + } + foreach ($data as $key=>$val){ + if(!in_array($key,$fields,true)){ + if(!empty($this->options['strict'])){ + E(L('_DATA_TYPE_INVALID_').':['.$key.'=>'.$val.']'); + } + unset($data[$key]); + }elseif(is_scalar($val)) { + // 字段类型检查 和 强制转换 + $this->_parseType($data,$key); + } + } + } + + // 安全过滤 + if(!empty($this->options['filter'])) { + $data = array_map($this->options['filter'],$data); + unset($this->options['filter']); + } + $this->_before_write($data); + return $data; + } + + // 写入数据前的回调方法 包括新增和更新 + protected function _before_write(&$data) {} + + /** + * 新增数据 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @param boolean $replace 是否replace + * @return mixed + */ + public function add($data='',$options=array(),$replace=false) { + if(empty($data)) { + // 没有传递数据,获取当前数据对象的值 + if(!empty($this->data)) { + $data = $this->data; + // 重置数据 + $this->data = array(); + }else{ + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + } + // 数据处理 + $data = $this->_facade($data); + // 分析表达式 + $options = $this->_parseOptions($options); + if(false === $this->_before_insert($data,$options)) { + return false; + } + // 写入数据到数据库 + $result = $this->db->insert($data,$options,$replace); + if(false !== $result && is_numeric($result)) { + $pk = $this->getPk(); + // 增加复合主键支持 + if (is_array($pk)) return $result; + $insertId = $this->getLastInsID(); + if($insertId) { + // 自增主键返回插入ID + $data[$pk] = $insertId; + if(false === $this->_after_insert($data,$options)) { + return false; + } + return $insertId; + } + if(false === $this->_after_insert($data,$options)) { + return false; + } + } + return $result; + } + // 插入数据前的回调方法 + protected function _before_insert(&$data,$options) {} + // 插入成功后的回调方法 + protected function _after_insert($data,$options) {} + + public function addAll($dataList,$options=array(),$replace=false){ + if(empty($dataList)) { + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + // 数据处理 + foreach ($dataList as $key=>$data){ + $dataList[$key] = $this->_facade($data); + } + // 分析表达式 + $options = $this->_parseOptions($options); + // 写入数据到数据库 + $result = $this->db->insertAll($dataList,$options,$replace); + if(false !== $result ) { + $insertId = $this->getLastInsID(); + if($insertId) { + return $insertId; + } + } + return $result; + } + + /** + * 通过Select方式添加记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @param array $options 表达式 + * @return boolean + */ + public function selectAdd($fields='',$table='',$options=array()) { + // 分析表达式 + $options = $this->_parseOptions($options); + // 写入数据到数据库 + if(false === $result = $this->db->selectInsert($fields?:$options['field'],$table?:$this->getTableName(),$options)){ + // 数据库插入操作失败 + $this->error = L('_OPERATION_WRONG_'); + return false; + }else { + // 插入成功 + return $result; + } + } + + /** + * 保存数据 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return boolean + */ + public function save($data='',$options=array()) { + if(empty($data)) { + // 没有传递数据,获取当前数据对象的值 + if(!empty($this->data)) { + $data = $this->data; + // 重置数据 + $this->data = array(); + }else{ + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + } + // 数据处理 + $data = $this->_facade($data); + if(empty($data)){ + // 没有数据则不执行 + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + // 分析表达式 + $options = $this->_parseOptions($options); + $pk = $this->getPk(); + if(!isset($options['where']) ) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $where[$pk] = $data[$pk]; + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if(isset($data[$field])) { + $where[$field] = $data[$field]; + } else { + // 如果缺少复合主键数据则不执行 + $this->error = L('_OPERATION_WRONG_'); + return false; + } + unset($data[$field]); + } + } + if(!isset($where)){ + // 如果没有任何更新条件则不执行 + $this->error = L('_OPERATION_WRONG_'); + return false; + }else{ + $options['where'] = $where; + } + } + + if(is_array($options['where']) && isset($options['where'][$pk])){ + $pkValue = $options['where'][$pk]; + } + if(false === $this->_before_update($data,$options)) { + return false; + } + $result = $this->db->update($data,$options); + if(false !== $result && is_numeric($result)) { + if(isset($pkValue)) $data[$pk] = $pkValue; + $this->_after_update($data,$options); + } + return $result; + } + // 更新数据前的回调方法 + protected function _before_update(&$data,$options) {} + // 更新成功后的回调方法 + protected function _after_update($data,$options) {} + + /** + * 删除数据 + * @access public + * @param mixed $options 表达式 + * @return mixed + */ + public function delete($options=array()) { + $pk = $this->getPk(); + if(empty($options) && empty($this->options['where'])) { + // 如果删除条件为空 则删除当前数据对象所对应的记录 + if(!empty($this->data) && isset($this->data[$pk])) + return $this->delete($this->data[$pk]); + else + return false; + } + if(is_numeric($options) || is_string($options)) { + // 根据主键删除记录 + if(strpos($options,',')) { + $where[$pk] = array('IN', $options); + }else{ + $where[$pk] = $options; + } + $options = array(); + $options['where'] = $where; + } + // 根据复合主键删除记录 + if (is_array($options) && (count($options) > 0) && is_array($pk)) { + $count = 0; + foreach (array_keys($options) as $key) { + if (is_int($key)) $count++; + } + if ($count == count($pk)) { + $i = 0; + foreach ($pk as $field) { + $where[$field] = $options[$i]; + unset($options[$i++]); + } + $options['where'] = $where; + } else { + return false; + } + } + // 分析表达式 + $options = $this->_parseOptions($options); + if(empty($options['where'])){ + // 如果条件为空 不进行删除操作 除非设置 1=1 + return false; + } + if(is_array($options['where']) && isset($options['where'][$pk])){ + $pkValue = $options['where'][$pk]; + } + + if(false === $this->_before_delete($options)) { + return false; + } + $result = $this->db->delete($options); + if(false !== $result && is_numeric($result)) { + $data = array(); + if(isset($pkValue)) $data[$pk] = $pkValue; + $this->_after_delete($data,$options); + } + // 返回删除记录个数 + return $result; + } + // 删除数据前的回调方法 + protected function _before_delete($options) {} + // 删除成功后的回调方法 + protected function _after_delete($data,$options) {} + + /** + * 查询数据集 + * @access public + * @param array $options 表达式参数 + * @return mixed + */ + public function select($options=array()) { + $pk = $this->getPk(); + if(is_string($options) || is_numeric($options)) { + // 根据主键查询 + if(strpos($options,',')) { + $where[$pk] = array('IN',$options); + }else{ + $where[$pk] = $options; + } + $options = array(); + $options['where'] = $where; + }elseif (is_array($options) && (count($options) > 0) && is_array($pk)) { + // 根据复合主键查询 + $count = 0; + foreach (array_keys($options) as $key) { + if (is_int($key)) $count++; + } + if ($count == count($pk)) { + $i = 0; + foreach ($pk as $field) { + $where[$field] = $options[$i]; + unset($options[$i++]); + } + $options['where'] = $where; + } else { + return false; + } + } elseif(false === $options){ // 用于子查询 不查询只返回SQL + $options['fetch_sql'] = true; + } + // 分析表达式 + $options = $this->_parseOptions($options); + // 判断查询缓存 + if(isset($options['cache'])){ + $cache = $options['cache']; + $key = is_string($cache['key'])?$cache['key']:md5(serialize($options)); + $data = S($key,'',$cache); + if(false !== $data){ + return $data; + } + } + $resultSet = $this->db->select($options); + if(false === $resultSet) { + return false; + } + if(!empty($resultSet)) { // 有查询结果 + if(is_string($resultSet)){ + return $resultSet; + } + + $resultSet = array_map(array($this,'_read_data'),$resultSet); + $this->_after_select($resultSet,$options); + if(isset($options['index'])){ // 对数据集进行索引 + $index = explode(',',$options['index']); + foreach ($resultSet as $result){ + $_key = $result[$index[0]]; + if(isset($index[1]) && isset($result[$index[1]])){ + $cols[$_key] = $result[$index[1]]; + }else{ + $cols[$_key] = $result; + } + } + $resultSet = $cols; + } + } + + if(isset($cache)){ + S($key,$resultSet,$cache); + } + return $resultSet; + } + // 查询成功后的回调方法 + protected function _after_select(&$resultSet,$options) {} + + /** + * 生成查询SQL 可用于子查询 + * @access public + * @return string + */ + public function buildSql() { + return '( '.$this->fetchSql(true)->select().' )'; + } + + /** + * 分析表达式 + * @access protected + * @param array $options 表达式参数 + * @return array + */ + protected function _parseOptions($options=array()) { + if(is_array($options)) + $options = array_merge($this->options,$options); + + if(!isset($options['table'])){ + // 自动获取表名 + $options['table'] = $this->getTableName(); + $fields = $this->fields; + }else{ + // 指定数据表 则重新获取字段列表 但不支持类型检测 + $fields = $this->getDbFields(); + } + + // 数据表别名 + if(!empty($options['alias'])) { + $options['table'] .= ' '.$options['alias']; + } + // 记录操作的模型名称 + $options['model'] = $this->name; + + // 字段类型验证 + if(isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) { + // 对数组查询条件进行字段类型检查 + foreach ($options['where'] as $key=>$val){ + $key = trim($key); + if(in_array($key,$fields,true)){ + if(is_scalar($val)) { + $this->_parseType($options['where'],$key); + } + }elseif(!is_numeric($key) && '_' != substr($key,0,1) && false === strpos($key,'.') && false === strpos($key,'(') && false === strpos($key,'|') && false === strpos($key,'&')){ + if(!empty($this->options['strict'])){ + E(L('_ERROR_QUERY_EXPRESS_').':['.$key.'=>'.$val.']'); + } + unset($options['where'][$key]); + } + } + } + // 查询过后清空sql表达式组装 避免影响下次查询 + $this->options = array(); + // 表达式过滤 + $this->_options_filter($options); + return $options; + } + // 表达式过滤回调方法 + protected function _options_filter(&$options) {} + + /** + * 数据类型检测 + * @access protected + * @param mixed $data 数据 + * @param string $key 字段名 + * @return void + */ + protected function _parseType(&$data,$key) { + if(!isset($this->options['bind'][':'.$key]) && isset($this->fields['_type'][$key])){ + $fieldType = strtolower($this->fields['_type'][$key]); + if(false !== strpos($fieldType,'enum')){ + // 支持ENUM类型优先检测 + }elseif(false === strpos($fieldType,'bigint') && false !== strpos($fieldType,'int')) { + $data[$key] = intval($data[$key]); + }elseif(false !== strpos($fieldType,'float') || false !== strpos($fieldType,'double')){ + $data[$key] = floatval($data[$key]); + }elseif(false !== strpos($fieldType,'bool')){ + $data[$key] = (bool)$data[$key]; + } + } + } + + /** + * 数据读取后的处理 + * @access protected + * @param array $data 当前数据 + * @return array + */ + protected function _read_data($data) { + // 检查字段映射 + if(!empty($this->_map) && C('READ_DATA_MAP')) { + foreach ($this->_map as $key=>$val){ + if(isset($data[$val])) { + $data[$key] = $data[$val]; + unset($data[$val]); + } + } + } + return $data; + } + + /** + * 查询数据 + * @access public + * @param mixed $options 表达式参数 + * @return mixed + */ + public function find($options=array()) { + if(is_numeric($options) || is_string($options)) { + $where[$this->getPk()] = $options; + $options = array(); + $options['where'] = $where; + } + // 根据复合主键查找记录 + $pk = $this->getPk(); + if (is_array($options) && (count($options) > 0) && is_array($pk)) { + // 根据复合主键查询 + $count = 0; + foreach (array_keys($options) as $key) { + if (is_int($key)) $count++; + } + if ($count == count($pk)) { + $i = 0; + foreach ($pk as $field) { + $where[$field] = $options[$i]; + unset($options[$i++]); + } + $options['where'] = $where; + } else { + return false; + } + } + // 总是查找一条记录 + $options['limit'] = 1; + // 分析表达式 + $options = $this->_parseOptions($options); + // 判断查询缓存 + if(isset($options['cache'])){ + $cache = $options['cache']; + $key = is_string($cache['key'])?$cache['key']:md5(serialize($options)); + $data = S($key,'',$cache); + if(false !== $data){ + $this->data = $data; + return $data; + } + } + $resultSet = $this->db->select($options); + if(false === $resultSet) { + return false; + } + if(empty($resultSet)) {// 查询结果为空 + return null; + } + if(is_string($resultSet)){ + return $resultSet; + } + + // 读取数据后的处理 + $data = $this->_read_data($resultSet[0]); + $this->_after_find($data,$options); + if(!empty($this->options['result'])) { + return $this->returnResult($data,$this->options['result']); + } + $this->data = $data; + if(isset($cache)){ + S($key,$data,$cache); + } + return $this->data; + } + // 查询成功的回调方法 + protected function _after_find(&$result,$options) {} + + protected function returnResult($data,$type=''){ + if ($type){ + if(is_callable($type)){ + return call_user_func($type,$data); + } + switch (strtolower($type)){ + case 'json': + return json_encode($data); + case 'xml': + return xml_encode($data); + } + } + return $data; + } + + /** + * 处理字段映射 + * @access public + * @param array $data 当前数据 + * @param integer $type 类型 0 写入 1 读取 + * @return array + */ + public function parseFieldsMap($data,$type=1) { + // 检查字段映射 + if(!empty($this->_map)) { + foreach ($this->_map as $key=>$val){ + if($type==1) { // 读取 + if(isset($data[$val])) { + $data[$key] = $data[$val]; + unset($data[$val]); + } + }else{ + if(isset($data[$key])) { + $data[$val] = $data[$key]; + unset($data[$key]); + } + } + } + } + return $data; + } + + /** + * 设置记录的某个字段值 + * 支持使用数据库字段和方法 + * @access public + * @param string|array $field 字段名 + * @param string $value 字段值 + * @return boolean + */ + public function setField($field,$value='') { + if(is_array($field)) { + $data = $field; + }else{ + $data[$field] = $value; + } + return $this->save($data); + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return boolean + */ + public function setInc($field,$step=1,$lazyTime=0) { + if($lazyTime>0) {// 延迟写入 + $condition = $this->options['where']; + $guid = md5($this->name.'_'.$field.'_'.serialize($condition)); + $step = $this->lazyWrite($guid,$step,$lazyTime); + if(empty($step)) { + return true; // 等待下次写入 + }elseif($step < 0) { + $step = '-'.$step; + } + } + return $this->setField($field,array('exp',$field.'+'.$step)); + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return boolean + */ + public function setDec($field,$step=1,$lazyTime=0) { + if($lazyTime>0) {// 延迟写入 + $condition = $this->options['where']; + $guid = md5($this->name.'_'.$field.'_'.serialize($condition)); + $step = $this->lazyWrite($guid,-$step,$lazyTime); + if(empty($step)) { + return true; // 等待下次写入 + }elseif($step > 0) { + $step = '-'.$step; + } + } + return $this->setField($field,array('exp',$field.'-'.$step)); + } + + /** + * 延时更新检查 返回false表示需要延时 + * 否则返回实际写入的数值 + * @access public + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) + * @return false|integer + */ + protected function lazyWrite($guid,$step,$lazyTime) { + if(false !== ($value = S($guid))) { // 存在缓存写入数据 + if(NOW_TIME > S($guid.'_time')+$lazyTime) { + // 延时更新时间到了,删除缓存数据 并实际写入数据库 + S($guid,NULL); + S($guid.'_time',NULL); + return $value+$step; + }else{ + // 追加数据到缓存 + S($guid,$value+$step); + return false; + } + }else{ // 没有缓存数据 + S($guid,$step); + // 计时开始 + S($guid.'_time',NOW_TIME); + return false; + } + } + + /** + * 获取一条记录的某个字段值 + * @access public + * @param string $field 字段名 + * @param string $spea 字段数据间隔符号 NULL返回数组 + * @return mixed + */ + public function getField($field,$sepa=null) { + $options['field'] = $field; + $options = $this->_parseOptions($options); + // 判断查询缓存 + if(isset($options['cache'])){ + $cache = $options['cache']; + $key = is_string($cache['key'])?$cache['key']:md5($sepa.serialize($options)); + $data = S($key,'',$cache); + if(false !== $data){ + return $data; + } + } + $field = trim($field); + if(strpos($field,',') && false !== $sepa) { // 多字段 + if(!isset($options['limit'])){ + $options['limit'] = is_numeric($sepa)?$sepa:''; + } + $resultSet = $this->db->select($options); + if(!empty($resultSet)) { + if(is_string($resultSet)){ + return $resultSet; + } + $_field = explode(',', $field); + $field = array_keys($resultSet[0]); + $key1 = array_shift($field); + $key2 = array_shift($field); + $cols = array(); + $count = count($_field); + foreach ($resultSet as $result){ + $name = $result[$key1]; + if(2==$count) { + $cols[$name] = $result[$key2]; + }else{ + $cols[$name] = is_string($sepa)?implode($sepa,array_slice($result,1)):$result; + } + } + if(isset($cache)){ + S($key,$cols,$cache); + } + return $cols; + } + }else{ // 查找一条记录 + // 返回数据个数 + if(true !== $sepa) {// 当sepa指定为true的时候 返回所有数据 + $options['limit'] = is_numeric($sepa)?$sepa:1; + } + $result = $this->db->select($options); + if(!empty($result)) { + if(is_string($result)){ + return $result; + } + if(true !== $sepa && 1==$options['limit']) { + $data = reset($result[0]); + if(isset($cache)){ + S($key,$data,$cache); + } + return $data; + } + foreach ($result as $val){ + $array[] = $val[$field]; + } + if(isset($cache)){ + S($key,$array,$cache); + } + return $array; + } + } + return null; + } + + /** + * 创建数据对象 但不保存到数据库 + * @access public + * @param mixed $data 创建数据 + * @param string $type 状态 + * @return mixed + */ + public function create($data='',$type='') { + // 如果没有传值默认取POST数据 + if(empty($data)) { + $data = I('post.'); + }elseif(is_object($data)){ + $data = get_object_vars($data); + } + // 验证数据 + if(empty($data) || !is_array($data)) { + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + + // 状态 + $type = $type?:(!empty($data[$this->getPk()])?self::MODEL_UPDATE:self::MODEL_INSERT); + + // 检查字段映射 + $data = $this->parseFieldsMap($data,0); + + // 检测提交字段的合法性 + if(isset($this->options['field'])) { // $this->field('field1,field2...')->create() + $fields = $this->options['field']; + unset($this->options['field']); + }elseif($type == self::MODEL_INSERT && isset($this->insertFields)) { + $fields = $this->insertFields; + }elseif($type == self::MODEL_UPDATE && isset($this->updateFields)) { + $fields = $this->updateFields; + } + if(isset($fields)) { + if(is_string($fields)) { + $fields = explode(',',$fields); + } + // 判断令牌验证字段 + if(C('TOKEN_ON')) $fields[] = C('TOKEN_NAME', null, '__hash__'); + foreach ($data as $key=>$val){ + if(!in_array($key,$fields)) { + unset($data[$key]); + } + } + } + + // 数据自动验证 + if(!$this->autoValidation($data,$type)) return false; + + // 表单令牌验证 + if(!$this->autoCheckToken($data)) { + $this->error = L('_TOKEN_ERROR_'); + return false; + } + + // 验证完成生成数据对象 + if($this->autoCheckFields) { // 开启字段检测 则过滤非法字段数据 + $fields = $this->getDbFields(); + foreach ($data as $key=>$val){ + if(!in_array($key,$fields)) { + unset($data[$key]); + }elseif(MAGIC_QUOTES_GPC && is_string($val)){ + $data[$key] = stripslashes($val); + } + } + } + + // 创建完成对数据进行自动处理 + $this->autoOperation($data,$type); + // 赋值当前数据对象 + $this->data = $data; + // 返回创建的数据以供其他调用 + return $data; + } + + // 自动表单令牌验证 + // TODO ajax无刷新多次提交暂不能满足 + public function autoCheckToken($data) { + // 支持使用token(false) 关闭令牌验证 + if(isset($this->options['token']) && !$this->options['token']) return true; + if(C('TOKEN_ON')){ + $name = C('TOKEN_NAME', null, '__hash__'); + if(!isset($data[$name]) || !isset($_SESSION[$name])) { // 令牌数据无效 + return false; + } + + // 令牌验证 + list($key,$value) = explode('_',$data[$name]); + if(isset($_SESSION[$name][$key]) && $value && $_SESSION[$name][$key] === $value) { // 防止重复提交 + unset($_SESSION[$name][$key]); // 验证完成销毁session + return true; + } + // 开启TOKEN重置 + if(C('TOKEN_RESET')) unset($_SESSION[$name][$key]); + return false; + } + return true; + } + + /** + * 使用正则验证数据 + * @access public + * @param string $value 要验证的数据 + * @param string $rule 验证规则 + * @return boolean + */ + public function regex($value,$rule) { + $validate = array( + 'require' => '/\S+/', + 'email' => '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/', + 'url' => '/^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(:\d+)?(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/', + 'currency' => '/^\d+(\.\d+)?$/', + 'number' => '/^\d+$/', + 'zip' => '/^\d{6}$/', + 'integer' => '/^[-\+]?\d+$/', + 'double' => '/^[-\+]?\d+(\.\d+)?$/', + 'english' => '/^[A-Za-z]+$/', + ); + // 检查是否有内置的正则表达式 + if(isset($validate[strtolower($rule)])) + $rule = $validate[strtolower($rule)]; + return preg_match($rule,$value)===1; + } + + /** + * 自动表单处理 + * @access public + * @param array $data 创建数据 + * @param string $type 创建类型 + * @return mixed + */ + private function autoOperation(&$data,$type) { + if(false === $this->options['auto']){ + // 关闭自动完成 + return $data; + } + if(!empty($this->options['auto'])) { + $_auto = $this->options['auto']; + unset($this->options['auto']); + }elseif(!empty($this->_auto)){ + $_auto = $this->_auto; + } + // 自动填充 + if(isset($_auto)) { + foreach ($_auto as $auto){ + // 填充因子定义格式 + // array('field','填充内容','填充条件','附加规则',[额外参数]) + if(empty($auto[2])) $auto[2] = self::MODEL_INSERT; // 默认为新增的时候自动填充 + if( $type == $auto[2] || $auto[2] == self::MODEL_BOTH) { + if(empty($auto[3])) $auto[3] = 'string'; + switch(trim($auto[3])) { + case 'function': // 使用函数进行填充 字段的值作为参数 + case 'callback': // 使用回调方法 + $args = isset($auto[4])?(array)$auto[4]:array(); + if(isset($data[$auto[0]])) { + array_unshift($args,$data[$auto[0]]); + } + if('function'==$auto[3]) { + $data[$auto[0]] = call_user_func_array($auto[1], $args); + }else{ + $data[$auto[0]] = call_user_func_array(array(&$this,$auto[1]), $args); + } + break; + case 'field': // 用其它字段的值进行填充 + $data[$auto[0]] = $data[$auto[1]]; + break; + case 'ignore': // 为空忽略 + if($auto[1]===$data[$auto[0]]) + unset($data[$auto[0]]); + break; + case 'string': + default: // 默认作为字符串填充 + $data[$auto[0]] = $auto[1]; + } + if(isset($data[$auto[0]]) && false === $data[$auto[0]] ) unset($data[$auto[0]]); + } + } + } + return $data; + } + + /** + * 自动表单验证 + * @access protected + * @param array $data 创建数据 + * @param string $type 创建类型 + * @return boolean + */ + protected function autoValidation($data,$type) { + if(false === $this->options['validate'] ){ + // 关闭自动验证 + return true; + } + if(!empty($this->options['validate'])) { + $_validate = $this->options['validate']; + unset($this->options['validate']); + }elseif(!empty($this->_validate)){ + $_validate = $this->_validate; + } + // 属性验证 + if(isset($_validate)) { // 如果设置了数据自动验证则进行数据验证 + if($this->patchValidate) { // 重置验证错误信息 + $this->error = array(); + } + foreach($_validate as $key=>$val) { + // 验证因子定义格式 + // array(field,rule,message,condition,type,when,params) + // 判断是否需要执行验证 + if(empty($val[5]) || ( $val[5]== self::MODEL_BOTH && $type < 3 ) || $val[5]== $type ) { + if(0==strpos($val[2],'{%') && strpos($val[2],'}')) + // 支持提示信息的多语言 使用 {%语言定义} 方式 + $val[2] = L(substr($val[2],2,-1)); + $val[3] = isset($val[3])?$val[3]:self::EXISTS_VALIDATE; + $val[4] = isset($val[4])?$val[4]:'regex'; + // 判断验证条件 + switch($val[3]) { + case self::MUST_VALIDATE: // 必须验证 不管表单是否有设置该字段 + if(false === $this->_validationField($data,$val)) + return false; + break; + case self::VALUE_VALIDATE: // 值不为空的时候才验证 + if('' != trim($data[$val[0]])) + if(false === $this->_validationField($data,$val)) + return false; + break; + default: // 默认表单存在该字段就验证 + if(isset($data[$val[0]])) + if(false === $this->_validationField($data,$val)) + return false; + } + } + } + // 批量验证的时候最后返回错误 + if(!empty($this->error)) return false; + } + return true; + } + + /** + * 验证表单字段 支持批量验证 + * 如果批量验证返回错误的数组信息 + * @access protected + * @param array $data 创建数据 + * @param array $val 验证因子 + * @return boolean + */ + protected function _validationField($data,$val) { + if($this->patchValidate && isset($this->error[$val[0]])) + return ; //当前字段已经有规则验证没有通过 + if(false === $this->_validationFieldItem($data,$val)){ + if($this->patchValidate) { + $this->error[$val[0]] = $val[2]; + }else{ + $this->error = $val[2]; + return false; + } + } + return ; + } + + /** + * 根据验证因子验证字段 + * @access protected + * @param array $data 创建数据 + * @param array $val 验证因子 + * @return boolean + */ + protected function _validationFieldItem($data,$val) { + switch(strtolower(trim($val[4]))) { + case 'function':// 使用函数进行验证 + case 'callback':// 调用方法进行验证 + $args = isset($val[6])?(array)$val[6]:array(); + if(is_string($val[0]) && strpos($val[0], ',')) + $val[0] = explode(',', $val[0]); + if(is_array($val[0])){ + // 支持多个字段验证 + foreach($val[0] as $field) + $_data[$field] = $data[$field]; + array_unshift($args, $_data); + }else{ + array_unshift($args, $data[$val[0]]); + } + if('function'==$val[4]) { + return call_user_func_array($val[1], $args); + }else{ + return call_user_func_array(array(&$this, $val[1]), $args); + } + case 'confirm': // 验证两个字段是否相同 + return $data[$val[0]] == $data[$val[1]]; + case 'unique': // 验证某个值是否唯一 + if(is_string($val[0]) && strpos($val[0],',')) + $val[0] = explode(',',$val[0]); + $map = array(); + if(is_array($val[0])) { + // 支持多个字段验证 + foreach ($val[0] as $field) + $map[$field] = $data[$field]; + }else{ + $map[$val[0]] = $data[$val[0]]; + } + $pk = $this->getPk(); + if(!empty($data[$pk]) && is_string($pk)) { // 完善编辑的时候验证唯一 + $map[$pk] = array('neq',$data[$pk]); + } + if($this->where($map)->find()) return false; + return true; + default: // 检查附加规则 + return $this->check($data[$val[0]],$val[1],$val[4]); + } + } + + /** + * 验证数据 支持 in between equal length regex expire ip_allow ip_deny + * @access public + * @param string $value 验证数据 + * @param mixed $rule 验证表达式 + * @param string $type 验证方式 默认为正则验证 + * @return boolean + */ + public function check($value,$rule,$type='regex'){ + $type = strtolower(trim($type)); + switch($type) { + case 'in': // 验证是否在某个指定范围之内 逗号分隔字符串或者数组 + case 'notin': + $range = is_array($rule)? $rule : explode(',',$rule); + return $type == 'in' ? in_array($value ,$range) : !in_array($value ,$range); + case 'between': // 验证是否在某个范围 + case 'notbetween': // 验证是否不在某个范围 + if (is_array($rule)){ + $min = $rule[0]; + $max = $rule[1]; + }else{ + list($min,$max) = explode(',',$rule); + } + return $type == 'between' ? $value>=$min && $value<=$max : $value<$min || $value>$max; + case 'equal': // 验证是否等于某个值 + case 'notequal': // 验证是否等于某个值 + return $type == 'equal' ? $value == $rule : $value != $rule; + case 'length': // 验证长度 + $length = mb_strlen($value,'utf-8'); // 当前数据长度 + if(strpos($rule,',')) { // 长度区间 + list($min,$max) = explode(',',$rule); + return $length >= $min && $length <= $max; + }else{// 指定长度 + return $length == $rule; + } + case 'expire': + list($start,$end) = explode(',',$rule); + if(!is_numeric($start)) $start = strtotime($start); + if(!is_numeric($end)) $end = strtotime($end); + return NOW_TIME >= $start && NOW_TIME <= $end; + case 'ip_allow': // IP 操作许可验证 + return in_array(get_client_ip(),explode(',',$rule)); + case 'ip_deny': // IP 操作禁止验证 + return !in_array(get_client_ip(),explode(',',$rule)); + case 'regex': + default: // 默认使用正则验证 可以使用验证类中定义的验证名称 + // 检查附加规则 + return $this->regex($value,$rule); + } + } + + /** + * 存储过程返回多数据集 + * @access public + * @param string $sql SQL指令 + * @param mixed $parse 是否需要解析SQL + * @return array + */ + public function procedure($sql, $parse = false) { + return $this->db->procedure($sql, $parse); + } + + /** + * SQL查询 + * @access public + * @param string $sql SQL指令 + * @param mixed $parse 是否需要解析SQL + * @return mixed + */ + public function query($sql,$parse=false) { + if(!is_bool($parse) && !is_array($parse)) { + $parse = func_get_args(); + array_shift($parse); + } + $sql = $this->parseSql($sql,$parse); + return $this->db->query($sql); + } + + /** + * 执行SQL语句 + * @access public + * @param string $sql SQL指令 + * @param mixed $parse 是否需要解析SQL + * @return false | integer + */ + public function execute($sql,$parse=false) { + if(!is_bool($parse) && !is_array($parse)) { + $parse = func_get_args(); + array_shift($parse); + } + $sql = $this->parseSql($sql,$parse); + return $this->db->execute($sql); + } + + /** + * 解析SQL语句 + * @access public + * @param string $sql SQL指令 + * @param boolean $parse 是否需要解析SQL + * @return string + */ + protected function parseSql($sql,$parse) { + // 分析表达式 + if(true === $parse) { + $options = $this->_parseOptions(); + $sql = $this->db->parseSql($sql,$options); + }elseif(is_array($parse)){ // SQL预处理 + $parse = array_map(array($this->db,'escapeString'),$parse); + $sql = vsprintf($sql,$parse); + }else{ + $sql = strtr($sql,array('__TABLE__'=>$this->getTableName(),'__PREFIX__'=>$this->tablePrefix)); + $prefix = $this->tablePrefix; + $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function($match) use($prefix){ return $prefix.strtolower($match[1]);}, $sql); + } + $this->db->setModel($this->name); + return $sql; + } + + /** + * 切换当前的数据库连接 + * @access public + * @param integer $linkNum 连接序号 + * @param mixed $config 数据库连接信息 + * @param boolean $force 强制重新连接 + * @return Model + */ + public function db($linkNum='',$config='',$force=false) { + if('' === $linkNum && $this->db) { + return $this->db; + } + + if(!isset($this->_db[$linkNum]) || $force ) { + // 创建一个新的实例 + if(!empty($config) && is_string($config) && false === strpos($config,'/')) { // 支持读取配置参数 + $config = C($config); + } + $this->_db[$linkNum] = Db::getInstance($config); + }elseif(NULL === $config){ + $this->_db[$linkNum]->close(); // 关闭数据库连接 + unset($this->_db[$linkNum]); + return ; + } + + // 切换数据库连接 + $this->db = $this->_db[$linkNum]; + $this->_after_db(); + // 字段检测 + if(!empty($this->name) && $this->autoCheckFields) $this->_checkTableInfo(); + return $this; + } + // 数据库切换后回调方法 + protected function _after_db() {} + + /** + * 得到当前的数据对象名称 + * @access public + * @return string + */ + public function getModelName() { + if(empty($this->name)){ + $name = substr(get_class($this),0,-strlen(C('DEFAULT_M_LAYER'))); + if ( $pos = strrpos($name,'\\') ) {//有命名空间 + $this->name = substr($name,$pos+1); + }else{ + $this->name = $name; + } + } + return $this->name; + } + + /** + * 得到完整的数据表名 + * @access public + * @return string + */ + public function getTableName() { + if(empty($this->trueTableName)) { + $tableName = !empty($this->tablePrefix) ? $this->tablePrefix : ''; + if(!empty($this->tableName)) { + $tableName .= $this->tableName; + }else{ + $tableName .= parse_name($this->name); + } + $this->trueTableName = strtolower($tableName); + } + return (!empty($this->dbName)?$this->dbName.'.':'').$this->trueTableName; + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->commit(); + $this->db->startTrans(); + return ; + } + + /** + * 提交事务 + * @access public + * @return boolean + */ + public function commit() { + return $this->db->commit(); + } + + /** + * 事务回滚 + * @access public + * @return boolean + */ + public function rollback() { + return $this->db->rollback(); + } + + /** + * 返回模型的错误信息 + * @access public + * @return string + */ + public function getError(){ + return $this->error; + } + + /** + * 返回数据库的错误信息 + * @access public + * @return string + */ + public function getDbError() { + return $this->db->getError(); + } + + /** + * 返回最后插入的ID + * @access public + * @return string + */ + public function getLastInsID() { + return $this->db->getLastInsID(); + } + + /** + * 返回最后执行的sql语句 + * @access public + * @return string + */ + public function getLastSql() { + return $this->db->getLastSql($this->name); + } + // 鉴于getLastSql比较常用 增加_sql 别名 + public function _sql(){ + return $this->getLastSql(); + } + + /** + * 获取主键名称 + * @access public + * @return string + */ + public function getPk() { + return $this->pk; + } + + /** + * 获取数据表字段信息 + * @access public + * @return array + */ + public function getDbFields(){ + if(isset($this->options['table'])) {// 动态指定表名 + if(is_array($this->options['table'])){ + $table = key($this->options['table']); + }else{ + $table = $this->options['table']; + if(strpos($table,')')){ + // 子查询 + return false; + } + } + $fields = $this->db->getFields($table); + return $fields ? array_keys($fields) : false; + } + if($this->fields) { + $fields = $this->fields; + unset($fields['_type'],$fields['_pk']); + return $fields; + } + return false; + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据 + * @return Model + */ + public function data($data=''){ + if('' === $data && !empty($this->data)) { + return $this->data; + } + if(is_object($data)){ + $data = get_object_vars($data); + }elseif(is_string($data)){ + parse_str($data,$data); + }elseif(!is_array($data)){ + E(L('_DATA_TYPE_INVALID_')); + } + $this->data = $data; + return $this; + } + + /** + * 指定当前的数据表 + * @access public + * @param mixed $table + * @return Model + */ + public function table($table) { + $prefix = $this->tablePrefix; + if(is_array($table)) { + $this->options['table'] = $table; + }elseif(!empty($table)) { + //将__TABLE_NAME__替换成带前缀的表名 + $table = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function($match) use($prefix){ return $prefix.strtolower($match[1]);}, $table); + $this->options['table'] = $table; + } + return $this; + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using + * @return Model + */ + public function using($using){ + $prefix = $this->tablePrefix; + if(is_array($using)) { + $this->options['using'] = $using; + }elseif(!empty($using)) { + //将__TABLE_NAME__替换成带前缀的表名 + $using = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function($match) use($prefix){ return $prefix.strtolower($match[1]);}, $using); + $this->options['using'] = $using; + } + return $this; + } + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join + * @param string $type JOIN类型 + * @return Model + */ + public function join($join,$type='INNER') { + $prefix = $this->tablePrefix; + if(is_array($join)) { + foreach ($join as $key=>&$_join){ + $_join = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function($match) use($prefix){ return $prefix.strtolower($match[1]);}, $_join); + $_join = false !== stripos($_join,'JOIN')? $_join : $type.' JOIN ' .$_join; + } + $this->options['join'] = $join; + }elseif(!empty($join)) { + //将__TABLE_NAME__字符串替换成带前缀的表名 + $join = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function($match) use($prefix){ return $prefix.strtolower($match[1]);}, $join); + $this->options['join'][] = false !== stripos($join,'JOIN')? $join : $type.' JOIN '.$join; + } + return $this; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union + * @param boolean $all + * @return Model + */ + public function union($union,$all=false) { + if(empty($union)) return $this; + if($all) { + $this->options['union']['_all'] = true; + } + if(is_object($union)) { + $union = get_object_vars($union); + } + // 转换union表达式 + if(is_string($union) ) { + $prefix = $this->tablePrefix; + //将__TABLE_NAME__字符串替换成带前缀的表名 + $options = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function($match) use($prefix){ return $prefix.strtolower($match[1]);}, $union); + }elseif(is_array($union)){ + if(isset($union[0])) { + $this->options['union'] = array_merge($this->options['union'],$union); + return $this; + }else{ + $options = $union; + } + }else{ + E(L('_DATA_TYPE_INVALID_')); + } + $this->options['union'][] = $options; + return $this; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key + * @param integer $expire + * @param string $type + * @return Model + */ + public function cache($key=true,$expire=null,$type=''){ + // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) + if(is_numeric($key) && is_null($expire)){ + $expire = $key; + $key = true; + } + if(false !== $key) + $this->options['cache'] = array('key'=>$key,'expire'=>$expire,'type'=>$type); + return $this; + } + + /** + * 指定查询字段 支持字段排除 + * @access public + * @param mixed $field + * @param boolean $except 是否排除 + * @return Model + */ + public function field($field,$except=false){ + if(true === $field) {// 获取全部字段 + $fields = $this->getDbFields(); + $field = $fields?:'*'; + }elseif($except) {// 字段排除 + if(is_string($field)) { + $field = explode(',',$field); + } + $fields = $this->getDbFields(); + $field = $fields?array_diff($fields,$field):$field; + } + $this->options['field'] = $field; + return $this; + } + + /** + * 调用命名范围 + * @access public + * @param mixed $scope 命名范围名称 支持多个 和直接定义 + * @param array $args 参数 + * @return Model + */ + public function scope($scope='',$args=NULL){ + if('' === $scope) { + if(isset($this->_scope['default'])) { + // 默认的命名范围 + $options = $this->_scope['default']; + }else{ + return $this; + } + }elseif(is_string($scope)){ // 支持多个命名范围调用 用逗号分割 + $scopes = explode(',',$scope); + $options = array(); + foreach ($scopes as $name){ + if(!isset($this->_scope[$name])) continue; + $options = array_merge($options,$this->_scope[$name]); + } + if(!empty($args) && is_array($args)) { + $options = array_merge($options,$args); + } + }elseif(is_array($scope)){ // 直接传入命名范围定义 + $options = $scope; + } + + if(is_array($options) && !empty($options)){ + $this->options = array_merge($this->options,array_change_key_case($options)); + } + return $this; + } + + /** + * 指定查询条件 支持安全过滤 + * @access public + * @param mixed $where 条件表达式 + * @param mixed $parse 预处理参数 + * @return Model + */ + public function where($where,$parse=null){ + if(!is_null($parse) && is_string($where)) { + if(!is_array($parse)) { + $parse = func_get_args(); + array_shift($parse); + } + $parse = array_map(array($this->db,'escapeString'),$parse); + $where = vsprintf($where,$parse); + }elseif(is_object($where)){ + $where = get_object_vars($where); + } + if(is_string($where) && '' != $where){ + $map = array(); + $map['_string'] = $where; + $where = $map; + } + if(isset($this->options['where'])){ + $this->options['where'] = array_merge($this->options['where'],$where); + }else{ + $this->options['where'] = $where; + } + + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 + * @return Model + */ + public function limit($offset,$length=null){ + if(is_null($length) && strpos($offset,',')){ + list($offset,$length) = explode(',',$offset); + } + $this->options['limit'] = intval($offset).( $length? ','.intval($length) : '' ); + return $this; + } + + /** + * 指定分页 + * @access public + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 + * @return Model + */ + public function page($page,$listRows=null){ + if(is_null($listRows) && strpos($page,',')){ + list($page,$listRows) = explode(',',$page); + } + $this->options['page'] = array(intval($page),intval($listRows)); + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return Model + */ + public function comment($comment){ + $this->options['comment'] = $comment; + return $this; + } + + /** + * 获取执行的SQL语句 + * @access public + * @param boolean $fetch 是否返回sql + * @return Model + */ + public function fetchSql($fetch=true){ + $this->options['fetch_sql'] = $fetch; + return $this; + } + + /** + * 参数绑定 + * @access public + * @param string $key 参数名 + * @param mixed $value 绑定的变量及绑定参数 + * @return Model + */ + public function bind($key,$value=false) { + if(is_array($key)){ + $this->options['bind'] = $key; + }else{ + $num = func_num_args(); + if($num>2){ + $params = func_get_args(); + array_shift($params); + $this->options['bind'][$key] = $params; + }else{ + $this->options['bind'][$key] = $value; + } + } + return $this; + } + + /** + * 设置模型的属性值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return Model + */ + public function setProperty($name,$value) { + if(property_exists($this,$name)) + $this->$name = $value; + return $this; + } + +} diff --git a/ThinkPHP/Library/Think/Model/AdvModel.class.php b/ThinkPHP/Library/Think/Model/AdvModel.class.php new file mode 100644 index 0000000..2e91992 --- /dev/null +++ b/ThinkPHP/Library/Think/Model/AdvModel.class.php @@ -0,0 +1,595 @@ + +// +---------------------------------------------------------------------- +namespace Think\Model; +use Think\Model; +/** + * 高级模型扩展 + */ +class AdvModel extends Model { + protected $optimLock = 'lock_version'; + protected $returnType = 'array'; + protected $blobFields = array(); + protected $blobValues = null; + protected $serializeField = array(); + protected $readonlyField = array(); + protected $_filter = array(); + protected $partition = array(); + + public function __construct($name='',$tablePrefix='',$connection='') { + if('' !== $name || is_subclass_of($this,'AdvModel') ){ + // 如果是AdvModel子类或者有传入模型名称则获取字段缓存 + }else{ + // 空的模型 关闭字段缓存 + $this->autoCheckFields = false; + } + parent::__construct($name,$tablePrefix,$connection); + } + + /** + * 利用__call方法重载 实现一些特殊的Model方法 (魔术方法) + * @access public + * @param string $method 方法名称 + * @param mixed $args 调用参数 + * @return mixed + */ + public function __call($method,$args) { + if(strtolower(substr($method,0,3))=='top'){ + // 获取前N条记录 + $count = substr($method,3); + array_unshift($args,$count); + return call_user_func_array(array(&$this, 'topN'), $args); + }else{ + return parent::__call($method,$args); + } + } + + /** + * 对保存到数据库的数据进行处理 + * @access protected + * @param mixed $data 要操作的数据 + * @return boolean + */ + protected function _facade($data) { + // 检查序列化字段 + $data = $this->serializeField($data); + return parent::_facade($data); + } + + // 查询成功后的回调方法 + protected function _after_find(&$result,$options='') { + // 检查序列化字段 + $this->checkSerializeField($result); + // 获取文本字段 + $this->getBlobFields($result); + // 检查字段过滤 + $result = $this->getFilterFields($result); + // 缓存乐观锁 + $this->cacheLockVersion($result); + } + + // 查询数据集成功后的回调方法 + protected function _after_select(&$resultSet,$options='') { + // 检查序列化字段 + $resultSet = $this->checkListSerializeField($resultSet); + // 获取文本字段 + $resultSet = $this->getListBlobFields($resultSet); + // 检查列表字段过滤 + $resultSet = $this->getFilterListFields($resultSet); + } + + // 写入前的回调方法 + protected function _before_insert(&$data,$options='') { + // 记录乐观锁 + $data = $this->recordLockVersion($data); + // 检查文本字段 + $data = $this->checkBlobFields($data); + // 检查字段过滤 + $data = $this->setFilterFields($data); + } + + protected function _after_insert($data,$options) { + // 保存文本字段 + $this->saveBlobFields($data); + } + + // 更新前的回调方法 + protected function _before_update(&$data,$options='') { + // 检查乐观锁 + $pk = $this->getPK(); + if(isset($options['where'][$pk])){ + $id = $options['where'][$pk]; + if(!$this->checkLockVersion($id,$data)) { + return false; + } + } + // 检查文本字段 + $data = $this->checkBlobFields($data); + // 检查只读字段 + $data = $this->checkReadonlyField($data); + // 检查字段过滤 + $data = $this->setFilterFields($data); + } + + protected function _after_update($data,$options) { + // 保存文本字段 + $this->saveBlobFields($data); + } + + protected function _after_delete($data,$options) { + // 删除Blob数据 + $this->delBlobFields($data); + } + + /** + * 记录乐观锁 + * @access protected + * @param array $data 数据对象 + * @return array + */ + protected function recordLockVersion($data) { + // 记录乐观锁 + if($this->optimLock && !isset($data[$this->optimLock]) ) { + if(in_array($this->optimLock,$this->fields,true)) { + $data[$this->optimLock] = 0; + } + } + return $data; + } + + /** + * 缓存乐观锁 + * @access protected + * @param array $data 数据对象 + * @return void + */ + protected function cacheLockVersion($data) { + if($this->optimLock) { + if(isset($data[$this->optimLock]) && isset($data[$this->getPk()])) { + // 只有当存在乐观锁字段和主键有值的时候才记录乐观锁 + $_SESSION[$this->name.'_'.$data[$this->getPk()].'_lock_version'] = $data[$this->optimLock]; + } + } + } + + /** + * 检查乐观锁 + * @access protected + * @param inteter $id 当前主键 + * @param array $data 当前数据 + * @return mixed + */ + protected function checkLockVersion($id,&$data) { + // 检查乐观锁 + $identify = $this->name.'_'.$id.'_lock_version'; + if($this->optimLock && isset($_SESSION[$identify])) { + $lock_version = $_SESSION[$identify]; + $vo = $this->field($this->optimLock)->find($id); + $_SESSION[$identify] = $lock_version; + $curr_version = $vo[$this->optimLock]; + if(isset($curr_version)) { + if($curr_version>0 && $lock_version != $curr_version) { + // 记录已经更新 + $this->error = L('_RECORD_HAS_UPDATE_'); + return false; + }else{ + // 更新乐观锁 + $save_version = $data[$this->optimLock]; + if($save_version != $lock_version+1) { + $data[$this->optimLock] = $lock_version+1; + } + $_SESSION[$identify] = $lock_version+1; + } + } + } + return true; + } + + /** + * 查找前N个记录 + * @access public + * @param integer $count 记录个数 + * @param array $options 查询表达式 + * @return array + */ + public function topN($count,$options=array()) { + $options['limit'] = $count; + return $this->select($options); + } + + /** + * 查询符合条件的第N条记录 + * 0 表示第一条记录 -1 表示最后一条记录 + * @access public + * @param integer $position 记录位置 + * @param array $options 查询表达式 + * @return mixed + */ + public function getN($position=0,$options=array()) { + if($position>=0) { // 正向查找 + $options['limit'] = $position.',1'; + $list = $this->select($options); + return $list?$list[0]:false; + }else{ // 逆序查找 + $list = $this->select($options); + return $list?$list[count($list)-abs($position)]:false; + } + } + + /** + * 获取满足条件的第一条记录 + * @access public + * @param array $options 查询表达式 + * @return mixed + */ + public function first($options=array()) { + return $this->getN(0,$options); + } + + /** + * 获取满足条件的最后一条记录 + * @access public + * @param array $options 查询表达式 + * @return mixed + */ + public function last($options=array()) { + return $this->getN(-1,$options); + } + + /** + * 返回数据 + * @access public + * @param array $data 数据 + * @param string $type 返回类型 默认为数组 + * @return mixed + */ + public function returnResult($data,$type='') { + if('' === $type) + $type = $this->returnType; + switch($type) { + case 'array' : return $data; + case 'object': return (object)$data; + default:// 允许用户自定义返回类型 + if(class_exists($type)) + return new $type($data); + else + E(L('_CLASS_NOT_EXIST_').':'.$type); + } + } + + /** + * 获取数据的时候过滤数据字段 + * @access protected + * @param mixed $result 查询的数据 + * @return array + */ + protected function getFilterFields(&$result) { + if(!empty($this->_filter)) { + foreach ($this->_filter as $field=>$filter){ + if(isset($result[$field])) { + $fun = $filter[1]; + if(!empty($fun)) { + if(isset($filter[2]) && $filter[2]){ + // 传递整个数据对象作为参数 + $result[$field] = call_user_func($fun,$result); + }else{ + // 传递字段的值作为参数 + $result[$field] = call_user_func($fun,$result[$field]); + } + } + } + } + } + return $result; + } + + protected function getFilterListFields(&$resultSet) { + if(!empty($this->_filter)) { + foreach ($resultSet as $key=>$result) + $resultSet[$key] = $this->getFilterFields($result); + } + return $resultSet; + } + + /** + * 写入数据的时候过滤数据字段 + * @access protected + * @param mixed $result 查询的数据 + * @return array + */ + protected function setFilterFields($data) { + if(!empty($this->_filter)) { + foreach ($this->_filter as $field=>$filter){ + if(isset($data[$field])) { + $fun = $filter[0]; + if(!empty($fun)) { + if(isset($filter[2]) && $filter[2]) { + // 传递整个数据对象作为参数 + $data[$field] = call_user_func($fun,$data); + }else{ + // 传递字段的值作为参数 + $data[$field] = call_user_func($fun,$data[$field]); + } + } + } + } + } + return $data; + } + + /** + * 返回数据列表 + * @access protected + * @param array $resultSet 数据 + * @param string $type 返回类型 默认为数组 + * @return void + */ + protected function returnResultSet(&$resultSet,$type='') { + foreach ($resultSet as $key=>$data) + $resultSet[$key] = $this->returnResult($data,$type); + return $resultSet; + } + + protected function checkBlobFields(&$data) { + // 检查Blob文件保存字段 + if(!empty($this->blobFields)) { + foreach ($this->blobFields as $field){ + if(isset($data[$field])) { + if(isset($data[$this->getPk()])) + $this->blobValues[$this->name.'/'.$data[$this->getPk()].'_'.$field] = $data[$field]; + else + $this->blobValues[$this->name.'/@?id@_'.$field] = $data[$field]; + unset($data[$field]); + } + } + } + return $data; + } + + /** + * 获取数据集的文本字段 + * @access protected + * @param mixed $resultSet 查询的数据 + * @param string $field 查询的字段 + * @return void + */ + protected function getListBlobFields(&$resultSet,$field='') { + if(!empty($this->blobFields)) { + foreach ($resultSet as $key=>$result){ + $result = $this->getBlobFields($result,$field); + $resultSet[$key] = $result; + } + } + return $resultSet; + } + + /** + * 获取数据的文本字段 + * @access protected + * @param mixed $data 查询的数据 + * @param string $field 查询的字段 + * @return void + */ + protected function getBlobFields(&$data,$field='') { + if(!empty($this->blobFields)) { + $pk = $this->getPk(); + $id = $data[$pk]; + if(empty($field)) { + foreach ($this->blobFields as $field){ + $identify = $this->name.'/'.$id.'_'.$field; + $data[$field] = F($identify); + } + return $data; + }else{ + $identify = $this->name.'/'.$id.'_'.$field; + return F($identify); + } + } + } + + /** + * 保存File方式的字段 + * @access protected + * @param mixed $data 保存的数据 + * @return void + */ + protected function saveBlobFields(&$data) { + if(!empty($this->blobFields)) { + foreach ($this->blobValues as $key=>$val){ + if(strpos($key,'@?id@')) + $key = str_replace('@?id@',$data[$this->getPk()],$key); + F($key,$val); + } + } + } + + /** + * 删除File方式的字段 + * @access protected + * @param mixed $data 保存的数据 + * @param string $field 查询的字段 + * @return void + */ + protected function delBlobFields(&$data,$field='') { + if(!empty($this->blobFields)) { + $pk = $this->getPk(); + $id = $data[$pk]; + if(empty($field)) { + foreach ($this->blobFields as $field){ + $identify = $this->name.'/'.$id.'_'.$field; + F($identify,null); + } + }else{ + $identify = $this->name.'/'.$id.'_'.$field; + F($identify,null); + } + } + } + + /** + * 检查序列化数据字段 + * @access protected + * @param array $data 数据 + * @return array + */ + protected function serializeField(&$data) { + // 检查序列化字段 + if(!empty($this->serializeField)) { + // 定义方式 $this->serializeField = array('ser'=>array('name','email')); + foreach ($this->serializeField as $key=>$val){ + if(empty($data[$key])) { + $serialize = array(); + foreach ($val as $name){ + if(isset($data[$name])) { + $serialize[$name] = $data[$name]; + unset($data[$name]); + } + } + if(!empty($serialize)) { + $data[$key] = serialize($serialize); + } + } + } + } + return $data; + } + + // 检查返回数据的序列化字段 + protected function checkSerializeField(&$result) { + // 检查序列化字段 + if(!empty($this->serializeField)) { + foreach ($this->serializeField as $key=>$val){ + if(isset($result[$key])) { + $serialize = unserialize($result[$key]); + foreach ($serialize as $name=>$value) + $result[$name] = $value; + unset($serialize,$result[$key]); + } + } + } + return $result; + } + + // 检查数据集的序列化字段 + protected function checkListSerializeField(&$resultSet) { + // 检查序列化字段 + if(!empty($this->serializeField)) { + foreach ($this->serializeField as $key=>$val){ + foreach ($resultSet as $k=>$result){ + if(isset($result[$key])) { + $serialize = unserialize($result[$key]); + foreach ($serialize as $name=>$value) + $result[$name] = $value; + unset($serialize,$result[$key]); + $resultSet[$k] = $result; + } + } + } + } + return $resultSet; + } + + /** + * 检查只读字段 + * @access protected + * @param array $data 数据 + * @return array + */ + protected function checkReadonlyField(&$data) { + if(!empty($this->readonlyField)) { + foreach ($this->readonlyField as $key=>$field){ + if(isset($data[$field])) + unset($data[$field]); + } + } + return $data; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return boolean + */ + public function patchQuery($sql=array()) { + if(!is_array($sql)) return false; + // 自动启动事务支持 + $this->startTrans(); + try{ + foreach ($sql as $_sql){ + $result = $this->execute($_sql); + if(false === $result) { + // 发生错误自动回滚事务 + $this->rollback(); + return false; + } + } + // 提交事务 + $this->commit(); + } catch (ThinkException $e) { + $this->rollback(); + } + return true; + } + + /** + * 得到分表的的数据表名 + * @access public + * @param array $data 操作的数据 + * @return string + */ + public function getPartitionTableName($data=array()) { + // 对数据表进行分区 + if(isset($data[$this->partition['field']])) { + $field = $data[$this->partition['field']]; + switch($this->partition['type']) { + case 'id': + // 按照id范围分表 + $step = $this->partition['expr']; + $seq = floor($field / $step)+1; + break; + case 'year': + // 按照年份分表 + if(!is_numeric($field)) { + $field = strtotime($field); + } + $seq = date('Y',$field)-$this->partition['expr']+1; + break; + case 'mod': + // 按照id的模数分表 + $seq = ($field % $this->partition['num'])+1; + break; + case 'md5': + // 按照md5的序列分表 + $seq = (ord(substr(md5($field),0,1)) % $this->partition['num'])+1; + break; + default : + if(function_exists($this->partition['type'])) { + // 支持指定函数哈希 + $fun = $this->partition['type']; + $seq = (ord(substr($fun($field),0,1)) % $this->partition['num'])+1; + }else{ + // 按照字段的首字母的值分表 + $seq = (ord($field{0}) % $this->partition['num'])+1; + } + } + return $this->getTableName().'_'.$seq; + }else{ + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = array(); + for($i=0;$i<$this->partition['num'];$i++) + $tableName[] = 'SELECT * FROM '.$this->getTableName().'_'.($i+1); + $tableName = '( '.implode(" UNION ",$tableName).') AS '.$this->name; + return $tableName; + } + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Model/MergeModel.class.php b/ThinkPHP/Library/Think/Model/MergeModel.class.php new file mode 100644 index 0000000..21b32a8 --- /dev/null +++ b/ThinkPHP/Library/Think/Model/MergeModel.class.php @@ -0,0 +1,403 @@ + +// +---------------------------------------------------------------------- +namespace Think\Model; +use Think\Model; +/** + * ThinkPHP 聚合模型扩展 + */ +class MergeModel extends Model { + + protected $modelList = array(); // 包含的模型列表 第一个必须是主表模型 + protected $masterModel = ''; // 主模型 + protected $joinType = 'INNER'; // 聚合模型的查询JOIN类型 + protected $fk = ''; // 外键名 默认为主表名_id + protected $mapFields = array(); // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) + + /** + * 架构函数 + * 取得DB类的实例对象 字段检查 + * @access public + * @param string $name 模型名称 + * @param string $tablePrefix 表前缀 + * @param mixed $connection 数据库连接信息 + */ + public function __construct($name='',$tablePrefix='',$connection=''){ + parent::__construct($name,$tablePrefix,$connection); + // 聚合模型的字段信息 + if(empty($this->fields) && !empty($this->modelList)){ + $fields = array(); + foreach($this->modelList as $model){ + // 获取模型的字段信息 + $result = $this->db->getFields(M($model)->getTableName()); + $_fields = array_keys($result); + // $this->mapFields = array_intersect($fields,$_fields); + $fields = array_merge($fields,$_fields); + } + $this->fields = $fields; + } + + // 设置第一个模型为主表模型 + if(empty($this->masterModel) && !empty($this->modelList)){ + $this->masterModel = $this->modelList[0]; + } + // 主表的主键名 + $this->pk = M($this->masterModel)->getPk(); + + // 设置默认外键名 仅支持单一外键 + if(empty($this->fk)){ + $this->fk = strtolower($this->masterModel).'_id'; + } + + } + + /** + * 得到完整的数据表名 + * @access public + * @return string + */ + public function getTableName() { + if(empty($this->trueTableName)) { + $tableName = array(); + $models = $this->modelList; + foreach($models as $model){ + $tableName[] = M($model)->getTableName().' '.$model; + } + $this->trueTableName = implode(',',$tableName); + } + return $this->trueTableName; + } + + /** + * 自动检测数据表信息 + * @access protected + * @return void + */ + protected function _checkTableInfo() {} + + /** + * 新增聚合数据 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @param boolean $replace 是否replace + * @return mixed + */ + public function add($data='',$options=array(),$replace=false){ + if(empty($data)) { + // 没有传递数据,获取当前数据对象的值 + if(!empty($this->data)) { + $data = $this->data; + // 重置数据 + $this->data = array(); + }else{ + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + } + // 启动事务 + $this->startTrans(); + // 写入主表数据 + $result = M($this->masterModel)->strict(false)->add($data); + if($result){ + // 写入外键数据 + $data[$this->fk] = $result; + $models = $this->modelList; + array_shift($models); + // 写入附表数据 + foreach($models as $model){ + $res = M($model)->strict(false)->add($data); + if(!$res){ + $this->rollback(); + return false; + } + } + // 提交事务 + $this->commit(); + }else{ + $this->rollback(); + return false; + } + return $result; + } + + /** + * 对保存到数据库的数据进行处理 + * @access protected + * @param mixed $data 要操作的数据 + * @return boolean + */ + protected function _facade($data) { + + // 检查数据字段合法性 + if(!empty($this->fields)) { + if(!empty($this->options['field'])) { + $fields = $this->options['field']; + unset($this->options['field']); + if(is_string($fields)) { + $fields = explode(',',$fields); + } + }else{ + $fields = $this->fields; + } + foreach ($data as $key=>$val){ + if(!in_array($key,$fields,true)){ + unset($data[$key]); + }elseif(array_key_exists($key,$this->mapFields)){ + // 需要处理映射字段 + $data[$this->mapFields[$key]] = $val; + unset($data[$key]); + } + } + } + + // 安全过滤 + if(!empty($this->options['filter'])) { + $data = array_map($this->options['filter'],$data); + unset($this->options['filter']); + } + $this->_before_write($data); + return $data; + } + + /** + * 保存聚合模型数据 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return boolean + */ + public function save($data='',$options=array()){ + // 根据主表的主键更新 + if(empty($data)) { + // 没有传递数据,获取当前数据对象的值 + if(!empty($this->data)) { + $data = $this->data; + // 重置数据 + $this->data = array(); + }else{ + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + } + if(empty($data)){ + // 没有数据则不执行 + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + // 如果存在主键数据 则自动作为更新条件 + $pk = $this->pk; + if(isset($data[$pk])) { + $where[$pk] = $data[$pk]; + $options['where'] = $where; + unset($data[$pk]); + } + $options['join'] = ''; + $options = $this->_parseOptions($options); + // 更新操作不使用JOIN + $options['table'] = $this->getTableName(); + + if(is_array($options['where']) && isset($options['where'][$pk])){ + $pkValue = $options['where'][$pk]; + } + if(false === $this->_before_update($data,$options)) { + return false; + } + $result = $this->db->update($data,$options); + if(false !== $result) { + if(isset($pkValue)) $data[$pk] = $pkValue; + $this->_after_update($data,$options); + } + return $result; + } + + /** + * 删除聚合模型数据 + * @access public + * @param mixed $options 表达式 + * @return mixed + */ + public function delete($options=array()){ + $pk = $this->pk; + if(empty($options) && empty($this->options['where'])) { + // 如果删除条件为空 则删除当前数据对象所对应的记录 + if(!empty($this->data) && isset($this->data[$pk])) + return $this->delete($this->data[$pk]); + else + return false; + } + + if(is_numeric($options) || is_string($options)) { + // 根据主键删除记录 + if(strpos($options,',')) { + $where[$pk] = array('IN', $options); + }else{ + $where[$pk] = $options; + } + $options = array(); + $options['where'] = $where; + } + // 分析表达式 + $options['join'] = ''; + $options = $this->_parseOptions($options); + if(empty($options['where'])){ + // 如果条件为空 不进行删除操作 除非设置 1=1 + return false; + } + if(is_array($options['where']) && isset($options['where'][$pk])){ + $pkValue = $options['where'][$pk]; + } + + $options['table'] = implode(',',$this->modelList); + $options['using'] = $this->getTableName(); + if(false === $this->_before_delete($options)) { + return false; + } + $result = $this->db->delete($options); + if(false !== $result) { + $data = array(); + if(isset($pkValue)) $data[$pk] = $pkValue; + $this->_after_delete($data,$options); + } + // 返回删除记录个数 + return $result; + } + + /** + * 表达式过滤方法 + * @access protected + * @param string $options 表达式 + * @return void + */ + protected function _options_filter(&$options) { + if(!isset($options['join'])){ + $models = $this->modelList; + array_shift($models); + foreach($models as $model){ + $options['join'][] = $this->joinType.' JOIN '.M($model)->getTableName().' '.$model.' ON '.$this->masterModel.'.'.$this->pk.' = '.$model.'.'.$this->fk; + } + } + $options['table'] = M($this->masterModel)->getTableName().' '.$this->masterModel; + $options['field'] = $this->checkFields(isset($options['field'])?$options['field']:''); + if(isset($options['group'])) + $options['group'] = $this->checkGroup($options['group']); + if(isset($options['where'])) + $options['where'] = $this->checkCondition($options['where']); + if(isset($options['order'])) + $options['order'] = $this->checkOrder($options['order']); + } + + /** + * 检查条件中的聚合字段 + * @access protected + * @param mixed $data 条件表达式 + * @return array + */ + protected function checkCondition($where) { + if(is_array($where)) { + $view = array(); + foreach($where as $name=>$value){ + if(array_key_exists($name,$this->mapFields)){ + // 需要处理映射字段 + $view[$this->mapFields[$name]] = $value; + unset($where[$name]); + } + } + $where = array_merge($where,$view); + } + return $where; + } + + /** + * 检查Order表达式中的聚合字段 + * @access protected + * @param string $order 字段 + * @return string + */ + protected function checkOrder($order='') { + if(is_string($order) && !empty($order)) { + $orders = explode(',',$order); + $_order = array(); + foreach ($orders as $order){ + $array = explode(' ',trim($order)); + $field = $array[0]; + $sort = isset($array[1])?$array[1]:'ASC'; + if(array_key_exists($field,$this->mapFields)){ + // 需要处理映射字段 + $field = $this->mapFields[$field]; + } + $_order[] = $field.' '.$sort; + } + $order = implode(',',$_order); + } + return $order; + } + + /** + * 检查Group表达式中的聚合字段 + * @access protected + * @param string $group 字段 + * @return string + */ + protected function checkGroup($group='') { + if(!empty($group)) { + $groups = explode(',',$group); + $_group = array(); + foreach ($groups as $field){ + // 解析成聚合字段 + if(array_key_exists($field,$this->mapFields)){ + // 需要处理映射字段 + $field = $this->mapFields[$field]; + } + $_group[] = $field; + } + $group = implode(',',$_group); + } + return $group; + } + + /** + * 检查fields表达式中的聚合字段 + * @access protected + * @param string $fields 字段 + * @return string + */ + protected function checkFields($fields='') { + if(empty($fields) || '*'==$fields ) { + // 获取全部聚合字段 + $fields = $this->fields; + } + if(!is_array($fields)) + $fields = explode(',',$fields); + + // 解析成聚合字段 + $array = array(); + foreach ($fields as $field){ + if(array_key_exists($field,$this->mapFields)){ + // 需要处理映射字段 + $array[] = $this->mapFields[$field].' AS '.$field; + }else{ + $array[] = $field; + } + } + $fields = implode(',',$array); + return $fields; + } + + /** + * 获取数据表字段信息 + * @access public + * @return array + */ + public function getDbFields(){ + return $this->fields; + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Model/MongoModel.class.php b/ThinkPHP/Library/Think/Model/MongoModel.class.php new file mode 100644 index 0000000..d1927d7 --- /dev/null +++ b/ThinkPHP/Library/Think/Model/MongoModel.class.php @@ -0,0 +1,422 @@ + +// +---------------------------------------------------------------------- +namespace Think\Model; +use Think\Model; +/** + * MongoModel模型类 + * 实现了ODM和ActiveRecords模式 + */ +class MongoModel extends Model{ + // 主键类型 + const TYPE_OBJECT = 1; + const TYPE_INT = 2; + const TYPE_STRING = 3; + + // 主键名称 + protected $pk = '_id'; + // _id 类型 1 Object 采用MongoId对象 2 Int 整形 支持自动增长 3 String 字符串Hash + protected $_idType = self::TYPE_OBJECT; + // 主键是否自增 + protected $_autoinc = true; + // Mongo默认关闭字段检测 可以动态追加字段 + protected $autoCheckFields = false; + // 链操作方法列表 + protected $methods = array('table','order','auto','filter','validate'); + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + */ + public function __call($method,$args) { + if(in_array(strtolower($method),$this->methods,true)) { + // 连贯操作的实现 + $this->options[strtolower($method)] = $args[0]; + return $this; + }elseif(strtolower(substr($method,0,5))=='getby') { + // 根据某个字段获取记录 + $field = parse_name(substr($method,5)); + $where[$field] =$args[0]; + return $this->where($where)->find(); + }elseif(strtolower(substr($method,0,10))=='getfieldby') { + // 根据某个字段获取记录的某个值 + $name = parse_name(substr($method,10)); + $where[$name] =$args[0]; + return $this->where($where)->getField($args[1]); + }else{ + E(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_')); + return; + } + } + + /** + * 获取字段信息并缓存 主键和自增信息直接配置 + * @access public + * @return void + */ + public function flush() { + // 缓存不存在则查询数据表信息 + $fields = $this->db->getFields(); + if(!$fields) { // 暂时没有数据无法获取字段信息 下次查询 + return false; + } + $this->fields = array_keys($fields); + foreach ($fields as $key=>$val){ + // 记录字段类型 + $type[$key] = $val['type']; + } + // 记录字段类型信息 + if(C('DB_FIELDTYPE_CHECK')) $this->fields['_type'] = $type; + + // 2008-3-7 增加缓存开关控制 + if(C('DB_FIELDS_CACHE')){ + // 永久缓存数据表信息 + $db = $this->dbName?$this->dbName:C('DB_NAME'); + F('_fields/'.$db.'.'.$this->name,$this->fields); + } + } + + // 写入数据前的回调方法 包括新增和更新 + protected function _before_write(&$data) { + $pk = $this->getPk(); + // 根据主键类型处理主键数据 + if(isset($data[$pk]) && $this->_idType == self::TYPE_OBJECT) { + $data[$pk] = new \MongoId($data[$pk]); + } + } + + /** + * count统计 配合where连贯操作 + * @access public + * @return integer + */ + public function count(){ + // 分析表达式 + $options = $this->_parseOptions(); + return $this->db->count($options); + } + + /** + * 获取唯一值 + * @access public + * @return array | false + */ + public function distinct($field, $where=array() ){ + // 分析表达式 + $this->options = $this->_parseOptions(); + $this->options['where'] = array_merge((array)$this->options['where'], $where); + + $command = array( + "distinct" => $this->options['table'], + "key" => $field, + "query" => $this->options['where'] + ); + + $result = $this->command($command); + return isset($result['values']) ? $result['values'] : false; + } + + /** + * 获取下一ID 用于自动增长型 + * @access public + * @param string $pk 字段名 默认为主键 + * @return mixed + */ + public function getMongoNextId($pk=''){ + if(empty($pk)) { + $pk = $this->getPk(); + } + return $this->db->getMongoNextId($pk); + } + + /** + * 新增数据 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @param boolean $replace 是否replace + * @return mixed + */ + public function add($data='',$options=array(),$replace=false) { + if(empty($data)) { + // 没有传递数据,获取当前数据对象的值 + if(!empty($this->data)) { + $data = $this->data; + // 重置数据 + $this->data = array(); + }else{ + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + } + // 分析表达式 + $options = $this->_parseOptions($options); + // 数据处理 + $data = $this->_facade($data); + if(false === $this->_before_insert($data,$options)) { + return false; + } + // 写入数据到数据库 + $result = $this->db->insert($data,$options,$replace); + if(false !== $result ) { + $this->_after_insert($data,$options); + if(isset($data[$this->getPk()])){ + return $data[$this->getPk()]; + } + } + return $result; + } + + // 插入数据前的回调方法 + protected function _before_insert(&$data,$options) { + // 写入数据到数据库 + if($this->_autoinc && $this->_idType== self::TYPE_INT) { // 主键自动增长 + $pk = $this->getPk(); + if(!isset($data[$pk])) { + $data[$pk] = $this->db->getMongoNextId($pk); + } + } + } + + public function clear(){ + return $this->db->clear(); + } + + // 查询成功后的回调方法 + protected function _after_select(&$resultSet,$options) { + array_walk($resultSet,array($this,'checkMongoId')); + } + + /** + * 获取MongoId + * @access protected + * @param array $result 返回数据 + * @return array + */ + protected function checkMongoId(&$result){ + if(is_object($result['_id'])) { + $result['_id'] = $result['_id']->__toString(); + } + return $result; + } + + // 表达式过滤回调方法 + protected function _options_filter(&$options) { + $id = $this->getPk(); + if(isset($options['where'][$id]) && is_scalar($options['where'][$id]) && $this->_idType== self::TYPE_OBJECT) { + $options['where'][$id] = new \MongoId($options['where'][$id]); + } + } + + /** + * 查询数据 + * @access public + * @param mixed $options 表达式参数 + * @return mixed + */ + public function find($options=array()) { + if( is_numeric($options) || is_string($options)) { + $id = $this->getPk(); + $where[$id] = $options; + $options = array(); + $options['where'] = $where; + } + // 分析表达式 + $options = $this->_parseOptions($options); + $result = $this->db->find($options); + if(false === $result) { + return false; + } + if(empty($result)) {// 查询结果为空 + return null; + }else{ + $this->checkMongoId($result); + } + $this->data = $result; + $this->_after_find($this->data,$options); + return $this->data; + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @return boolean + */ + public function setInc($field,$step=1) { + return $this->setField($field,array('inc',$step)); + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @return boolean + */ + public function setDec($field,$step=1) { + return $this->setField($field,array('inc','-'.$step)); + } + + /** + * 获取一条记录的某个字段值 + * @access public + * @param string $field 字段名 + * @param string $spea 字段数据间隔符号 + * @return mixed + */ + public function getField($field,$sepa=null) { + $options['field'] = $field; + $options = $this->_parseOptions($options); + if(strpos($field,',')) { // 多字段 + if(is_numeric($sepa)) {// 限定数量 + $options['limit'] = $sepa; + $sepa = null;// 重置为null 返回数组 + } + $resultSet = $this->db->select($options); + if(!empty($resultSet)) { + $_field = explode(',', $field); + $field = array_keys($resultSet[0]); + $key = array_shift($field); + $key2 = array_shift($field); + $cols = array(); + $count = count($_field); + foreach ($resultSet as $result){ + $name = $result[$key]; + if(2==$count) { + $cols[$name] = $result[$key2]; + }else{ + $cols[$name] = is_null($sepa)?$result:implode($sepa,$result); + } + } + return $cols; + } + }else{ + // 返回数据个数 + if(true !== $sepa) {// 当sepa指定为true的时候 返回所有数据 + $options['limit'] = is_numeric($sepa)?$sepa:1; + } // 查找符合的记录 + $result = $this->db->select($options); + if(!empty($result)) { + if(1==$options['limit']) { + $result = reset($result); + return $result[$field]; + } + foreach ($result as $val){ + $array[] = $val[$field]; + } + return $array; + } + } + return null; + } + + /** + * 执行Mongo指令 + * @access public + * @param array $command 指令 + * @return mixed + */ + public function command($command, $options=array()) { + $options = $this->_parseOptions($options); + return $this->db->command($command, $options); + } + + /** + * 执行MongoCode + * @access public + * @param string $code MongoCode + * @param array $args 参数 + * @return mixed + */ + public function mongoCode($code,$args=array()) { + return $this->db->execute($code,$args); + } + + // 数据库切换后回调方法 + protected function _after_db() { + // 切换Collection + $this->db->switchCollection($this->getTableName(),$this->dbName?$this->dbName:C('db_name')); + } + + /** + * 得到完整的数据表名 Mongo表名不带dbName + * @access public + * @return string + */ + public function getTableName() { + if(empty($this->trueTableName)) { + $tableName = !empty($this->tablePrefix) ? $this->tablePrefix : ''; + if(!empty($this->tableName)) { + $tableName .= $this->tableName; + }else{ + $tableName .= parse_name($this->name); + } + $this->trueTableName = strtolower($tableName); + } + return $this->trueTableName; + } + + /** + * 分组查询 + * @access public + * @return string + */ + public function group($key, $init, $reduce, $option=array()) { + $option = $this->_parseOptions($option); + + //合并查询条件 + if(isset($option['where'])) + $option['condition'] = array_merge((array)$option['condition'], $option['where']); + + return $this->db->group($key, $init, $reduce, $option); + } + + /** + * 返回Mongo运行错误信息 + * @access public + * @return json + */ + public function getLastError(){ + return $this->db->command(array('getLastError'=>1)); + } + + /** + * 返回指定集合的统计信息,包括数据大小、已分配的存储空间和索引的大小 + * @access public + * @return json + */ + public function status(){ + $option = $this->_parseOptions(); + return $this->db->command(array('collStats'=>$option['table'])); + } + + /** + * 取得当前数据库的对象 + * @access public + * @return object + */ + public function getDB(){ + return $this->db->getDB(); + } + + /** + * 取得集合对象,可以进行创建索引等查询 + * @access public + * @return object + */ + public function getCollection(){ + return $this->db->getCollection(); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Model/RelationModel.class.php b/ThinkPHP/Library/Think/Model/RelationModel.class.php new file mode 100644 index 0000000..6a3b39e --- /dev/null +++ b/ThinkPHP/Library/Think/Model/RelationModel.class.php @@ -0,0 +1,412 @@ + +// +---------------------------------------------------------------------- +namespace Think\Model; +use Think\Model; +/** + * ThinkPHP关联模型扩展 + */ +class RelationModel extends Model { + + const HAS_ONE = 1; + const BELONGS_TO = 2; + const HAS_MANY = 3; + const MANY_TO_MANY= 4; + + // 关联定义 + protected $_link = array(); + + /** + * 动态方法实现 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + */ + public function __call($method,$args) { + if(strtolower(substr($method,0,8))=='relation'){ + $type = strtoupper(substr($method,8)); + if(in_array($type,array('ADD','SAVE','DEL'),true)) { + array_unshift($args,$type); + return call_user_func_array(array(&$this, 'opRelation'), $args); + } + }else{ + return parent::__call($method,$args); + } + } + + /** + * 得到关联的数据表名 + * @access public + * @return string + */ + public function getRelationTableName($relation) { + $relationTable = !empty($this->tablePrefix) ? $this->tablePrefix : ''; + $relationTable .= $this->tableName?$this->tableName:$this->name; + $relationTable .= '_'.$relation->getModelName(); + return strtolower($relationTable); + } + + // 查询成功后的回调方法 + protected function _after_find(&$result,$options) { + // 获取关联数据 并附加到结果中 + if(!empty($options['link'])) + $this->getRelation($result,$options['link']); + } + + // 查询数据集成功后的回调方法 + protected function _after_select(&$result,$options) { + // 获取关联数据 并附加到结果中 + if(!empty($options['link'])) + $this->getRelations($result,$options['link']); + } + + // 写入成功后的回调方法 + protected function _after_insert($data,$options) { + // 关联写入 + if(!empty($options['link'])) + $this->opRelation('ADD',$data,$options['link']); + } + + // 更新成功后的回调方法 + protected function _after_update($data,$options) { + // 关联更新 + if(!empty($options['link'])) + $this->opRelation('SAVE',$data,$options['link']); + } + + // 删除成功后的回调方法 + protected function _after_delete($data,$options) { + // 关联删除 + if(!empty($options['link'])) + $this->opRelation('DEL',$data,$options['link']); + } + + /** + * 对保存到数据库的数据进行处理 + * @access protected + * @param mixed $data 要操作的数据 + * @return boolean + */ + protected function _facade($data) { + $this->_before_write($data); + return $data; + } + + /** + * 获取返回数据集的关联记录 + * @access protected + * @param array $resultSet 返回数据 + * @param string|array $name 关联名称 + * @return array + */ + protected function getRelations(&$resultSet,$name='') { + // 获取记录集的主键列表 + foreach($resultSet as $key=>$val) { + $val = $this->getRelation($val,$name); + $resultSet[$key] = $val; + } + return $resultSet; + } + + /** + * 获取返回数据的关联记录 + * @access protected + * @param mixed $result 返回数据 + * @param string|array $name 关联名称 + * @param boolean $return 是否返回关联数据本身 + * @return array + */ + protected function getRelation(&$result,$name='',$return=false) { + if(!empty($this->_link)) { + foreach($this->_link as $key=>$val) { + $mappingName = !empty($val['mapping_name'])?$val['mapping_name']:$key; // 映射名称 + if(empty($name) || true === $name || $mappingName == $name || (is_array($name) && in_array($mappingName,$name))) { + $mappingType = !empty($val['mapping_type'])?$val['mapping_type']:$val; // 关联类型 + $mappingClass = !empty($val['class_name'])?$val['class_name']:$key; // 关联类名 + $mappingFields = !empty($val['mapping_fields'])?$val['mapping_fields']:'*'; // 映射字段 + $mappingCondition = !empty($val['condition'])?$val['condition']:'1=1'; // 关联条件 + $mappingKey =!empty($val['mapping_key'])? $val['mapping_key'] : $this->getPk(); // 关联键名 + if(strtoupper($mappingClass)==strtoupper($this->name)) { + // 自引用关联 获取父键名 + $mappingFk = !empty($val['parent_key'])? $val['parent_key'] : 'parent_id'; + }else{ + $mappingFk = !empty($val['foreign_key'])?$val['foreign_key']:strtolower($this->name).'_id'; // 关联外键 + } + // 获取关联模型对象 + $model = D($mappingClass); + switch($mappingType) { + case self::HAS_ONE: + $pk = $result[$mappingKey]; + $mappingCondition .= " AND {$mappingFk}='{$pk}'"; + $relationData = $model->where($mappingCondition)->field($mappingFields)->find(); + if (!empty($val['relation_deep'])){ + $model->getRelation($relationData,$val['relation_deep']); + } + break; + case self::BELONGS_TO: + if(strtoupper($mappingClass)==strtoupper($this->name)) { + // 自引用关联 获取父键名 + $mappingFk = !empty($val['parent_key'])? $val['parent_key'] : 'parent_id'; + }else{ + $mappingFk = !empty($val['foreign_key'])?$val['foreign_key']:strtolower($model->getModelName()).'_id'; // 关联外键 + } + $fk = $result[$mappingFk]; + $mappingCondition .= " AND {$model->getPk()}='{$fk}'"; + $relationData = $model->where($mappingCondition)->field($mappingFields)->find(); + if (!empty($val['relation_deep'])){ + $model->getRelation($relationData,$val['relation_deep']); + } + break; + case self::HAS_MANY: + $pk = $result[$mappingKey]; + $mappingCondition .= " AND {$mappingFk}='{$pk}'"; + $mappingOrder = !empty($val['mapping_order'])?$val['mapping_order']:''; + $mappingLimit = !empty($val['mapping_limit'])?$val['mapping_limit']:''; + // 延时获取关联记录 + $relationData = $model->where($mappingCondition)->field($mappingFields)->order($mappingOrder)->limit($mappingLimit)->select(); + if (!empty($val['relation_deep'])){ + foreach($relationData as $key=>$data){ + $model->getRelation($data,$val['relation_deep']); + $relationData[$key] = $data; + } + } + break; + case self::MANY_TO_MANY: + $pk = $result[$mappingKey]; + $prefix = $this->tablePrefix; + $mappingCondition = " {$mappingFk}='{$pk}'"; + $mappingOrder = $val['mapping_order']; + $mappingLimit = $val['mapping_limit']; + $mappingRelationFk = $val['relation_foreign_key']?$val['relation_foreign_key']:$model->getModelName().'_id'; + if(isset($val['relation_table'])){ + $mappingRelationTable = preg_replace_callback("/__([A-Z_-]+)__/sU", function($match) use($prefix){ return $prefix.strtolower($match[1]);}, $val['relation_table']); + }else{ + $mappingRelationTable = $this->getRelationTableName($model); + } + $sql = "SELECT b.{$mappingFields} FROM {$mappingRelationTable} AS a, ".$model->getTableName()." AS b WHERE a.{$mappingRelationFk} = b.{$model->getPk()} AND a.{$mappingCondition}"; + if(!empty($val['condition'])) { + $sql .= ' AND '.$val['condition']; + } + if(!empty($mappingOrder)) { + $sql .= ' ORDER BY '.$mappingOrder; + } + if(!empty($mappingLimit)) { + $sql .= ' LIMIT '.$mappingLimit; + } + $relationData = $this->query($sql); + if (!empty($val['relation_deep'])){ + foreach($relationData as $key=>$data){ + $model->getRelation($data,$val['relation_deep']); + $relationData[$key] = $data; + } + } + break; + } + if(!$return){ + if(isset($val['as_fields']) && in_array($mappingType,array(self::HAS_ONE,self::BELONGS_TO)) ) { + // 支持直接把关联的字段值映射成数据对象中的某个字段 + // 仅仅支持HAS_ONE BELONGS_TO + $fields = explode(',',$val['as_fields']); + foreach ($fields as $field){ + if(strpos($field,':')) { + list($relationName,$nick) = explode(':',$field); + $result[$nick] = $relationData[$relationName]; + }else{ + $result[$field] = $relationData[$field]; + } + } + }else{ + $result[$mappingName] = $relationData; + } + unset($relationData); + }else{ + return $relationData; + } + } + } + } + return $result; + } + + /** + * 操作关联数据 + * @access protected + * @param string $opType 操作方式 ADD SAVE DEL + * @param mixed $data 数据对象 + * @param string $name 关联名称 + * @return mixed + */ + protected function opRelation($opType,$data='',$name='') { + $result = false; + if(empty($data) && !empty($this->data)){ + $data = $this->data; + }elseif(!is_array($data)){ + // 数据无效返回 + return false; + } + if(!empty($this->_link)) { + // 遍历关联定义 + foreach($this->_link as $key=>$val) { + // 操作制定关联类型 + $mappingName = $val['mapping_name']?$val['mapping_name']:$key; // 映射名称 + if(empty($name) || true === $name || $mappingName == $name || (is_array($name) && in_array($mappingName,$name)) ) { + // 操作制定的关联 + $mappingType = !empty($val['mapping_type'])?$val['mapping_type']:$val; // 关联类型 + $mappingClass = !empty($val['class_name'])?$val['class_name']:$key; // 关联类名 + $mappingKey =!empty($val['mapping_key'])? $val['mapping_key'] : $this->getPk(); // 关联键名 + // 当前数据对象主键值 + $pk = $data[$mappingKey]; + if(strtoupper($mappingClass)==strtoupper($this->name)) { + // 自引用关联 获取父键名 + $mappingFk = !empty($val['parent_key'])? $val['parent_key'] : 'parent_id'; + }else{ + $mappingFk = !empty($val['foreign_key'])?$val['foreign_key']:strtolower($this->name).'_id'; // 关联外键 + } + if(!empty($val['condition'])) { + $mappingCondition = $val['condition']; + }else{ + $mappingCondition = array(); + $mappingCondition[$mappingFk] = $pk; + } + // 获取关联model对象 + $model = D($mappingClass); + $mappingData = isset($data[$mappingName])?$data[$mappingName]:false; + if(!empty($mappingData) || $opType == 'DEL') { + switch($mappingType) { + case self::HAS_ONE: + switch (strtoupper($opType)){ + case 'ADD': // 增加关联数据 + $mappingData[$mappingFk] = $pk; + $result = $model->add($mappingData); + break; + case 'SAVE': // 更新关联数据 + $result = $model->where($mappingCondition)->save($mappingData); + break; + case 'DEL': // 根据外键删除关联数据 + $result = $model->where($mappingCondition)->delete(); + break; + } + break; + case self::BELONGS_TO: + break; + case self::HAS_MANY: + switch (strtoupper($opType)){ + case 'ADD' : // 增加关联数据 + $model->startTrans(); + foreach ($mappingData as $val){ + $val[$mappingFk] = $pk; + $result = $model->add($val); + } + $model->commit(); + break; + case 'SAVE' : // 更新关联数据 + $model->startTrans(); + $pk = $model->getPk(); + foreach ($mappingData as $vo){ + if(isset($vo[$pk])) {// 更新数据 + $mappingCondition = "$pk ={$vo[$pk]}"; + $result = $model->where($mappingCondition)->save($vo); + }else{ // 新增数据 + $vo[$mappingFk] = $data[$mappingKey]; + $result = $model->add($vo); + } + } + $model->commit(); + break; + case 'DEL' : // 删除关联数据 + $result = $model->where($mappingCondition)->delete(); + break; + } + break; + case self::MANY_TO_MANY: + $mappingRelationFk = $val['relation_foreign_key']?$val['relation_foreign_key']:$model->getModelName().'_id';// 关联 + $prefix = $this->tablePrefix; + if(isset($val['relation_table'])){ + $mappingRelationTable = preg_replace_callback("/__([A-Z_-]+)__/sU", function($match) use($prefix){ return $prefix.strtolower($match[1]);}, $val['relation_table']); + }else{ + $mappingRelationTable = $this->getRelationTableName($model); + } + if(is_array($mappingData)) { + $ids = array(); + foreach ($mappingData as $vo) + $ids[] = $vo[$mappingKey]; + $relationId = implode(',',$ids); + } + switch (strtoupper($opType)){ + case 'ADD': // 增加关联数据 + if(isset($relationId)) { + $this->startTrans(); + // 插入关联表数据 + $sql = 'INSERT INTO '.$mappingRelationTable.' ('.$mappingFk.','.$mappingRelationFk.') SELECT a.'.$this->getPk().',b.'.$model->getPk().' FROM '.$this->getTableName().' AS a ,'.$model->getTableName()." AS b where a.".$this->getPk().' ='. $pk.' AND b.'.$model->getPk().' IN ('.$relationId.") "; + $result = $model->execute($sql); + if(false !== $result) + // 提交事务 + $this->commit(); + else + // 事务回滚 + $this->rollback(); + } + break; + case 'SAVE': // 更新关联数据 + if(isset($relationId)) { + $this->startTrans(); + // 删除关联表数据 + $this->table($mappingRelationTable)->where($mappingCondition)->delete(); + // 插入关联表数据 + $sql = 'INSERT INTO '.$mappingRelationTable.' ('.$mappingFk.','.$mappingRelationFk.') SELECT a.'.$this->getPk().',b.'.$model->getPk().' FROM '.$this->getTableName().' AS a ,'.$model->getTableName()." AS b where a.".$this->getPk().' ='. $pk.' AND b.'.$model->getPk().' IN ('.$relationId.") "; + $result = $model->execute($sql); + if(false !== $result) + // 提交事务 + $this->commit(); + else + // 事务回滚 + $this->rollback(); + } + break; + case 'DEL': // 根据外键删除中间表关联数据 + $result = $this->table($mappingRelationTable)->where($mappingCondition)->delete(); + break; + } + break; + } + if (!empty($val['relation_deep'])){ + $model->opRelation($opType,$mappingData,$val['relation_deep']); + } + } + } + } + } + return $result; + } + + /** + * 进行关联查询 + * @access public + * @param mixed $name 关联名称 + * @return Model + */ + public function relation($name) { + $this->options['link'] = $name; + return $this; + } + + /** + * 关联数据获取 仅用于查询后 + * @access public + * @param string $name 关联名称 + * @return array + */ + public function relationGet($name) { + if(empty($this->data)) + return false; + return $this->getRelation($this->data,$name,true); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Model/ViewModel.class.php b/ThinkPHP/Library/Think/Model/ViewModel.class.php new file mode 100644 index 0000000..692874e --- /dev/null +++ b/ThinkPHP/Library/Think/Model/ViewModel.class.php @@ -0,0 +1,243 @@ + +// +---------------------------------------------------------------------- +namespace Think\Model; +use Think\Model; +/** + * ThinkPHP视图模型扩展 + */ +class ViewModel extends Model { + + protected $viewFields = array(); + + /** + * 自动检测数据表信息 + * @access protected + * @return void + */ + protected function _checkTableInfo() {} + + /** + * 得到完整的数据表名 + * @access public + * @return string + */ + public function getTableName() { + if(empty($this->trueTableName)) { + $tableName = ''; + foreach ($this->viewFields as $key=>$view){ + // 获取数据表名称 + if(isset($view['_table'])) { // 2011/10/17 添加实际表名定义支持 可以实现同一个表的视图 + $tableName .= $view['_table']; + $prefix = $this->tablePrefix; + $tableName = preg_replace_callback("/__([A-Z_-]+)__/sU", function($match) use($prefix){ return $prefix.strtolower($match[1]);}, $tableName); + }else{ + $class = $key.'Model'; + $Model = class_exists($class)?new $class():M($key); + $tableName .= $Model->getTableName(); + } + // 表别名定义 + $tableName .= !empty($view['_as'])?' '.$view['_as']:' '.$key; + // 支持ON 条件定义 + $tableName .= !empty($view['_on'])?' ON '.$view['_on']:''; + // 指定JOIN类型 例如 RIGHT INNER LEFT 下一个表有效 + $type = !empty($view['_type'])?$view['_type']:''; + $tableName .= ' '.strtoupper($type).' JOIN '; + $len = strlen($type.'_JOIN '); + } + $tableName = substr($tableName,0,-$len); + $this->trueTableName = $tableName; + } + return $this->trueTableName; + } + + /** + * 表达式过滤方法 + * @access protected + * @param string $options 表达式 + * @return void + */ + protected function _options_filter(&$options) { + if(isset($options['field'])) + $options['field'] = $this->checkFields($options['field']); + else + $options['field'] = $this->checkFields(); + if(isset($options['group'])) + $options['group'] = $this->checkGroup($options['group']); + if(isset($options['where'])) + $options['where'] = $this->checkCondition($options['where']); + if(isset($options['order'])) + $options['order'] = $this->checkOrder($options['order']); + } + + /** + * 检查是否定义了所有字段 + * @access protected + * @param string $name 模型名称 + * @param array $fields 字段数组 + * @return array + */ + private function _checkFields($name,$fields) { + if(false !== $pos = array_search('*',$fields)) {// 定义所有字段 + $fields = array_merge($fields,M($name)->getDbFields()); + unset($fields[$pos]); + } + return $fields; + } + + /** + * 检查条件中的视图字段 + * @access protected + * @param mixed $data 条件表达式 + * @return array + */ + protected function checkCondition($where) { + if(is_array($where)) { + $view = array(); + // 检查视图字段 + foreach ($this->viewFields as $key=>$val){ + $k = isset($val['_as'])?$val['_as']:$key; + $val = $this->_checkFields($key,$val); + foreach ($where as $name=>$value){ + if(false !== $field = array_search($name,$val,true)) { + // 存在视图字段 + $_key = is_numeric($field)? $k.'.'.$name : $k.'.'.$field; + $view[$_key] = $value; + unset($where[$name]); + } + } + } + $where = array_merge($where,$view); + } + return $where; + } + + /** + * 检查Order表达式中的视图字段 + * @access protected + * @param string $order 字段 + * @return string + */ + protected function checkOrder($order='') { + if(is_string($order) && !empty($order)) { + $orders = explode(',',$order); + $_order = array(); + foreach ($orders as $order){ + $array = explode(' ',trim($order)); + $field = $array[0]; + $sort = isset($array[1])?$array[1]:'ASC'; + // 解析成视图字段 + foreach ($this->viewFields as $name=>$val){ + $k = isset($val['_as'])?$val['_as']:$name; + $val = $this->_checkFields($name,$val); + if(false !== $_field = array_search($field,$val,true)) { + // 存在视图字段 + $field = is_numeric($_field)?$k.'.'.$field:$k.'.'.$_field; + break; + } + } + $_order[] = $field.' '.$sort; + } + $order = implode(',',$_order); + } + return $order; + } + + /** + * 检查Group表达式中的视图字段 + * @access protected + * @param string $group 字段 + * @return string + */ + protected function checkGroup($group='') { + if(!empty($group)) { + $groups = explode(',',$group); + $_group = array(); + foreach ($groups as $field){ + // 解析成视图字段 + foreach ($this->viewFields as $name=>$val){ + $k = isset($val['_as'])?$val['_as']:$name; + $val = $this->_checkFields($name,$val); + if(false !== $_field = array_search($field,$val,true)) { + // 存在视图字段 + $field = is_numeric($_field)?$k.'.'.$field:$k.'.'.$_field; + break; + } + } + $_group[] = $field; + } + $group = implode(',',$_group); + } + return $group; + } + + /** + * 检查fields表达式中的视图字段 + * @access protected + * @param string $fields 字段 + * @return string + */ + protected function checkFields($fields='') { + if(empty($fields) || '*'==$fields ) { + // 获取全部视图字段 + $fields = array(); + foreach ($this->viewFields as $name=>$val){ + $k = isset($val['_as'])?$val['_as']:$name; + $val = $this->_checkFields($name,$val); + foreach ($val as $key=>$field){ + if(is_numeric($key)) { + $fields[] = $k.'.'.$field.' AS '.$field; + }elseif('_' != substr($key,0,1)) { + // 以_开头的为特殊定义 + if( false !== strpos($key,'*') || false !== strpos($key,'(') || false !== strpos($key,'.')) { + //如果包含* 或者 使用了sql方法 则不再添加前面的表名 + $fields[] = $key.' AS '.$field; + }else{ + $fields[] = $k.'.'.$key.' AS '.$field; + } + } + } + } + $fields = implode(',',$fields); + }else{ + if(!is_array($fields)) + $fields = explode(',',$fields); + // 解析成视图字段 + $array = array(); + foreach ($fields as $key=>$field){ + if(strpos($field,'(') || strpos(strtolower($field),' as ')){ + // 使用了函数或者别名 + $array[] = $field; + unset($fields[$key]); + } + } + foreach ($this->viewFields as $name=>$val){ + $k = isset($val['_as'])?$val['_as']:$name; + $val = $this->_checkFields($name,$val); + foreach ($fields as $key=>$field){ + if(false !== $_field = array_search($field,$val,true)) { + // 存在视图字段 + if(is_numeric($_field)) { + $array[] = $k.'.'.$field.' AS '.$field; + }elseif('_' != substr($_field,0,1)){ + if( false !== strpos($_field,'*') || false !== strpos($_field,'(') || false !== strpos($_field,'.')) + //如果包含* 或者 使用了sql方法 则不再添加前面的表名 + $array[] = $_field.' AS '.$field; + else + $array[] = $k.'.'.$_field.' AS '.$field; + } + } + } + } + $fields = implode(',',$array); + } + return $fields; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Page.class.php b/ThinkPHP/Library/Think/Page.class.php new file mode 100644 index 0000000..8a57cb6 --- /dev/null +++ b/ThinkPHP/Library/Think/Page.class.php @@ -0,0 +1,145 @@ + +// +---------------------------------------------------------------------- +namespace Think; + +class Page{ + public $firstRow; // 起始行数 + public $listRows; // 列表每页显示行数 + public $parameter; // 分页跳转时要带的参数 + public $totalRows; // 总行数 + public $totalPages; // 分页总页面数 + public $rollPage = 11;// 分页栏每页显示的页数 + public $lastSuffix = true; // 最后一页是否显示总页数 + + private $p = 'p'; //分页参数名 + private $url = ''; //当前链接URL + private $nowPage = 1; + + // 分页显示定制 + private $config = array( + 'header' => '共 %TOTAL_ROW% 条记录', + 'prev' => '<<', + 'next' => '>>', + 'first' => '1...', + 'last' => '...%TOTAL_PAGE%', + 'theme' => '%FIRST% %UP_PAGE% %LINK_PAGE% %DOWN_PAGE% %END%', + ); + + /** + * 架构函数 + * @param array $totalRows 总的记录数 + * @param array $listRows 每页显示记录数 + * @param array $parameter 分页跳转的参数 + */ + public function __construct($totalRows, $listRows=20, $parameter = array()) { + C('VAR_PAGE') && $this->p = C('VAR_PAGE'); //设置分页参数名称 + /* 基础设置 */ + $this->totalRows = $totalRows; //设置总记录数 + $this->listRows = $listRows; //设置每页显示行数 + $this->parameter = empty($parameter) ? $_GET : $parameter; + $this->nowPage = empty($_GET[$this->p]) ? 1 : intval($_GET[$this->p]); + $this->nowPage = $this->nowPage>0 ? $this->nowPage : 1; + $this->firstRow = $this->listRows * ($this->nowPage - 1); + } + + /** + * 定制分页链接设置 + * @param string $name 设置名称 + * @param string $value 设置值 + */ + public function setConfig($name,$value) { + if(isset($this->config[$name])) { + $this->config[$name] = $value; + } + } + + /** + * 生成链接URL + * @param integer $page 页码 + * @return string + */ + private function url($page){ + return str_replace(urlencode('[PAGE]'), $page, $this->url); + } + + /** + * 组装分页链接 + * @return string + */ + public function show() { + if(0 == $this->totalRows) return ''; + + /* 生成URL */ + $this->parameter[$this->p] = '[PAGE]'; + $this->url = U(ACTION_NAME, $this->parameter); + /* 计算分页信息 */ + $this->totalPages = ceil($this->totalRows / $this->listRows); //总页数 + if(!empty($this->totalPages) && $this->nowPage > $this->totalPages) { + $this->nowPage = $this->totalPages; + } + + /* 计算分页临时变量 */ + $now_cool_page = $this->rollPage/2; + $now_cool_page_ceil = ceil($now_cool_page); + $this->lastSuffix && $this->config['last'] = $this->totalPages; + + //上一页 + $up_row = $this->nowPage - 1; + $up_page = $up_row > 0 ? '' : ''; + + //下一页 + $down_row = $this->nowPage + 1; + $down_page = ($down_row <= $this->totalPages) ? '' : ''; + + //第一页 + $the_first = ''; + if($this->totalPages > $this->rollPage && ($this->nowPage - $now_cool_page) >= 1){ + $the_first = '' . $this->config['first'] . ''; + } + + //最后一页 + $the_end = ''; + if($this->totalPages > $this->rollPage && ($this->nowPage + $now_cool_page) < $this->totalPages){ + $the_end = '' . $this->config['last'] . ''; + } + + //数字连接 + $link_page = ""; + for($i = 1; $i <= $this->rollPage; $i++){ + if(($this->nowPage - $now_cool_page) <= 0 ){ + $page = $i; + }elseif(($this->nowPage + $now_cool_page - 1) >= $this->totalPages){ + $page = $this->totalPages - $this->rollPage + $i; + }else{ + $page = $this->nowPage - $now_cool_page_ceil + $i; + } + if($page > 0 && $page != $this->nowPage){ + + if($page <= $this->totalPages){ + $link_page .= '' . $page . ''; + }else{ + break; + } + }else{ + if($page > 0 && $this->totalPages != 1){ + $link_page .= '' . $page . ''; + } + } + } + + //替换分页内容 + $page_str = str_replace( + array('%HEADER%', '%NOW_PAGE%', '%UP_PAGE%', '%DOWN_PAGE%', '%FIRST%', '%LINK_PAGE%', '%END%', '%TOTAL_ROW%', '%TOTAL_PAGE%'), + array($this->config['header'], $this->nowPage, $up_page, $down_page, $the_first, $link_page, $the_end, $this->totalRows, $this->totalPages), + $this->config['theme']); + return "
{$page_str}
"; + } +} diff --git a/ThinkPHP/Library/Think/Route.class.php b/ThinkPHP/Library/Think/Route.class.php new file mode 100644 index 0000000..c1bf8a5 --- /dev/null +++ b/ThinkPHP/Library/Think/Route.class.php @@ -0,0 +1,316 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP路由解析类 + */ +class Route { + + // 路由检测 + public static function check(){ + $depr = C('URL_PATHINFO_DEPR'); + $regx = preg_replace('/\.'.__EXT__.'$/i','',trim($_SERVER['PATH_INFO'],$depr)); + // 分隔符替换 确保路由定义使用统一的分隔符 + if('/' != $depr){ + $regx = str_replace($depr,'/',$regx); + } + // URL映射定义(静态路由) + $maps = C('URL_MAP_RULES'); + if(isset($maps[$regx])) { + $var = self::parseUrl($maps[$regx]); + $_GET = array_merge($var, $_GET); + return true; + } + // 动态路由处理 + $routes = C('URL_ROUTE_RULES'); + if(!empty($routes)) { + foreach ($routes as $rule=>$route){ + if(is_numeric($rule)){ + // 支持 array('rule','adddress',...) 定义路由 + $rule = array_shift($route); + } + if(is_array($route) && isset($route[2])){ + // 路由参数 + $options = $route[2]; + if(isset($options['ext']) && __EXT__ != $options['ext']){ + // URL后缀检测 + continue; + } + if(isset($options['method']) && REQUEST_METHOD != strtoupper($options['method'])){ + // 请求类型检测 + continue; + } + // 自定义检测 + if(!empty($options['callback']) && is_callable($options['callback'])) { + if(false === call_user_func($options['callback'])) { + continue; + } + } + } + if(0===strpos($rule,'/') && preg_match($rule,$regx,$matches)) { // 正则路由 + if($route instanceof \Closure) { + // 执行闭包 + $result = self::invokeRegx($route, $matches); + // 如果返回布尔值 则继续执行 + return is_bool($result) ? $result : exit; + }else{ + return self::parseRegex($matches,$route,$regx); + } + }else{ // 规则路由 + $len1 = substr_count($regx,'/'); + $len2 = substr_count($rule,'/'); + if($len1>=$len2 || strpos($rule,'[')) { + if('$' == substr($rule,-1,1)) {// 完整匹配 + if($len1 != $len2) { + continue; + }else{ + $rule = substr($rule,0,-1); + } + } + $match = self::checkUrlMatch($regx,$rule); + if(false !== $match) { + if($route instanceof \Closure) { + // 执行闭包 + $result = self::invokeRule($route, $match); + // 如果返回布尔值 则继续执行 + return is_bool($result) ? $result : exit; + }else{ + return self::parseRule($rule,$route,$regx); + } + } + } + } + } + } + return false; + } + + // 检测URL和规则路由是否匹配 + private static function checkUrlMatch($regx,$rule) { + $m1 = explode('/',$regx); + $m2 = explode('/',$rule); + $var = array(); + foreach ($m2 as $key=>$val){ + if(0 === strpos($val,'[:')){ + $val = substr($val,1,-1); + } + + if(':' == substr($val,0,1)) {// 动态变量 + if($pos = strpos($val,'|')){ + // 使用函数过滤 + $val = substr($val,1,$pos-1); + } + if(strpos($val,'\\')) { + $type = substr($val,-1); + if('d'==$type) { + if(isset($m1[$key]) && !is_numeric($m1[$key])) + return false; + } + $name = substr($val, 1, -2); + }elseif($pos = strpos($val,'^')){ + $array = explode('-',substr(strstr($val,'^'),1)); + if(in_array($m1[$key],$array)) { + return false; + } + $name = substr($val, 1, $pos - 1); + }else{ + $name = substr($val, 1); + } + $var[$name] = isset($m1[$key])?$m1[$key]:''; + }elseif(0 !== strcasecmp($val,$m1[$key])){ + return false; + } + } + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + + // 解析规范的路由地址 + // 地址格式 [控制器/操作?]参数1=值1&参数2=值2... + private static function parseUrl($url) { + $var = array(); + if(false !== strpos($url,'?')) { // [控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/',$info['path']); + parse_str($info['query'],$var); + }elseif(strpos($url,'/')){ // [控制器/操作] + $path = explode('/',$url); + }else{ // 参数1=值1&参数2=值2... + parse_str($url,$var); + } + if(isset($path)) { + $var[C('VAR_ACTION')] = array_pop($path); + if(!empty($path)) { + $var[C('VAR_CONTROLLER')] = array_pop($path); + } + if(!empty($path)) { + $var[C('VAR_MODULE')] = array_pop($path); + } + } + return $var; + } + + // 解析规则路由 + // '路由规则'=>'[控制器/操作]?额外参数1=值1&额外参数2=值2...' + // '路由规则'=>array('[控制器/操作]','额外参数1=值1&额外参数2=值2...') + // '路由规则'=>'外部地址' + // '路由规则'=>array('外部地址','重定向代码') + // 路由规则中 :开头 表示动态变量 + // 外部地址中可以用动态变量 采用 :1 :2 的方式 + // 'news/:month/:day/:id'=>array('News/read?cate=1','status=1'), + // 'new/:id'=>array('/new.php?id=:1',301), 重定向 + private static function parseRule($rule,$route,$regx) { + // 获取路由地址规则 + $url = is_array($route)?$route[0]:$route; + // 获取URL地址中的参数 + $paths = explode('/',$regx); + // 解析路由规则 + $matches = array(); + $rule = explode('/',$rule); + foreach ($rule as $item){ + $fun = ''; + if(0 === strpos($item,'[:')){ + $item = substr($item,1,-1); + } + if(0===strpos($item,':')) { // 动态变量获取 + if($pos = strpos($item,'|')){ + // 支持函数过滤 + $fun = substr($item,$pos+1); + $item = substr($item,0,$pos); + } + if($pos = strpos($item,'^') ) { + $var = substr($item,1,$pos-1); + }elseif(strpos($item,'\\')){ + $var = substr($item,1,-2); + }else{ + $var = substr($item,1); + } + $matches[$var] = !empty($fun)? $fun(array_shift($paths)) : array_shift($paths); + }else{ // 过滤URL中的静态变量 + array_shift($paths); + } + } + + if(0=== strpos($url,'/') || 0===strpos($url,'http')) { // 路由重定向跳转 + if(strpos($url,':')) { // 传递动态参数 + $values = array_values($matches); + $url = preg_replace_callback('/:(\d+)/', function($match) use($values){ return $values[$match[1] - 1]; }, $url); + } + header("Location: $url", true,(is_array($route) && isset($route[1]))?$route[1]:301); + exit; + }else{ + // 解析路由地址 + $var = self::parseUrl($url); + // 解析路由地址里面的动态参数 + $values = array_values($matches); + foreach ($var as $key=>$val){ + if(0===strpos($val,':')) { + $var[$key] = $values[substr($val,1)-1]; + } + } + $var = array_merge($matches,$var); + // 解析剩余的URL参数 + if(!empty($paths)) { + preg_replace_callback('/(\w+)\/([^\/]+)/', function($match) use(&$var){ $var[strtolower($match[1])]=strip_tags($match[2]);}, implode('/',$paths)); + } + // 解析路由自动传入参数 + if(is_array($route) && isset($route[1])) { + if(is_array($route[1])){ + $params = $route[1]; + }else{ + parse_str($route[1],$params); + } + $var = array_merge($var,$params); + } + $_GET = array_merge($var,$_GET); + } + return true; + } + + // 解析正则路由 + // '路由正则'=>'[控制器/操作]?参数1=值1&参数2=值2...' + // '路由正则'=>array('[控制器/操作]?参数1=值1&参数2=值2...','额外参数1=值1&额外参数2=值2...') + // '路由正则'=>'外部地址' + // '路由正则'=>array('外部地址','重定向代码') + // 参数值和外部地址中可以用动态变量 采用 :1 :2 的方式 + // '/new\/(\d+)\/(\d+)/'=>array('News/read?id=:1&page=:2&cate=1','status=1'), + // '/new\/(\d+)/'=>array('/new.php?id=:1&page=:2&status=1','301'), 重定向 + private static function parseRegex($matches,$route,$regx) { + // 获取路由地址规则 + $url = is_array($route)?$route[0]:$route; + $url = preg_replace_callback('/:(\d+)/', function($match) use($matches){return $matches[$match[1]];}, $url); + if(0=== strpos($url,'/') || 0===strpos($url,'http')) { // 路由重定向跳转 + header("Location: $url", true,(is_array($route) && isset($route[1]))?$route[1]:301); + exit; + }else{ + // 解析路由地址 + $var = self::parseUrl($url); + // 处理函数 + foreach($var as $key=>$val){ + if(strpos($val,'|')){ + list($val,$fun) = explode('|',$val); + $var[$key] = $fun($val); + } + } + // 解析剩余的URL参数 + $regx = substr_replace($regx,'',0,strlen($matches[0])); + if($regx) { + preg_replace_callback('/(\w+)\/([^\/]+)/', function($match) use(&$var){ + $var[strtolower($match[1])] = strip_tags($match[2]); + }, $regx); + } + // 解析路由自动传入参数 + if(is_array($route) && isset($route[1])) { + if(is_array($route[1])){ + $params = $route[1]; + }else{ + parse_str($route[1],$params); + } + $var = array_merge($var,$params); + } + $_GET = array_merge($var,$_GET); + } + return true; + } + + // 执行正则匹配下的闭包方法 支持参数调用 + static private function invokeRegx($closure, $var = array()) { + $reflect = new \ReflectionFunction($closure); + $params = $reflect->getParameters(); + $args = array(); + array_shift($var); + foreach ($params as $param){ + if(!empty($var)) { + $args[] = array_shift($var); + }elseif($param->isDefaultValueAvailable()){ + $args[] = $param->getDefaultValue(); + } + } + return $reflect->invokeArgs($args); + } + + // 执行规则匹配下的闭包方法 支持参数调用 + static private function invokeRule($closure, $var = array()) { + $reflect = new \ReflectionFunction($closure); + $params = $reflect->getParameters(); + $args = array(); + foreach ($params as $param){ + $name = $param->getName(); + if(isset($var[$name])) { + $args[] = $var[$name]; + }elseif($param->isDefaultValueAvailable()){ + $args[] = $param->getDefaultValue(); + } + } + return $reflect->invokeArgs($args); + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Session/Driver/Db.class.php b/ThinkPHP/Library/Think/Session/Driver/Db.class.php new file mode 100644 index 0000000..76ab3be --- /dev/null +++ b/ThinkPHP/Library/Think/Session/Driver/Db.class.php @@ -0,0 +1,173 @@ + +// +---------------------------------------------------------------------- +namespace Think\Session\Driver; +/** + * 数据库方式Session驱动 + * CREATE TABLE think_session ( + * session_id varchar(255) NOT NULL, + * session_expire int(11) NOT NULL, + * session_data blob, + * UNIQUE KEY `session_id` (`session_id`) + * ); + */ +class Db { + + /** + * Session有效时间 + */ + protected $lifeTime = ''; + + /** + * session保存的数据库名 + */ + protected $sessionTable = ''; + + /** + * 数据库句柄 + */ + protected $hander = array(); + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) { + $this->lifeTime = C('SESSION_EXPIRE')?C('SESSION_EXPIRE'):ini_get('session.gc_maxlifetime'); + $this->sessionTable = C('SESSION_TABLE')?C('SESSION_TABLE'):C("DB_PREFIX")."session"; + //分布式数据库 + $host = explode(',',C('DB_HOST')); + $port = explode(',',C('DB_PORT')); + $name = explode(',',C('DB_NAME')); + $user = explode(',',C('DB_USER')); + $pwd = explode(',',C('DB_PWD')); + if(1 == C('DB_DEPLOY_TYPE')){ + //读写分离 + if(C('DB_RW_SEPARATE')){ + $w = floor(mt_rand(0,C('DB_MASTER_NUM')-1)); + if(is_numeric(C('DB_SLAVE_NO'))){//指定服务器读 + $r = C('DB_SLAVE_NO'); + }else{ + $r = floor(mt_rand(C('DB_MASTER_NUM'),count($host)-1)); + } + //主数据库链接 + $hander = mysql_connect( + $host[$w].(isset($port[$w])?':'.$port[$w]:':'.$port[0]), + isset($user[$w])?$user[$w]:$user[0], + isset($pwd[$w])?$pwd[$w]:$pwd[0] + ); + $dbSel = mysql_select_db( + isset($name[$w])?$name[$w]:$name[0] + ,$hander); + if(!$hander || !$dbSel) + return false; + $this->hander[0] = $hander; + //从数据库链接 + $hander = mysql_connect( + $host[$r].(isset($port[$r])?':'.$port[$r]:':'.$port[0]), + isset($user[$r])?$user[$r]:$user[0], + isset($pwd[$r])?$pwd[$r]:$pwd[0] + ); + $dbSel = mysql_select_db( + isset($name[$r])?$name[$r]:$name[0] + ,$hander); + if(!$hander || !$dbSel) + return false; + $this->hander[1] = $hander; + return true; + } + } + //从数据库链接 + $r = floor(mt_rand(0,count($host)-1)); + $hander = mysql_connect( + $host[$r].(isset($port[$r])?':'.$port[$r]:':'.$port[0]), + isset($user[$r])?$user[$r]:$user[0], + isset($pwd[$r])?$pwd[$r]:$pwd[0] + ); + $dbSel = mysql_select_db( + isset($name[$r])?$name[$r]:$name[0] + ,$hander); + if(!$hander || !$dbSel) + return false; + $this->hander = $hander; + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() { + if(is_array($this->hander)){ + $this->gc($this->lifeTime); + return (mysql_close($this->hander[0]) && mysql_close($this->hander[1])); + } + $this->gc($this->lifeTime); + return mysql_close($this->hander); + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) { + $hander = is_array($this->hander)?$this->hander[1]:$this->hander; + $res = mysql_query('SELECT session_data AS data FROM '.$this->sessionTable." WHERE session_id = '$sessID' AND session_expire >".time(),$hander); + if($res) { + $row = mysql_fetch_assoc($res); + return $row['data']; + } + return ""; + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + */ + public function write($sessID,$sessData) { + $hander = is_array($this->hander)?$this->hander[0]:$this->hander; + $expire = time() + $this->lifeTime; + $sessData = addslashes($sessData); + mysql_query('REPLACE INTO '.$this->sessionTable." ( session_id, session_expire, session_data) VALUES( '$sessID', '$expire', '$sessData')",$hander); + if(mysql_affected_rows($hander)) + return true; + return false; + } + + /** + * 删除Session + * @access public + * @param string $sessID + */ + public function destroy($sessID) { + $hander = is_array($this->hander)?$this->hander[0]:$this->hander; + mysql_query('DELETE FROM '.$this->sessionTable." WHERE session_id = '$sessID'",$hander); + if(mysql_affected_rows($hander)) + return true; + return false; + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + */ + public function gc($sessMaxLifeTime) { + $hander = is_array($this->hander)?$this->hander[0]:$this->hander; + mysql_query('DELETE FROM '.$this->sessionTable.' WHERE session_expire < '.time(),$hander); + return mysql_affected_rows($hander); + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Session/Driver/Memcache.class.php b/ThinkPHP/Library/Think/Session/Driver/Memcache.class.php new file mode 100644 index 0000000..4c80bbe --- /dev/null +++ b/ThinkPHP/Library/Think/Session/Driver/Memcache.class.php @@ -0,0 +1,79 @@ +lifeTime = C('SESSION_EXPIRE') ? C('SESSION_EXPIRE') : $this->lifeTime; + // $this->sessionName = $sessName; + $options = array( + 'timeout' => C('SESSION_TIMEOUT') ? C('SESSION_TIMEOUT') : 1, + 'persistent' => C('SESSION_PERSISTENT') ? C('SESSION_PERSISTENT') : 0 + ); + $this->handle = new \Memcache; + $hosts = explode(',', C('MEMCACHE_HOST')); + $ports = explode(',', C('MEMCACHE_PORT')); + foreach ($hosts as $i=>$host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->handle->addServer($host, $port, true, 1, $options['timeout']); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handle->close(); + $this->handle = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) { + return $this->handle->get($this->sessionName.$sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + */ + public function write($sessID, $sessData) { + return $this->handle->set($this->sessionName.$sessID, $sessData, 0, $this->lifeTime); + } + + /** + * 删除Session + * @access public + * @param string $sessID + */ + public function destroy($sessID) { + return $this->handle->delete($this->sessionName.$sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + */ + public function gc($sessMaxLifeTime) { + return true; + } +} diff --git a/ThinkPHP/Library/Think/Session/Driver/Mysqli.class.php b/ThinkPHP/Library/Think/Session/Driver/Mysqli.class.php new file mode 100644 index 0000000..b29ec2f --- /dev/null +++ b/ThinkPHP/Library/Think/Session/Driver/Mysqli.class.php @@ -0,0 +1,184 @@ + liu21st +// +---------------------------------------------------------------------- +// | change mysql to mysqli 解决php7没有mysql扩展时数据库存放session无法操作的问题 +// +---------------------------------------------------------------------- +namespace Think\Session\Driver; +/** + * 数据库方式Session驱动 + * CREATE TABLE think_session ( + * session_id varchar(255) NOT NULL, + * session_expire int(11) NOT NULL, + * session_data blob, + * UNIQUE KEY `session_id` (`session_id`) + * ); + */ +class Mysqli +{ + + /** + * Session有效时间 + */ + protected $lifeTime = ''; + + /** + * session保存的数据库名 + */ + protected $sessionTable = ''; + + /** + * 数据库句柄 + */ + protected $hander = array(); + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + $this->lifeTime = C('SESSION_EXPIRE') ? C('SESSION_EXPIRE') : ini_get('session.gc_maxlifetime'); + $this->sessionTable = C('SESSION_TABLE') ? C('SESSION_TABLE') : C("DB_PREFIX") . "session"; + //分布式数据库 + $host = explode(',', C('DB_HOST')); + $port = explode(',', C('DB_PORT')); + $name = explode(',', C('DB_NAME')); + $user = explode(',', C('DB_USER')); + $pwd = explode(',', C('DB_PWD')); + if (1 == C('DB_DEPLOY_TYPE')) { + //读写分离 + if (C('DB_RW_SEPARATE')) { + $w = floor(mt_rand(0, C('DB_MASTER_NUM') - 1)); + if (is_numeric(C('DB_SLAVE_NO'))) {//指定服务器读 + $r = C('DB_SLAVE_NO'); + } else { + $r = floor(mt_rand(C('DB_MASTER_NUM'), count($host) - 1)); + } + //主数据库链接 + $hander = mysqli_connect( + $host[$w] . (isset($port[$w]) ? ':' . $port[$w] : ':' . $port[0]), + isset($user[$w]) ? $user[$w] : $user[0], + isset($pwd[$w]) ? $pwd[$w] : $pwd[0] + ); + $dbSel = mysqli_select_db( + $hander, + isset($name[$w]) ? $name[$w] : $name[0] + ); + if (!$hander || !$dbSel) + return false; + $this->hander[0] = $hander; + //从数据库链接 + $hander = mysqli_connect( + $host[$r] . (isset($port[$r]) ? ':' . $port[$r] : ':' . $port[0]), + isset($user[$r]) ? $user[$r] : $user[0], + isset($pwd[$r]) ? $pwd[$r] : $pwd[0] + ); + $dbSel = mysqli_select_db( + $hander, + isset($name[$r]) ? $name[$r] : $name[0] + ); + if (!$hander || !$dbSel) + return false; + $this->hander[1] = $hander; + return true; + } + } + //从数据库链接 + $r = floor(mt_rand(0, count($host) - 1)); + $hander = mysqli_connect( + $host[$r] . (isset($port[$r]) ? ':' . $port[$r] : ':' . $port[0]), + isset($user[$r]) ? $user[$r] : $user[0], + isset($pwd[$r]) ? $pwd[$r] : $pwd[0] + ); + $dbSel = mysqli_select_db( + $hander, + isset($name[$r]) ? $name[$r] : $name[0] + ); + if (!$hander || !$dbSel) + return false; + $this->hander = $hander; + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + if (is_array($this->hander)) { + $this->gc($this->lifeTime); + return (mysqli_close($this->hander[0]) && mysqli_close($this->hander[1])); + } + $this->gc($this->lifeTime); + return mysqli_close($this->hander); + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + $hander = is_array($this->hander) ? $this->hander[1] : $this->hander; + $res = mysqli_query($hander, "SELECT session_data AS data FROM " . $this->sessionTable . " WHERE session_id = '$sessID' AND session_expire >" . time()); + if ($res) { + $row = mysqli_fetch_assoc($res); + return $row['data']; + } + return ""; + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + */ + public function write($sessID, $sessData) + { + $hander = is_array($this->hander) ? $this->hander[0] : $this->hander; + $expire = time() + $this->lifeTime; + mysqli_query($hander, "REPLACE INTO " . $this->sessionTable . " ( session_id, session_expire, session_data) VALUES( '$sessID', '$expire', '$sessData')"); + if (mysqli_affected_rows($hander)) + return true; + return false; + } + + /** + * 删除Session + * @access public + * @param string $sessID + */ + public function destroy($sessID) + { + $hander = is_array($this->hander) ? $this->hander[0] : $this->hander; + mysqli_query($hander, "DELETE FROM " . $this->sessionTable . " WHERE session_id = '$sessID'"); + if (mysqli_affected_rows($hander)) + return true; + return false; + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + */ + public function gc($sessMaxLifeTime) + { + $hander = is_array($this->hander) ? $this->hander[0] : $this->hander; + mysqli_query($hander, "DELETE FROM " . $this->sessionTable . " WHERE session_expire < " . time()); + return mysqli_affected_rows($hander); + } + +} diff --git a/ThinkPHP/Library/Think/Storage.class.php b/ThinkPHP/Library/Think/Storage.class.php new file mode 100644 index 0000000..653e136 --- /dev/null +++ b/ThinkPHP/Library/Think/Storage.class.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- +namespace Think; +// 分布式文件存储类 +class Storage { + + /** + * 操作句柄 + * @var string + * @access protected + */ + static protected $handler ; + + /** + * 连接分布式文件系统 + * @access public + * @param string $type 文件类型 + * @param array $options 配置数组 + * @return void + */ + static public function connect($type='File',$options=array()) { + $class = 'Think\\Storage\\Driver\\'.ucwords($type); + self::$handler = new $class($options); + } + + static public function __callstatic($method,$args){ + //调用缓存驱动的方法 + if(method_exists(self::$handler, $method)){ + return call_user_func_array(array(self::$handler,$method), $args); + } + } +} diff --git a/ThinkPHP/Library/Think/Storage/Driver/File.class.php b/ThinkPHP/Library/Think/Storage/Driver/File.class.php new file mode 100644 index 0000000..ea9d925 --- /dev/null +++ b/ThinkPHP/Library/Think/Storage/Driver/File.class.php @@ -0,0 +1,123 @@ + +// +---------------------------------------------------------------------- +namespace Think\Storage\Driver; +use Think\Storage; +// 本地文件写入存储类 +class File extends Storage{ + + private $contents=array(); + + /** + * 架构函数 + * @access public + */ + public function __construct() { + } + + /** + * 文件内容读取 + * @access public + * @param string $filename 文件名 + * @return string + */ + public function read($filename,$type=''){ + return $this->get($filename,'content',$type); + } + + /** + * 文件写入 + * @access public + * @param string $filename 文件名 + * @param string $content 文件内容 + * @return boolean + */ + public function put($filename,$content,$type=''){ + $dir = dirname($filename); + if(!is_dir($dir)){ + mkdir($dir,0777,true); + } + if(false === file_put_contents($filename,$content)){ + E(L('_STORAGE_WRITE_ERROR_').':'.$filename); + }else{ + $this->contents[$filename]=$content; + return true; + } + } + + /** + * 文件追加写入 + * @access public + * @param string $filename 文件名 + * @param string $content 追加的文件内容 + * @return boolean + */ + public function append($filename,$content,$type=''){ + if(is_file($filename)){ + $content = $this->read($filename,$type).$content; + } + return $this->put($filename,$content,$type); + } + + /** + * 加载文件 + * @access public + * @param string $filename 文件名 + * @param array $vars 传入变量 + * @return void + */ + public function load($_filename,$vars=null){ + if(!is_null($vars)){ + extract($vars, EXTR_OVERWRITE); + } + include $_filename; + } + + /** + * 文件是否存在 + * @access public + * @param string $filename 文件名 + * @return boolean + */ + public function has($filename,$type=''){ + return is_file($filename); + } + + /** + * 文件删除 + * @access public + * @param string $filename 文件名 + * @return boolean + */ + public function unlink($filename,$type=''){ + unset($this->contents[$filename]); + return is_file($filename) ? unlink($filename) : false; + } + + /** + * 读取文件信息 + * @access public + * @param string $filename 文件名 + * @param string $name 信息名 mtime或者content + * @return boolean + */ + public function get($filename,$name,$type=''){ + if(!isset($this->contents[$filename])){ + if(!is_file($filename)) return false; + $this->contents[$filename]=file_get_contents($filename); + } + $content=$this->contents[$filename]; + $info = array( + 'mtime' => filemtime($filename), + 'content' => $content + ); + return $info[$name]; + } +} diff --git a/ThinkPHP/Library/Think/Storage/Driver/Sae.class.php b/ThinkPHP/Library/Think/Storage/Driver/Sae.class.php new file mode 100644 index 0000000..3756115 --- /dev/null +++ b/ThinkPHP/Library/Think/Storage/Driver/Sae.class.php @@ -0,0 +1,193 @@ + +// +---------------------------------------------------------------------- +namespace Think\Storage\Driver; +use Think\Storage; +// SAE环境文件写入存储类 +class Sae extends Storage{ + + /** + * 架构函数 + * @access public + */ + private $mc; + private $kvs = array(); + private $htmls = array(); + private $contents = array(); + public function __construct() { + if(!function_exists('memcache_init')){ + header('Content-Type:text/html;charset=utf-8'); + exit('请在SAE平台上运行代码。'); + } + $this->mc = @memcache_init(); + if(!$this->mc){ + header('Content-Type:text/html;charset=utf-8'); + exit('您未开通Memcache服务,请在SAE管理平台初始化Memcache服务'); + } + } + + /** + * 获得SaeKv对象 + */ + private function getKv(){ + static $kv; + if(!$kv){ + $kv = new \SaeKV(); + if(!$kv->init()) + E('您没有初始化KVDB,请在SAE管理平台初始化KVDB服务'); + } + return $kv; + } + + + /** + * 文件内容读取 + * @access public + * @param string $filename 文件名 + * @return string + */ + public function read($filename,$type=''){ + switch(strtolower($type)){ + case 'f': + $kv = $this->getKv(); + if(!isset($this->kvs[$filename])){ + $this->kvs[$filename]=$kv->get($filename); + } + return $this->kvs[$filename]; + default: + return $this->get($filename,'content',$type); + } + } + + /** + * 文件写入 + * @access public + * @param string $filename 文件名 + * @param string $content 文件内容 + * @return boolean + */ + public function put($filename,$content,$type=''){ + switch(strtolower($type)){ + case 'f': + $kv = $this->getKv(); + $this->kvs[$filename] = $content; + return $kv->set($filename,$content); + case 'html': + $kv = $this->getKv(); + $content = time().$content; + $this->htmls[$filename] = $content; + return $kv->set($filename,$content); + default: + $content = time().$content; + if(!$this->mc->set($filename,$content,MEMCACHE_COMPRESSED,0)){ + E(L('_STORAGE_WRITE_ERROR_').':'.$filename); + }else{ + $this->contents[$filename] = $content; + return true; + } + } + } + + /** + * 文件追加写入 + * @access public + * @param string $filename 文件名 + * @param string $content 追加的文件内容 + * @return boolean + */ + public function append($filename,$content,$type=''){ + if($old_content = $this->read($filename,$type)){ + $content = $old_content.$content; + } + return $this->put($filename,$content,$type); + } + + /** + * 加载文件 + * @access public + * @param string $_filename 文件名 + * @param array $vars 传入变量 + * @return void + */ + public function load($_filename,$vars=null){ + if(!is_null($vars)) + extract($vars, EXTR_OVERWRITE); + eval('?>'.$this->read($_filename)); + } + + /** + * 文件是否存在 + * @access public + * @param string $filename 文件名 + * @return boolean + */ + public function has($filename,$type=''){ + if($this->read($filename,$type)){ + return true; + }else{ + return false; + } + } + + /** + * 文件删除 + * @access public + * @param string $filename 文件名 + * @return boolean + */ + public function unlink($filename,$type=''){ + switch(strtolower($type)){ + case 'f': + $kv = $this->getKv(); + unset($this->kvs[$filename]); + return $kv->delete($filename); + case 'html': + $kv = $this->getKv(); + unset($this->htmls[$filename]); + return $kv->delete($filename); + default: + unset($this->contents[$filename]); + return $this->mc->delete($filename); + } + } + + /** + * 读取文件信息 + * @access public + * @param string $filename 文件名 + * @param string $name 信息名 mtime或者content + * @return boolean + */ + public function get($filename,$name,$type=''){ + switch(strtolower($type)){ + case 'html': + if(!isset($this->htmls[$filename])){ + $kv = $this->getKv(); + $this->htmls[$filename] = $kv->get($filename); + } + $content = $this->htmls[$filename]; + break; + default: + if(!isset($this->contents[$filename])){ + $this->contents[$filename] = $this->mc->get($filename); + } + $content = $this->contents[$filename]; + } + if(false===$content){ + return false; + } + $info = array( + 'mtime' => substr($content,0,10), + 'content' => substr($content,10) + ); + return $info[$name]; + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Template.class.php b/ThinkPHP/Library/Think/Template.class.php new file mode 100644 index 0000000..c43a97a --- /dev/null +++ b/ThinkPHP/Library/Think/Template.class.php @@ -0,0 +1,700 @@ + +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP内置模板引擎类 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template { + + // 模板页面中引入的标签库列表 + protected $tagLib = array(); + // 当前模板文件 + protected $templateFile = ''; + // 模板变量 + public $tVar = array(); + public $config = array(); + private $literal = array(); + private $block = array(); + + /** + * 架构函数 + * @access public + */ + public function __construct(){ + $this->config['cache_path'] = C('CACHE_PATH'); + $this->config['template_suffix'] = C('TMPL_TEMPLATE_SUFFIX'); + $this->config['cache_suffix'] = C('TMPL_CACHFILE_SUFFIX'); + $this->config['tmpl_cache'] = C('TMPL_CACHE_ON'); + $this->config['cache_time'] = C('TMPL_CACHE_TIME'); + $this->config['taglib_begin'] = $this->stripPreg(C('TAGLIB_BEGIN')); + $this->config['taglib_end'] = $this->stripPreg(C('TAGLIB_END')); + $this->config['tmpl_begin'] = $this->stripPreg(C('TMPL_L_DELIM')); + $this->config['tmpl_end'] = $this->stripPreg(C('TMPL_R_DELIM')); + $this->config['default_tmpl'] = C('TEMPLATE_NAME'); + $this->config['layout_item'] = C('TMPL_LAYOUT_ITEM'); + } + + private function stripPreg($str) { + return str_replace( + array('{','}','(',')','|','[',']','-','+','*','.','^','?'), + array('\{','\}','\(','\)','\|','\[','\]','\-','\+','\*','\.','\^','\?'), + $str); + } + + // 模板变量获取和设置 + public function get($name) { + if(isset($this->tVar[$name])) + return $this->tVar[$name]; + else + return false; + } + + public function set($name,$value) { + $this->tVar[$name]= $value; + } + + /** + * 加载模板 + * @access public + * @param string $templateFile 模板文件 + * @param array $templateVar 模板变量 + * @param string $prefix 模板标识前缀 + * @return void + */ + public function fetch($templateFile,$templateVar,$prefix='') { + $this->tVar = $templateVar; + $templateCacheFile = $this->loadTemplate($templateFile,$prefix); + Storage::load($templateCacheFile,$this->tVar,null,'tpl'); + } + + /** + * 加载主模板并缓存 + * @access public + * @param string $templateFile 模板文件 + * @param string $prefix 模板标识前缀 + * @return string + * @throws ThinkExecption + */ + public function loadTemplate ($templateFile,$prefix='') { + if(is_file($templateFile)) { + $this->templateFile = $templateFile; + // 读取模板文件内容 + $tmplContent = file_get_contents($templateFile); + }else{ + $tmplContent = $templateFile; + } + // 根据模版文件名定位缓存文件 + $tmplCacheFile = $this->config['cache_path'].$prefix.md5($templateFile).$this->config['cache_suffix']; + + // 判断是否启用布局 + if(C('LAYOUT_ON')) { + if(false !== strpos($tmplContent,'{__NOLAYOUT__}')) { // 可以单独定义不使用布局 + $tmplContent = str_replace('{__NOLAYOUT__}','',$tmplContent); + }else{ // 替换布局的主体内容 + $layoutFile = THEME_PATH.C('LAYOUT_NAME').$this->config['template_suffix']; + // 检查布局文件 + if(!is_file($layoutFile)) { + E(L('_TEMPLATE_NOT_EXIST_').':'.$layoutFile); + } + $tmplContent = str_replace($this->config['layout_item'],$tmplContent,file_get_contents($layoutFile)); + } + } + // 编译模板内容 + $tmplContent = $this->compiler($tmplContent); + Storage::put($tmplCacheFile,trim($tmplContent),'tpl'); + return $tmplCacheFile; + } + + /** + * 编译模板文件内容 + * @access protected + * @param mixed $tmplContent 模板内容 + * @return string + */ + protected function compiler($tmplContent) { + //模板解析 + $tmplContent = $this->parse($tmplContent); + // 还原被替换的Literal标签 + $tmplContent = preg_replace_callback('//is', array($this, 'restoreLiteral'), $tmplContent); + // 添加安全代码 + $tmplContent = ''.$tmplContent; + // 优化生成的php代码 + $tmplContent = str_replace('?>config['taglib_begin']; + $end = $this->config['taglib_end']; + // 检查include语法 + $content = $this->parseInclude($content); + // 检查PHP语法 + $content = $this->parsePhp($content); + // 首先替换literal标签内容 + $content = preg_replace_callback('/'.$begin.'literal'.$end.'(.*?)'.$begin.'\/literal'.$end.'/is', array($this, 'parseLiteral'),$content); + + // 获取需要引入的标签库列表 + // 标签库只需要定义一次,允许引入多个一次 + // 一般放在文件的最前面 + // 格式: + // 当TAGLIB_LOAD配置为true时才会进行检测 + if(C('TAGLIB_LOAD')) { + $this->getIncludeTagLib($content); + if(!empty($this->tagLib)) { + // 对导入的TagLib进行解析 + foreach($this->tagLib as $tagLibName) { + $this->parseTagLib($tagLibName,$content); + } + } + } + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 + if(C('TAGLIB_PRE_LOAD')) { + $tagLibs = explode(',',C('TAGLIB_PRE_LOAD')); + foreach ($tagLibs as $tag){ + $this->parseTagLib($tag,$content); + } + } + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 + $tagLibs = explode(',',C('TAGLIB_BUILD_IN')); + foreach ($tagLibs as $tag){ + $this->parseTagLib($tag,$content,true); + } + //解析普通模板标签 {$tagName} + $content = preg_replace_callback('/('.$this->config['tmpl_begin'].')([^\d\w\s'.$this->config['tmpl_begin'].$this->config['tmpl_end'].'].+?)('.$this->config['tmpl_end'].')/is', array($this, 'parseTag'),$content); + return $content; + } + + // 检查PHP语法 + protected function parsePhp($content) { + if(ini_get('short_open_tag')){ + // 开启短标签的情况要将'."\n", $content ); + } + // PHP语法检查 + if(C('TMPL_DENY_PHP') && false !== strpos($content,'config['taglib_begin'].'layout\s(.+?)\s*?\/'.$this->config['taglib_end'].'/is',$content,$matches); + if($find) { + //替换Layout标签 + $content = str_replace($matches[0],'',$content); + //解析Layout标签 + $array = $this->parseXmlAttrs($matches[1]); + if(!C('LAYOUT_ON') || C('LAYOUT_NAME') !=$array['name'] ) { + // 读取布局模板 + $layoutFile = THEME_PATH.$array['name'].$this->config['template_suffix']; + $replace = isset($array['replace'])?$array['replace']:$this->config['layout_item']; + // 替换布局的主体内容 + $content = str_replace($replace,$content,file_get_contents($layoutFile)); + } + }else{ + $content = str_replace('{__NOLAYOUT__}','',$content); + } + return $content; + } + + // 解析模板中的include标签 + protected function parseInclude($content, $extend = true) { + // 解析继承 + if($extend) + $content = $this->parseExtend($content); + // 解析布局 + $content = $this->parseLayout($content); + // 读取模板中的include标签 + $find = preg_match_all('/'.$this->config['taglib_begin'].'include\s(.+?)\s*?\/'.$this->config['taglib_end'].'/is',$content,$matches); + if($find) { + for($i=0;$i<$find;$i++) { + $include = $matches[1][$i]; + $array = $this->parseXmlAttrs($include); + $file = $array['file']; + unset($array['file']); + $content = str_replace($matches[0][$i],$this->parseIncludeItem($file,$array,$extend),$content); + } + } + return $content; + } + + // 解析模板中的extend标签 + protected function parseExtend($content) { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + // 读取模板中的继承标签 + $find = preg_match('/'.$begin.'extend\s(.+?)\s*?\/'.$end.'/is',$content,$matches); + if($find) { + //替换extend标签 + $content = str_replace($matches[0],'',$content); + // 记录页面中的block标签 + preg_replace_callback('/'.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.'(.*?)'.$begin.'\/block'.$end.'/is', array($this, 'parseBlock'),$content); + // 读取继承模板 + $array = $this->parseXmlAttrs($matches[1]); + $content = $this->parseTemplateName($array['name']); + $content = $this->parseInclude($content, false); //对继承模板中的include进行分析 + // 替换block标签 + $content = $this->replaceBlock($content); + }else{ + $content = preg_replace_callback('/'.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.'(.*?)'.$begin.'\/block'.$end.'/is', function($match){return stripslashes($match[2]);}, $content); + } + return $content; + } + + /** + * 分析XML属性 + * @access private + * @param string $attrs XML属性字符串 + * @return array + */ + private function parseXmlAttrs($attrs) { + $xml = ''; + $xml = simplexml_load_string($xml); + if(!$xml) + E(L('_XML_TAG_ERROR_')); + $xml = (array)($xml->tag->attributes()); + $array = array_change_key_case($xml['@attributes']); + return $array; + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @return string|false + */ + private function parseLiteral($content) { + if(is_array($content)) $content = $content[1]; + if(trim($content)=='') return ''; + //$content = stripslashes($content); + $i = count($this->literal); + $parseStr = ""; + $this->literal[$i] = $content; + return $parseStr; + } + + /** + * 还原被替换的literal标签 + * @access private + * @param string $tag literal标签序号 + * @return string|false + */ + private function restoreLiteral($tag) { + if(is_array($tag)) $tag = $tag[1]; + // 还原literal标签 + $parseStr = $this->literal[$tag]; + // 销毁literal记录 + unset($this->literal[$tag]); + return $parseStr; + } + + /** + * 记录当前页面中的block标签 + * @access private + * @param string $name block名称 + * @param string $content 模板内容 + * @return string + */ + private function parseBlock($name,$content = '') { + if(is_array($name)){ + $content = $name[2]; + $name = $name[1]; + } + $this->block[$name] = $content; + return ''; + } + + /** + * 替换继承模板中的block标签 + * @access private + * @param string $content 模板内容 + * @return string + */ + private function replaceBlock($content){ + static $parse = 0; + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + $reg = '/('.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.')(.*?)'.$begin.'\/block'.$end.'/is'; + if(is_string($content)){ + do{ + $content = preg_replace_callback($reg, array($this, 'replaceBlock'), $content); + } while ($parse && $parse--); + return $content; + } elseif(is_array($content)){ + if(preg_match('/'.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.'/is', $content[3])){ //存在嵌套,进一步解析 + $parse = 1; + $content[3] = preg_replace_callback($reg, array($this, 'replaceBlock'), "{$content[3]}{$begin}/block{$end}"); + return $content[1] . $content[3]; + } else { + $name = $content[2]; + $content = $content[3]; + $content = isset($this->block[$name]) ? $this->block[$name] : $content; + return $content; + } + } + } + + /** + * 搜索模板页面中包含的TagLib库 + * 并返回列表 + * @access public + * @param string $content 模板内容 + * @return string|false + */ + public function getIncludeTagLib(& $content) { + //搜索是否有TagLib标签 + $find = preg_match('/'.$this->config['taglib_begin'].'taglib\s(.+?)(\s*?)\/'.$this->config['taglib_end'].'\W/is',$content,$matches); + if($find) { + //替换TagLib标签 + $content = str_replace($matches[0],'',$content); + //解析TagLib标签 + $array = $this->parseXmlAttrs($matches[1]); + $this->tagLib = explode(',',$array['name']); + } + return; + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return string + */ + public function parseTagLib($tagLib,&$content,$hide=false) { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + if(strpos($tagLib,'\\')){ + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib,strrpos($tagLib,'\\')+1); + }else{ + $className = 'Think\\Template\TagLib\\'.ucwords($tagLib); + } + $tLib = \Think\Think::instance($className); + $that = $this; + foreach ($tLib->getTags() as $name=>$val){ + $tags = array($name); + if(isset($val['alias'])) {// 别名设置 + $tags = explode(',',$val['alias']); + $tags[] = $name; + } + $level = isset($val['level'])?$val['level']:1; + $closeTag = isset($val['close'])?$val['close']:true; + foreach ($tags as $tag){ + $parseTag = !$hide? $tagLib.':'.$tag: $tag;// 实际要解析的标签名称 + if(!method_exists($tLib,'_'.$tag)) { + // 别名可以无需定义解析方法 + $tag = $name; + } + $n1 = empty($val['attr'])?'(\s*?)':'\s([^'.$end.']*)'; + $this->tempVar = array($tagLib, $tag); + + if (!$closeTag){ + $patterns = '/'.$begin.$parseTag.$n1.'\/(\s*?)'.$end.'/is'; + $content = preg_replace_callback($patterns, function($matches) use($tLib,$tag,$that){ + return $that->parseXmlTag($tLib,$tag,$matches[1],$matches[2]); + },$content); + }else{ + $patterns = '/'.$begin.$parseTag.$n1.$end.'(.*?)'.$begin.'\/'.$parseTag.'(\s*?)'.$end.'/is'; + for($i=0;$i<$level;$i++) { + $content=preg_replace_callback($patterns,function($matches) use($tLib,$tag,$that){ + return $that->parseXmlTag($tLib,$tag,$matches[1],$matches[2]); + },$content); + } + } + } + } + } + + /** + * 解析标签库的标签 + * 需要调用对应的标签库文件解析类 + * @access public + * @param object $tagLib 标签库对象实例 + * @param string $tag 标签名 + * @param string $attr 标签属性 + * @param string $content 标签内容 + * @return string|false + */ + public function parseXmlTag($tagLib,$tag,$attr,$content) { + if(ini_get('magic_quotes_sybase')) + $attr = str_replace('\"','\'',$attr); + $parse = '_'.$tag; + $content = trim($content); + $tags = $tagLib->parseXmlAttr($attr,$tag); + return $tagLib->$parse($tags,$content); + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access public + * @param string $tagStr 标签内容 + * @return string + */ + public function parseTag($tagStr){ + if(is_array($tagStr)) $tagStr = $tagStr[2]; + //if (MAGIC_QUOTES_GPC) { + $tagStr = stripslashes($tagStr); + //} + $flag = substr($tagStr,0,1); + $flag2 = substr($tagStr,1,1); + $name = substr($tagStr,1); + if('$' == $flag && '.' != $flag2 && '(' != $flag2){ //解析模板变量 格式 {$varName} + return $this->parseVar($name); + }elseif('-' == $flag || '+'== $flag){ // 输出计算 + return ''; + }elseif(':' == $flag){ // 输出某个函数的结果 + return ''; + }elseif('~' == $flag){ // 执行某个函数 + return ''; + }elseif(substr($tagStr,0,2)=='//' || (substr($tagStr,0,2)=='/*' && substr(rtrim($tagStr),-2)=='*/')){ + //注释标签 + return ''; + } + // 未识别的标签直接返回 + return C('TMPL_L_DELIM') . $tagStr .C('TMPL_R_DELIM'); + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return string + */ + public function parseVar($varStr){ + $varStr = trim($varStr); + static $_varParseList = array(); + //如果已经解析过该变量字串,则直接返回变量值 + if(isset($_varParseList[$varStr])) return $_varParseList[$varStr]; + $parseStr = ''; + $varExists = true; + if(!empty($varStr)){ + $varArray = explode('|',$varStr); + //取得变量名称 + $var = array_shift($varArray); + if('Think.' == substr($var,0,6)){ + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $name = $this->parseThinkVar($var); + }elseif( false !== strpos($var,'.')) { + //支持 {$var.property} + $vars = explode('.',$var); + $var = array_shift($vars); + switch(strtolower(C('TMPL_VAR_IDENTIFY'))) { + case 'array': // 识别为数组 + $name = '$'.$var; + foreach ($vars as $key=>$val) + $name .= '["'.$val.'"]'; + break; + case 'obj': // 识别为对象 + $name = '$'.$var; + foreach ($vars as $key=>$val) + $name .= '->'.$val; + break; + default: // 自动判断数组或对象 只支持二维 + $name = 'is_array($'.$var.')?$'.$var.'["'.$vars[0].'"]:$'.$var.'->'.$vars[0]; + } + }elseif(false !== strpos($var,'[')) { + //支持 {$var['key']} 方式输出数组 + $name = "$".$var; + preg_match('/(.+?)\[(.+?)\]/is',$var,$match); + $var = $match[1]; + }elseif(false !==strpos($var,':') && false ===strpos($var,'(') && false ===strpos($var,'::') && false ===strpos($var,'?')){ + //支持 {$var:property} 方式输出对象的属性 + $vars = explode(':',$var); + $var = str_replace(':','->',$var); + $name = "$".$var; + $var = $vars[0]; + }else { + $name = "$$var"; + } + //对变量使用函数 + if(count($varArray)>0) + $name = $this->parseVarFunction($name,$varArray); + $parseStr = ''; + } + $_varParseList[$varStr] = $parseStr; + return $parseStr; + } + + /** + * 对模板变量使用函数 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $name 变量名 + * @param array $varArray 函数列表 + * @return string + */ + public function parseVarFunction($name,$varArray){ + //对变量使用函数 + $length = count($varArray); + //取得模板禁止使用函数列表 + $template_deny_funs = explode(',',C('TMPL_DENY_FUNC_LIST')); + for($i=0;$i<$length ;$i++ ){ + $args = explode('=',$varArray[$i],2); + //模板函数过滤 + $fun = trim($args[0]); + switch($fun) { + case 'default': // 特殊模板函数 + $name = '(isset('.$name.') && ('.$name.' !== ""))?('.$name.'):'.$args[1]; + break; + default: // 通用模板函数 + if(!in_array($fun,$template_deny_funs)){ + if(isset($args[1])){ + if(strstr($args[1],'###')){ + $args[1] = str_replace('###',$name,$args[1]); + $name = "$fun($args[1])"; + }else{ + $name = "$fun($name,$args[1])"; + } + }else if(!empty($args[0])){ + $name = "$fun($name)"; + } + } + } + } + return $name; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param string $varStr 变量字符串 + * @return string + */ + public function parseThinkVar($varStr){ + $vars = explode('.',$varStr); + $vars[1] = strtoupper(trim($vars[1])); + $parseStr = ''; + if(count($vars)>=3){ + $vars[2] = trim($vars[2]); + switch($vars[1]){ + case 'SERVER': + $parseStr = '$_SERVER[\''.strtoupper($vars[2]).'\']';break; + case 'GET': + $parseStr = '$_GET[\''.$vars[2].'\']';break; + case 'POST': + $parseStr = '$_POST[\''.$vars[2].'\']';break; + case 'COOKIE': + if(isset($vars[3])) { + $parseStr = '$_COOKIE[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }else{ + $parseStr = 'cookie(\''.$vars[2].'\')'; + } + break; + case 'SESSION': + if(isset($vars[3])) { + $parseStr = '$_SESSION[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }else{ + $parseStr = 'session(\''.$vars[2].'\')'; + } + break; + case 'ENV': + $parseStr = '$_ENV[\''.strtoupper($vars[2]).'\']';break; + case 'REQUEST': + $parseStr = '$_REQUEST[\''.$vars[2].'\']';break; + case 'CONST': + $parseStr = strtoupper($vars[2]);break; + case 'LANG': + $parseStr = 'L("'.$vars[2].'")';break; + case 'CONFIG': + if(isset($vars[3])) { + $vars[2] .= '.'.$vars[3]; + } + $parseStr = 'C("'.$vars[2].'")';break; + default:break; + } + }else if(count($vars)==2){ + switch($vars[1]){ + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'THINK_VERSION'; + break; + case 'TEMPLATE': + $parseStr = "'".$this->templateFile."'";//'C("TEMPLATE_NAME")'; + break; + case 'LDELIM': + $parseStr = 'C("TMPL_L_DELIM")'; + break; + case 'RDELIM': + $parseStr = 'C("TMPL_R_DELIM")'; + break; + default: + if(defined($vars[1])) + $parseStr = $vars[1]; + } + } + return $parseStr; + } + + /** + * 加载公共模板并缓存 和当前模板在同一路径,否则使用相对路径 + * @access private + * @param string $tmplPublicName 公共模板文件名 + * @param array $vars 要传递的变量列表 + * @return string + */ + private function parseIncludeItem($tmplPublicName,$vars=array(),$extend){ + // 分析模板文件名并读取内容 + $parseStr = $this->parseTemplateName($tmplPublicName); + // 替换变量 + foreach ($vars as $key=>$val) { + $parseStr = str_replace('['.$key.']',$val,$parseStr); + } + // 再次对包含文件进行模板分析 + return $this->parseInclude($parseStr,$extend); + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $tmplPublicName 模板文件名 + * @return string + */ + private function parseTemplateName($templateName){ + if(substr($templateName,0,1)=='$') + //支持加载变量文件名 + $templateName = $this->get(substr($templateName,1)); + $array = explode(',',$templateName); + $parseStr = ''; + foreach ($array as $templateName){ + if(empty($templateName)) continue; + if(false === strpos($templateName,$this->config['template_suffix'])) { + // 解析规则为 模块@主题/控制器/操作 + $templateName = T($templateName); + } + // 获取模板文件内容 + $parseStr .= file_get_contents($templateName); + } + return $parseStr; + } +} diff --git a/ThinkPHP/Library/Think/Template/Driver/Ease.class.php b/ThinkPHP/Library/Think/Template/Driver/Ease.class.php new file mode 100644 index 0000000..192bde0 --- /dev/null +++ b/ThinkPHP/Library/Think/Template/Driver/Ease.class.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- +namespace Think\Template\Driver; +/** + * EaseTemplate模板引擎驱动 + */ +class Ease { + /** + * 渲染模板输出 + * @access public + * @param string $templateFile 模板文件名 + * @param array $var 模板变量 + * @return void + */ + public function fetch($templateFile,$var) { + $templateFile = substr($templateFile,strlen(THEME_PATH),-5); + $CacheDir = substr(CACHE_PATH,0,-1); + $TemplateDir = substr(THEME_PATH,0,-1); + vendor('EaseTemplate.template#ease'); + $config = array( + 'CacheDir' => $CacheDir, + 'TemplateDir' => $TemplateDir, + 'TplType' => 'html' + ); + if(C('TMPL_ENGINE_CONFIG')) { + $config = array_merge($config,C('TMPL_ENGINE_CONFIG')); + } + $tpl = new \EaseTemplate($config); + $tpl->set_var($var); + $tpl->set_file($templateFile); + $tpl->p(); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Template/Driver/Lite.class.php b/ThinkPHP/Library/Think/Template/Driver/Lite.class.php new file mode 100644 index 0000000..5f73a02 --- /dev/null +++ b/ThinkPHP/Library/Think/Template/Driver/Lite.class.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- +namespace Think\Template\Driver; +/** + * TemplateLite模板引擎驱动 + */ +class Lite { + /** + * 渲染模板输出 + * @access public + * @param string $templateFile 模板文件名 + * @param array $var 模板变量 + * @return void + */ + public function fetch($templateFile,$var) { + vendor("TemplateLite.class#template"); + $templateFile = substr($templateFile,strlen(THEME_PATH)); + $tpl = new \Template_Lite(); + $tpl->template_dir = THEME_PATH; + $tpl->compile_dir = CACHE_PATH ; + $tpl->cache_dir = TEMP_PATH ; + if(C('TMPL_ENGINE_CONFIG')) { + $config = C('TMPL_ENGINE_CONFIG'); + foreach ($config as $key=>$val){ + $tpl->{$key} = $val; + } + } + $tpl->assign($var); + $tpl->display($templateFile); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Template/Driver/Mobile.class.php b/ThinkPHP/Library/Think/Template/Driver/Mobile.class.php new file mode 100644 index 0000000..db39e7f --- /dev/null +++ b/ThinkPHP/Library/Think/Template/Driver/Mobile.class.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- +namespace Think\Template\Driver; +/** + * MobileTemplate模板引擎驱动 + */ +class Mobile { + /** + * 渲染模板输出 + * @access public + * @param string $templateFile 模板文件名 + * @param array $var 模板变量 + * @return void + */ + public function fetch($templateFile,$var) { + $templateFile=substr($templateFile,strlen(THEME_PATH)); + $var['_think_template_path']=$templateFile; + exit(json_encode($var)); + } +} diff --git a/ThinkPHP/Library/Think/Template/Driver/Smart.class.php b/ThinkPHP/Library/Think/Template/Driver/Smart.class.php new file mode 100644 index 0000000..5a2eb1c --- /dev/null +++ b/ThinkPHP/Library/Think/Template/Driver/Smart.class.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- +namespace Think\Template\Driver; +/** + * Smart模板引擎驱动 + */ +class Smart { + /** + * 渲染模板输出 + * @access public + * @param string $templateFile 模板文件名 + * @param array $var 模板变量 + * @return void + */ + public function fetch($templateFile,$var) { + $templateFile = substr($templateFile,strlen(THEME_PATH)); + vendor('SmartTemplate.class#smarttemplate'); + $tpl = new \SmartTemplate($templateFile); + $tpl->caching = C('TMPL_CACHE_ON'); + $tpl->template_dir = THEME_PATH; + $tpl->compile_dir = CACHE_PATH ; + $tpl->cache_dir = TEMP_PATH ; + if(C('TMPL_ENGINE_CONFIG')) { + $config = C('TMPL_ENGINE_CONFIG'); + foreach ($config as $key=>$val){ + $tpl->{$key} = $val; + } + } + $tpl->assign($var); + $tpl->output(); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Template/Driver/Smarty.class.php b/ThinkPHP/Library/Think/Template/Driver/Smarty.class.php new file mode 100644 index 0000000..aecdc10 --- /dev/null +++ b/ThinkPHP/Library/Think/Template/Driver/Smarty.class.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- +namespace Think\Template\Driver; +/** + * Smarty模板引擎驱动 + */ +class Smarty { + + /** + * 渲染模板输出 + * @access public + * @param string $templateFile 模板文件名 + * @param array $var 模板变量 + * @return void + */ + public function fetch($templateFile,$var) { + $templateFile = substr($templateFile,strlen(THEME_PATH)); + vendor('Smarty.Smarty#class'); + $tpl = new \Smarty(); + $tpl->caching = C('TMPL_CACHE_ON'); + $tpl->template_dir = THEME_PATH; + $tpl->compile_dir = CACHE_PATH ; + $tpl->cache_dir = TEMP_PATH ; + if(C('TMPL_ENGINE_CONFIG')) { + $config = C('TMPL_ENGINE_CONFIG'); + foreach ($config as $key=>$val){ + $tpl->{$key} = $val; + } + } + $tpl->assign($var); + $tpl->display($templateFile); + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Template/TagLib.class.php b/ThinkPHP/Library/Think/Template/TagLib.class.php new file mode 100644 index 0000000..18e3be8 --- /dev/null +++ b/ThinkPHP/Library/Think/Template/TagLib.class.php @@ -0,0 +1,246 @@ + +// +---------------------------------------------------------------------- +namespace Think\Template; +/** + * ThinkPHP标签库TagLib解析基类 + */ +class TagLib { + + /** + * 标签库定义XML文件 + * @var string + * @access protected + */ + protected $xml = ''; + protected $tags = array();// 标签定义 + /** + * 标签库名称 + * @var string + * @access protected + */ + protected $tagLib =''; + + /** + * 标签库标签列表 + * @var string + * @access protected + */ + protected $tagList = array(); + + /** + * 标签库分析数组 + * @var string + * @access protected + */ + protected $parse = array(); + + /** + * 标签库是否有效 + * @var string + * @access protected + */ + protected $valid = false; + + /** + * 当前模板对象 + * @var object + * @access protected + */ + protected $tpl; + + protected $comparison = array(' nheq '=>' !== ',' heq '=>' === ',' neq '=>' != ',' eq '=>' == ',' egt '=>' >= ',' gt '=>' > ',' elt '=>' <= ',' lt '=>' < '); + + /** + * 架构函数 + * @access public + */ + public function __construct() { + $this->tagLib = strtolower(substr(get_class($this),6)); + $this->tpl = \Think\Think::instance('Think\\Template'); + } + + /** + * TagLib标签属性分析 返回标签属性数组 + * @access public + * @param string $tagStr 标签内容 + * @return array + */ + public function parseXmlAttr($attr,$tag) { + //XML解析安全过滤 + $attr = str_replace('&','___', $attr); + $xml = ''; + $xml = simplexml_load_string($xml); + if(!$xml) { + E(L('_XML_TAG_ERROR_').' : '.$attr); + } + $xml = (array)($xml->tag->attributes()); + if(isset($xml['@attributes'])){ + $array = array_change_key_case($xml['@attributes']); + if($array) { + $tag = strtolower($tag); + if(!isset($this->tags[$tag])){ + // 检测是否存在别名定义 + foreach($this->tags as $key=>$val){ + if(isset($val['alias']) && in_array($tag,explode(',',$val['alias']))){ + $item = $val; + break; + } + } + }else{ + $item = $this->tags[$tag]; + } + $attrs = explode(',',$item['attr']); + if(isset($item['must'])){ + $must = explode(',',$item['must']); + }else{ + $must = array(); + } + foreach($attrs as $name) { + if( isset($array[$name])) { + $array[$name] = str_replace('___','&',$array[$name]); + }elseif(false !== array_search($name,$must)){ + E(L('_PARAM_ERROR_').':'.$name); + } + } + return $array; + } + }else{ + return array(); + } + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return array + */ + public function parseCondition($condition) { + $condition = str_ireplace(array_keys($this->comparison),array_values($this->comparison),$condition); + $condition = preg_replace('/\$(\w+):(\w+)\s/is','$\\1->\\2 ',$condition); + switch(strtolower(C('TMPL_VAR_IDENTIFY'))) { + case 'array': // 识别为数组 + $condition = preg_replace('/\$(\w+)\.(\w+)\s/is','$\\1["\\2"] ',$condition); + break; + case 'obj': // 识别为对象 + $condition = preg_replace('/\$(\w+)\.(\w+)\s/is','$\\1->\\2 ',$condition); + break; + default: // 自动判断数组或对象 只支持二维 + $condition = preg_replace('/\$(\w+)\.(\w+)\s/is','(is_array($\\1)?$\\1["\\2"]:$\\1->\\2) ',$condition); + } + if(false !== strpos($condition, '$Think')) + $condition = preg_replace_callback('/(\$Think.*?)\s/is', array($this, 'parseThinkVar'), $condition); + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar($name) { + if('Think.' == substr($name,0,6)){ + // 特殊变量 + return $this->parseThinkVar($name); + }elseif(strpos($name,'.')) { + $vars = explode('.',$name); + $var = array_shift($vars); + switch(strtolower(C('TMPL_VAR_IDENTIFY'))) { + case 'array': // 识别为数组 + $name = '$'.$var; + foreach ($vars as $key=>$val){ + if(0===strpos($val,'$')) { + $name .= '["{'.$val.'}"]'; + }else{ + $name .= '["'.$val.'"]'; + } + } + break; + case 'obj': // 识别为对象 + $name = '$'.$var; + foreach ($vars as $key=>$val) + $name .= '->'.$val; + break; + default: // 自动判断数组或对象 只支持二维 + $name = 'is_array($'.$var.')?$'.$var.'["'.$vars[0].'"]:$'.$var.'->'.$vars[0]; + } + }elseif(strpos($name,':')){ + // 额外的对象方式支持 + $name = '$'.str_replace(':','->',$name); + }elseif(!defined($name)) { + $name = '$'.$name; + } + return $name; + } + + /** + * 用于标签属性里面的特殊模板变量解析 + * 格式 以 Think. 打头的变量属于特殊模板变量 + * @access public + * @param string $varStr 变量字符串 + * @return string + */ + public function parseThinkVar($varStr){ + if(is_array($varStr)){//用于正则替换回调函数 + $varStr = $varStr[1]; + } + $vars = explode('.',$varStr); + $vars[1] = strtoupper(trim($vars[1])); + $parseStr = ''; + if(count($vars)>=3){ + $vars[2] = trim($vars[2]); + switch($vars[1]){ + case 'SERVER': $parseStr = '$_SERVER[\''.$vars[2].'\']';break; + case 'GET': $parseStr = '$_GET[\''.$vars[2].'\']';break; + case 'POST': $parseStr = '$_POST[\''.$vars[2].'\']';break; + case 'COOKIE': + if(isset($vars[3])) { + $parseStr = '$_COOKIE[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }elseif(C('COOKIE_PREFIX')){ + $parseStr = '$_COOKIE[\''.C('COOKIE_PREFIX').$vars[2].'\']'; + }else{ + $parseStr = '$_COOKIE[\''.$vars[2].'\']'; + } + break; + case 'SESSION': + if(isset($vars[3])) { + $parseStr = '$_SESSION[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }elseif(C('SESSION_PREFIX')){ + $parseStr = '$_SESSION[\''.C('SESSION_PREFIX').'\'][\''.$vars[2].'\']'; + }else{ + $parseStr = '$_SESSION[\''.$vars[2].'\']'; + } + break; + case 'ENV': $parseStr = '$_ENV[\''.$vars[2].'\']';break; + case 'REQUEST': $parseStr = '$_REQUEST[\''.$vars[2].'\']';break; + case 'CONST': $parseStr = strtoupper($vars[2]);break; + case 'LANG': $parseStr = 'L("'.$vars[2].'")';break; + case 'CONFIG': $parseStr = 'C("'.$vars[2].'")';break; + } + }else if(count($vars)==2){ + switch($vars[1]){ + case 'NOW': $parseStr = "date('Y-m-d g:i a',time())";break; + case 'VERSION': $parseStr = 'THINK_VERSION';break; + case 'TEMPLATE':$parseStr = 'C("TEMPLATE_NAME")';break; + case 'LDELIM': $parseStr = 'C("TMPL_L_DELIM")';break; + case 'RDELIM': $parseStr = 'C("TMPL_R_DELIM")';break; + default: if(defined($vars[1])) $parseStr = $vars[1]; + } + } + return $parseStr; + } + + // 获取标签定义 + public function getTags(){ + return $this->tags; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Template/TagLib/Cx.class.php b/ThinkPHP/Library/Think/Template/TagLib/Cx.class.php new file mode 100644 index 0000000..67b5dd4 --- /dev/null +++ b/ThinkPHP/Library/Think/Template/TagLib/Cx.class.php @@ -0,0 +1,614 @@ + +// +---------------------------------------------------------------------- +namespace Think\Template\TagLib; +use Think\Template\TagLib; +/** + * CX标签库解析类 + */ +class Cx extends TagLib { + + // 标签定义 + protected $tags = array( + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'php' => array(), + 'volist' => array('attr'=>'name,id,offset,length,key,mod','level'=>3,'alias'=>'iterate'), + 'foreach' => array('attr'=>'name,item,key','level'=>3), + 'if' => array('attr'=>'condition','level'=>2), + 'elseif' => array('attr'=>'condition','close'=>0), + 'else' => array('attr'=>'','close'=>0), + 'switch' => array('attr'=>'name','level'=>2), + 'case' => array('attr'=>'value,break'), + 'default' => array('attr'=>'','close'=>0), + 'compare' => array('attr'=>'name,value,type','level'=>3,'alias'=>'eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq'), + 'range' => array('attr'=>'name,value,type','level'=>3,'alias'=>'in,notin,between,notbetween'), + 'empty' => array('attr'=>'name','level'=>3), + 'notempty' => array('attr'=>'name','level'=>3), + 'present' => array('attr'=>'name','level'=>3), + 'notpresent'=> array('attr'=>'name','level'=>3), + 'defined' => array('attr'=>'name','level'=>3), + 'notdefined'=> array('attr'=>'name','level'=>3), + 'import' => array('attr'=>'file,href,type,value,basepath','close'=>0,'alias'=>'load,css,js'), + 'assign' => array('attr'=>'name,value','close'=>0), + 'define' => array('attr'=>'name,value','close'=>0), + 'for' => array('attr'=>'start,end,name,comparison,step', 'level'=>3), + ); + + /** + * php标签解析 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _php($tag,$content) { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * + * {user.username} + * {user.email} + * + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function _volist($tag,$content) { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty'])?$tag['empty']:''; + $key = !empty($tag['key'])?$tag['key']:'i'; + $mod = isset($tag['mod'])?$tag['mod']:'2'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = 'autoBuildVar($name); + } + $parseStr .= 'if(is_array('.$name.')): $'.$key.' = 0;'; + if(isset($tag['length']) && '' !=$tag['length'] ) { + $parseStr .= ' $__LIST__ = array_slice('.$name.','.$tag['offset'].','.$tag['length'].',true);'; + }elseif(isset($tag['offset']) && '' !=$tag['offset']){ + $parseStr .= ' $__LIST__ = array_slice('.$name.','.$tag['offset'].',null,true);'; + }else{ + $parseStr .= ' $__LIST__ = '.$name.';'; + } + $parseStr .= 'if( count($__LIST__)==0 ) : echo "'.$empty.'" ;'; + $parseStr .= 'else: '; + $parseStr .= 'foreach($__LIST__ as $key=>$'.$id.'): '; + $parseStr .= '$mod = ($'.$key.' % '.$mod.' );'; + $parseStr .= '++$'.$key.';?>'; + $parseStr .= $this->tpl->parse($content); + $parseStr .= ''; + + if(!empty($parseStr)) { + return $parseStr; + } + return ; + } + + /** + * foreach标签解析 循环输出数据集 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function _foreach($tag,$content) { + $name = $tag['name']; + $item = $tag['item']; + $key = !empty($tag['key'])?$tag['key']:'key'; + $name = $this->autoBuildVar($name); + $parseStr = '$'.$item.'): ?>'; + $parseStr .= $this->tpl->parse($content); + $parseStr .= ''; + + if(!empty($parseStr)) { + return $parseStr; + } + return ; + } + + /** + * if标签解析 + * 格式: + * + * + * + * + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _if($tag,$content) { + $condition = $this->parseCondition($tag['condition']); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _elseif($tag,$content) { + $condition = $this->parseCondition($tag['condition']); + $parseStr = ''; + return $parseStr; + } + + /** + * else标签解析 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function _else($tag) { + $parseStr = ''; + return $parseStr; + } + + /** + * switch标签解析 + * 格式: + * + * 1 + * 2 + * other + * + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _switch($tag,$content) { + $name = $tag['name']; + $varArray = explode('|',$name); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if(count($varArray)>0) + $name = $this->tpl->parseVarFunction($name,$varArray); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _case($tag,$content) { + $value = $tag['value']; + if('$' == substr($value,0,1)) { + $varArray = explode('|',$value); + $value = array_shift($varArray); + $value = $this->autoBuildVar(substr($value,1)); + if(count($varArray)>0) + $value = $this->tpl->parseVarFunction($value,$varArray); + $value = 'case '.$value.': '; + }elseif(strpos($value,'|')){ + $values = explode('|',$value); + $value = ''; + foreach ($values as $val){ + $value .= 'case "'.addslashes($val).'": '; + } + }else{ + $value = 'case "'.$value.'": '; + } + $parseStr = ''.$content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + if('' ==$isBreak || $isBreak) { + $parseStr .= ''; + } + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: ddfdf + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _default($tag) { + $parseStr = ''; + return $parseStr; + } + + /** + * compare标签解析 + * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _compare($tag,$content,$type='eq') { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type'])?$tag['type']:$type; + $type = $this->parseCondition(' '.$type.' '); + $varArray = explode('|',$name); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if(count($varArray)>0) + $name = $this->tpl->parseVarFunction($name,$varArray); + if('$' == substr($value,0,1)) { + $value = $this->autoBuildVar(substr($value,1)); + }else { + $value = '"'.$value.'"'; + } + $parseStr = ''.$content.''; + return $parseStr; + } + + public function _eq($tag,$content) { + return $this->_compare($tag,$content,'eq'); + } + + public function _equal($tag,$content) { + return $this->_compare($tag,$content,'eq'); + } + + public function _neq($tag,$content) { + return $this->_compare($tag,$content,'neq'); + } + + public function _notequal($tag,$content) { + return $this->_compare($tag,$content,'neq'); + } + + public function _gt($tag,$content) { + return $this->_compare($tag,$content,'gt'); + } + + public function _lt($tag,$content) { + return $this->_compare($tag,$content,'lt'); + } + + public function _egt($tag,$content) { + return $this->_compare($tag,$content,'egt'); + } + + public function _elt($tag,$content) { + return $this->_compare($tag,$content,'elt'); + } + + public function _heq($tag,$content) { + return $this->_compare($tag,$content,'heq'); + } + + public function _nheq($tag,$content) { + return $this->_compare($tag,$content,'nheq'); + } + + /** + * range标签解析 + * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 + * 格式: content + * example: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @param string $type 比较类型 + * @return string + */ + public function _range($tag,$content,$type='in') { + $name = $tag['name']; + $value = $tag['value']; + $varArray = explode('|',$name); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if(count($varArray)>0) + $name = $this->tpl->parseVarFunction($name,$varArray); + + $type = isset($tag['type'])?$tag['type']:$type; + + if('$' == substr($value,0,1)) { + $value = $this->autoBuildVar(substr($value,1)); + $str = 'is_array('.$value.')?'.$value.':explode(\',\','.$value.')'; + }else{ + $value = '"'.$value.'"'; + $str = 'explode(\',\','.$value.')'; + } + if($type=='between') { + $parseStr = '= $_RANGE_VAR_[0] && '.$name.'<= $_RANGE_VAR_[1]):?>'.$content.''; + }elseif($type=='notbetween'){ + $parseStr = '$_RANGE_VAR_[1]):?>'.$content.''; + }else{ + $fun = ($type == 'in')? 'in_array' : '!in_array'; + $parseStr = ''.$content.''; + } + return $parseStr; + } + + // range标签的别名 用于in判断 + public function _in($tag,$content) { + return $this->_range($tag,$content,'in'); + } + + // range标签的别名 用于notin判断 + public function _notin($tag,$content) { + return $this->_range($tag,$content,'notin'); + } + + public function _between($tag,$content){ + return $this->_range($tag,$content,'between'); + } + + public function _notbetween($tag,$content){ + return $this->_range($tag,$content,'notbetween'); + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _present($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _notpresent($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _empty($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + public function _notempty($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * 已定义 + * @param $attr + * @param $content + * @return string + */ + public function _defined($tag,$content) { + $name = $tag['name']; + $parseStr = ''.$content.''; + return $parseStr; + } + + public function _notdefined($tag,$content) { + $name = $tag['name']; + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * import 标签解析 + * + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @param boolean $isFile 是否文件方式 + * @param string $type 类型 + * @return string + */ + public function _import($tag,$content,$isFile=false,$type='') { + $file = isset($tag['file'])?$tag['file']:$tag['href']; + $parseStr = ''; + $endStr = ''; + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $varArray = explode('|',$tag['value']); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if (!empty($varArray)) + $name = $this->tpl->parseVarFunction($name,$varArray); + else + $name = 'isset('.$name.')'; + $parseStr .= ''; + $endStr = ''; + } + if($isFile) { + // 根据文件名后缀自动识别 + $type = $type?$type:(!empty($tag['type'])?strtolower($tag['type']):null); + // 文件方式导入 + $array = explode(',',$file); + foreach ($array as $val){ + if (!$type || isset($reset)) { + $type = $reset = strtolower(substr(strrchr($val, '.'),1)); + } + switch($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + }else{ + // 命名空间导入模式 默认是js + $type = $type?$type:(!empty($tag['type'])?strtolower($tag['type']):'js'); + $basepath = !empty($tag['basepath'])?$tag['basepath']:__ROOT__.'/Public'; + // 命名空间方式导入外部文件 + $array = explode(',',$file); + foreach ($array as $val){ + if(strpos ($val, '?')) { + list($val,$version) = explode('?',$val); + } else { + $version = ''; + } + switch($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + } + return $parseStr.$endStr; + } + + // import别名 采用文件方式加载(要使用命名空间必须用import) 例如 + public function _load($tag,$content) { + return $this->_import($tag,$content,true); + } + + // import别名使用 导入css文件 + public function _css($tag,$content) { + return $this->_import($tag,$content,true,'css'); + } + + // import别名使用 导入js文件 + public function _js($tag,$content) { + return $this->_import($tag,$content,true,'js'); + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _assign($tag,$content) { + $name = $this->autoBuildVar($tag['name']); + if('$'==substr($tag['value'],0,1)) { + $value = $this->autoBuildVar(substr($tag['value'],1)); + }else{ + $value = '\''.$tag['value']. '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _define($tag,$content) { + $name = '\''.$tag['name']. '\''; + if('$'==substr($tag['value'],0,1)) { + $value = $this->autoBuildVar(substr($tag['value'],1)); + }else{ + $value = '\''.$tag['value']. '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _for($tag, $content){ + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + //获取属性 + foreach ($tag as $key => $value){ + $value = trim($value); + if(':'==substr($value,0,1)) + $value = substr($value,1); + elseif('$'==substr($value,0,1)) + $value = $this->autoBuildVar(substr($value,1)); + switch ($key){ + case 'start': + $start = $value; break; + case 'end' : + $end = $value; break; + case 'step': + $step = $value; break; + case 'comparison': + $comparison = $value; break; + case 'name': + $name = $value; break; + } + } + + $parseStr = 'parseCondition('$'.$name.' '.$comparison.' $__FOR_END_'.$rand.'__').';$'.$name.'+='.$step.'){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + +} diff --git a/ThinkPHP/Library/Think/Template/TagLib/Html.class.php b/ThinkPHP/Library/Think/Template/TagLib/Html.class.php new file mode 100644 index 0000000..4b39499 --- /dev/null +++ b/ThinkPHP/Library/Think/Template/TagLib/Html.class.php @@ -0,0 +1,523 @@ + +// +---------------------------------------------------------------------- +namespace Think\Template\TagLib; +use Think\Template\TagLib; +/** + * Html标签库驱动 + */ +class Html extends TagLib{ + // 标签定义 + protected $tags = array( + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'editor' => array('attr'=>'id,name,style,width,height,type','close'=>1), + 'select' => array('attr'=>'name,options,values,output,multiple,id,size,first,change,selected,dblclick','close'=>0), + 'grid' => array('attr'=>'id,pk,style,action,actionlist,show,datasource','close'=>0), + 'list' => array('attr'=>'id,pk,style,action,actionlist,show,datasource,checkbox','close'=>0), + 'imagebtn' => array('attr'=>'id,name,value,type,style,click','close'=>0), + 'checkbox' => array('attr'=>'name,checkboxes,checked,separator','close'=>0), + 'radio' => array('attr'=>'name,radios,checked,separator','close'=>0) + ); + + /** + * editor标签解析 插入可视化编辑器 + * 格式: {$vo.remark} + * @access public + * @param array $tag 标签属性 + * @return string|void + */ + public function _editor($tag,$content) { + $id = !empty($tag['id'])?$tag['id']: '_editor'; + $name = $tag['name']; + $style = !empty($tag['style'])?$tag['style']:''; + $width = !empty($tag['width'])?$tag['width']: '100%'; + $height = !empty($tag['height'])?$tag['height'] :'320px'; + // $content = $tag['content']; + $type = $tag['type'] ; + switch(strtoupper($type)) { + case 'FCKEDITOR': + $parseStr = ' '; + break; + case 'FCKMINI': + $parseStr = ' '; + break; + case 'EWEBEDITOR': + $parseStr = ""; + break; + case 'NETEASE': + $parseStr = ''; + break; + case 'UBB': + $parseStr = '
'; + break; + case 'KINDEDITOR': + $parseStr = ''; + break; + default : + $parseStr = ''; + } + + return $parseStr; + } + + /** + * imageBtn标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @return string|void + */ + public function _imageBtn($tag) { + $name = $tag['name']; //名称 + $value = $tag['value']; //文字 + $id = isset($tag['id'])?$tag['id']:''; //ID + $style = isset($tag['style'])?$tag['style']:''; //样式名 + $click = isset($tag['click'])?$tag['click']:''; //点击 + $type = empty($tag['type'])?'button':$tag['type']; //按钮类型 + + if(!empty($name)) { + $parseStr = '
'; + }else { + $parseStr = '
'; + } + + return $parseStr; + } + + /** + * imageLink标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @return string|void + */ + public function _imgLink($tag) { + $name = $tag['name']; //名称 + $alt = $tag['alt']; //文字 + $id = $tag['id']; //ID + $style = $tag['style']; //样式名 + $click = $tag['click']; //点击 + $type = $tag['type']; //点击 + if(empty($type)) { + $type = 'button'; + } + $parseStr = ''; + + return $parseStr; + } + + /** + * select标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @return string|void + */ + public function _select($tag) { + $name = $tag['name']; + $options = $tag['options']; + $values = $tag['values']; + $output = $tag['output']; + $multiple = $tag['multiple']; + $id = $tag['id']; + $size = $tag['size']; + $first = $tag['first']; + $selected = $tag['selected']; + $style = $tag['style']; + $ondblclick = $tag['dblclick']; + $onchange = $tag['change']; + + if(!empty($multiple)) { + $parseStr = ''; + } + if(!empty($first)) { + $parseStr .= ''; + } + if(!empty($options)) { + $parseStr .= '$val) { ?>'; + if(!empty($selected)) { + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + }else { + $parseStr .= ''; + } + $parseStr .= ''; + }else if(!empty($values)) { + $parseStr .= ''; + if(!empty($selected)) { + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + }else { + $parseStr .= ''; + } + $parseStr .= ''; + } + $parseStr .= ''; + return $parseStr; + } + + /** + * checkbox标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @return string|void + */ + public function _checkbox($tag) { + $name = $tag['name']; + $checkboxes = $tag['checkboxes']; + $checked = $tag['checked']; + $separator = $tag['separator']; + $checkboxes = $this->tpl->get($checkboxes); + $checked = $this->tpl->get($checked)?$this->tpl->get($checked):$checked; + $parseStr = ''; + foreach($checkboxes as $key=>$val) { + if($checked == $key || in_array($key,$checked) ) { + $parseStr .= ''.$val.$separator; + }else { + $parseStr .= ''.$val.$separator; + } + } + return $parseStr; + } + + /** + * radio标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @return string|void + */ + public function _radio($tag) { + $name = $tag['name']; + $radios = $tag['radios']; + $checked = $tag['checked']; + $separator = $tag['separator']; + $radios = $this->tpl->get($radios); + $checked = $this->tpl->get($checked)?$this->tpl->get($checked):$checked; + $parseStr = ''; + foreach($radios as $key=>$val) { + if($checked == $key ) { + $parseStr .= ''.$val.$separator; + }else { + $parseStr .= ''.$val.$separator; + } + + } + return $parseStr; + } + + /** + * list标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function _grid($tag) { + $id = $tag['id']; //表格ID + $datasource = $tag['datasource']; //列表显示的数据源VoList名称 + $pk = empty($tag['pk'])?'id':$tag['pk'];//主键名,默认为id + $style = $tag['style']; //样式名 + $name = !empty($tag['name'])?$tag['name']:'vo'; //Vo对象名 + $action = !empty($tag['action'])?$tag['action']:false; //是否显示功能操作 + $key = !empty($tag['key'])?true:false; + if(isset($tag['actionlist'])) { + $actionlist = explode(',',trim($tag['actionlist'])); //指定功能列表 + } + + if(substr($tag['show'],0,1)=='$') { + $show = $this->tpl->get(substr($tag['show'],1)); + }else { + $show = $tag['show']; + } + $show = explode(',',$show); //列表显示字段列表 + + //计算表格的列数 + $colNum = count($show); + if(!empty($action)) $colNum++; + if(!empty($key)) $colNum++; + + //显示开始 + $parseStr = "\n"; + $parseStr .= '
'; + $parseStr .= ''; + $parseStr .= ''; + //列表需要显示的字段 + $fields = array(); + foreach($show as $val) { + $fields[] = explode(':',$val); + } + + if(!empty($key)) { + $parseStr .= ''; + } + foreach($fields as $field) {//显示指定的字段 + $property = explode('|',$field[0]); + $showname = explode('|',$field[1]); + if(isset($showname[1])) { + $parseStr .= ''; + } + if(!empty($action)) {//如果指定显示操作功能列 + $parseStr .= ''; + } + $parseStr .= ''; + $parseStr .= ''; //支持鼠标移动单元行颜色变化 具体方法在js中定义 + + if(!empty($key)) { + $parseStr .= ''; + } + foreach($fields as $field) { + //显示定义的列表字段 + $parseStr .= ''; + + } + if(!empty($action)) {//显示功能操作 + if(!empty($actionlist[0])) {//显示指定的功能项 + $parseStr .= ''; + } + } + $parseStr .= '
No'; + }else { + $parseStr .= ''; + } + $parseStr .= $showname[0].'操作
{$i}'; + if(!empty($field[2])) { + // 支持列表字段链接功能 具体方法由JS函数实现 + $href = explode('|',$field[2]); + if(count($href)>1) { + //指定链接传的字段值 + // 支持多个字段传递 + $array = explode('^',$href[1]); + if(count($array)>1) { + foreach ($array as $a){ + $temp[] = '\'{$'.$name.'.'.$a.'|addslashes}\''; + } + $parseStr .= ''; + }else{ + $parseStr .= ''; + } + }else { + //如果没有指定默认传编号值 + $parseStr .= ''; + } + } + if(strpos($field[0],'^')) { + $property = explode('^',$field[0]); + foreach ($property as $p){ + $unit = explode('|',$p); + if(count($unit)>1) { + $parseStr .= '{$'.$name.'.'.$unit[0].'|'.$unit[1].'} '; + }else { + $parseStr .= '{$'.$name.'.'.$p.'} '; + } + } + }else{ + $property = explode('|',$field[0]); + if(count($property)>1) { + $parseStr .= '{$'.$name.'.'.$property[0].'|'.$property[1].'}'; + }else { + $parseStr .= '{$'.$name.'.'.$field[0].'}'; + } + } + if(!empty($field[2])) { + $parseStr .= ''; + } + $parseStr .= ''; + foreach($actionlist as $val) { + if(strpos($val,':')) { + $a = explode(':',$val); + if(count($a)>2) { + $parseStr .= ''.$a[1].' '; + }else { + $parseStr .= ''.$a[1].' '; + } + }else{ + $array = explode('|',$val); + if(count($array)>2) { + $parseStr .= ' '.$array[2].' '; + }else{ + $parseStr .= ' {$'.$name.'.'.$val.'} '; + } + } + } + $parseStr .= '
'; + $parseStr .= "\n\n"; + return $parseStr; + } + + /** + * list标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function _list($tag) { + $id = $tag['id']; //表格ID + $datasource = $tag['datasource']; //列表显示的数据源VoList名称 + $pk = empty($tag['pk'])?'id':$tag['pk'];//主键名,默认为id + $style = $tag['style']; //样式名 + $name = !empty($tag['name'])?$tag['name']:'vo'; //Vo对象名 + $action = $tag['action']=='true'?true:false; //是否显示功能操作 + $key = !empty($tag['key'])?true:false; + $sort = $tag['sort']=='false'?false:true; + $checkbox = $tag['checkbox']; //是否显示Checkbox + if(isset($tag['actionlist'])) { + if(substr($tag['actionlist'],0,1)=='$') { + $actionlist = $this->tpl->get(substr($tag['actionlist'],1)); + }else { + $actionlist = $tag['actionlist']; + } + $actionlist = explode(',',trim($actionlist)); //指定功能列表 + } + + if(substr($tag['show'],0,1)=='$') { + $show = $this->tpl->get(substr($tag['show'],1)); + }else { + $show = $tag['show']; + } + $show = explode(',',$show); //列表显示字段列表 + + //计算表格的列数 + $colNum = count($show); + if(!empty($checkbox)) $colNum++; + if(!empty($action)) $colNum++; + if(!empty($key)) $colNum++; + + //显示开始 + $parseStr = "\n"; + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + //列表需要显示的字段 + $fields = array(); + foreach($show as $val) { + $fields[] = explode(':',$val); + } + if(!empty($checkbox) && 'true'==strtolower($checkbox)) {//如果指定需要显示checkbox列 + $parseStr .=''; + } + if(!empty($key)) { + $parseStr .= ''; + } + foreach($fields as $field) {//显示指定的字段 + $property = explode('|',$field[0]); + $showname = explode('|',$field[1]); + if(isset($showname[1])) { + $parseStr .= ''; + }else{ + $parseStr .= $showname[0].''; + } + + } + if(!empty($action)) {//如果指定显示操作功能列 + $parseStr .= ''; + } + + $parseStr .= ''; + $parseStr .= ''; + } + if(!empty($key)) { + $parseStr .= ''; + } + foreach($fields as $field) { + //显示定义的列表字段 + $parseStr .= ''; + + } + if(!empty($action)) {//显示功能操作 + if(!empty($actionlist[0])) {//显示指定的功能项 + $parseStr .= ''; + } + } + $parseStr .= '
No'; + }else { + $parseStr .= ''; + } + $showname[2] = isset($showname[2])?$showname[2]:$showname[0]; + if($sort) { + $parseStr .= ''.$showname[0].'操作
{$i}'; + if(!empty($field[2])) { + // 支持列表字段链接功能 具体方法由JS函数实现 + $href = explode('|',$field[2]); + if(count($href)>1) { + //指定链接传的字段值 + // 支持多个字段传递 + $array = explode('^',$href[1]); + if(count($array)>1) { + foreach ($array as $a){ + $temp[] = '\'{$'.$name.'.'.$a.'|addslashes}\''; + } + $parseStr .= ''; + }else{ + $parseStr .= ''; + } + }else { + //如果没有指定默认传编号值 + $parseStr .= ''; + } + } + if(strpos($field[0],'^')) { + $property = explode('^',$field[0]); + foreach ($property as $p){ + $unit = explode('|',$p); + if(count($unit)>1) { + $parseStr .= '{$'.$name.'.'.$unit[0].'|'.$unit[1].'} '; + }else { + $parseStr .= '{$'.$name.'.'.$p.'} '; + } + } + }else{ + $property = explode('|',$field[0]); + if(count($property)>1) { + $parseStr .= '{$'.$name.'.'.$property[0].'|'.$property[1].'}'; + }else { + $parseStr .= '{$'.$name.'.'.$field[0].'}'; + } + } + if(!empty($field[2])) { + $parseStr .= ''; + } + $parseStr .= ''; + foreach($actionlist as $val) { + if(strpos($val,':')) { + $a = explode(':',$val); + if(count($a)>2) { + $parseStr .= ''.$a[1].' '; + }else { + $parseStr .= ''.$a[1].' '; + } + }else{ + $array = explode('|',$val); + if(count($array)>2) { + $parseStr .= ' '.$array[2].' '; + }else{ + $parseStr .= ' {$'.$name.'.'.$val.'} '; + } + } + } + $parseStr .= '
'; + $parseStr .= "\n\n"; + return $parseStr; + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Think.class.php b/ThinkPHP/Library/Think/Think.class.php new file mode 100644 index 0000000..3bd1864 --- /dev/null +++ b/ThinkPHP/Library/Think/Think.class.php @@ -0,0 +1,344 @@ + +// +---------------------------------------------------------------------- + +namespace Think; +/** + * ThinkPHP 引导类 + */ +class Think { + + // 类映射 + private static $_map = array(); + + // 实例化对象 + private static $_instance = array(); + + /** + * 应用程序初始化 + * @access public + * @return void + */ + static public function start() { + // 注册AUTOLOAD方法 + spl_autoload_register('Think\Think::autoload'); + // 设定错误和异常处理 + register_shutdown_function('Think\Think::fatalError'); + set_error_handler('Think\Think::appError'); + set_exception_handler('Think\Think::appException'); + + // 初始化文件存储方式 + Storage::connect(STORAGE_TYPE); + + $runtimefile = RUNTIME_PATH.APP_MODE.'~runtime.php'; + if(!APP_DEBUG && Storage::has($runtimefile)){ + Storage::load($runtimefile); + }else{ + if(Storage::has($runtimefile)) + Storage::unlink($runtimefile); + $content = ''; + // 读取应用模式 + $mode = include is_file(CONF_PATH.'core.php')?CONF_PATH.'core.php':MODE_PATH.APP_MODE.'.php'; + // 加载核心文件 + foreach ($mode['core'] as $file){ + if(is_file($file)) { + include $file; + if(!APP_DEBUG) $content .= compile($file); + } + } + + // 加载应用模式配置文件 + foreach ($mode['config'] as $key=>$file){ + is_numeric($key)?C(load_config($file)):C($key,load_config($file)); + } + + // 读取当前应用模式对应的配置文件 + if('common' != APP_MODE && is_file(CONF_PATH.'config_'.APP_MODE.CONF_EXT)) + C(load_config(CONF_PATH.'config_'.APP_MODE.CONF_EXT)); + + // 加载模式别名定义 + if(isset($mode['alias'])){ + self::addMap(is_array($mode['alias'])?$mode['alias']:include $mode['alias']); + } + + // 加载应用别名定义文件 + if(is_file(CONF_PATH.'alias.php')) + self::addMap(include CONF_PATH.'alias.php'); + + // 加载模式行为定义 + if(isset($mode['tags'])) { + Hook::import(is_array($mode['tags'])?$mode['tags']:include $mode['tags']); + } + + // 加载应用行为定义 + if(is_file(CONF_PATH.'tags.php')) + // 允许应用增加开发模式配置定义 + Hook::import(include CONF_PATH.'tags.php'); + + // 加载框架底层语言包 + L(include THINK_PATH.'Lang/'.strtolower(C('DEFAULT_LANG')).'.php'); + + if(!APP_DEBUG){ + $content .= "\nnamespace { Think\\Think::addMap(".var_export(self::$_map,true).");"; + $content .= "\nL(".var_export(L(),true).");\nC(".var_export(C(),true).');Think\Hook::import('.var_export(Hook::get(),true).');}'; + Storage::put($runtimefile,strip_whitespace('getMessage(); + $trace = $e->getTrace(); + if('E'==$trace[0]['function']) { + $error['file'] = $trace[0]['file']; + $error['line'] = $trace[0]['line']; + }else{ + $error['file'] = $e->getFile(); + $error['line'] = $e->getLine(); + } + $error['trace'] = $e->getTraceAsString(); + Log::record($error['message'],Log::ERR); + // 发送404信息 + header('HTTP/1.1 404 Not Found'); + header('Status:404 Not Found'); + self::halt($error); + } + + /** + * 自定义错误处理 + * @access public + * @param int $errno 错误类型 + * @param string $errstr 错误信息 + * @param string $errfile 错误文件 + * @param int $errline 错误行数 + * @return void + */ + static public function appError($errno, $errstr, $errfile, $errline) { + switch ($errno) { + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_USER_ERROR: + ob_end_clean(); + $errorStr = "$errstr ".$errfile." 第 $errline 行."; + if(C('LOG_RECORD')) Log::write("[$errno] ".$errorStr,Log::ERR); + self::halt($errorStr); + break; + default: + $errorStr = "[$errno] $errstr ".$errfile." 第 $errline 行."; + self::trace($errorStr,'','NOTIC'); + break; + } + } + + // 致命错误捕获 + static public function fatalError() { + Log::save(); + if ($e = error_get_last()) { + switch($e['type']){ + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_USER_ERROR: + ob_end_clean(); + self::halt($e); + break; + } + } + } + + /** + * 错误输出 + * @param mixed $error 错误 + * @return void + */ + static public function halt($error) { + $e = array(); + if (APP_DEBUG || IS_CLI) { + //调试模式下输出错误信息 + if (!is_array($error)) { + $trace = debug_backtrace(); + $e['message'] = $error; + $e['file'] = $trace[0]['file']; + $e['line'] = $trace[0]['line']; + ob_start(); + debug_print_backtrace(); + $e['trace'] = ob_get_clean(); + } else { + $e = $error; + } + if(IS_CLI){ + exit(iconv('UTF-8','gbk',$e['message']).PHP_EOL.'FILE: '.$e['file'].'('.$e['line'].')'.PHP_EOL.$e['trace']); + } + } else { + //否则定向到错误页面 + $error_page = C('ERROR_PAGE'); + if (!empty($error_page)) { + redirect($error_page); + } else { + $message = is_array($error) ? $error['message'] : $error; + $e['message'] = C('SHOW_ERROR_MSG')? $message : C('ERROR_MESSAGE'); + } + } + // 包含异常页面模板 + $exceptionFile = C('TMPL_EXCEPTION_FILE',null,THINK_PATH.'Tpl/think_exception.tpl'); + include $exceptionFile; + exit; + } + + /** + * 添加和获取页面Trace记录 + * @param string $value 变量 + * @param string $label 标签 + * @param string $level 日志级别(或者页面Trace的选项卡) + * @param boolean $record 是否记录日志 + * @return void|array + */ + static public function trace($value='[think]',$label='',$level='DEBUG',$record=false) { + static $_trace = array(); + if('[think]' === $value){ // 获取trace信息 + return $_trace; + }else{ + $info = ($label?$label.':':'').print_r($value,true); + $level = strtoupper($level); + + if((defined('IS_AJAX') && IS_AJAX) || !C('SHOW_PAGE_TRACE') || $record) { + Log::record($info,$level,$record); + }else{ + if(!isset($_trace[$level]) || count($_trace[$level])>C('TRACE_MAX_RECORD')) { + $_trace[$level] = array(); + } + $_trace[$level][] = $info; + } + } + } +} diff --git a/ThinkPHP/Library/Think/Upload.class.php b/ThinkPHP/Library/Think/Upload.class.php new file mode 100644 index 0000000..89a04ac --- /dev/null +++ b/ThinkPHP/Library/Think/Upload.class.php @@ -0,0 +1,429 @@ + +// +---------------------------------------------------------------------- +namespace Think; +class Upload { + /** + * 默认上传配置 + * @var array + */ + private $config = array( + 'mimes' => array(), //允许上传的文件MiMe类型 + 'maxSize' => 0, //上传的文件大小限制 (0-不做限制) + 'exts' => array(), //允许上传的文件后缀 + 'autoSub' => true, //自动子目录保存文件 + 'subName' => array('date', 'Y-m-d'), //子目录创建方式,[0]-函数名,[1]-参数,多个参数使用数组 + 'rootPath' => './Uploads/', //保存根路径 + 'savePath' => '', //保存路径 + 'saveName' => array('uniqid', ''), //上传文件命名规则,[0]-函数名,[1]-参数,多个参数使用数组 + 'saveExt' => '', //文件保存后缀,空则使用原后缀 + 'replace' => false, //存在同名是否覆盖 + 'hash' => true, //是否生成hash编码 + 'callback' => false, //检测文件是否存在回调,如果存在返回文件信息数组 + 'driver' => '', // 文件上传驱动 + 'driverConfig' => array(), // 上传驱动配置 + ); + + /** + * 上传错误信息 + * @var string + */ + private $error = ''; //上传错误信息 + + /** + * 上传驱动实例 + * @var Object + */ + private $uploader; + + /** + * 构造方法,用于构造上传实例 + * @param array $config 配置 + * @param string $driver 要使用的上传驱动 LOCAL-本地上传驱动,FTP-FTP上传驱动 + */ + public function __construct($config = array(), $driver = '', $driverConfig = null){ + /* 获取配置 */ + $this->config = array_merge($this->config, $config); + + /* 设置上传驱动 */ + $this->setDriver($driver, $driverConfig); + + /* 调整配置,把字符串配置参数转换为数组 */ + if(!empty($this->config['mimes'])){ + if(is_string($this->mimes)) { + $this->config['mimes'] = explode(',', $this->mimes); + } + $this->config['mimes'] = array_map('strtolower', $this->mimes); + } + if(!empty($this->config['exts'])){ + if (is_string($this->exts)){ + $this->config['exts'] = explode(',', $this->exts); + } + $this->config['exts'] = array_map('strtolower', $this->exts); + } + } + + /** + * 使用 $this->name 获取配置 + * @param string $name 配置名称 + * @return multitype 配置值 + */ + public function __get($name) { + return $this->config[$name]; + } + + public function __set($name,$value){ + if(isset($this->config[$name])) { + $this->config[$name] = $value; + if($name == 'driverConfig'){ + //改变驱动配置后重置上传驱动 + //注意:必须选改变驱动然后再改变驱动配置 + $this->setDriver(); + } + } + } + + public function __isset($name){ + return isset($this->config[$name]); + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError(){ + return $this->error; + } + + /** + * 上传单个文件 + * @param array $file 文件数组 + * @return array 上传成功后的文件信息 + */ + public function uploadOne($file){ + $info = $this->upload(array($file)); + return $info ? $info[0] : $info; + } + + /** + * 上传文件 + * @param 文件信息数组 $files ,通常是 $_FILES数组 + */ + public function upload($files='') { + if('' === $files){ + $files = $_FILES; + } + if(empty($files)){ + $this->error = '没有上传的文件!'; + return false; + } + + /* 检测上传根目录 */ + if(!$this->uploader->checkRootPath($this->rootPath)){ + $this->error = $this->uploader->getError(); + return false; + } + + /* 检查上传目录 */ + if(!$this->uploader->checkSavePath($this->savePath)){ + $this->error = $this->uploader->getError(); + return false; + } + + /* 逐个检测并上传文件 */ + $info = array(); + if(function_exists('finfo_open')){ + $finfo = finfo_open ( FILEINFO_MIME_TYPE ); + } + // 对上传文件数组信息处理 + $files = $this->dealFiles($files); + foreach ($files as $key => $file) { + $file['name'] = strip_tags($file['name']); + if(!isset($file['key'])) $file['key'] = $key; + /* 通过扩展获取文件类型,可解决FLASH上传$FILES数组返回文件类型错误的问题 */ + if(isset($finfo)){ + $file['type'] = finfo_file ( $finfo , $file['tmp_name'] ); + } + + /* 获取上传文件后缀,允许上传无后缀文件 */ + $file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION); + + /* 文件上传检测 */ + if (!$this->check($file)){ + continue; + } + + /* 获取文件hash */ + if($this->hash){ + $file['md5'] = md5_file($file['tmp_name']); + $file['sha1'] = sha1_file($file['tmp_name']); + } + + /* 调用回调函数检测文件是否存在 */ + $data = call_user_func($this->callback, $file); + if( $this->callback && $data ){ + if ( file_exists('.'.$data['path']) ) { + $info[$key] = $data; + continue; + }elseif($this->removeTrash){ + call_user_func($this->removeTrash,$data);//删除垃圾据 + } + } + + /* 生成保存文件名 */ + $savename = $this->getSaveName($file); + if(false == $savename){ + continue; + } else { + $file['savename'] = $savename; + } + + /* 检测并创建子目录 */ + $subpath = $this->getSubPath($file['name']); + if(false === $subpath){ + continue; + } else { + $file['savepath'] = $this->savePath . $subpath; + } + + /* 对图像文件进行严格检测 */ + $ext = strtolower($file['ext']); + if(in_array($ext, array('gif','jpg','jpeg','bmp','png','swf'))) { + $imginfo = getimagesize($file['tmp_name']); + if(empty($imginfo) || ($ext == 'gif' && empty($imginfo['bits']))){ + $this->error = '非法图像文件!'; + continue; + } + } + + /* 保存文件 并记录保存成功的文件 */ + if ($this->uploader->save($file,$this->replace)) { + unset($file['error'], $file['tmp_name']); + $info[$key] = $file; + } else { + $this->error = $this->uploader->getError(); + } + } + if(isset($finfo)){ + finfo_close($finfo); + } + return empty($info) ? false : $info; + } + + /** + * 转换上传文件数组变量为正确的方式 + * @access private + * @param array $files 上传的文件变量 + * @return array + */ + private function dealFiles($files) { + $fileArray = array(); + $n = 0; + foreach ($files as $key=>$file){ + if(is_array($file['name'])) { + $keys = array_keys($file); + $count = count($file['name']); + for ($i=0; $i<$count; $i++) { + $fileArray[$n]['key'] = $key; + foreach ($keys as $_key){ + $fileArray[$n][$_key] = $file[$_key][$i]; + } + $n++; + } + }else{ + $fileArray = $files; + break; + } + } + return $fileArray; + } + + /** + * 设置上传驱动 + * @param string $driver 驱动名称 + * @param array $config 驱动配置 + */ + private function setDriver($driver = null, $config = null){ + $driver = $driver ? : ($this->driver ? : C('FILE_UPLOAD_TYPE')); + $config = $config ? : ($this->driverConfig ? : C('UPLOAD_TYPE_CONFIG')); + $class = strpos($driver,'\\')? $driver : 'Think\\Upload\\Driver\\'.ucfirst(strtolower($driver)); + $this->uploader = new $class($config); + if(!$this->uploader){ + E("不存在上传驱动:{$name}"); + } + } + + /** + * 检查上传的文件 + * @param array $file 文件信息 + */ + private function check($file) { + /* 文件上传失败,捕获错误代码 */ + if ($file['error']) { + $this->error($file['error']); + return false; + } + + /* 无效上传 */ + if (empty($file['name'])){ + $this->error = '未知上传错误!'; + } + + /* 检查是否合法上传 */ + if (!is_uploaded_file($file['tmp_name'])) { + $this->error = '非法上传文件!'; + return false; + } + + /* 检查文件大小 */ + if (!$this->checkSize($file['size'])) { + $this->error = '上传文件大小不符!'; + return false; + } + + /* 检查文件Mime类型 */ + //TODO:FLASH上传的文件获取到的mime类型都为application/octet-stream + if (!$this->checkMime($file['type'])) { + $this->error = '上传文件MIME类型不允许!'; + return false; + } + + /* 检查文件后缀 */ + if (!$this->checkExt($file['ext'])) { + $this->error = '上传文件后缀不允许'; + return false; + } + + /* 通过检测 */ + return true; + } + + + /** + * 获取错误代码信息 + * @param string $errorNo 错误号 + */ + private function error($errorNo) { + switch ($errorNo) { + case 1: + $this->error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值!'; + break; + case 2: + $this->error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值!'; + break; + case 3: + $this->error = '文件只有部分被上传!'; + break; + case 4: + $this->error = '没有文件被上传!'; + break; + case 6: + $this->error = '找不到临时文件夹!'; + break; + case 7: + $this->error = '文件写入失败!'; + break; + default: + $this->error = '未知上传错误!'; + } + } + + /** + * 检查文件大小是否合法 + * @param integer $size 数据 + */ + private function checkSize($size) { + return !($size > $this->maxSize) || (0 == $this->maxSize); + } + + /** + * 检查上传的文件MIME类型是否合法 + * @param string $mime 数据 + */ + private function checkMime($mime) { + return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->mimes); + } + + /** + * 检查上传的文件后缀是否合法 + * @param string $ext 后缀 + */ + private function checkExt($ext) { + return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts); + } + + /** + * 根据上传文件命名规则取得保存文件名 + * @param string $file 文件信息 + */ + private function getSaveName($file) { + $rule = $this->saveName; + if (empty($rule)) { //保持文件名不变 + /* 解决pathinfo中文文件名BUG */ + $filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1); + $savename = $filename; + } else { + $savename = $this->getName($rule, $file['name']); + if(empty($savename)){ + $this->error = '文件命名规则错误!'; + return false; + } + } + + /* 文件保存后缀,支持强制更改文件后缀 */ + $ext = empty($this->config['saveExt']) ? $file['ext'] : $this->saveExt; + + return $savename . '.' . $ext; + } + + /** + * 获取子目录的名称 + * @param array $file 上传的文件信息 + */ + private function getSubPath($filename) { + $subpath = ''; + $rule = $this->subName; + if ($this->autoSub && !empty($rule)) { + $subpath = $this->getName($rule, $filename) . '/'; + + if(!empty($subpath) && !$this->uploader->mkdir($this->savePath . $subpath)){ + $this->error = $this->uploader->getError(); + return false; + } + } + return $subpath; + } + + /** + * 根据指定的规则获取文件或目录名称 + * @param array $rule 规则 + * @param string $filename 原文件名 + * @return string 文件或目录名称 + */ + private function getName($rule, $filename){ + $name = ''; + if(is_array($rule)){ //数组规则 + $func = $rule[0]; + $param = (array)$rule[1]; + foreach ($param as &$value) { + $value = str_replace('__FILE__', $filename, $value); + } + $name = call_user_func_array($func, $param); + } elseif (is_string($rule)){ //字符串规则 + if(function_exists($rule)){ + $name = call_user_func($rule); + } else { + $name = $rule; + } + } + return $name; + } + +} diff --git a/ThinkPHP/Library/Think/Upload/Driver/Bcs.class.php b/ThinkPHP/Library/Think/Upload/Driver/Bcs.class.php new file mode 100644 index 0000000..437fefd --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Bcs.class.php @@ -0,0 +1,238 @@ + +// +---------------------------------------------------------------------- +namespace Think\Upload\Driver; +use Think\Upload\Driver\Bcs\BaiduBcs; +class Bcs { + /** + * 上传文件根目录 + * @var string + */ + private $rootPath; + const DEFAULT_URL = 'bcs.duapp.com'; + + /** + * 上传错误信息 + * @var string + */ + private $error = ''; + + public $config = array( + 'AccessKey'=> '', + 'SecretKey'=> '', //百度云服务器 + 'bucket' => '', //空间名称 + 'rename' => false, + 'timeout' => 3600, //超时时间 + ); + + public $bcs = null; + + /** + * 构造函数,用于设置上传根路径 + * @param array $config FTP配置 + */ + public function __construct($config){ + /* 默认FTP配置 */ + $this->config = array_merge($this->config, $config); + + $bcsClass = dirname(__FILE__). "/Bcs/bcs.class.php"; + if(is_file($bcsClass)) + require_once($bcsClass); + $this->bcs = new BaiduBCS ( $this->config['AccessKey'], $this->config['SecretKey'], self:: DEFAULT_URL ); + } + + /** + * 检测上传根目录(百度云上传时支持自动创建目录,直接返回) + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath){ + /* 设置根目录 */ + $this->rootPath = str_replace('./', '/', $rootpath); + return true; + } + + /** + * 检测上传目录(百度云上传时支持自动创建目录,直接返回) + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath){ + return true; + } + + /** + * 创建文件夹 (百度云上传时支持自动创建目录,直接返回) + * @param string $savepath 目录名称 + * @return boolean true-创建成功,false-创建失败 + */ + public function mkdir($savepath){ + return true; + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save(&$file,$replace=true) { + $opt = array (); + $opt ['acl'] = BaiduBCS::BCS_SDK_ACL_TYPE_PUBLIC_WRITE; + $opt ['curlopts'] = array ( + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_TIMEOUT => 1800 + ); + $object = "/{$file['savepath']}{$file['savename']}"; + $response = $this->bcs->create_object ( $this->config['bucket'], $object, $file['tmp_name'], $opt ); + $url = $this->download($object); + $file['url'] = $url; + return $response->isOK() ? true : false; + } + + public function download($file){ + $file = str_replace('./', '/', $file); + $opt = array(); + $opt['time'] = mktime('2049-12-31'); //这是最长有效时间!-- + $response = $this->bcs->generate_get_object_url ( $this->config['bucket'], $file, $opt ); + return $response; + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError(){ + return $this->error; + } + + /** + * 请求百度云服务器 + * @param string $path 请求的PATH + * @param string $method 请求方法 + * @param array $headers 请求header + * @param resource $body 上传文件资源 + * @return boolean + */ + private function request($path, $method, $headers = null, $body = null){ + $ch = curl_init($path); + + $_headers = array('Expect:'); + if (!is_null($headers) && is_array($headers)){ + foreach($headers as $k => $v) { + array_push($_headers, "{$k}: {$v}"); + } + } + + $length = 0; + $date = gmdate('D, d M Y H:i:s \G\M\T'); + + if (!is_null($body)) { + if(is_resource($body)){ + fseek($body, 0, SEEK_END); + $length = ftell($body); + fseek($body, 0); + + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_INFILE, $body); + curl_setopt($ch, CURLOPT_INFILESIZE, $length); + } else { + $length = @strlen($body); + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + } else { + array_push($_headers, "Content-Length: {$length}"); + } + + // array_push($_headers, 'Authorization: ' . $this->sign($method, $uri, $date, $length)); + array_push($_headers, "Date: {$date}"); + + curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->config['timeout']); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + + if ($method == 'PUT' || $method == 'POST') { + curl_setopt($ch, CURLOPT_POST, 1); + } else { + curl_setopt($ch, CURLOPT_POST, 0); + } + + if ($method == 'HEAD') { + curl_setopt($ch, CURLOPT_NOBODY, true); + } + + $response = curl_exec($ch); + $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + list($header, $body) = explode("\r\n\r\n", $response, 2); + + if ($status == 200) { + if ($method == 'GET') { + return $body; + } else { + $data = $this->response($header); + return count($data) > 0 ? $data : true; + } + } else { + $this->error($header); + return false; + } + } + + /** + * 获取响应数据 + * @param string $text 响应头字符串 + * @return array 响应数据列表 + */ + private function response($text){ + $items = json_decode($text, true); + return $items; + } + + /** + * 生成请求签名 + * @return string 请求签名 + */ + private function sign($method, $Bucket, $object='/', $size=''){ + if(!$size) + $size = $this->config['size']; + $param = array( + 'ak'=>$this->config['AccessKey'], + 'sk'=>$this->config['SecretKey'], + 'size'=>$size, + 'bucket'=>$Bucket, + 'host'=>self :: DEFAULT_URL, + 'date'=>time()+$this->config['timeout'], + 'ip'=>'', + 'object'=>$object + ); + $response = $this->request($this->apiurl.'?'.http_build_query($param), 'POST'); + if($response) + $response = json_decode($response, true); + return $response['content'][$method]; + } + + + /** + * 获取请求错误信息 + * @param string $header 请求返回头信息 + */ + private function error($header) { + list($status, $stash) = explode("\r\n", $header, 2); + list($v, $code, $message) = explode(" ", $status, 3); + $message = is_null($message) ? 'File Not Found' : "[{$status}]:{$message}"; + $this->error = $message; + } + +} diff --git a/ThinkPHP/Library/Think/Upload/Driver/Bcs/bcs.class.php b/ThinkPHP/Library/Think/Upload/Driver/Bcs/bcs.class.php new file mode 100644 index 0000000..a990aec --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Bcs/bcs.class.php @@ -0,0 +1,1318 @@ +ak = $ak; + $this->sk = $sk; + } elseif (defined ( 'BCS_AK' ) && defined ( 'BCS_SK' ) && strlen ( BCS_AK ) > 0 && strlen ( BCS_SK ) > 0) { + $this->ak = BCS_AK; + $this->sk = BCS_SK; + } elseif (false !== getenv ( 'HTTP_BAE_ENV_AK' ) && false !== getenv ( 'HTTP_BAE_ENV_SK' )) { + $this->ak = getenv ( 'HTTP_BAE_ENV_AK' ); + $this->sk = getenv ( 'HTTP_BAE_ENV_SK' ); + } else { + throw new BCS_Exception ( 'Construct can not get ak &sk pair, please check!' ); + } + //valid $hostname + if (NULL !== $hostname) { + $this->hostname = $hostname; + } elseif (false !== getenv ( 'HTTP_BAE_ENV_ADDR_BCS' )) { + $this->hostname = getenv ( 'HTTP_BAE_ENV_ADDR_BCS' ); + } else { + $this->hostname = self::DEFAULT_URL; + } + } + + /** + * 将消息发往Baidu BCS. + * @param array $opt + * @return BCS_ResponseCore + */ + private function authenticate($opt) { + //set common param into opt + $opt [self::AK] = $this->ak; + $opt [self::SK] = $this->sk; + + // Validate the S3 bucket name, only list_bucket didnot need validate_bucket + if (! ('/' == $opt [self::OBJECT] && '' == $opt [self::BUCKET] && 'GET' == $opt [self::METHOD] && ! isset ( $opt [self::QUERY_STRING] [self::ACL] )) && ! self::validate_bucket ( $opt [self::BUCKET] )) { + throw new BCS_Exception ( $opt [self::BUCKET] . 'is not valid, please check!' ); + } + //Validate object + if (isset ( $opt [self::OBJECT] ) && ! self::validate_object ( $opt [self::OBJECT] )) { + throw new BCS_Exception ( "Invalid object param[" . $opt [self::OBJECT] . "], please check.", - 1 ); + } + //construct url + $url = $this->format_url ( $opt ); + if ($url === false) { + throw new BCS_Exception ( 'Can not format url, please check your param!', - 1 ); + } + $opt ['url'] = $url; + $this->log ( "[method:" . $opt [self::METHOD] . "][url:$url]", $opt ); + //build request + $request = new BCS_RequestCore ( $opt ['url'] ); + $headers = array ( + 'Content-Type' => 'application/x-www-form-urlencoded' ); + + $request->set_method ( $opt [self::METHOD] ); + //Write get_object content to fileWriteTo + if (isset ( $opt ['fileWriteTo'] )) { + $request->set_write_file ( $opt ['fileWriteTo'] ); + } + // Merge the HTTP headers + if (isset ( $opt [self::HEADERS] )) { + $headers = array_merge ( $headers, $opt [self::HEADERS] ); + } + // Set content to Http-Body + if (isset ( $opt ['content'] )) { + $request->set_body ( $opt ['content'] ); + } + // Upload file + if (isset ( $opt ['fileUpload'] )) { + if (! file_exists ( $opt ['fileUpload'] )) { + throw new BCS_Exception ( 'File[' . $opt ['fileUpload'] . '] not found!', - 1 ); + } + $request->set_read_file ( $opt ['fileUpload'] ); + // Determine the length to read from the file + $length = $request->read_stream_size; // The file size by default + $file_size = $length; + if (isset ( $opt ["length"] )) { + if ($opt ["length"] > $file_size) { + throw new BCS_Exception ( "Input opt[length] invalid! It can not bigger than file-size", - 1 ); + } + $length = $opt ['length']; + } + if (isset ( $opt ['seekTo'] ) && ! isset ( $opt ["length"] )) { + // Read from seekTo until EOF by default, when set seekTo but not set $opt["length"] + $length -= ( integer ) $opt ['seekTo']; + } + $request->set_read_stream_size ( $length ); + // Attempt to guess the correct mime-type + if ($headers ['Content-Type'] === 'application/x-www-form-urlencoded') { + $extension = explode ( '.', $opt ['fileUpload'] ); + $extension = array_pop ( $extension ); + $mime_type = BCS_MimeTypes::get_mimetype ( $extension ); + $headers ['Content-Type'] = $mime_type; + } + $headers ['Content-MD5'] = ''; + } + // Handle streaming file offsets + if (isset ( $opt ['seekTo'] )) { + // Pass the seek position to BCS_RequestCore + $request->set_seek_position ( ( integer ) $opt ['seekTo'] ); + } + // Add headers to request and compute the string to sign + foreach ( $headers as $header_key => $header_value ) { + // Strip linebreaks from header values as they're illegal and can allow for security issues + $header_value = str_replace ( array ( + "\r", + "\n" ), '', $header_value ); + // Add the header if it has a value + if ($header_value !== '') { + $request->add_header ( $header_key, $header_value ); + } + } + // Set the curl options. + if (isset ( $opt ['curlopts'] ) && count ( $opt ['curlopts'] )) { + $request->set_curlopts ( $opt ['curlopts'] ); + } + $request->send_request (); + require_once(dirname(__FILE__). "/requestcore.class.php"); + return new BCS_ResponseCore ( $request->get_response_header (), $request->get_response_body (), $request->get_response_code () ); + } + + /** + * 获取当前密钥对拥有者的bucket列表 + * @param array $opt (Optional) + * BaiduBCS::IMPORT_BCS_LOG_METHOD - String - Optional: 支持用户传入日志处理函数,函数定义如 function f($log) + * @throws BCS_Exception + * @return BCS_ResponseCore + */ + public function list_bucket($opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = ''; + $opt [self::METHOD] = 'GET'; + $opt [self::OBJECT] = '/'; + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "List bucket success!" : "List bucket failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 创建 bucket + * @param string $bucket (Required) bucket名称 + * @param string $acl (Optional) bucket权限设置,若为null,使用server分配的默认权限 + * @param array $opt (Optional) + * @throws BCS_Exception + * @return BCS_ResponseCore + */ + public function create_bucket($bucket, $acl = NULL, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'PUT'; + $opt [self::OBJECT] = '/'; + if (NULL !== $acl) { + if (! in_array ( $acl, self::$ACL_TYPES )) { + throw new BCS_Exception ( "Invalid acl_type[" . $acl . "], please check!", - 1 ); + } + self::set_header_into_opt ( "x-bs-acl", $acl, $opt ); + } + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Create bucket success!" : "Create bucket failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 删除bucket + * @param string $bucket (Required) + * @param array $opt (Optional) + * @return boolean|BCS_ResponseCore + */ + public function delete_bucket($bucket, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'DELETE'; + $opt [self::OBJECT] = '/'; + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Delete bucket success!" : "Delete bucket failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 设置bucket的acl,有三种模式, + * (1).设置详细json格式的acl; + * a. $acl 为json的array + * b. $acl 为json的string + * (2).通过acl_type字段进行设置 + * a. $acl 为BaiduBCS::$ACL_TYPES中的字段 + * @param string $bucket (Required) + * @param string $acl (Required) + * @param array $opt (Optional) + * @return boolean|BCS_ResponseCore + */ + public function set_bucket_acl($bucket, $acl, $opt = array()) { + $this->assertParameterArray ( $opt ); + $result = $this->analyze_user_acl ( $acl ); + $opt = array_merge ( $opt, $result ); + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'PUT'; + $opt [self::OBJECT] = '/'; + $opt [self::QUERY_STRING] = array ( + self::ACL => 1 ); + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Set bucket acl success!" : "Set bucket acl failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 获取bucket的acl + * @param string $bucket (Required) + * @param array $opt (Optional) + * @return BCS_ResponseCore + */ + public function get_bucket_acl($bucket, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'GET'; + $opt [self::OBJECT] = '/'; + $opt [self::QUERY_STRING] = array ( + self::ACL => 1 ); + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Get bucket acl success!" : "Get bucket acl failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 获取bucket中object列表 + * @param string $bucket (Required) + * @param array $opt (Optional) + * start : 主要用于翻页功能,用法同mysql中start的用法 + * limit : 主要用于翻页功能,用法同mysql中limit的用法 + * prefix: 只返回以prefix为前缀的object,此处prefix必须以'/'开头 + * @throws BCS_Exception + * @return BCS_ResponseCore + */ + public function list_object($bucket, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + if (empty ( $opt [self::BUCKET] )) { + throw new BCS_Exception ( "Bucket should not be empty, please check", - 1 ); + } + $opt [self::METHOD] = 'GET'; + $opt [self::OBJECT] = '/'; + $opt [self::QUERY_STRING] = array (); + if (isset ( $opt ['start'] ) && is_int ( $opt ['start'] )) { + $opt [self::QUERY_STRING] ['start'] = $opt ['start']; + } + if (isset ( $opt ['limit'] ) && is_int ( $opt ['limit'] )) { + $opt [self::QUERY_STRING] ['limit'] = $opt ['limit']; + } + if (isset ( $opt ['prefix'] )) { + $opt [self::QUERY_STRING] ['prefix'] = rawurlencode ( $opt ['prefix'] ); + } + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "List object success!" : "Lit object failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 以目录形式获取bucket中object列表 + * @param string $bucket (Required) + * @param $dir (Required) + * 目录名,格式为必须以'/'开头和结尾,默认为'/' + * @param string $list_model (Required) + * 目录展现形式,值可以为0,1,2,默认为2,以下对各个值的功能进行介绍: + * 0->只返回object列表,不返回子目录列表 + * 1->只返回子目录列表,不返回object列表 + * 2->同时返回子目录列表和object列表 + * @param array $opt (Optional) + * start : 主要用于翻页功能,用法同mysql中start的用法 + * limit : 主要用于翻页功能,用法同mysql中limit的用法 + * @throws BCS_Exception + * @return BCS_ResponseCore + */ + public function list_object_by_dir($bucket, $dir = '/', $list_model = 2, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + if (empty ( $opt [self::BUCKET] )) { + throw new BCS_Exception ( "Bucket should not be empty, please check", - 1 ); + } + $opt [self::METHOD] = 'GET'; + $opt [self::OBJECT] = '/'; + $opt [self::QUERY_STRING] = array (); + if (isset ( $opt ['start'] ) && is_int ( $opt ['start'] )) { + $opt [self::QUERY_STRING] ['start'] = $opt ['start']; + } + if (isset ( $opt ['limit'] ) && is_int ( $opt ['limit'] )) { + $opt [self::QUERY_STRING] ['limit'] = $opt ['limit']; + } + + $opt [self::QUERY_STRING] ['prefix'] = rawurlencode ( $dir ); + $opt [self::QUERY_STRING] ['dir'] = $list_model; + + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "List object success!" : "Lit object failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 上传文件 + * @param string $bucket (Required) + * @param string $object (Required) + * @param string $file (Required); 需要上传的文件的文件路径 + * @param array $opt (Optional) + * filename - Optional; 指定文件名 + * acl - Optional ; 上传文件的acl,只能使用acl_type + * seekTo - Optional; 上传文件的偏移位置 + * length - Optional; 待上传长度 + * @return BCS_ResponseCore + */ + public function create_object($bucket, $object, $file, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt [self::OBJECT] = $object; + $opt ['fileUpload'] = $file; + $opt [self::METHOD] = 'PUT'; + if (isset ( $opt ['acl'] )) { + if (in_array ( $opt ['acl'], self::$ACL_TYPES )) { + self::set_header_into_opt ( "x-bs-acl", $opt ['acl'], $opt ); + } else { + throw new BCS_Exception ( "Invalid acl string, it should be acl_type", - 1 ); + } + unset ( $opt ['acl'] ); + } + if (isset ( $opt ['filename'] )) { + self::set_header_into_opt ( "Content-Disposition", 'attachment; filename=' . $opt ['filename'], $opt ); + } + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Create object[$object] file[$file] success!" : "Create object[$object] file[$file] failed! Response: [" . $response->body . "] Logid[" . $response->header ["x-bs-request-id"] . "]", $opt ); + return $response; + } + + /** + * 上传文件 + * @param string $bucket (Required) + * @param string $object (Required) + * @param string $file (Required); 需要上传的文件的文件路径 + * @param array $opt (Optional) + * filename - Optional; 指定文件名 + * acl - Optional ; 上传文件的acl,只能使用acl_type + * @return BCS_ResponseCore + */ + public function create_object_by_content($bucket, $object, $content, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt [self::OBJECT] = $object; + $opt [self::METHOD] = 'PUT'; + if ($content !== NULL && is_string ( $content )) { + $opt ['content'] = $content; + } else { + throw new BCS_Exception ( "Invalid object content, please check.", - 1 ); + } + if (isset ( $opt ['acl'] )) { + if (in_array ( $opt ['acl'], self::$ACL_TYPES )) { + self::set_header_into_opt ( "x-bs-acl", $opt ['acl'], $opt ); + } else { + throw new BCS_Exception ( "Invalid acl string, it should be acl_type", - 1 ); + } + unset ( $opt ['acl'] ); + } + if (isset ( $opt ['filename'] )) { + self::set_header_into_opt ( "Content-Disposition", 'attachment; filename=' . $opt ['filename'], $opt ); + } + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Create object[$object] success!" : "Create object[$object] failed! Response: [" . $response->body . "] Logid[" . $response->header ["x-bs-request-id"] . "]", $opt ); + return $response; + } + + /** + * 通过superfile的方式上传文件 + * @param string $bucket (Required) + * @param string $object (Required) + * @param string $file (Required); 需要上传的文件的文件路径 + * @param array $opt (Optional) + * filename - Optional; 指定文件名 + * sub_object_size - Optional; 指定子文件的划分大小,单位B,建议以256KB为单位进行子object划分,默认为1MB进行划分 + * @return BCS_ResponseCore + */ + public function create_object_superfile($bucket, $object, $file, $opt = array()) { + if (isset ( $opt ['length'] ) || isset ( $opt ['seekTo'] )) { + throw new BCS_Exception ( "Temporary unsupport opt of length and seekTo of superfile.", - 1 ); + } + //$opt array + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt ['fileUpload'] = $file; + $opt [self::METHOD] = 'PUT'; + if (isset ( $opt ['acl'] )) { + if (in_array ( $opt ['acl'], self::$ACL_TYPES )) { + self::set_header_into_opt ( "x-bs-acl", $opt ['acl'], $opt ); + } else { + throw new BCS_Exception ( "Invalid acl string, it should be acl_type", - 1 ); + } + unset ( $opt ['acl'] ); + } + //切片上传 + if (! file_exists ( $opt ['fileUpload'] )) { + throw new BCS_Exception ( 'File not found!', - 1 ); + } + $fileSize = filesize ( $opt ['fileUpload'] ); + $sub_object_size = 1024 * 1024; //default 1MB + if (defined ( "BCS_SUPERFILE_SLICE_SIZE" )) { + $sub_object_size = BCS_SUPERFILE_SLICE_SIZE; + } + if (isset ( $opt ["sub_object_size"] )) { + if (is_int ( $opt ["sub_object_size"] ) && $opt ["sub_object_size"] > 0) { + $sub_object_size = $opt ["sub_object_size"]; + } else { + throw new BCS_Exception ( "Param [sub_object_size] invalid ,please check!", - 1 ); + } + } + $sliceNum = intval ( ceil ( $fileSize / $sub_object_size ) ); + $this->log ( "File[" . $opt ['fileUpload'] . "], size=[$fileSize], sub_object_size=[$sub_object_size], sub_object_num=[$sliceNum]", $opt ); + $object_list = array ( + 'object_list' => array () ); + for($i = 0; $i < $sliceNum; $i ++) { + //send slice + $opt ['seekTo'] = $i * $sub_object_size; + + if (($i + 1) === $sliceNum) { + //last sub object + $opt ['length'] = (0 === $fileSize % $sub_object_size) ? $sub_object_size : $fileSize % $sub_object_size; + } else { + $opt ['length'] = $sub_object_size; + } + $opt [self::OBJECT] = $object . BCS_SUPERFILE_POSTFIX . $i; + $object_list ['object_list'] ['part_' . $i] = array (); + $object_list ['object_list'] ['part_' . $i] ['url'] = 'bs://' . $bucket . $opt [self::OBJECT]; + $this->log ( "Begin to upload Sub-object[" . $opt [self::OBJECT] . "][$i/$sliceNum][seekto:" . $opt ['seekTo'] . "][Length:" . $opt ['length'] . "]", $opt ); + $response = $this->create_object ( $bucket, $opt [self::OBJECT], $file, $opt ); + if ($response->isOK ()) { + $this->log ( "Sub-object upload[" . $opt [self::OBJECT] . "][$i/$sliceNum][seekto:" . $opt ['seekTo'] . "][Length:" . $opt ['length'] . "]success! ", $opt ); + $object_list ['object_list'] ['part_' . $i] ['etag'] = $response->header ['Content-MD5']; + continue; + } else { + $this->log ( "Sub-object upload[" . $opt [self::OBJECT] . "][$i/$sliceNum] failed! ", $opt ); + return $response; + } + } + //将子文件分片的列表构造成 superfile + unset ( $opt ['fileUpload'] ); + unset ( $opt ['length'] ); + unset ( $opt ['seekTo'] ); + $opt ['content'] = self::array_to_json ( $object_list ); + $opt [self::QUERY_STRING] = array ( + "superfile" => 1 ); + $opt [self::OBJECT] = $object; + if (isset ( $opt ['filename'] )) { + self::set_header_into_opt ( "Content-Disposition", 'attachment; filename=' . $opt ['filename'], $opt ); + } + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Create object-superfile success!" : "Create object-superfile failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 将目录中的所有文件进行上传,每个文件为单独object,object命名方式下详: + * 如有 /home/worker/a/b/c.txt 需上传目录为$dir=/home/worker/a + * object命令方式为 + * 1. object默认命名方式为 “子目录名 +文件名”,如上述文件c.txt,默认为 '/b/c.txt' + * 2. 增强命名模式,在$opt中有可选参数进行配置 + * 举例说明 :prefix . has_sub_directory?"/b":"" . '/c.txt' + * @param string $bucket (Required) + * @param string $dir (Required) + * @param array $opt(Optional) + * string prefix 文件object前缀 + * boolean has_sub_directory(default=true) object命名中是否携带文件的子目录结构,若置为false,请确认待上传的目录和所有子目录中没有重名文件,否则会产生object覆盖问题 + * BaiduBCS::IMPORT_BCS_PRE_FILTER 用户可自定义上传文件前的操作函数 + * 1. 函数参数列表顺序需为 ($bucket,$object,$file,&$opt),注意$opt为upload_directory函数传入的$opt的拷贝,只对当前object生效 + * 2. 函数返回值必须为boolean,当true该文件进行上传,若false跳过上传 + * 3. 如果函数返回false,将不会进行post_filter的调用 + * BaiduBCS::IMPORT_BCS_POST_FILTER 用户可自定义上传文件后的操作函数 + * 1. 函数参数列表顺序需为 ($bucket,$object,$file,&$opt,$response),注意$opt为upload_directory函数传入的$opt的拷贝,只对当前object生效 + * 2. 函数返回值无要求 + * string seek_object 用户断点续传,需要为object名称,如果该object在目录中不存在,抛出异常,若存在则将该object和此后的object进行上传 + * string seek_object_id 作用同seek_object,只需要传入上传过程中日志中展示的[a/b]中object序号即可,注意object序号是以1开始计算的 + * @return array 数组形式的上传结果 + * 'success' => int 上传成功的文件数目 + * 'skipped' => int 被跳过的文件 + * 'failed' => array() 上传失败的文件 + * + */ + public function upload_directory($bucket, $dir, $opt = array()) { + $this->assertParameterArray ( $opt ); + if (! is_dir ( $dir )) { + throw new BCS_Exception ( "$dir is not a dir!", - 1 ); + } + $result = array ( + "success" => 0, + "failed" => array (), + "skipped" => 0 ); + $prefix = ""; + if (isset ( $opt ['prefix'] )) { + $prefix = $opt ['prefix']; + } + $has_sub_directory = true; + if (isset ( $opt ['has_sub_directory'] ) && is_bool ( $opt ['has_sub_directory'] )) { + $has_sub_directory = $opt ['has_sub_directory']; + } + //获取文件树和构造object名 + $file_tree = self::get_filetree ( $dir ); + $objects = array (); + foreach ( $file_tree as $file ) { + $object = $has_sub_directory == true ? substr ( $file, strlen ( $dir ) ) : "/" . basename ( $file ); + $objects [$prefix . $object] = $file; + } + $objectCount = count ( $objects ); + $before_upload_log = "Upload directory: bucket[$bucket] upload_dir[$dir] file_sum[$objectCount]"; + if (isset ( $opt ["seek_object_id"] )) { + $before_upload_log .= " seek_object_id[" . $opt ["seek_object_id"] . "/$objectCount]"; + } + if (isset ( $opt ["seek_object"] )) { + $before_upload_log .= " seek_object[" . $opt ["seek_object"] . "]"; + } + $this->log ( $before_upload_log, $opt ); + //查看是否需要查询断点,进行断点续传 + if (isset ( $opt ["seek_object_id"] ) && isset ( $opt ["seek_object"] )) { + throw new BCS_Exception ( "Can not set see_object_id and seek_object at the same time!", - 1 ); + } + + $num = 1; + if (isset ( $opt ["seek_object"] )) { + if (isset ( $objects [$opt ["seek_object"]] )) { + foreach ( $objects as $object => $file ) { + if ($object != $opt ["seek_object"]) { + //当非断点文件,该object已完成上传 + $this->log ( "Seeking[" . $opt ["seek_object"] . "]. Skip id[$num/$objectCount]object[$object]file[$file].", $opt ); + //$result ['skipped'] [] = "[$num/$objectCount] " . $file; + $result ['skipped'] ++; + unset ( $objects [$object] ); + } else { + //当找到断点文件,停止循环,从断点文件重新上传 + //当非断点文件,该object已完成上传 + $this->log ( "Found seek id[$num/$objectCount]object[$object]file[$file], begin from here.", $opt ); + break; + } + $num ++; + } + } else { + throw new BCS_Exception ( "Can not find you seek object, please check!", - 1 ); + } + } + if (isset ( $opt ["seek_object_id"] )) { + if (is_int ( $opt ["seek_object_id"] ) && $opt ["seek_object_id"] <= $objectCount) { + foreach ( $objects as $object => $file ) { + if ($num < $opt ["seek_object_id"]) { + $this->log ( "Seeking object of [" . $opt ["seek_object_id"] . "/$objectCount]. Skip id[$num/$objectCount]object[$object]file[$file].", $opt ); + //$result ['skipped'] [] = "[$num/$objectCount] " . $file; + $result ['skipped'] ++; + unset ( $objects [$object] ); + } else { + break; + } + $num ++; + } + } else { + throw new BCS_Exception ( "Param seek_object_id not valid, please check!", - 1 ); + } + } + //上传objects + $objectCount = count ( $objects ); + foreach ( $objects as $object => $file ) { + $tmp_opt = array_merge ( $opt ); + if (isset ( $opt [self::IMPORT_BCS_PRE_FILTER] ) && function_exists ( $opt [self::IMPORT_BCS_PRE_FILTER] )) { + $bolRes = $opt [self::IMPORT_BCS_PRE_FILTER] ( $bucket, $object, $file, $tmp_opt ); + if ($bolRes !== true) { + $this->log ( "User pre_filter_function return un-true. Skip id[$num/$objectCount]object[$object]file[$file].", $opt ); + //$result ['skipped'] [] = "id[$num/$objectCount]object[$object]file[$file]"; + $result ['skipped'] ++; + $num ++; + continue; + } + } + try { + $response = $this->create_object ( $bucket, $object, $file, $tmp_opt ); + } catch ( Exception $e ) { + $this->log ( $e->getMessage (), $opt ); + $this->log ( "Upload Failed id[$num/$objectCount]object[$object]file[$file].", $opt ); + $num ++; + continue; + } + if ($response->isOK ()) { + $result ["success"] ++; + $this->log ( "Upload Success id[$num/$objectCount]object[$object]file[$file].", $opt ); + } else { + $result ["failed"] [] = "id[$num/$objectCount]object[$object]file[$file]"; + $this->log ( "Upload Failed id[$num/$objectCount]object[$object]file[$file].", $opt ); + } + if (isset ( $opt [self::IMPORT_BCS_POST_FILTER] ) && function_exists ( $opt [self::IMPORT_BCS_POST_FILTER] )) { + $opt [self::IMPORT_BCS_POST_FILTER] ( $bucket, $object, $file, $tmp_opt, $response ); + } + $num ++; + } + //打印日志并返回结果数组 + $result_str = "\r\n\r\nUpload $dir to $bucket finished!\r\n"; + $result_str .= "**********************************************************\r\n"; + $result_str .= "**********************Result Summary**********************\r\n"; + $result_str .= "**********************************************************\r\n"; + $result_str .= "Upload directory : [$dir]\r\n"; + $result_str .= "File num : [$objectCount]\r\n"; + $result_str .= "Success: \r\n\tNum: " . $result ["success"] . "\r\n"; + $result_str .= "Skipped:\r\n\tNum:" . $result ["skipped"] . "\r\n"; + // foreach ( $result ["skipped"] as $skip ) { + // $result_str .= "\t$skip\r\n"; + // } + $result_str .= "Failed:\r\n\tNum:" . count ( $result ["failed"] ) . "\r\n"; + foreach ( $result ["failed"] as $fail ) { + $result_str .= "\t$fail\r\n"; + } + if (isset ( $opt [self::IMPORT_BCS_LOG_METHOD] )) { + $this->log ( $result_str, $opt ); + } else { + echo $result_str; + } + return $result; + } + + /** + * 通过此方法以拷贝的方式创建object,object来源为$source + * @param array $source (Required) object 来源 + * bucket(Required) + * object(Required) + * @param array $dest (Required) 待拷贝的目标object + * bucket(Required) + * object(Required) + * @param array $opt (Optional) + * source_tag 指定拷贝对象的版本号 + * @throws BCS_Exception + * @return BCS_ResponseCore + */ + public function copy_object($source, $dest, $opt = array()) { + $this->assertParameterArray ( $opt ); + //valid source and dest + if (empty ( $source ) || ! is_array ( $source ) || ! isset ( $source [self::BUCKET] ) || ! isset ( $source [self::OBJECT] )) { + throw new BCS_Exception ( '$source invalid, please check!', - 1 ); + } + if (empty ( $dest ) || ! is_array ( $dest ) || ! isset ( $dest [self::BUCKET] ) || ! isset ( $dest [self::OBJECT] ) || ! self::validate_bucket ( $dest [self::BUCKET] ) || ! self::validate_object ( $dest [self::OBJECT] )) { + throw new BCS_Exception ( '$dest invalid, please check!', - 1 ); + } + $opt [self::BUCKET] = $dest [self::BUCKET]; + $opt [self::OBJECT] = $dest [self::OBJECT]; + $opt [self::METHOD] = 'PUT'; + self::set_header_into_opt ( 'x-bs-copy-source', 'bs://' . $source [self::BUCKET] . $source [self::OBJECT], $opt ); + if (isset ( $opt ['source_tag'] )) { + self::set_header_into_opt ( 'x-bs-copy-source-tag', $opt ['source_tag'], $opt ); + } + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Copy object success!" : "Copy object failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 设置object的meta信息 + * @param string $bucket (Required) + * @param string $object (Required) + * @param array $opt (Optional) + * 目前支持的meta信息如下: + * Content-Type + * Cache-Control + * Content-Disposition + * Content-Encoding + * Content-MD5 + * Expires + * @return BCS_ResponseCore + */ + public function set_object_meta($bucket, $object, $meta, $opt = array()) { + $this->assertParameterArray ( $opt ); + $this->assertParameterArray ( $meta ); + $opt [self::BUCKET] = $bucket; + $opt [self::OBJECT] = $object; + $opt [self::METHOD] = 'PUT'; + //利用copy_object接口来设置meta信息 + $source = "bs://$bucket$object"; + if (empty ( $meta )) { + throw new BCS_Exception ( '$meta can not be empty! And $meta must be array.', - 1 ); + } + foreach ( $meta as $header => $value ) { + self::set_header_into_opt ( $header, $value, $opt ); + } + $source = array ( + self::BUCKET => $bucket, + self::OBJECT => $object ); + $response = $this->copy_object ( $source, $source, $opt ); + $this->log ( $response->isOK () ? "Set object meta success!" : "Set object meta failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 获取object的acl + * @param string $bucket (Required) + * @param string $object (Required) + * @param array $opt (Optional) + * @throws BCS_Exception + * @return BCS_ResponseCore + */ + public function get_object_acl($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'GET'; + $opt [self::OBJECT] = $object; + $opt [self::QUERY_STRING] = array ( + self::ACL => 1 ); + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Get object acl success!" : "Get object acl failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 设置object的acl,有三种模式, + * (1).设置详细json格式的acl; + * a. $acl 为json的array + * b. $acl 为json的string + * (2).通过acl_type字段进行设置 + * a. $acl 为BaiduBCS::$ACL_ACTIONS中的字段 + * @param string $bucket (Required) + * @param string $object (Required) + * @param string|array $acl (Required) + * @param array $opt (Optional) + * @return BCS_ResponseCore + */ + public function set_object_acl($bucket, $object, $acl, $opt = array()) { + $this->assertParameterArray ( $opt ); + //analyze acl + $result = $this->analyze_user_acl ( $acl ); + $opt = array_merge ( $opt, $result ); + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'PUT'; + $opt [self::OBJECT] = $object; + $opt [self::QUERY_STRING] = array ( + self::ACL => 1 ); + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Set object acl success!" : "Set object acl failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 删除object + * @param string $bucket (Required) + * @param string $object (Required) + * @param array $opt (Optional) + * @throws BCS_Exception + * @return BCS_ResponseCore + */ + public function delete_object($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'DELETE'; + $opt [self::OBJECT] = $object; + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Delete object success!" : "Delete object failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 判断object是否存在 + * @param string $bucket (Required) + * @param string $object (Required) + * @param array $opt (Optional) + * @throws BCS_Exception + * @return boolean true|boolean false|BCS_ResponseCore + * true:object存在 + * false:不存在 + * BCS_ResponseCore其他错误 + */ + public function is_object_exist($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'HEAD'; + $opt [self::OBJECT] = $object; + $response = $this->get_object_info ( $bucket, $object, $opt ); + if ($response->isOK ()) { + return true; + } elseif ($response->status === 404) { + return false; + } + return $response; + } + + /** + * 获取文件信息,发送的为HTTP HEAD请求,文件信息都在http response的header中,不会提取文件的内容 + * @param string $bucket (Required) + * @param string $object (Required) + * @param array $opt (Optional) + * @throws BCS_Exception + * @return array BCS_ResponseCore + */ + public function get_object_info($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'HEAD'; + $opt [self::OBJECT] = $object; + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Get object info success!" : "Get object info failed! Response: [" . $response->body . "]", $opt ); + return $response; + } + + /** + * 下载object + * @param string $bucket (Required) + * @param string $object (Required) + * @param array $opt (Optional) + * fileWriteTo (Optional)直接将请求结果写入该文件,如果fileWriteTo文件存在,sdk进行重命名再存储 + * @throws BCS_Exception + * @return BCS_ResponseCore + */ + public function get_object($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + //若fileWriteTo待写入的文件已经存在,需要进行重命名 + if (isset ( $opt ["fileWriteTo"] ) && file_exists ( $opt ["fileWriteTo"] )) { + $original_file_write_to = $opt ["fileWriteTo"]; + $arr = explode ( DIRECTORY_SEPARATOR, $opt ["fileWriteTo"] ); + $file_name = $arr [count ( $arr ) - 1]; + $num = 1; + while ( file_exists ( $opt ["fileWriteTo"] ) ) { + $new_name_arr = explode ( ".", $file_name ); + if (count ( $new_name_arr ) > 1) { + $new_name_arr [count ( $new_name_arr ) - 2] .= " ($num)"; + } else { + $new_name_arr [0] .= " ($num)"; + } + $arr [count ( $arr ) - 1] = implode ( ".", $new_name_arr ); + $opt ["fileWriteTo"] = implode ( DIRECTORY_SEPARATOR, $arr ); + $num ++; + } + $this->log ( "[$original_file_write_to] already exist, rename it to [" . $opt ["fileWriteTo"] . "]", $opt ); + } + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = 'GET'; + $opt [self::OBJECT] = $object; + $response = $this->authenticate ( $opt ); + $this->log ( $response->isOK () ? "Get object success!" : "Get object failed! Response: [" . $response->body . "]", $opt ); + if (! $response->isOK () && isset ( $opt ["fileWriteTo"] )) { + unlink ( $opt ["fileWriteTo"] ); + } + return $response; + } + + /** + * 生成签名链接 + */ + private function generate_user_url($method, $bucket, $object, $opt = array()) { + $opt [self::AK] = $this->ak; + $opt [self::SK] = $this->sk; + $opt [self::BUCKET] = $bucket; + $opt [self::METHOD] = $method; + $opt [self::OBJECT] = $object; + $opt [self::QUERY_STRING] = array (); + if (isset ( $opt ["time"] )) { + $opt [self::QUERY_STRING] ["time"] = $opt ["time"]; + } + if (isset ( $opt ["size"] )) { + $opt [self::QUERY_STRING] ["size"] = $opt ["size"]; + } + return $this->format_url ( $opt ); + } + + /** + * 生成get_object的url + * @param string $bucket (Required) + * @param string $object (Required) + * return false| string url + */ + public function generate_get_object_url($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + return $this->generate_user_url ( "GET", $bucket, $object, $opt ); + } + + /** + * 生成put_object的url + * @param string $bucket (Required) + * @param string $object (Required) + * return false| string url + */ + public function generate_put_object_url($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + return $this->generate_user_url ( "PUT", $bucket, $object, $opt ); + } + + /** + * 生成post_object的url + * @param string $bucket (Required) + * @param string $object (Required) + * return false| string url + */ + public function generate_post_object_url($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + return $this->generate_user_url ( "POST", $bucket, $object, $opt ); + } + + /** + * 生成delete_object的url + * @param string $bucket (Required) + * @param string $object (Required) + * return false| string url + */ + public function generate_delete_object_url($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + return $this->generate_user_url ( "DELETE", $bucket, $object, $opt ); + } + + /** + * 生成head_object的url + * @param string $bucket (Required) + * @param string $object (Required) + * return false| string url + */ + public function generate_head_object_url($bucket, $object, $opt = array()) { + $this->assertParameterArray ( $opt ); + return $this->generate_user_url ( "HEAD", $bucket, $object, $opt ); + } + + /** + * @return the $use_ssl + */ + public function getUse_ssl() { + return $this->use_ssl; + } + + /** + * @param boolean $use_ssl + */ + public function setUse_ssl($use_ssl) { + $this->use_ssl = $use_ssl; + } + + /** + * 校验bucket是否合法,bucket规范 + * 1. 由小写字母,数字和横线'-'组成,长度为6~63位 + * 2. 不能以数字作为Bucket开头 + * 3. 不能以'-'作为Bucket的开头或者结尾 + * @param string $bucket + * @return boolean + */ + public static function validate_bucket($bucket) { + //bucket 正则 + $pattern1 = '/^[a-z][-a-z0-9]{4,61}[a-z0-9]$/'; + if (! preg_match ( $pattern1, $bucket )) { + return false; + } + return true; + } + + /** + * 校验object是否合法,object命名规范 + * 1. object必须以'/'开头 + * @param string $object + * @return boolean + */ + public static function validate_object($object) { + $pattern = '/^\//'; + if (empty ( $object ) || ! preg_match ( $pattern, $object )) { + return false; + } + return true; + } + + /** + * 将常用set http-header的动作抽离出来 + * @param string $header + * @param string $value + * @param array $opt + * @throws BCS_Exception + * @return void + */ + private static function set_header_into_opt($header, $value, &$opt) { + if (isset ( $opt [self::HEADERS] )) { + if (! is_array ( $opt [self::HEADERS] )) { + trigger_error ( 'Invalid $opt[\'headers\'], please check.' ); + throw new BCS_Exception ( 'Invalid $opt[\'headers\'], please check.', - 1 ); + } + } else { + $opt [self::HEADERS] = array (); + } + $opt [self::HEADERS] [$header] = $value; + } + + /** + * 使用特定function对数组中所有元素做处理 + * @param string &$array 要处理的字符串 + * @param string $function 要执行的函数 + * @param boolean $apply_to_keys_also 是否也应用到key上 + */ + private static function array_recursive(&$array, $function, $apply_to_keys_also = false) { + foreach ( $array as $key => $value ) { + if (is_array ( $value )) { + self::array_recursive ( $array [$key], $function, $apply_to_keys_also ); + } else { + $array [$key] = $function ( $value ); + } + + if ($apply_to_keys_also && is_string ( $key )) { + $new_key = $function ( $key ); + if ($new_key != $key) { + $array [$new_key] = $array [$key]; + unset ( $array [$key] ); + } + } + } + } + + /** + * 由数组构造json字符串,增加了一些特殊处理以支持特殊字符和不同编码的中文 + * @param array $array + */ + private static function array_to_json($array) { + if (! is_array ( $array )) { + throw new BCS_Exception ( "Param must be array in function array_to_json()", - 1 ); + } + self::array_recursive ( $array, 'addslashes', false ); + self::array_recursive ( $array, 'rawurlencode', false ); + return rawurldecode ( json_encode ( $array ) ); + } + + /** + * 根据用户传入的acl,进行相应的处理 + * (1).设置详细json格式的acl; + * a. $acl 为json的array + * b. $acl 为json的string + * (2).通过acl_type字段进行设置 + * @param string|array $acl + * @throws BCS_Exception + * @return array + */ + private function analyze_user_acl($acl) { + $result = array (); + if (is_array ( $acl )) { + //(1).a + $result ['content'] = $this->check_user_acl ( $acl ); + } else if (is_string ( $acl )) { + if (in_array ( $acl, self::$ACL_TYPES )) { + //(2).a + $result ["headers"] = array ( + "x-bs-acl" => $acl ); + } else { + //(1).b + $result ['content'] = $acl; + } + } else { + throw new BCS_Exception ( "Invalid acl.", - 1 ); + } + return $result; + } + + /** + * 生成签名 + * @param array $opt + * @return boolean|string + */ + private function format_signature($opt) { + $flags = ""; + $content = ''; + if (! isset ( $opt [self::AK] ) || ! isset ( $opt [self::SK] )) { + trigger_error ( 'ak or sk is not in the array when create factor!' ); + return false; + } + if (isset ( $opt [self::BUCKET] ) && isset ( $opt [self::METHOD] ) && isset ( $opt [self::OBJECT] )) { + $flags .= 'MBO'; + $content .= "Method=" . $opt [self::METHOD] . "\n"; //method + $content .= "Bucket=" . $opt [self::BUCKET] . "\n"; //bucket + $content .= "Object=" . self::trimUrl ( $opt [self::OBJECT] ) . "\n"; //object + } else { + trigger_error ( 'bucket、method and object cann`t be NULL!' ); + return false; + } + if (isset ( $opt ['ip'] )) { + $flags .= 'I'; + $content .= "Ip=" . $opt ['ip'] . "\n"; + } + if (isset ( $opt ['time'] )) { + $flags .= 'T'; + $content .= "Time=" . $opt ['time'] . "\n"; + } + if (isset ( $opt ['size'] )) { + $flags .= 'S'; + $content .= "Size=" . $opt ['size'] . "\n"; + } + $content = $flags . "\n" . $content; + $sign = base64_encode ( hash_hmac ( 'sha1', $content, $opt [self::SK], true ) ); + return 'sign=' . $flags . ':' . $opt [self::AK] . ':' . urlencode ( $sign ); + } + + /** + * 检查用户输入的acl array是否合法,并转为json + * @param array $acl + * @throws BCS_Exception + * @return string acl-json + */ + private function check_user_acl($acl) { + if (! is_array ( $acl )) { + throw new BCS_Exception ( "Invalid acl array" ); + } + foreach ( $acl ['statements'] as $key => $statement ) { + // user resource action effect must in statement + if (! isset ( $statement ['user'] ) || ! isset ( $statement ['resource'] ) || ! isset ( $statement ['action'] ) || ! isset ( $statement ['effect'] )) { + throw new BCS_Exception ( 'Param miss: format acl error, please check your param!' ); + } + if (! is_array ( $statement ['user'] ) || ! is_array ( $statement ['resource'] )) { + throw new BCS_Exception ( 'Param error: user or resource must be array, please check your param!' ); + } + if (! is_array ( $statement ['action'] ) || ! count ( array_diff ( $statement ['action'], self::$ACL_ACTIONS ) ) == 0) { + throw new BCS_Exception ( 'Param error: action, please check your param!' ); + } + if (! in_array ( $statement ['effect'], self::$ACL_EFFECTS )) { + throw new BCS_Exception ( 'Param error: effect, please check your param!' ); + } + if (isset ( $statement ['time'] )) { + if (! is_array ( $statement ['time'] )) { + throw new BCS_Exception ( 'Param error: time, please check your param!' ); + } + } + } + + return self::array_to_json ( $acl ); + } + + /** + * 构造url + * @param array $opt + * @return boolean|string + */ + private function format_url($opt) { + $sign = $this->format_signature ( $opt ); + if ($sign === false) { + trigger_error ( "Format signature failed, please check!" ); + return false; + } + $opt ['sign'] = $sign; + $url = ""; + $url .= $this->use_ssl ? 'https://' : 'http://'; + $url .= $this->hostname; + $url .= '/' . $opt [self::BUCKET]; + if (isset ( $opt [self::OBJECT] ) && '/' !== $opt [self::OBJECT]) { + $url .= "/" . rawurlencode ( $opt [self::OBJECT] ); + } + $url .= '?' . $sign; + if (isset ( $opt [self::QUERY_STRING] )) { + foreach ( $opt [self::QUERY_STRING] as $key => $value ) { + $url .= '&' . $key . '=' . $value; + } + } + return $url; + } + + /** + * 将url中 '//' 替换为 '/' + * @param $url + * @return string + */ + public static function trimUrl($url) { + $result = str_replace ( "//", "/", $url ); + while ( $result !== $url ) { + $url = $result; + $result = str_replace ( "//", "/", $url ); + } + return $result; + } + + /** + * 获取传入目录的文件列表 + * @param string $dir 文件目录 + * @return array 文件树 + */ + public static function get_filetree($dir, $file_prefix = "/*") { + $tree = array (); + foreach ( glob ( $dir . $file_prefix ) as $single ) { + if (is_dir ( $single )) { + $tree = array_merge ( $tree, self::get_filetree ( $single ) ); + } else { + $tree [] = $single; + } + } + return $tree; + } + + /** + * 内置的日志函数,可以根据用户传入的log函数,进行日志输出 + * @param string $log + * @param array $opt + */ + public function log($log, $opt) { + if (isset ( $opt [self::IMPORT_BCS_LOG_METHOD] ) && function_exists ( $opt [self::IMPORT_BCS_LOG_METHOD] )) { + $opt [self::IMPORT_BCS_LOG_METHOD] ( $log ); + } else { + trigger_error ( $log ); + } + } + + /** + * make sure $opt is an array + * @param $opt + */ + private function assertParameterArray($opt) { + if (! is_array ( $opt )) { + throw new BCS_Exception ( 'Parameter must be array, please check!', - 1 ); + } + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Upload/Driver/Bcs/mimetypes.class.php b/ThinkPHP/Library/Think/Upload/Driver/Bcs/mimetypes.class.php new file mode 100644 index 0000000..61c06c0 --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Bcs/mimetypes.class.php @@ -0,0 +1,137 @@ + 'video/3gpp', 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', 'asc' => 'text/plain', + 'atom' => 'application/atom+xml', 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', 'bcpio' => 'application/x-bcpio', + 'bin' => 'application/octet-stream', 'bmp' => 'image/bmp', + 'cdf' => 'application/x-netcdf', 'cgm' => 'image/cgm', + 'class' => 'application/octet-stream', + 'cpio' => 'application/x-cpio', + 'cpt' => 'application/mac-compactpro', + 'csh' => 'application/x-csh', 'css' => 'text/css', + 'dcr' => 'application/x-director', 'dif' => 'video/x-dv', + 'dir' => 'application/x-director', 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'doc' => 'application/msword', 'dtd' => 'application/xml-dtd', + 'dv' => 'video/x-dv', 'dvi' => 'application/x-dvi', + 'dxr' => 'application/x-director', + 'eps' => 'application/postscript', 'etx' => 'text/x-setext', + 'exe' => 'application/octet-stream', + 'ez' => 'application/andrew-inset', 'flv' => 'video/x-flv', + 'gif' => 'image/gif', 'gram' => 'application/srgs', + 'grxml' => 'application/srgs+xml', + 'gtar' => 'application/x-gtar', 'gz' => 'application/x-gzip', + 'hdf' => 'application/x-hdf', + 'hqx' => 'application/mac-binhex40', 'htm' => 'text/html', + 'html' => 'text/html', 'ice' => 'x-conference/x-cooltalk', + 'ico' => 'image/x-icon', 'ics' => 'text/calendar', + 'ief' => 'image/ief', 'ifb' => 'text/calendar', + 'iges' => 'model/iges', 'igs' => 'model/iges', + 'jnlp' => 'application/x-java-jnlp-file', 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', 'js' => 'application/x-javascript', + 'kar' => 'audio/midi', 'latex' => 'application/x-latex', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'm3u' => 'audio/x-mpegurl', 'm4a' => 'audio/mp4a-latm', + 'm4p' => 'audio/mp4a-latm', 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/x-m4v', 'mac' => 'image/x-macpaint', + 'man' => 'application/x-troff-man', + 'mathml' => 'application/mathml+xml', + 'me' => 'application/x-troff-me', 'mesh' => 'model/mesh', + 'mid' => 'audio/midi', 'midi' => 'audio/midi', + 'mif' => 'application/vnd.mif', 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', + 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', 'mpga' => 'audio/mpeg', + 'ms' => 'application/x-troff-ms', 'msh' => 'model/mesh', + 'mxu' => 'video/vnd.mpegurl', 'nc' => 'application/x-netcdf', + 'oda' => 'application/oda', 'ogg' => 'application/ogg', + 'ogv' => 'video/ogv', 'pbm' => 'image/x-portable-bitmap', + 'pct' => 'image/pict', 'pdb' => 'chemical/x-pdb', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', 'pic' => 'image/pict', + 'pict' => 'image/pict', 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'pnt' => 'image/x-macpaint', 'pntg' => 'image/x-macpaint', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'ps' => 'application/postscript', 'qt' => 'video/quicktime', + 'qti' => 'image/x-quicktime', 'qtif' => 'image/x-quicktime', + 'ra' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', 'ras' => 'image/x-cmu-raster', + 'rdf' => 'application/rdf+xml', 'rgb' => 'image/x-rgb', + 'rm' => 'application/vnd.rn-realmedia', + 'roff' => 'application/x-troff', 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', 'silo' => 'model/mesh', + 'sit' => 'application/x-stuffit', + 'skd' => 'application/x-koan', 'skm' => 'application/x-koan', + 'skp' => 'application/x-koan', 'skt' => 'application/x-koan', + 'smi' => 'application/smil', 'smil' => 'application/smil', + 'snd' => 'audio/basic', 'so' => 'application/octet-stream', + 'spl' => 'application/x-futuresplash', + 'src' => 'application/x-wais-source', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 't' => 'application/x-troff', 'tar' => 'application/x-tar', + 'tcl' => 'application/x-tcl', 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', 'tr' => 'application/x-troff', + 'tsv' => 'text/tab-separated-values', 'txt' => 'text/plain', + 'ustar' => 'application/x-ustar', + 'vcd' => 'application/x-cdlink', 'vrml' => 'model/vrml', + 'vxml' => 'application/voicexml+xml', 'wav' => 'audio/x-wav', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbxml' => 'application/vnd.wap.wbxml', 'webm' => 'video/webm', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', 'wrl' => 'model/vrml', + 'xbm' => 'image/x-xbitmap', 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xls' => 'application/vnd.ms-excel', + 'xml' => 'application/xml', 'xpm' => 'image/x-xpixmap', + 'xsl' => 'application/xml', 'xslt' => 'application/xslt+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xwd' => 'image/x-xwindowdump', 'xyz' => 'chemical/x-xyz', + 'zip' => 'application/zip', + //add by zhengkan 20110905 + "apk" => "application/vnd.android.package-archive", + "bin" => "application/octet-stream", + "cab" => "application/vnd.ms-cab-compressed", + "gb" => "application/chinese-gb", + "gba" => "application/octet-stream", + "gbc" => "application/octet-stream", + "jad" => "text/vnd.sun.j2me.app-descriptor", + "jar" => "application/java-archive", + "nes" => "application/octet-stream", + "rar" => "application/x-rar-compressed", + "sis" => "application/vnd.symbian.install", + "sisx" => "x-epoc/x-sisx-app", + "smc" => "application/octet-stream", + "smd" => "application/octet-stream", + "swf" => "application/x-shockwave-flash", + "zip" => "application/x-zip-compressed", + "wap" => "text/vnd.wap.wml wml", "mrp" => "application/mrp", + //add by zhengkan 20110914 + "wma" => "audio/x-ms-wma", + "lrc" => "application/lrc" ); + public static function get_mimetype($ext) { + $ext = strtolower ( $ext ); + return (isset ( self::$mime_types [$ext] ) ? self::$mime_types [$ext] : 'application/octet-stream'); + } +} diff --git a/ThinkPHP/Library/Think/Upload/Driver/Bcs/requestcore.class.php b/ThinkPHP/Library/Think/Upload/Driver/Bcs/requestcore.class.php new file mode 100644 index 0000000..283f23e --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Bcs/requestcore.class.php @@ -0,0 +1,837 @@ +). + */ + public $request_class = 'BCS_RequestCore'; + /** + * The default class to use for HTTP Responses (defaults to ). + */ + public $response_class = 'BCS_ResponseCore'; + /** + * Default useragent string to use. + */ + public $useragent = 'BCS_RequestCore/1.4.2'; + /** + * File to read from while streaming up. + */ + public $read_file = null; + /** + * The resource to read from while streaming up. + */ + public $read_stream = null; + /** + * The size of the stream to read from. + */ + public $read_stream_size = null; + /** + * The length already read from the stream. + */ + public $read_stream_read = 0; + /** + * File to write to while streaming down. + */ + public $write_file = null; + /** + * The resource to write to while streaming down. + */ + public $write_stream = null; + /** + * Stores the intended starting seek position. + */ + public $seek_position = null; + /** + * The user-defined callback function to call when a stream is read from. + */ + public $registered_streaming_read_callback = null; + /** + * The user-defined callback function to call when a stream is written to. + */ + public $registered_streaming_write_callback = null; + /*%******************************************************************************************%*/ + // CONSTANTS + /** + * GET HTTP Method + */ + const HTTP_GET = 'GET'; + /** + * POST HTTP Method + */ + const HTTP_POST = 'POST'; + /** + * PUT HTTP Method + */ + const HTTP_PUT = 'PUT'; + /** + * DELETE HTTP Method + */ + const HTTP_DELETE = 'DELETE'; + /** + * HEAD HTTP Method + */ + const HTTP_HEAD = 'HEAD'; + + /*%******************************************************************************************%*/ + // CONSTRUCTOR/DESTRUCTOR + /** + * Constructs a new instance of this class. + * + * @param string $url (Optional) The URL to request or service endpoint to query. + * @param string $proxy (Optional) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port` + * @param array $helpers (Optional) An associative array of classnames to use for request, and response functionality. Gets passed in automatically by the calling class. + * @return $this A reference to the current instance. + */ + public function __construct($url = null, $proxy = null, $helpers = null) { + // Set some default values. + $this->request_url = $url; + $this->method = self::HTTP_GET; + $this->request_headers = array (); + $this->request_body = ''; + // Set a new Request class if one was set. + if (isset ( $helpers ['request'] ) && ! empty ( $helpers ['request'] )) { + $this->request_class = $helpers ['request']; + } + // Set a new Request class if one was set. + if (isset ( $helpers ['response'] ) && ! empty ( $helpers ['response'] )) { + $this->response_class = $helpers ['response']; + } + if ($proxy) { + $this->set_proxy ( $proxy ); + } + return $this; + } + + /** + * Destructs the instance. Closes opened file handles. + * + * @return $this A reference to the current instance. + */ + public function __destruct() { + if (isset ( $this->read_file ) && isset ( $this->read_stream )) { + fclose ( $this->read_stream ); + } + if (isset ( $this->write_file ) && isset ( $this->write_stream )) { + fclose ( $this->write_stream ); + } + return $this; + } + + /*%******************************************************************************************%*/ + // REQUEST METHODS + /** + * Sets the credentials to use for authentication. + * + * @param string $user (Required) The username to authenticate with. + * @param string $pass (Required) The password to authenticate with. + * @return $this A reference to the current instance. + */ + public function set_credentials($user, $pass) { + $this->username = $user; + $this->password = $pass; + return $this; + } + + /** + * Adds a custom HTTP header to the cURL request. + * + * @param string $key (Required) The custom HTTP header to set. + * @param mixed $value (Required) The value to assign to the custom HTTP header. + * @return $this A reference to the current instance. + */ + public function add_header($key, $value) { + $this->request_headers [$key] = $value; + return $this; + } + + /** + * Removes an HTTP header from the cURL request. + * + * @param string $key (Required) The custom HTTP header to set. + * @return $this A reference to the current instance. + */ + public function remove_header($key) { + if (isset ( $this->request_headers [$key] )) { + unset ( $this->request_headers [$key] ); + } + return $this; + } + + /** + * Set the method type for the request. + * + * @param string $method (Required) One of the following constants: , , , , . + * @return $this A reference to the current instance. + */ + public function set_method($method) { + $this->method = strtoupper ( $method ); + return $this; + } + + /** + * Sets a custom useragent string for the class. + * + * @param string $ua (Required) The useragent string to use. + * @return $this A reference to the current instance. + */ + public function set_useragent($ua) { + $this->useragent = $ua; + return $this; + } + + /** + * Set the body to send in the request. + * + * @param string $body (Required) The textual content to send along in the body of the request. + * @return $this A reference to the current instance. + */ + public function set_body($body) { + $this->request_body = $body; + return $this; + } + + /** + * Set the URL to make the request to. + * + * @param string $url (Required) The URL to make the request to. + * @return $this A reference to the current instance. + */ + public function set_request_url($url) { + $this->request_url = $url; + return $this; + } + + /** + * Set additional CURLOPT settings. These will merge with the default settings, and override if + * there is a duplicate. + * + * @param array $curlopts (Optional) A set of key-value pairs that set `CURLOPT` options. These will merge with the existing CURLOPTs, and ones passed here will override the defaults. Keys should be the `CURLOPT_*` constants, not strings. + * @return $this A reference to the current instance. + */ + public function set_curlopts($curlopts) { + $this->curlopts = $curlopts; + return $this; + } + + /** + * Sets the length in bytes to read from the stream while streaming up. + * + * @param integer $size (Required) The length in bytes to read from the stream. + * @return $this A reference to the current instance. + */ + public function set_read_stream_size($size) { + $this->read_stream_size = $size; + return $this; + } + + /** + * Sets the resource to read from while streaming up. Reads the stream from its current position until + * EOF or `$size` bytes have been read. If `$size` is not given it will be determined by and + * . + * + * @param resource $resource (Required) The readable resource to read from. + * @param integer $size (Optional) The size of the stream to read. + * @return $this A reference to the current instance. + */ + public function set_read_stream($resource, $size = null) { + if (! isset ( $size ) || $size < 0) { + $stats = fstat ( $resource ); + if ($stats && $stats ['size'] >= 0) { + $position = ftell ( $resource ); + if ($position !== false && $position >= 0) { + $size = $stats ['size'] - $position; + } + } + } + $this->read_stream = $resource; + return $this->set_read_stream_size ( $size ); + } + + /** + * Sets the file to read from while streaming up. + * + * @param string $location (Required) The readable location to read from. + * @return $this A reference to the current instance. + */ + public function set_read_file($location) { + $this->read_file = $location; + $read_file_handle = fopen ( $location, 'r' ); + return $this->set_read_stream ( $read_file_handle ); + } + + /** + * Sets the resource to write to while streaming down. + * + * @param resource $resource (Required) The writeable resource to write to. + * @return $this A reference to the current instance. + */ + public function set_write_stream($resource) { + $this->write_stream = $resource; + return $this; + } + + /** + * Sets the file to write to while streaming down. + * + * @param string $location (Required) The writeable location to write to. + * @return $this A reference to the current instance. + */ + public function set_write_file($location) { + $this->write_file = $location; + $write_file_handle = fopen ( $location, 'w' ); + return $this->set_write_stream ( $write_file_handle ); + } + + /** + * Set the proxy to use for making requests. + * + * @param string $proxy (Required) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port` + * @return $this A reference to the current instance. + */ + public function set_proxy($proxy) { + $proxy = parse_url ( $proxy ); + $proxy ['user'] = isset ( $proxy ['user'] ) ? $proxy ['user'] : null; + $proxy ['pass'] = isset ( $proxy ['pass'] ) ? $proxy ['pass'] : null; + $proxy ['port'] = isset ( $proxy ['port'] ) ? $proxy ['port'] : null; + $this->proxy = $proxy; + return $this; + } + + /** + * Set the intended starting seek position. + * + * @param integer $position (Required) The byte-position of the stream to begin reading from. + * @return $this A reference to the current instance. + */ + public function set_seek_position($position) { + $this->seek_position = isset ( $position ) ? ( integer ) $position : null; + return $this; + } + + /** + * Register a callback function to execute whenever a data stream is read from using + * . + * + * The user-defined callback function should accept three arguments: + * + *
    + *
  • $curl_handle - resource - Required - The cURL handle resource that represents the in-progress transfer.
  • + *
  • $file_handle - resource - Required - The file handle resource that represents the file on the local file system.
  • + *
  • $length - integer - Required - The length in kilobytes of the data chunk that was transferred.
  • + *
+ * + * @param string|array|function $callback (Required) The callback function is called by , so you can pass the following values:
    + *
  • The name of a global function to execute, passed as a string.
  • + *
  • A method to execute, passed as array('ClassName', 'MethodName').
  • + *
  • An anonymous function (PHP 5.3+).
+ * @return $this A reference to the current instance. + */ + public function register_streaming_read_callback($callback) { + $this->registered_streaming_read_callback = $callback; + return $this; + } + + /** + * Register a callback function to execute whenever a data stream is written to using + * . + * + * The user-defined callback function should accept two arguments: + * + *
    + *
  • $curl_handle - resource - Required - The cURL handle resource that represents the in-progress transfer.
  • + *
  • $length - integer - Required - The length in kilobytes of the data chunk that was transferred.
  • + *
+ * + * @param string|array|function $callback (Required) The callback function is called by , so you can pass the following values:
    + *
  • The name of a global function to execute, passed as a string.
  • + *
  • A method to execute, passed as array('ClassName', 'MethodName').
  • + *
  • An anonymous function (PHP 5.3+).
+ * @return $this A reference to the current instance. + */ + public function register_streaming_write_callback($callback) { + $this->registered_streaming_write_callback = $callback; + return $this; + } + + /*%******************************************************************************************%*/ + // PREPARE, SEND, AND PROCESS REQUEST + /** + * A callback function that is invoked by cURL for streaming up. + * + * @param resource $curl_handle (Required) The cURL handle for the request. + * @param resource $file_handle (Required) The open file handle resource. + * @param integer $length (Required) The maximum number of bytes to read. + * @return binary Binary data from a stream. + */ + public function streaming_read_callback($curl_handle, $file_handle, $length) { + // Once we've sent as much as we're supposed to send... + if ($this->read_stream_read >= $this->read_stream_size) { + // Send EOF + return ''; + } + // If we're at the beginning of an upload and need to seek... + if ($this->read_stream_read == 0 && isset ( $this->seek_position ) && $this->seek_position !== ftell ( $this->read_stream )) { + if (fseek ( $this->read_stream, $this->seek_position ) !== 0) { + throw new BCS_RequestCore_Exception ( 'The stream does not support seeking and is either not at the requested position or the position is unknown.' ); + } + } + $read = fread ( $this->read_stream, min ( $this->read_stream_size - $this->read_stream_read, $length ) ); // Remaining upload data or cURL's requested chunk size + $this->read_stream_read += strlen ( $read ); + $out = $read === false ? '' : $read; + // Execute callback function + if ($this->registered_streaming_read_callback) { + call_user_func ( $this->registered_streaming_read_callback, $curl_handle, $file_handle, $out ); + } + return $out; + } + + /** + * A callback function that is invoked by cURL for streaming down. + * + * @param resource $curl_handle (Required) The cURL handle for the request. + * @param binary $data (Required) The data to write. + * @return integer The number of bytes written. + */ + public function streaming_write_callback($curl_handle, $data) { + $length = strlen ( $data ); + $written_total = 0; + $written_last = 0; + while ( $written_total < $length ) { + $written_last = fwrite ( $this->write_stream, substr ( $data, $written_total ) ); + if ($written_last === false) { + return $written_total; + } + $written_total += $written_last; + } + // Execute callback function + if ($this->registered_streaming_write_callback) { + call_user_func ( $this->registered_streaming_write_callback, $curl_handle, $written_total ); + } + return $written_total; + } + + /** + * Prepares and adds the details of the cURL request. This can be passed along to a + * function. + * + * @return resource The handle for the cURL object. + */ + public function prep_request() { + $curl_handle = curl_init (); + // Set default options. + curl_setopt ( $curl_handle, CURLOPT_URL, $this->request_url ); + curl_setopt ( $curl_handle, CURLOPT_FILETIME, true ); + curl_setopt ( $curl_handle, CURLOPT_FRESH_CONNECT, false ); + curl_setopt ( $curl_handle, CURLOPT_SSL_VERIFYPEER, false ); + curl_setopt ( $curl_handle, CURLOPT_SSL_VERIFYHOST, true ); + curl_setopt ( $curl_handle, CURLOPT_CLOSEPOLICY, CURLCLOSEPOLICY_LEAST_RECENTLY_USED ); + curl_setopt ( $curl_handle, CURLOPT_MAXREDIRS, 5 ); + curl_setopt ( $curl_handle, CURLOPT_HEADER, true ); + curl_setopt ( $curl_handle, CURLOPT_RETURNTRANSFER, true ); + curl_setopt ( $curl_handle, CURLOPT_TIMEOUT, 5184000 ); + curl_setopt ( $curl_handle, CURLOPT_CONNECTTIMEOUT, 120 ); + curl_setopt ( $curl_handle, CURLOPT_NOSIGNAL, true ); + curl_setopt ( $curl_handle, CURLOPT_REFERER, $this->request_url ); + curl_setopt ( $curl_handle, CURLOPT_USERAGENT, $this->useragent ); + curl_setopt ( $curl_handle, CURLOPT_READFUNCTION, array ( + $this, + 'streaming_read_callback' ) ); + if ($this->debug_mode) { + curl_setopt ( $curl_handle, CURLOPT_VERBOSE, true ); + } + //if (! ini_get ( 'safe_mode' )) { + //modify by zhengkan + //curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true); + //} + // Enable a proxy connection if requested. + if ($this->proxy) { + curl_setopt ( $curl_handle, CURLOPT_HTTPPROXYTUNNEL, true ); + $host = $this->proxy ['host']; + $host .= ($this->proxy ['port']) ? ':' . $this->proxy ['port'] : ''; + curl_setopt ( $curl_handle, CURLOPT_PROXY, $host ); + if (isset ( $this->proxy ['user'] ) && isset ( $this->proxy ['pass'] )) { + curl_setopt ( $curl_handle, CURLOPT_PROXYUSERPWD, $this->proxy ['user'] . ':' . $this->proxy ['pass'] ); + } + } + // Set credentials for HTTP Basic/Digest Authentication. + if ($this->username && $this->password) { + curl_setopt ( $curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY ); + curl_setopt ( $curl_handle, CURLOPT_USERPWD, $this->username . ':' . $this->password ); + } + // Handle the encoding if we can. + if (extension_loaded ( 'zlib' )) { + curl_setopt ( $curl_handle, CURLOPT_ENCODING, '' ); + } + // Process custom headers + if (isset ( $this->request_headers ) && count ( $this->request_headers )) { + $temp_headers = array (); + foreach ( $this->request_headers as $k => $v ) { + $temp_headers [] = $k . ': ' . $v; + } + curl_setopt ( $curl_handle, CURLOPT_HTTPHEADER, $temp_headers ); + } + switch ($this->method) { + case self::HTTP_PUT : + curl_setopt ( $curl_handle, CURLOPT_CUSTOMREQUEST, 'PUT' ); + if (isset ( $this->read_stream )) { + if (! isset ( $this->read_stream_size ) || $this->read_stream_size < 0) { + throw new BCS_RequestCore_Exception ( 'The stream size for the streaming upload cannot be determined.' ); + } + curl_setopt ( $curl_handle, CURLOPT_INFILESIZE, $this->read_stream_size ); + curl_setopt ( $curl_handle, CURLOPT_UPLOAD, true ); + } else { + curl_setopt ( $curl_handle, CURLOPT_POSTFIELDS, $this->request_body ); + } + break; + case self::HTTP_POST : + curl_setopt ( $curl_handle, CURLOPT_POST, true ); + curl_setopt ( $curl_handle, CURLOPT_POSTFIELDS, $this->request_body ); + break; + case self::HTTP_HEAD : + curl_setopt ( $curl_handle, CURLOPT_CUSTOMREQUEST, self::HTTP_HEAD ); + curl_setopt ( $curl_handle, CURLOPT_NOBODY, 1 ); + break; + default : // Assumed GET + curl_setopt ( $curl_handle, CURLOPT_CUSTOMREQUEST, $this->method ); + if (isset ( $this->write_stream )) { + curl_setopt ( $curl_handle, CURLOPT_WRITEFUNCTION, array ( + $this, + 'streaming_write_callback' ) ); + curl_setopt ( $curl_handle, CURLOPT_HEADER, false ); + } else { + curl_setopt ( $curl_handle, CURLOPT_POSTFIELDS, $this->request_body ); + } + break; + } + // Merge in the CURLOPTs + if (isset ( $this->curlopts ) && sizeof ( $this->curlopts ) > 0) { + foreach ( $this->curlopts as $k => $v ) { + curl_setopt ( $curl_handle, $k, $v ); + } + } + return $curl_handle; + } + + /** + * is the environment BAE? + * @return boolean the result of the answer + */ + private function isBaeEnv() { + if (isset ( $_SERVER ['HTTP_HOST'] )) { + $host = $_SERVER ['HTTP_HOST']; + $pos = strpos ( $host, '.' ); + if ($pos !== false) { + $substr = substr ( $host, $pos + 1 ); + if ($substr == 'duapp.com') { + return true; + } + } + } + if (isset ( $_SERVER ["HTTP_BAE_LOGID"] )) { + return true; + } + + return false; + } + + /** + * Take the post-processed cURL data and break it down into useful header/body/info chunks. Uses the + * data stored in the `curl_handle` and `response` properties unless replacement data is passed in via + * parameters. + * + * @param resource $curl_handle (Optional) The reference to the already executed cURL request. + * @param string $response (Optional) The actual response content itself that needs to be parsed. + * @return BCS_ResponseCore A object containing a parsed HTTP response. + */ + public function process_response($curl_handle = null, $response = null) { + // Accept a custom one if it's passed. + if ($curl_handle && $response) { + $this->curl_handle = $curl_handle; + $this->response = $response; + } + // As long as this came back as a valid resource... + if (is_resource ( $this->curl_handle )) { + // Determine what's what. + $header_size = curl_getinfo ( $this->curl_handle, CURLINFO_HEADER_SIZE ); + $this->response_headers = substr ( $this->response, 0, $header_size ); + $this->response_body = substr ( $this->response, $header_size ); + $this->response_code = curl_getinfo ( $this->curl_handle, CURLINFO_HTTP_CODE ); + $this->response_info = curl_getinfo ( $this->curl_handle ); + // Parse out the headers + $this->response_headers = explode ( "\r\n\r\n", trim ( $this->response_headers ) ); + $this->response_headers = array_pop ( $this->response_headers ); + $this->response_headers = explode ( "\r\n", $this->response_headers ); + array_shift ( $this->response_headers ); + // Loop through and split up the headers. + $header_assoc = array (); + foreach ( $this->response_headers as $header ) { + $kv = explode ( ': ', $header ); + //$header_assoc [strtolower ( $kv [0] )] = $kv [1]; + $header_assoc [$kv [0]] = $kv [1]; + } + // Reset the headers to the appropriate property. + $this->response_headers = $header_assoc; + $this->response_headers ['_info'] = $this->response_info; + $this->response_headers ['_info'] ['method'] = $this->method; + if ($curl_handle && $response) { + $class='\Think\Upload\Driver\Bcs\\'. $this->response_class; + return new $class ( $this->response_headers, $this->response_body, $this->response_code, $this->curl_handle ); + } + } + // Return false + return false; + } + + /** + * Sends the request, calling necessary utility functions to update built-in properties. + * + * @param boolean $parse (Optional) Whether to parse the response with BCS_ResponseCore or not. + * @return string The resulting unparsed data from the request. + */ + public function send_request($parse = false) { + if (false === $this->isBaeEnv ()) { + set_time_limit ( 0 ); + } + $curl_handle = $this->prep_request (); + $this->response = curl_exec ( $curl_handle ); + if ($this->response === false || + ($this->method === self::HTTP_GET && + curl_errno($curl_handle) === CURLE_PARTIAL_FILE)) { + throw new BCS_RequestCore_Exception ( 'cURL resource: ' . ( string ) $curl_handle . '; cURL error: ' . curl_error ( $curl_handle ) . ' (' . curl_errno ( $curl_handle ) . ')' ); + } + $parsed_response = $this->process_response ( $curl_handle, $this->response ); + curl_close ( $curl_handle ); + if ($parse) { + return $parsed_response; + } + return $this->response; + } + + /** + * Sends the request using , enabling parallel requests. Uses the "rolling" method. + * + * @param array $handles (Required) An indexed array of cURL handles to process simultaneously. + * @param array $opt (Optional) An associative array of parameters that can have the following keys:
    + *
  • callback - string|array - Optional - The string name of a function to pass the response data to. If this is a method, pass an array where the [0] index is the class and the [1] index is the method name.
  • + *
  • limit - integer - Optional - The number of simultaneous requests to make. This can be useful for scaling around slow server responses. Defaults to trusting cURLs judgement as to how many to use.
+ * @return array Post-processed cURL responses. + */ + public function send_multi_request($handles, $opt = null) { + if (false === $this->isBaeEnv ()) { + set_time_limit ( 0 ); + } + // Skip everything if there are no handles to process. + if (count ( $handles ) === 0) + return array (); + if (! $opt) + $opt = array (); + + // Initialize any missing options + $limit = isset ( $opt ['limit'] ) ? $opt ['limit'] : - 1; + // Initialize + $handle_list = $handles; + $http = new $this->request_class (); + $multi_handle = curl_multi_init (); + $handles_post = array (); + $added = count ( $handles ); + $last_handle = null; + $count = 0; + $i = 0; + // Loop through the cURL handles and add as many as it set by the limit parameter. + while ( $i < $added ) { + if ($limit > 0 && $i >= $limit) + break; + curl_multi_add_handle ( $multi_handle, array_shift ( $handles ) ); + $i ++; + } + do { + $active = false; + // Start executing and wait for a response. + while ( ($status = curl_multi_exec ( $multi_handle, $active )) === CURLM_CALL_MULTI_PERFORM ) { + // Start looking for possible responses immediately when we have to add more handles + if (count ( $handles ) > 0) + break; + } + // Figure out which requests finished. + $to_process = array (); + while ( $done = curl_multi_info_read ( $multi_handle ) ) { + // Since curl_errno() isn't reliable for handles that were in multirequests, we check the 'result' of the info read, which contains the curl error number, (listed here http://curl.haxx.se/libcurl/c/libcurl-errors.html ) + if ($done ['result'] > 0) { + throw new BCS_RequestCore_Exception ( 'cURL resource: ' . ( string ) $done ['handle'] . '; cURL error: ' . curl_error ( $done ['handle'] ) . ' (' . $done ['result'] . ')' ); + } // Because curl_multi_info_read() might return more than one message about a request, we check to see if this request is already in our array of completed requests +elseif (! isset ( $to_process [( int ) $done ['handle']] )) { + $to_process [( int ) $done ['handle']] = $done; + } + } + // Actually deal with the request + foreach ( $to_process as $pkey => $done ) { + $response = $http->process_response ( $done ['handle'], curl_multi_getcontent ( $done ['handle'] ) ); + $key = array_search ( $done ['handle'], $handle_list, true ); + $handles_post [$key] = $response; + if (count ( $handles ) > 0) { + curl_multi_add_handle ( $multi_handle, array_shift ( $handles ) ); + } + curl_multi_remove_handle ( $multi_handle, $done ['handle'] ); + curl_close ( $done ['handle'] ); + } + } while ( $active || count ( $handles_post ) < $added ); + curl_multi_close ( $multi_handle ); + ksort ( $handles_post, SORT_NUMERIC ); + return $handles_post; + } + + /*%******************************************************************************************%*/ + // RESPONSE METHODS + /** + * Get the HTTP response headers from the request. + * + * @param string $header (Optional) A specific header value to return. Defaults to all headers. + * @return string|array All or selected header values. + */ + public function get_response_header($header = null) { + if ($header) { + // return $this->response_headers [strtolower ( $header )]; + return $this->response_headers [$header]; + } + return $this->response_headers; + } + + /** + * Get the HTTP response body from the request. + * + * @return string The response body. + */ + public function get_response_body() { + return $this->response_body; + } + + /** + * Get the HTTP response code from the request. + * + * @return string The HTTP response code. + */ + public function get_response_code() { + return $this->response_code; + } +} +/** + * Container for all response-related methods. + */ +class BCS_ResponseCore { + /** + * Stores the HTTP header information. + */ + public $header; + /** + * Stores the SimpleXML response. + */ + public $body; + /** + * Stores the HTTP response code. + */ + public $status; + + /** + * Constructs a new instance of this class. + * + * @param array $header (Required) Associative array of HTTP headers (typically returned by ). + * @param string $body (Required) XML-formatted response from AWS. + * @param integer $status (Optional) HTTP response status code from the request. + * @return object Contains an `header` property (HTTP headers as an associative array), a or `body` property, and an `status` code. + */ + public function __construct($header, $body, $status = null) { + $this->header = $header; + $this->body = $body; + $this->status = $status; + return $this; + } + + /** + * Did we receive the status code we expected? + * + * @param integer|array $codes (Optional) The status code(s) to expect. Pass an for a single acceptable value, or an of integers for multiple acceptable values. + * @return boolean Whether we received the expected status code or not. + */ + public function isOK($codes = array(200, 201, 204, 206)) { + if (is_array ( $codes )) { + return in_array ( $this->status, $codes ); + } + return $this->status === $codes; + } +} +/** + * Default BCS_RequestCore Exception. + */ +class BCS_RequestCore_Exception extends \Exception { +} diff --git a/ThinkPHP/Library/Think/Upload/Driver/Ftp.class.php b/ThinkPHP/Library/Think/Upload/Driver/Ftp.class.php new file mode 100644 index 0000000..fccf9a6 --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Ftp.class.php @@ -0,0 +1,163 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Upload\Driver; +class Ftp { + /** + * 上传文件根目录 + * @var string + */ + private $rootPath; + + /** + * 本地上传错误信息 + * @var string + */ + private $error = ''; //上传错误信息 + + /** + * FTP连接 + * @var resource + */ + private $link; + + private $config = array( + 'host' => '', //服务器 + 'port' => 21, //端口 + 'timeout' => 90, //超时时间 + 'username' => '', //用户名 + 'password' => '', //密码 + ); + + /** + * 构造函数,用于设置上传根路径 + * @param array $config FTP配置 + */ + public function __construct($config){ + /* 默认FTP配置 */ + $this->config = array_merge($this->config, $config); + + /* 登录FTP服务器 */ + if(!$this->login()){ + E($this->error); + } + } + + /** + * 检测上传根目录 + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath){ + /* 设置根目录 */ + $this->rootPath = ftp_pwd($this->link) . '/' . ltrim($rootpath, '/'); + + if(!@ftp_chdir($this->link, $this->rootPath)){ + $this->error = '上传根目录不存在!'; + return false; + } + return true; + } + + /** + * 检测上传目录 + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath){ + /* 检测并创建目录 */ + if (!$this->mkdir($savepath)) { + return false; + } else { + //TODO:检测目录是否可写 + return true; + } + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save($file, $replace=true) { + $filename = $this->rootPath . $file['savepath'] . $file['savename']; + + /* 不覆盖同名文件 */ + // if (!$replace && is_file($filename)) { + // $this->error = '存在同名文件' . $file['savename']; + // return false; + // } + + /* 移动文件 */ + if (!ftp_put($this->link, $filename, $file['tmp_name'], FTP_BINARY)) { + $this->error = '文件上传保存错误!'; + return false; + } + return true; + } + + /** + * 创建目录 + * @param string $savepath 要创建的目录 + * @return boolean 创建状态,true-成功,false-失败 + */ + public function mkdir($savepath){ + $dir = $this->rootPath . $savepath; + if(ftp_chdir($this->link, $dir)){ + return true; + } + + if(ftp_mkdir($this->link, $dir)){ + return true; + } elseif($this->mkdir(dirname($savepath)) && ftp_mkdir($this->link, $dir)) { + return true; + } else { + $this->error = "目录 {$savepath} 创建失败!"; + return false; + } + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError(){ + return $this->error; + } + + /** + * 登录到FTP服务器 + * @return boolean true-登录成功,false-登录失败 + */ + private function login(){ + extract($this->config); + $this->link = ftp_connect($host, $port, $timeout); + if($this->link) { + if (ftp_login($this->link, $username, $password)) { + return true; + } else { + $this->error = "无法登录到FTP服务器:username - {$username}"; + } + } else { + $this->error = "无法连接到FTP服务器:{$host}"; + } + return false; + } + + /** + * 析构方法,用于断开当前FTP连接 + */ + public function __destruct() { + ftp_close($this->link); + } + +} diff --git a/ThinkPHP/Library/Think/Upload/Driver/Local.class.php b/ThinkPHP/Library/Think/Upload/Driver/Local.class.php new file mode 100644 index 0000000..5988d5a --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Local.class.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Upload\Driver; +class Local{ + /** + * 上传文件根目录 + * @var string + */ + private $rootPath; + + /** + * 本地上传错误信息 + * @var string + */ + private $error = ''; //上传错误信息 + + /** + * 构造函数,用于设置上传根路径 + */ + public function __construct($config = null){ + + } + + /** + * 检测上传根目录 + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath){ + if(!(is_dir($rootpath) && is_writable($rootpath))){ + $this->error = '上传根目录不存在!请尝试手动创建:'.$rootpath; + return false; + } + $this->rootPath = $rootpath; + return true; + } + + /** + * 检测上传目录 + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath){ + /* 检测并创建目录 */ + if (!$this->mkdir($savepath)) { + return false; + } else { + /* 检测目录是否可写 */ + if (!is_writable($this->rootPath . $savepath)) { + $this->error = '上传目录 ' . $savepath . ' 不可写!'; + return false; + } else { + return true; + } + } + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save($file, $replace=true) { + $filename = $this->rootPath . $file['savepath'] . $file['savename']; + + /* 不覆盖同名文件 */ + if (!$replace && is_file($filename)) { + $this->error = '存在同名文件' . $file['savename']; + return false; + } + + /* 移动文件 */ + if (!move_uploaded_file($file['tmp_name'], $filename)) { + $this->error = '文件上传保存错误!'; + return false; + } + + return true; + } + + /** + * 创建目录 + * @param string $savepath 要创建的穆里 + * @return boolean 创建状态,true-成功,false-失败 + */ + public function mkdir($savepath){ + $dir = $this->rootPath . $savepath; + if(is_dir($dir)){ + return true; + } + + if(mkdir($dir, 0777, true)){ + return true; + } else { + $this->error = "目录 {$savepath} 创建失败!"; + return false; + } + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError(){ + return $this->error; + } + +} diff --git a/ThinkPHP/Library/Think/Upload/Driver/Qiniu.class.php b/ThinkPHP/Library/Think/Upload/Driver/Qiniu.class.php new file mode 100644 index 0000000..c745f9f --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Qiniu.class.php @@ -0,0 +1,102 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Upload\Driver; +use Think\Upload\Driver\Qiniu\QiniuStorage; + +class Qiniu{ + /** + * 上传文件根目录 + * @var string + */ + private $rootPath; + + /** + * 上传错误信息 + * @var string + */ + private $error = ''; + + private $config = array( + 'secretKey' => '', //七牛服务器 + 'accessKey' => '', //七牛用户 + 'domain' => '', //七牛密码 + 'bucket' => '', //空间名称 + 'timeout' => 300, //超时时间 + ); + + /** + * 构造函数,用于设置上传根路径 + * @param array $config FTP配置 + */ + public function __construct($config){ + $this->config = array_merge($this->config, $config); + /* 设置根目录 */ + $this->qiniu = new QiniuStorage($config); + } + + /** + * 检测上传根目录(七牛上传时支持自动创建目录,直接返回) + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath){ + $this->rootPath = trim($rootpath, './') . '/'; + return true; + } + + /** + * 检测上传目录(七牛上传时支持自动创建目录,直接返回) + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath){ + return true; + } + + /** + * 创建文件夹 (七牛上传时支持自动创建目录,直接返回) + * @param string $savepath 目录名称 + * @return boolean true-创建成功,false-创建失败 + */ + public function mkdir($savepath){ + return true; + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save(&$file,$replace=true) { + $file['name'] = $file['savepath'] . $file['savename']; + $key = str_replace('/', '_', $file['name']); + $upfile = array( + 'name'=>'file', + 'fileName'=>$key, + 'fileBody'=>file_get_contents($file['tmp_name']) + ); + $config = array(); + $result = $this->qiniu->upload($config, $upfile); + $url = $this->qiniu->downlink($key); + $file['url'] = $url; + return false ===$result ? false : true; + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError(){ + return $this->qiniu->errorStr; + } +} diff --git a/ThinkPHP/Library/Think/Upload/Driver/Qiniu/QiniuStorage.class.php b/ThinkPHP/Library/Think/Upload/Driver/Qiniu/QiniuStorage.class.php new file mode 100644 index 0000000..472e0b4 --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Qiniu/QiniuStorage.class.php @@ -0,0 +1,333 @@ +sk = $config['secretKey']; + $this->ak = $config['accessKey']; + $this->domain = $config['domain']; + $this->bucket = $config['bucket']; + $this->timeout = isset($config['timeout'])? $config['timeout'] : 3600; + } + + static function sign($sk, $ak, $data){ + $sign = hash_hmac('sha1', $data, $sk, true); + return $ak . ':' . self::Qiniu_Encode($sign); + } + + static function signWithData($sk, $ak, $data){ + $data = self::Qiniu_Encode($data); + return self::sign($sk, $ak, $data) . ':' . $data; + } + + public function accessToken($url, $body=''){ + $parsed_url = parse_url($url); + $path = $parsed_url['path']; + $access = $path; + if (isset($parsed_url['query'])) { + $access .= "?" . $parsed_url['query']; + } + $access .= "\n"; + + if($body){ + $access .= $body; + } + return self::sign($this->sk, $this->ak, $access); + } + + public function UploadToken($sk ,$ak ,$param){ + $param['deadline'] = $param['Expires'] == 0? 3600: $param['Expires']; + $param['deadline'] += time(); + $data = array('scope'=> $this->bucket, 'deadline'=>$param['deadline']); + if (!empty($param['CallbackUrl'])) { + $data['callbackUrl'] = $param['CallbackUrl']; + } + if (!empty($param['CallbackBody'])) { + $data['callbackBody'] = $param['CallbackBody']; + } + if (!empty($param['ReturnUrl'])) { + $data['returnUrl'] = $param['ReturnUrl']; + } + if (!empty($param['ReturnBody'])) { + $data['returnBody'] = $param['ReturnBody']; + } + if (!empty($param['AsyncOps'])) { + $data['asyncOps'] = $param['AsyncOps']; + } + if (!empty($param['EndUser'])) { + $data['endUser'] = $param['EndUser']; + } + $data = json_encode($data); + return self::SignWithData($sk, $ak, $data); + } + + public function upload($config, $file){ + $uploadToken = $this->UploadToken($this->sk, $this->ak, $config); + + $url = "{$this->QINIU_UP_HOST}"; + $mimeBoundary = md5(microtime()); + $header = array('Content-Type'=>'multipart/form-data;boundary='.$mimeBoundary); + $data = array(); + + $fields = array( + 'token' => $uploadToken, + 'key' => $config['saveName']? : $file['fileName'], + ); + + if(is_array($config['custom_fields']) && $config['custom_fields'] !== array()){ + $fields = array_merge($fields, $config['custom_fields']); + } + + foreach ($fields as $name => $val) { + array_push($data, '--' . $mimeBoundary); + array_push($data, "Content-Disposition: form-data; name=\"$name\""); + array_push($data, ''); + array_push($data, $val); + } + + //文件 + array_push($data, '--' . $mimeBoundary); + $name = $file['name']; + $fileName = $file['fileName']; + $fileBody = $file['fileBody']; + $fileName = self::Qiniu_escapeQuotes($fileName); + array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$fileName\""); + array_push($data, 'Content-Type: application/octet-stream'); + array_push($data, ''); + array_push($data, $fileBody); + + array_push($data, '--' . $mimeBoundary . '--'); + array_push($data, ''); + + $body = implode("\r\n", $data); + $response = $this->request($url, 'POST', $header, $body); + return $response; + } + + public function dealWithType($key, $type){ + $param = $this->buildUrlParam(); + $url = ''; + + switch($type){ + case 'img': + $url = $this->downLink($key); + if($param['imageInfo']){ + $url .= '?imageInfo'; + }else if($param['exif']){ + $url .= '?exif'; + }else if($param['imageView']){ + $url .= '?imageView/'.$param['mode']; + if($param['w']) + $url .= "/w/{$param['w']}"; + if($param['h']) + $url .= "/h/{$param['h']}"; + if($param['q']) + $url .= "/q/{$param['q']}"; + if($param['format']) + $url .= "/format/{$param['format']}"; + } + break; + case 'video': //TODO 视频处理 + case 'doc': + $url = $this->downLink($key); + $url .= '?md2html'; + if(isset($param['mode'])) + $url .= '/'.(int)$param['mode']; + if($param['cssurl']) + $url .= '/'. self::Qiniu_Encode($param['cssurl']); + break; + + } + return $url; + } + + public function buildUrlParam(){ + return $_REQUEST; + } + + //获取某个路径下的文件列表 + public function getList($query = array(), $path = ''){ + $query = array_merge(array('bucket'=>$this->bucket), $query); + $url = "{$this->QINIU_RSF_HOST}/list?".http_build_query($query); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array('Authorization'=>"QBox $accessToken")); + return $response; + } + + //获取某个文件的信息 + public function info($key){ + $key = trim($key); + $url = "{$this->QINIU_RS_HOST}/stat/" . self::Qiniu_Encode("{$this->bucket}:{$key}"); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array( + 'Authorization' => "QBox $accessToken", + )); + return $response; + } + + //获取文件下载资源链接 + public function downLink($key){ + $key = urlencode($key); + $key = self::Qiniu_escapeQuotes($key); + $url = "http://{$this->domain}/{$key}"; + return $url; + } + + //重命名单个文件 + public function rename($file, $new_file){ + $key = trim($file); + $url = "{$this->QINIU_RS_HOST}/move/" . self::Qiniu_Encode("{$this->bucket}:{$key}") .'/'. self::Qiniu_Encode("{$this->bucket}:{$new_file}"); + trace($url); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array('Authorization'=>"QBox $accessToken")); + return $response; + } + + //删除单个文件 + public function del($file){ + $key = trim($file); + $url = "{$this->QINIU_RS_HOST}/delete/" . self::Qiniu_Encode("{$this->bucket}:{$key}"); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array('Authorization'=>"QBox $accessToken")); + return $response; + } + + //批量删除文件 + public function delBatch($files){ + $url = $this->QINIU_RS_HOST . '/batch'; + $ops = array(); + foreach ($files as $file) { + $ops[] = "/delete/". self::Qiniu_Encode("{$this->bucket}:{$file}"); + } + $params = 'op=' . implode('&op=', $ops); + $url .= '?'.$params; + trace($url); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array('Authorization'=>"QBox $accessToken")); + return $response; + } + + static function Qiniu_Encode($str) {// URLSafeBase64Encode + $find = array('+', '/'); + $replace = array('-', '_'); + return str_replace($find, $replace, base64_encode($str)); + } + + static function Qiniu_escapeQuotes($str){ + $find = array("\\", "\""); + $replace = array("\\\\", "\\\""); + return str_replace($find, $replace, $str); + } + + /** + * 请求云服务器 + * @param string $path 请求的PATH + * @param string $method 请求方法 + * @param array $headers 请求header + * @param resource $body 上传文件资源 + * @return boolean + */ + private function request($path, $method, $headers = null, $body = null){ + $ch = curl_init($path); + + $_headers = array('Expect:'); + if (!is_null($headers) && is_array($headers)){ + foreach($headers as $k => $v) { + array_push($_headers, "{$k}: {$v}"); + } + } + + $length = 0; + $date = gmdate('D, d M Y H:i:s \G\M\T'); + + if (!is_null($body)) { + if(is_resource($body)){ + fseek($body, 0, SEEK_END); + $length = ftell($body); + fseek($body, 0); + + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_INFILE, $body); + curl_setopt($ch, CURLOPT_INFILESIZE, $length); + } else { + $length = @strlen($body); + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + } else { + array_push($_headers, "Content-Length: {$length}"); + } + + // array_push($_headers, 'Authorization: ' . $this->sign($method, $uri, $date, $length)); + array_push($_headers, "Date: {$date}"); + + curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + + if ($method == 'PUT' || $method == 'POST') { + curl_setopt($ch, CURLOPT_POST, 1); + } else { + curl_setopt($ch, CURLOPT_POST, 0); + } + + if ($method == 'HEAD') { + curl_setopt($ch, CURLOPT_NOBODY, true); + } + + $response = curl_exec($ch); + $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + list($header, $body) = explode("\r\n\r\n", $response, 2); + if ($status == 200) { + if ($method == 'GET') { + return $body; + } else { + return $this->response($response); + } + } else { + $this->error($header , $body); + return false; + } + } + + /** + * 获取响应数据 + * @param string $text 响应头字符串 + * @return array 响应数据列表 + */ + private function response($text){ + $headers = explode(PHP_EOL, $text); + $items = array(); + foreach($headers as $header) { + $header = trim($header); + if(strpos($header, '{') !== False){ + $items = json_decode($header, 1); + break; + } + } + return $items; + } + + /** + * 获取请求错误信息 + * @param string $header 请求返回头信息 + */ + private function error($header, $body) { + list($status, $stash) = explode("\r\n", $header, 2); + list($v, $code, $message) = explode(" ", $status, 3); + $message = is_null($message) ? 'File Not Found' : "[{$status}]:{$message}]"; + $this->error = $message; + $this->errorStr = json_decode($body ,1); + $this->errorStr = $this->errorStr['error']; + } + } diff --git a/ThinkPHP/Library/Think/Upload/Driver/Sae.class.php b/ThinkPHP/Library/Think/Upload/Driver/Sae.class.php new file mode 100644 index 0000000..82c77d6 --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Sae.class.php @@ -0,0 +1,106 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Upload\Driver; +class Sae{ + /** + * Storage的Domain + * @var string + */ + private $domain = ''; + + private $rootPath = ''; + + /** + * 本地上传错误信息 + * @var string + */ + private $error = ''; + + /** + * 构造函数,设置storage的domain, 如果有传配置,则domain为配置项,如果没有传domain为第一个路径的目录名称。 + * @param mixed $config 上传配置 + */ + public function __construct($config = null){ + if(is_array($config) && !empty($config['domain'])){ + $this->domain = strtolower($config['domain']); + } + } + + /** + * 检测上传根目录 + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath){ + $rootpath = trim($rootpath,'./'); + if(!$this->domain){ + $rootpath = explode('/', $rootpath); + $this->domain = strtolower(array_shift($rootpath)); + $rootpath = implode('/', $rootpath); + } + + $this->rootPath = $rootpath; + $st = new \SaeStorage(); + if(false===$st->getDomainCapacity($this->domain)){ + $this->error = '您好像没有建立Storage的domain['.$this->domain.']'; + return false; + } + return true; + } + + /** + * 检测上传目录 + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath){ + return true; + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save(&$file, $replace=true) { + $filename = ltrim($this->rootPath .'/'. $file['savepath'] . $file['savename'],'/'); + $st = new \SaeStorage(); + /* 不覆盖同名文件 */ + if (!$replace && $st->fileExists($this->domain,$filename)) { + $this->error = '存在同名文件' . $file['savename']; + return false; + } + + /* 移动文件 */ + if (!$st->upload($this->domain,$filename,$file['tmp_name'])) { + $this->error = '文件上传保存错误!['.$st->errno().']:'.$st->errmsg(); + return false; + }else{ + $file['url'] = $st->getUrl($this->domain, $filename); + } + return true; + } + + public function mkdir(){ + return true; + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError(){ + return $this->error; + } + +} diff --git a/ThinkPHP/Library/Think/Upload/Driver/Upyun.class.php b/ThinkPHP/Library/Think/Upload/Driver/Upyun.class.php new file mode 100644 index 0000000..c159526 --- /dev/null +++ b/ThinkPHP/Library/Think/Upload/Driver/Upyun.class.php @@ -0,0 +1,218 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Upload\Driver; +class Upyun{ + /** + * 上传文件根目录 + * @var string + */ + private $rootPath; + + /** + * 上传错误信息 + * @var string + */ + private $error = ''; + + private $config = array( + 'host' => '', //又拍云服务器 + 'username' => '', //又拍云用户 + 'password' => '', //又拍云密码 + 'bucket' => '', //空间名称 + 'timeout' => 90, //超时时间 + ); + + /** + * 构造函数,用于设置上传根路径 + * @param array $config FTP配置 + */ + public function __construct($config){ + /* 默认FTP配置 */ + $this->config = array_merge($this->config, $config); + $this->config['password'] = md5($this->config['password']); + } + + /** + * 检测上传根目录(又拍云上传时支持自动创建目录,直接返回) + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath){ + /* 设置根目录 */ + $this->rootPath = trim($rootpath, './') . '/'; + return true; + } + + /** + * 检测上传目录(又拍云上传时支持自动创建目录,直接返回) + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath){ + return true; + } + + /** + * 创建文件夹 (又拍云上传时支持自动创建目录,直接返回) + * @param string $savepath 目录名称 + * @return boolean true-创建成功,false-创建失败 + */ + public function mkdir($savepath){ + return true; + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save($file, $replace = true) { + $header['Content-Type'] = $file['type']; + $header['Content-MD5'] = $file['md5']; + $header['Mkdir'] = 'true'; + $resource = fopen($file['tmp_name'], 'r'); + + $save = $this->rootPath . $file['savepath'] . $file['savename']; + $data = $this->request($save, 'PUT', $header, $resource); + return false === $data ? false : true; + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError(){ + return $this->error; + } + + /** + * 请求又拍云服务器 + * @param string $path 请求的PATH + * @param string $method 请求方法 + * @param array $headers 请求header + * @param resource $body 上传文件资源 + * @return boolean + */ + private function request($path, $method, $headers = null, $body = null){ + $uri = "/{$this->config['bucket']}/{$path}"; + $ch = curl_init($this->config['host'] . $uri); + + $_headers = array('Expect:'); + if (!is_null($headers) && is_array($headers)){ + foreach($headers as $k => $v) { + array_push($_headers, "{$k}: {$v}"); + } + } + + $length = 0; + $date = gmdate('D, d M Y H:i:s \G\M\T'); + + if (!is_null($body)) { + if(is_resource($body)){ + fseek($body, 0, SEEK_END); + $length = ftell($body); + fseek($body, 0); + + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_INFILE, $body); + curl_setopt($ch, CURLOPT_INFILESIZE, $length); + } else { + $length = @strlen($body); + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + } else { + array_push($_headers, "Content-Length: {$length}"); + } + + array_push($_headers, 'Authorization: ' . $this->sign($method, $uri, $date, $length)); + array_push($_headers, "Date: {$date}"); + + curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->config['timeout']); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + + if ($method == 'PUT' || $method == 'POST') { + curl_setopt($ch, CURLOPT_POST, 1); + } else { + curl_setopt($ch, CURLOPT_POST, 0); + } + + if ($method == 'HEAD') { + curl_setopt($ch, CURLOPT_NOBODY, true); + } + + $response = curl_exec($ch); + $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + list($header, $body) = explode("\r\n\r\n", $response, 2); + + if ($status == 200) { + if ($method == 'GET') { + return $body; + } else { + $data = $this->response($header); + return count($data) > 0 ? $data : true; + } + } else { + $this->error($header); + return false; + } + } + + /** + * 获取响应数据 + * @param string $text 响应头字符串 + * @return array 响应数据列表 + */ + private function response($text){ + $headers = explode("\r\n", $text); + $items = array(); + foreach($headers as $header) { + $header = trim($header); + if(strpos($header, 'x-upyun') !== False){ + list($k, $v) = explode(':', $header); + $items[trim($k)] = in_array(substr($k,8,5), array('width','heigh','frame')) ? intval($v) : trim($v); + } + } + return $items; + } + + /** + * 生成请求签名 + * @param string $method 请求方法 + * @param string $uri 请求URI + * @param string $date 请求时间 + * @param integer $length 请求内容大小 + * @return string 请求签名 + */ + private function sign($method, $uri, $date, $length){ + $sign = "{$method}&{$uri}&{$date}&{$length}&{$this->config['password']}"; + return 'UpYun ' . $this->config['username'] . ':' . md5($sign); + } + + /** + * 获取请求错误信息 + * @param string $header 请求返回头信息 + */ + private function error($header) { + list($status, $stash) = explode("\r\n", $header, 2); + list($v, $code, $message) = explode(" ", $status, 3); + $message = is_null($message) ? 'File Not Found' : "[{$status}]:{$message}"; + $this->error = $message; + } + +} diff --git a/ThinkPHP/Library/Think/Verify.class.php b/ThinkPHP/Library/Think/Verify.class.php new file mode 100644 index 0000000..6e5e38a --- /dev/null +++ b/ThinkPHP/Library/Think/Verify.class.php @@ -0,0 +1,293 @@ + +// +---------------------------------------------------------------------- + +namespace Think; + +class Verify { + protected $config = array( + 'seKey' => 'ThinkPHP.CN', // 验证码加密密钥 + 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', // 验证码字符集合 + 'expire' => 1800, // 验证码过期时间(s) + 'useZh' => false, // 使用中文验证码 + 'zhSet' => '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借', // 中文验证码字符串 + 'useImgBg' => false, // 使用背景图片 + 'fontSize' => 25, // 验证码字体大小(px) + 'useCurve' => true, // 是否画混淆曲线 + 'useNoise' => true, // 是否添加杂点 + 'imageH' => 0, // 验证码图片高度 + 'imageW' => 0, // 验证码图片宽度 + 'length' => 5, // 验证码位数 + 'fontttf' => '', // 验证码字体,不设置随机获取 + 'bg' => array(243, 251, 254), // 背景颜色 + 'reset' => true, // 验证成功后是否重置 + ); + + private $_image = NULL; // 验证码图片实例 + private $_color = NULL; // 验证码字体颜色 + + /** + * 架构方法 设置参数 + * @access public + * @param array $config 配置参数 + */ + public function __construct($config=array()){ + $this->config = array_merge($this->config, $config); + } + + /** + * 使用 $this->name 获取配置 + * @access public + * @param string $name 配置名称 + * @return multitype 配置值 + */ + public function __get($name) { + return $this->config[$name]; + } + + /** + * 设置验证码配置 + * @access public + * @param string $name 配置名称 + * @param string $value 配置值 + * @return void + */ + public function __set($name,$value){ + if(isset($this->config[$name])) { + $this->config[$name] = $value; + } + } + + /** + * 检查配置 + * @access public + * @param string $name 配置名称 + * @return bool + */ + public function __isset($name){ + return isset($this->config[$name]); + } + + /** + * 验证验证码是否正确 + * @access public + * @param string $code 用户验证码 + * @param string $id 验证码标识 + * @return bool 用户验证码是否正确 + */ + public function check($code, $id = '') { + $key = $this->authcode($this->seKey).$id; + // 验证码不能为空 + $secode = session($key); + if(empty($code) || empty($secode)) { + return false; + } + // session 过期 + if(NOW_TIME - $secode['verify_time'] > $this->expire) { + session($key, null); + return false; + } + + if($this->authcode(strtoupper($code)) == $secode['verify_code']) { + $this->reset && session($key, null); + return true; + } + + return false; + } + + /** + * 输出验证码并把验证码的值保存的session中 + * 验证码保存到session的格式为: array('verify_code' => '验证码值', 'verify_time' => '验证码创建时间'); + * @access public + * @param string $id 要生成验证码的标识 + * @return void + */ + public function entry($id = '') { + // 图片宽(px) + $this->imageW || $this->imageW = $this->length*$this->fontSize*1.5 + $this->length*$this->fontSize/2; + // 图片高(px) + $this->imageH || $this->imageH = $this->fontSize * 2.5; + // 建立一幅 $this->imageW x $this->imageH 的图像 + $this->_image = imagecreate($this->imageW, $this->imageH); + // 设置背景 + imagecolorallocate($this->_image, $this->bg[0], $this->bg[1], $this->bg[2]); + + // 验证码字体随机颜色 + $this->_color = imagecolorallocate($this->_image, mt_rand(1,150), mt_rand(1,150), mt_rand(1,150)); + // 验证码使用随机字体 + $ttfPath = dirname(__FILE__) . '/Verify/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/'; + + if(empty($this->fontttf)){ + $dir = dir($ttfPath); + $ttfs = array(); + while (false !== ($file = $dir->read())) { + if($file[0] != '.' && substr($file, -4) == '.ttf') { + $ttfs[] = $file; + } + } + $dir->close(); + $this->fontttf = $ttfs[array_rand($ttfs)]; + } + $this->fontttf = $ttfPath . $this->fontttf; + + if($this->useImgBg) { + $this->_background(); + } + + if ($this->useNoise) { + // 绘杂点 + $this->_writeNoise(); + } + if ($this->useCurve) { + // 绘干扰线 + $this->_writeCurve(); + } + + // 绘验证码 + $code = array(); // 验证码 + $codeNX = 0; // 验证码第N个字符的左边距 + if($this->useZh){ // 中文验证码 + for ($i = 0; $i<$this->length; $i++) { + $code[$i] = iconv_substr($this->zhSet,floor(mt_rand(0,mb_strlen($this->zhSet,'utf-8')-1)),1,'utf-8'); + imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $this->fontSize*($i+1)*1.5, $this->fontSize + mt_rand(10, 20), $this->_color, $this->fontttf, $code[$i]); + } + }else{ + for ($i = 0; $i<$this->length; $i++) { + $code[$i] = $this->codeSet[mt_rand(0, strlen($this->codeSet)-1)]; + $codeNX += mt_rand($this->fontSize*1.2, $this->fontSize*1.6); + imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $codeNX, $this->fontSize*1.6, $this->_color, $this->fontttf, $code[$i]); + } + } + + // 保存验证码 + $key = $this->authcode($this->seKey); + $code = $this->authcode(strtoupper(implode('', $code))); + $secode = array(); + $secode['verify_code'] = $code; // 把校验码保存到session + $secode['verify_time'] = NOW_TIME; // 验证码创建时间 + session($key.$id, $secode); + + header('Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate'); + header('Cache-Control: post-check=0, pre-check=0', false); + header('Pragma: no-cache'); + header("content-type: image/png"); + + // 输出图像 + imagepng($this->_image); + imagedestroy($this->_image); + } + + /** + * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数) + * + * 高中的数学公式咋都忘了涅,写出来 + * 正弦型函数解析式:y=Asin(ωx+φ)+b + * 各常数值对函数图像的影响: + * A:决定峰值(即纵向拉伸压缩的倍数) + * b:表示波形在Y轴的位置关系或纵向移动距离(上加下减) + * φ:决定波形与X轴位置关系或横向移动距离(左加右减) + * ω:决定周期(最小正周期T=2π/∣ω∣) + * + */ + private function _writeCurve() { + $px = $py = 0; + + // 曲线前部分 + $A = mt_rand(1, $this->imageH/2); // 振幅 + $b = mt_rand(-$this->imageH/4, $this->imageH/4); // Y轴方向偏移量 + $f = mt_rand(-$this->imageH/4, $this->imageH/4); // X轴方向偏移量 + $T = mt_rand($this->imageH, $this->imageW*2); // 周期 + $w = (2* M_PI)/$T; + + $px1 = 0; // 曲线横坐标起始位置 + $px2 = mt_rand($this->imageW/2, $this->imageW * 0.8); // 曲线横坐标结束位置 + + for ($px=$px1; $px<=$px2; $px = $px + 1) { + if ($w!=0) { + $py = $A * sin($w*$px + $f)+ $b + $this->imageH/2; // y = Asin(ωx+φ) + b + $i = (int) ($this->fontSize/5); + while ($i > 0) { + imagesetpixel($this->_image, $px + $i , $py + $i, $this->_color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多 + $i--; + } + } + } + + // 曲线后部分 + $A = mt_rand(1, $this->imageH/2); // 振幅 + $f = mt_rand(-$this->imageH/4, $this->imageH/4); // X轴方向偏移量 + $T = mt_rand($this->imageH, $this->imageW*2); // 周期 + $w = (2* M_PI)/$T; + $b = $py - $A * sin($w*$px + $f) - $this->imageH/2; + $px1 = $px2; + $px2 = $this->imageW; + + for ($px=$px1; $px<=$px2; $px=$px+ 1) { + if ($w!=0) { + $py = $A * sin($w*$px + $f)+ $b + $this->imageH/2; // y = Asin(ωx+φ) + b + $i = (int) ($this->fontSize/5); + while ($i > 0) { + imagesetpixel($this->_image, $px + $i, $py + $i, $this->_color); + $i--; + } + } + } + } + + /** + * 画杂点 + * 往图片上写不同颜色的字母或数字 + */ + private function _writeNoise() { + $codeSet = '2345678abcdefhijkmnpqrstuvwxyz'; + for($i = 0; $i < 10; $i++){ + //杂点颜色 + $noiseColor = imagecolorallocate($this->_image, mt_rand(150,225), mt_rand(150,225), mt_rand(150,225)); + for($j = 0; $j < 5; $j++) { + // 绘杂点 + imagestring($this->_image, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor); + } + } + } + + /** + * 绘制背景图片 + * 注:如果验证码输出图片比较大,将占用比较多的系统资源 + */ + private function _background() { + $path = dirname(__FILE__).'/Verify/bgs/'; + $dir = dir($path); + + $bgs = array(); + while (false !== ($file = $dir->read())) { + if($file[0] != '.' && substr($file, -4) == '.jpg') { + $bgs[] = $path . $file; + } + } + $dir->close(); + + $gb = $bgs[array_rand($bgs)]; + + list($width, $height) = @getimagesize($gb); + // Resample + $bgImage = @imagecreatefromjpeg($gb); + @imagecopyresampled($this->_image, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height); + @imagedestroy($bgImage); + } + + /* 加密验证码 */ + private function authcode($str){ + $key = substr(md5($this->seKey), 5, 8); + $str = substr(md5($str), 8, 10); + return md5($key . $str); + } + +} diff --git a/ThinkPHP/Library/Think/Verify/bgs/1.jpg b/ThinkPHP/Library/Think/Verify/bgs/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d417136bb4613984dd88ac25590d2794344c990f GIT binary patch literal 30428 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 literal 0 HcmV?d00001 diff --git a/ThinkPHP/Library/Think/Verify/bgs/2.jpg b/ThinkPHP/Library/Think/Verify/bgs/2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..56640bde4ed9a02c4e46b951f2108e5e4814b14a GIT binary patch literal 29677 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 literal 0 HcmV?d00001 diff --git a/ThinkPHP/Library/Think/Verify/bgs/3.jpg b/ThinkPHP/Library/Think/Verify/bgs/3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..83e5bd9026f9d46c96738a4209f1abb91f7e1e6f GIT binary patch literal 32109 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 WW2Q1p-X6|ip1UGwC*Qp@{Qm&B(nP5M literal 0 HcmV?d00001 diff --git a/ThinkPHP/Library/Think/Verify/bgs/4.jpg b/ThinkPHP/Library/Think/Verify/bgs/4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..97a3721bc777a569c6665e38dec04a82df9176da GIT binary patch literal 29081 zcmeHwcU%+Q((ooB34~q+1P#5H1OlN-mr$e^5etL>p+iCu8z5Lvl%j~(XevcSk)|k! z4Y5-M1ylqZB8Up2qVk;$D(Lgv=U(3L-v3yCJ3D9E&Y3g2oMUn(QxB&4AU+!_iWP)F zA`q((s~~9VDeiz}9Niy+?Cl|C2!hxlEJ6T60Sp2DK?rdO&B7tb7qRdc9)wW*A%g@m z1PDM#0-R9@A`2G+_^z#Vi0y}N5xCxiNCABiq`>#{YU&s6OT#+`hBLz%f#DH&a~C4s zgC6D=9?O7;nwmQL1fo7s2T#z_*VfV3)CMLXR32i+w>;!8``7bOzi>xb5F8i;>W3_5 zc0Uk{X8M7UpWP26;-?LSI`Wro4%)Oi$QoQhM5eD(vttR<0C#E_;$rn_wYfcnm>Pk2 zSS`pe0oI)Afs!FEc6N3Sb}kMME`ClNC%-T+7Z5$HB<>Iz~B@Dhz+ z9l&AuK!HSILu0V)95_yp4!<)I5EAv1$p<0W5GW)Y5{<>MqfuO1fXR^((`GWV!p=>;TG~-bt>##{OqbE8@2CutNI~Uhd5bJpKg&ynOq~U7ZvC|P zXOb_sM-P0kQrJ_JQnmL=$Fs4c)mQI7A9weU*^+&%=33{AkGe}e0%EuBJ6_w|^>TtA zLLz~;Y%HI!7&M9Hf)qi34R{bKEr=#2W(dI^G1YRUROjY`TicM+yCwBN~LU{-|HdR?l6I+s{eBL{)d-?XA*`BhKOuB*t+HB~~MAdH8 z6&XK6_Hf3MLlUY(Ws#@v()rphJ|#wfedd~b9N8tg)+jwBkY}G*5~)Wy40#CA#*@v7T1 z0bU;dG3LOl<3%;~5-)kY?zNHdyS@|^MN{9%S1wdrcp*(ZiP@doRCD{(I*V<}G2WM6 z)K&T%E!rpca?sZC3H9*Dje#MMJoAXauF_3Y_eMCQC)?=wG^|XeX7KgaN>ZoeB5y8{ z&1~Gbs*4TZNFSVe+eb_py^k##9L|$R@LyyXrJXeq=;u{9vt8NIFI&N*? z^H}+C=*3K3zK<5B+55%63Ayf8kzYNwJ*{`QK+%vX*ZS|tr$}GfnaaD54J@x74(xuI zc8~g{gm|oH1*zLIEqI&JHtS+74H4JwD+k1FnG#hS774a>El9Xzo+q~Wv+VlNTY>6q zAyr8nePpH1;PSVJL^ouT?iCe<@$yX6uL{`z`B-(b_IX3Sa?LfH;*HJZ^_TweRWa64bT4NSKi8g z91Y43Td_RzWmsyrDI{RGWzxCdNZ4Vjb&OMO&6>3zFB+_nNLGH(Wqxo#J&7k>`{Rfk zu|gwc7;(>UM8P3RkRdM4@ja?0l=}$Xkknf;eoX#q{>WI1rp~$50uRHA?a%H~^`G!q zg9?-}S6o(kq0xFBow3lnsn_8wrE|rK;EI^e>iefSc!Co*H&omSZrrfYIllXa=c8f# zy3KZUrHhe;Plyuuy(0VSJXf{9ts>-IzZWO{BshWbJuCNP=e<|>O|Ei5M{~PGw(gh8 zs){TvU$8tqz*(myFyoVSLrs0>!_}qc31crH?4ZG3obt)~;jm_d;9Ryv;tsM?kQ}F* z?&@@9Iw~?SUFtBj@qJmWGcy}&F^ugdr>weZdvl0t-on@%lT~rKcPq_3(ewV@xXe5K z+Ez*XSEMhBCD*013mCS0cjn$)QC@Aj@lYzZ$h${k;FLT5y5aZN{yiD%dK`&%u}JE~ z-It-3T!UjP%{MnO4sU46yQRD3M$o$x-R55AtJx+gIbRg39Q$4%U!;%rH)Gc=YaMxW zY~pM-u{8Ilw~2ArbqMb#XlvL|>U+^db|1MalR9y*_~Bu>+abff_qcc<#fJ+n9KC<8 zkrBJBOMrxWl~np<*EPWl0}StL4=%n^d-N{6G5lqy*<{J$?%Nd$&GV{z6JPdK#e1Hr zesloI)6%)A^tt;^Z}D#i%Igll@4rUr=6J7JbLwrh>xKNp&XJRzLmXQUJ$}Yv&82$v zdW=yPgI%_{>LXz?b^Blq+0*-@VNI*$$O~djBkUS zPMfCMhTiAmKekV7I!mj!Jt#Y>;E>ySe$p-nb+^ee4*i0?jlJutXN@e+x)oIUXpEio z7yHT~iA4*`^yni`l8&#F2=m)=?aYX=ToB>u-s($tj}Cs1-5hf?$iGKNTd(u2s%(-M zr7y1NYeghq7e{bk)vJEv?9{9en>RO?j8PIe(}(IBJLD=<t}JMe8o#y8@vRHc_{4 zL|p7!``11D_br$D7{>dko&ilbywLN%aEED5J#1HWZ2@G+E>T!;fa#~{HPQG$@-+v_ z>aK)Rt$KNn=*Kz_ThSK{UR`0vFW!D_R^8e)=sdAtY1`MQeZoNu<l6U`$9O`akcCqEr9k+YD zmc4LjY<~1mI$lw~pkJ+|d~E-w;{nCDb?%O=+fd(;rd}(ZomSo$Y=1kjo1`B({6*zJ ztVaz6(w4mU=#Jsn)`)Y}{h#Gdp)72-B;NjDs@~{X-m!XUf#k`|N9F1RU&?e1O0cz> z@wXTfLFApmCZ{XDxW^wk{A4&k+HSzFDht~|M~tNLj_lpBN_TKIecgpS!Fh)aYCjbn z4|(x@H@kv(2Wdhf~A6J7=bcTI!k**^FY(CdZna@Uh@50P|48(>!=;3npvGpI4Ti zJd4LOqp~CcnzqQzqQR5ZIF_W?ul6FA)eo#T2MYp#nAXCv+F(iu%YbPueBfA9L>NP| zG{<2SOb#{t{Q)NgKrk5v2jT}Me}C>T3Xw(k0`R8{iA7PHp|EUz0&NJ2029u?Jy5S; z_TRFm{(pGzz2Ojn0|{YREqK}wL82`9H-IIh$zpI#&nu@Piw^e+?kPN_=4DaXSrj;q zfQB@gK7WkRB0z;$)AQ-ri^EM6n6dtd72Jd%M{r&TCpthm5Sx<&sX+wLg75I>F`EW! zz?#1uvju)Kez*oV!=gh%zy%Hf#y31V%#Y#V8$<=IA9~JICVXpFyj;QBCL}y80JK?$ z9nGIPyKTk{5B&L2hGB%zsWgUrh#dp85Wn3A&Ef&F064L%2E}5QZEQeP zcyz>XBusb|J%AoY3yTf~I$V}6uyqF(4Yav^qM6}KX<@V|A12KY5FppJ5wst|xM^XS z0&^�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
5eqK))^(a`g}#Vel}`0jqQ{$m(#9bM;B`pV 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%0Dja6n-|cc61%dSLS`lUd7W-JGp5n>o88 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૦ilU=I8^3w$Rh1aryLRN1@vEe`O?@pzT+nv6 zBdxEccv@0i|2-w-2XoYD++K!SClg-v%e|^vckm}skW|oe0OqHcYhVsqna^#_fKmDz z9Jt&kt>+uNM+-?8k2HOTzai4;fh--m{Mk(%NIKBQ<;ioak+iW9-{0b00W;f1zrioM zjj)3zWdUgN6^t5!f00_7p zha3lh?NRbE%^;LB02mkmf&c&z14IxS01847@DG460|Yo2030DKzhF1Wp&vYWAP*b> zOMn&JeIepFm=1(5UqAu)KV);j<10YQWdr~O4*Wc-I-wnroW`zb4BF2X?airfD$Z$% z@^V54`T^o%V$$+(ad~lRPPl};l!UyPB&Y&_-h%wL?H1lI{ZDQ|f5FCBMsOp9K!5NO z?(PSK{dYeg{JZ;s2l=T3B7*lzH(BwHIzR_Jf*#p9ZtwCX*m)f+c6$~e$Mxxix&Z*$ zo&zXxHNYtXE4e)cL;>W)#Ka`TxJ^th0v;?l@oU2G`>HV;nTiMz-v1H*Ka6UP&s;z%=Sw`E^CGbK@ zes%l6)Cx6#hX-1VkF$x0kU$D&0vDVHAGE-So0dTQ%vCz91&>U+<|TL?M|~p`)J^FT zev%7(gjfTJ_@$b=K_jvmoixp*G03rBCj8G92>do-dk`Rp;@YGJRDj0^Vfgy8^A=@a zKW-T-lUBcB9gwwRZR=mY-%iZQsCDLji?h236DhN@-aq zBXKZ1Sde>3USyovko`IB6mH)!8jJbPC6yvC*M8}AKeG*(){-S>czVO7qYwuqY3B_-UJ3RydRGCLuvxQO|AeS+LICJPX9aTK1ZS;& zaj*8IEPX+;^?EtGU37SzzL~Q1IeDN9bb;etCZZkn4Rv-^7!lW%$#P$`ZJ3kAjglF) zm};itI5ueXX6>>>LuOs}HlQo>uH+GiW~fVj`izx|()jqDV?ORJiqahm2P*8B0*fJ% zl_?cI(>4Qw%Zpq2M&Dld1B$jP7q#SkZaxu9iMbwV^!Q$9-dbmzmwBhbpmTA4#p&6| zaQeI>gzPW!M^p_eb9@mG3sl)i5b!zH_v(B%YU9~?)Z$EIj2d%z9#k+&cCOHUTCj2a zIOa5vU#BeOPBW`Y$-(y&$}V=-;@OP*4LfKNq_dbKh$zzLIw$G5%kk)#V0eKIy)~E5 z;^YM4o=RvY1!czl;ZU#47dAmk=up;AEL;(6ihXvey1j0ld>)YwYb;Faxtoya4Ef__vYuHPzGoEJrXmTik}cdWKC4td30-^k+|)0 za`R-7HA8jP#Skjv`pfq5T`m)A{V}{}fdfHTM~Yv;D%P1fsz_4fX9XWLnH1@low;n- z$M4VI@6=N^{7jXYH9(S~2Fa$oR$YoD7nV9-^>}04O4n7T!#nE5%cn(pUn;ZL=HhED zyzChtuvk+CN3h)DS9zg(E=5-S{@D2ZcD7=6w@b*`ZldznG0WbK*IM2r{<#tm~qnfRx5~&XZ?-aOZ7 zIRdB_iMKpo2t3S=^>8mFFHT#LM)q~!hY6(j+m@C!;2lX_4bI!l^3!UzuU>I6s#O_G zQLBC%Z~SJRi`$km$yF|?+TA_W#>#4Dkpe%?W6L&)bnTii-o2NFosh4*ya?sGTSgpe zY5BFq6bR-4&)4=M7Zo!u4Aok-GV~%FTi8dU82lCevK%%@CH&@}jKn8!Xsm@^*zRh6 zu^ymeux?+YPV?Tjr?4dO!F_>V?-ZGJy&%e}nZscfLrpwBln7O%Lz8x2W?-Y;jAv)r zHS-gCFGNnhN5)Z}s(pXG^1ZrSUyF>Xy<`&$?+F7c#Eev;v?9gb_lp7j@7)?(#gs$0 z=27!%+d%r=xs>$B9Rd33Gp5u1v+r|Z3dpk-TO{cjJ(q?M%KDBk?p^6-S$~1ac*=-rAT6v*V;Yc3mippQ?v*Da7du?m^JOWp(0SB$<$pe)!W2Hm;8dX%NxwOte&RvN6OWvjTGzc~YWZ*0$e zStf76St$-mQyB3sAw zs(V+9Lb}TBgGxfl(k7#)T#Jvagvp`5UUh4J6ncaTzb0erZtakEmwRA1pIScrCBh(& zoy7e?A~CvqMaaA>262|!A5p+d#-YZ(m8gNz@QJ8r`7P-P0NlsN7x#Z-;`(ER;?kM5y)vcjtC?Cucd8+96zDmrt=SfAIoqRTiK&#g1)Q`P32 zQY9|F{i?1$Gs*VtKxON#>Bhv{&|&*TnA0jX@r|ghi)%bX7hgARcgLJAcUpd#`B{lR z)B5Y66Y~1P>m}>riWc(~_wRU0LEjD6Bx%3;zJap^DYvugEYhxJZU!VZ*_7KjIbB~^ ze4F#7a?L)*^d?8U9vZ6IZp z;@DEXQcmdGFL7QfN&I2%4TdweP8qLID*7wQ>B@JP+Bqg|-e1x}_m!5OPA_n2$vfz{ z`Puzp=3M9eHqhvEeJSDo*e7om_bKndY{KxQS)dt9s%hsi4z8`<$FMNsH7)%pXBCZ2BqI;YT}1k&a6P2MqlXHw~ZuFrt zY$=AU(B|8`0rqn4qnz zsIO-?JAB%T`nF(AUy`&yA8iCxq&{c+JQQau4DV9?l;j<1LyfR<%mrU<8RxWsDR?MFCjGCP~Avy8JHX}>er<<6-nYA?a zo&1GoQSZ^yhKBTFxCPU{KoR3dCLP|R-3lpY|m1^{yoc(5D^1p)pQN;`Ea+m2XN)C0!xF=?$EF^ zB=DXe@8=OG$B&~?{uWV*0CqG|;BeSEPY{mt;IBG6PU|;Zodj2c1!PBx6jujtxv@Op z>uJ9*;yAnwSpMXZ!eNAeV{kfG z!8!nVgY%Yu+@K$^|9_!j+s3*G-0%QmTn#&W+?hY(fPX4*d}25XC2ofDtH4pP zeZ%$_J8_}HVTf@UtT#a%-jSXC$2hTrSO7QI+Zh37Y!wPle|~rjTLl1PaDM{s1TKQE zM5(EOFaQT@Kc_|@_-osZ!%AQ!|JZgj{3QG^4O{i{_wWD@ST_(lqW!&`{EQsk5Mb?x zoCyMhZ5rn_GjO8pf%b9%>$vSG(iyY6u7*K-{;1>3FmZAHNynN-=;wh#ApI;nj{1Q$ z$RF(x;AkM42Hc5o6N9$(#ziylp)tN#cDUg;lMhDgFgnX zjr2nLI$)4aAObK8_D23-Cf#AiVz3llPZv(`@Zat~)Zf>`Ow-B?Z0d(|vCjV!W8mk4 z`~ySi;DIr7aQOpAi9mvK$RLcapN^TqQAae|1FMkuC;AU&5?8cuh^hz5<)^2pb~>-~ z6AeldIU$`L{5>!rND_eb#ry-i#ZUB~>|~BE>SzzN?=I(3@90<4{zU*q07JAF*4HE$ zw6~Ez#t->3Ft9XI4=|Aah9!4IW59s=8=edtV%NX2v@&^jzSrI{=u2JMI@mDnJ!*0?>dXfCMK6Ho_Cft>{nTnjrZ8bmw4(?Y;sujTK%VldwFqM}}YA`aN!lSL3{PthO; zZ&A32m?)sA66EcGa7SV|9g*NXRf+pceG@k)%1MdaQp!Ngz*`;ZiqZ-3MVf{fnju2m z5pqu4D$1maLGnSK-kwN|180z@hnJsxkPgphYe`+7QHlu@1zE=W-~Z={Q&=x^+gHWWp%4ev4p*GsH|zu^C! zf#8_@vhW`V>ksQdZh3WIqyq+=)`C-OW!x|6|NN|gB6hg{VE*kM#a|?(2Dk>HjD4&? z7%na>E^YSnu>v76S$Q$BT}pO1|DwYH?Syg;{!eu5;{2-)Fmjw6Fb@BbmfgJng`qGo z_}7Es=_&e8QRas+LH-Wte;h_X>@tL?3DOVk?~6c!?7w3g8#Um77#PTFpb`GqxYX59 z_V-6Q$xDi9h-*qnX{k!eNouIU;i{T&byW#jH7Ri^F*!*|Dctb_uH7?YMuV#xBpb_F1g=oe>wGcCx4yX|5s-{2Ic%e#0!>!yGZYL zpduD44=x~rvEhKd>?y+)MgNA|)dd{Q3Og1a{y2UKDKYFO3dfc|WOvj5Dr^03WOvj5 zD*Id1+zW+K7XQ)QU(kP*G6XvofYd;_ApJlYDQTFvlnl7bg1a0{Tv{9^E+YxKRX8(N6!BYMn z30SPWF)9e@VWoiruN8jSXaKL@ilYBW+2!TmbnD-evVREfBK%blw_<7v-Zdbxv4_*O z3-wp-KU^juEh{YvlN6Ja7Q>a`Fv-BhcS>;_W~Y<@@lx;|9!VHhN(NVgLkbQThyhCw zBPlB-hAYA01YvM6!4e#P83`~ruqDV3mU7rq7UTp=Y+Djws9;NMD-vJ`VM|aJEOD}6 z07=3nK`OQctp&>+j0_Hz#g=lQonVRMk&^;p8F4IL1`IZ^1Vd61E(hwwmRMOiS*)y_ zES5)37Rw_ihvSih<6x{dIh-~*Ic$q^IGu7>OT@+C(l9V=VPL4kq+v2JS(qFQbPXI1 zgNwu95-_+V3=CtC2`&QzV_qEO1mc|3`7q-@NBS@F-^2YENnQu#2j2Ari~hV8 zW3T?dskm4K?=g_d5{jb#gx(dQzsb7l>c+lkXOst0+0R5|7PV+g$KwS@hT4@-B-(qyJmm?+*UAS^)L#H?k+^?z?N>U3>Dto|yNeYu{aa z^1z;$_oHjyU3>Dto|yNeYu{aa^1z;$_oHjyU3>Dto|yNeYu{aa^1z;$_oHjyU3>Dt zo|yNeYu{aa^1z;$_oHjyU3>Dto|yNeYu{aa^1z;$_oHjyU3>Dto|yNeYu{aa^1z;$ z_oHjyU3>Dto|yNeYu{aa^1z;$_oHjyU3>Dto|yNeYu{aa^1z;$_oHjyU3>Dto|yNe zYu{aa^1z;$_oHjyU3>Dto|yk-bdmmk{}ky3zH}N0zFoRKMs5W@j(@_~#7Il`s3z`J zS#q7DC@(Y~m=em<3*(D@K;O#RhLh+em=cNv%t-|%e{w+hc^jB&Ve=LN;KTcz*r)ih z@b7K8e5a~JUtNzNA_xV97IF~60pWuPLBt`l5M_uah7lIdscNs4g?>1gBUKQRWyk~fQcq4e9@Rq=Y?qpB~C>K;1DhpMI z8bNKK&QM?IX=pSw8G0L93ax{-Lwlej(9h8C_=Nbh_?-B{_zL(s_~!Tsd>{PN__6qD z_;>KD@LTYE@JI3I@wW&l2v`Y(2;>QL39JZQ2m%St6C@Ml5mXYi67&&F5G)fC5;719 z5Xunh5Ly$u5rz`R5@rzIC45NOML15lL_|b%kVuF~iO7h^fe1r%p6EJJF;NpyH_-&q zDlr)`2eCM@7O@Sn7jYzUGI1gC1LAJtDdKgK10;MT@+3wiP9(u3@gzAUbtIi66C~@T z)T9EWN~C6_Zlq^OQ%OrnTS?!Nej_6z<0g|MJ4S{giy%uSyGQnfY?SOfISsiWxjMNW zxj*@3@_h1#C5&0|N(^sA;L;)JD{v)Unis)E(3x zX^3d}X*6k^XrgGcX&%#z(?V%^Xw_*Ev}b8^Xj^F~=m_Zq=yd7a=wj$f=w8yz)6>vP z(p%66(Wlcl(vL7e8Tc7=89W%`8SXRmGpsSPF{&~m880!GGIlerFtIQlVL~!RGu>tC zV_G}Nc~JA9`@yRRs}Bw{<1-5~A7c(=zRBFm{F#N0MS%su63ud-Wsnt*Rgl$$HIy}* z^*QSj8#|jen>X8aw#RIr*%{c6u)DEeV}HOt%|Xkd%z@&##?izv!%5Gn%IU$G%Gts> z&&A56!{yJF#r2YFom+t0oI8^HF844GDUU3VGtV`iW}bOo4qgM^Q@lmIgM1`>vV5+5 zDSYjGEBpfdmi!m^tNA|)FbU`iga{M~ygfv5Nac{vq1%Ug4-+1iJ?wrsgxA5piVKP(#M8xlB`74cB*GP~v zYGP`>YL#j$>hkJg>JK%b8rmAs8ZR|zG|e^BG~a1)Yq@9@Y0Ya(X@_b*)FIH(*SV@Q zpv$I<&@IrN*OSo;(`(Tu*EiKq(;qu3c+~f3odKSKob($vMIKql=PDqRX_a zifgj#XOucB4YlB=2cSS*z=@ktrv}#yH^XE9UX-3@)q(w?>*up z?~~*+@2l^d=Lhk#_p8Csfj3jn{SWz{^B)aR4oDAJ4YUX>526n83VI$a7(rT3qhYFHS*IbV9Zxrh^Mpr*Pef=(+&M#f#{JBTNU_L-$i=gkXX~Qa zq9UTk&uO14I!}4t_x!*Gg$tP%@h_q-zPu!H>H4LuXs76>F=8>vF&nXn*r##uxYW4q z%g&cy#!JWFyh3=z>q`Gsm8*9Ws1rgG#uE(^tFCcgi@vs)bRwxESu!~@g*3%KWhC`z zYRz@t>sPLCq`9W`rK_diy}^3p(v6i2r;P5KM{bs8vSr3(uH8c2dYz@6Rdt*Hc5*gh zc0l%Ijzvy;u54~W9&=ty-e$g6{>UAZJ1qq=1qFqyh4DprMgB$8#dgK7N;FDpN`*_a z?lRnsxd+_yzc*8MvaJ8U{{7~1+49l~zKR=_w3X3SkgDLS`D)kdks9lou3Ej?<~oJC z%6gId{06Ru^hWx|_y?p9q8@BFg)}Wa^nUpHk;|j;<`d0BkF6f}wj67D*{a|Aq)n^s zal2}J(-Wm94IT0wwNGWARzH(|R`p!!dF2bK7nLuiUsiR>bk@9*dsWw^*!7@WrTbBj zMo(L>Ztt@`!@jP5^ZwTZb^{}?onC)@-_+vwo~!^!Vh-`#$H`2F1x z>5;}!jnU_0rej0ni1AM!{61_JAdKgrRapjYe~r|sn^qPXXoVR z<=-hNykA~XSyf$ATi5)!rM0d7NypRfp5DIxf!A*aN5{rLOiWIFoSs?u`fYJ(d1dwc z8fZ2L_)VH$>4e;kVf?wk%JE{xA+-iIyLYM zDUV2aBuwV%9euk<8F(c}`4+HU!}aVx>saLfs%O7C_Pbwye9mOVxp%B4W2y$lo2(6+ zd#Tw!k0@6R z>~qvMKr^*D6KU%&0I~gKQ>-?lpI1lcR3^7x@$oZ@NksGv0aYrt0sjy>Y7eY*(57 zn>L%sMB6K!bP`YMdp9^9-E_a( zJ*gP>{*GI7>78y8jDj%D!b9k3xzc*0I$qy*Nuy)8>ex$2>SsJ{pA+A1NP+gmR;VaE zcyagxfB1-b7V_4NpPe>!wN*^}B$x9{EmSegeI{S?vELNJ^hTzJZmB<$CsfkoOQhZ+ z;ItK4cmDiq>$e67KOv=@bR<bw-ndZcTeX5UpWX-nk9RqkoJi_ z$MOcNl8`gsN;sOaf6^xxq#6c_3o zi3?Fi^dJ-8v5IZ^Cx&LbGc@SC1ah2cq}~Qd-5IK!4bP;D4)SaRRxxMZoTvJF=Tx7- zc6WhQUye-Fj77u>iHVwb$l`6+i1CFbwK^)gSk+0J*GxXR*(71zo3=6al?40NmLm8&+k$8=zw6Ch}P#70ea`BO9X3ggJJi+kQ+;TOC&&w!!8KPk6KI_5nV#a zFb0x;Jp?~PNrm>`A|aHEBjA=sD$DGnUpex zt#3is%OZmS=gMNm&@tDDwmaSNb5ElmFVS}>!kq8+WsueuaKtRJJRz?RVNX1D_H3$J zRaj7=-zRp4dX~}+9#(_ll1ZMm0ggU9`;lkMS3-;ECMxGOAM%+Rmkaa-Ld+)SmoJG* zhm6H*CW*Pb8djWWC_P%xravt7Y_v9IOX&+|=Eo6pp%)w`QDg3>Xe6R%^Dp+ilgkk3 zeRi`uZ1hysTSH*zZEs+mV%5Syry%is$}^8n8r>`zXbQfAb0DQg>joTUAxCAkC-uU;PR7&og;_MXvth& z`Ir@&<7 z^kJr?`sI0DJo3Yz&P-d(CK$}z*XBKWg^YeAbU-1p@@lcuS_WU8EvhUg2Q?+zHnE<0 zSx98;gT7+Itg&5Ue2MQJ>F?M34qIE5@aFh|mtyui-J>~Iw3)q}WuXNnjmc4haY_`C zS8Q5cFdZMRT_I_Txv?}ptv^N~hkjmWQsR!hP=-$M__h z5I;TJT}{yoiygR>-@(Ff*!HZPqd5=bbm3ru>vNr1&k5*<*G746vlTMZ3rr=G_EA&r zoU$r$?z1c_)*{iXS2{etAg>-RnG}yb{Qe2Oqy>$Oc+T~u;k4=4Y{Wse=iSv2TCyHt z7SRS*5tCL;)t;+yD52j8jNo{*WyP3!bK}ToiO&y&%EmO3F4*;!ePV73uBYxReL9Ijp}GYlCbQKJ2w{<-1y91`B!@crr4?=NL?ezoG3_>$lK`EN6!Y zH#F&lZmD+-gKeoySa(o*2+?u#Hk0<@xrZ77xkjt65E1@ zzu2FC37bqpX`A#ts29BYO=j9gEGSv?;kzca%DAKQAMF*Lu7E^Bw!_p{jpxzPFp^NV_Ipx#7+W$0{^pal75Us(?oIZ6|-X|*M=izN7d^gkH z&-9zCe94@!n1A+J=R!1kJX*Ll|Dj6XJHz}};~PSH&yM!y9;~QMwr}*VZ670$3$+QU zV(k?SE#zCJtR_#_cuqV+>1VH*7?i+R*>V#jenX-)vAT9fNl?Jt^H8*GV7KNaMS}v{ z;STDmZ`qcDWc<3&%gt3|y%lW?lUt7i2gME)BJh_wr>%80g<4`@5%JR3`Z^y zp0cDhEW1iW^T{=O>!fRntk0{F{=^%Z4Vs5qg>2d)T>}CKV#hvp#u{; z-6TF4o)l=({^;Y1T%iMB?Io3SQmMMb9xoPc?yMIWJ?(Qgh zZRn#+;75P04d(JYqkI_ZY~DHhXrTz@Psi+NqP#q6v$IUwU?n57O{gBdmm|cE z&1FZ14XLEmNBiTP>a^}+U=9cxJbGKc)JOm-^@3G!*-O86U@0#z@j=_l#Zz)Q&kCo- zIuO)fOOCb@t*k=h+p>BZWDvn4tEFBrhOx1+Zz9EN5x1`1z`P_tE?M(l&u(p-)qTIF zNkzL#tG@2pU=hGL=%>$3F)bnKL3KPozPYX_j9EcRBxuZakTcy(-iz$f^mp@h(x-=y zuTY`24Aygn5zPi{=d!Kdw(vfHTz?k!c07qg-Q-c;jIT|}<+w0j(Uaqy0gq_b63?`U zbV~^huGFTKJ$}~_ZhNT|mG->ujCA#UF!+@~OBHFQZV)Anr zW6cjMcfum2toS;if&_>1mNcswyIbAs*WaG`&YSUAH_uj}IDol~YfS$8^H!J3C@eC`XzpkP(y(LlnGed}1530aLQOFxqL z1K@YFQE(#VoFoCmSIzM-y~88ELmAhKT<&oC(~DBrz$;5n^g9R?mbu$>NxEkt>F-82 zgmJ& z6vDy{2A<2I3gL&=nc(m3MkWx1E*p&oAw5IiWD@?x6 zh>_9tYa*$1{iKlHDu-D#O!$5oaA3@wb8TTTxBnEpX!9~lkY@@hQAZ8t(bOjm_htiW z(+k3Vb7ox|!iDX=-TEi<9cf~&sCxBbP{%o~XBD4Wkn`bp(9T;b5#RPdT&v_kZ91`P zfHJx?8f_$O`HX5qu?AR~wUs}8w?HJ5%iZ~H%}Zln_cYA3p1cB!U_$Km?A%WY9kMh- zhsKo%T-n?>gq}OS?D^*4ch)ZP)|ad<(wBopRADx)C1-VNDSaZ7SQAj>j@<8n&R8)! zj&CnqnElQdyzUuPowKa(sj$sShO?*-M}NQzb7wd~WtF*P`R(p4L7@;S7dGbFI8CBL zA>AyU?3j-^?B{}SSAk!Ft8+n2UiB}CpjzguR+-IQD1sXlYy*@@9!(ZfUp5^ZD=&T9 zYA4O;?dYnsn9dt?dGkW=tPL+De<=LD|Fxq2Ez_Xx#~t`K5L*oNsgVEA~*r@;TycZNH!>}_G+kBcX8 zQP*C)^)dJL;)dH&(dr~q)Ujb}0#=a^&O9I8N!A03XA!RQ)PcRQ&l?VG&2PgVysim* zTjU$^?FIUh;?XXBrcj;dmy3bg2vguHgj?Giek4wbak>=4T-NZ6Pgy8VaEVrsjIsQc9mBt52 ziD%#TOGF2-p~KOvS=Aq)@4HJkn~uVny2@%M%B^40crCCBKH|^OaqgL@w%Yg%>9k>2 zI7m%2cnyEON$dp@RbpK^()TK!zTqs9$w?)d@N=Bi=T+(p;2Q<+^?l>+dBEU%>T-RI zBp!=Y$!DcFDtBcwHkY8cY~82@#suGnljRyKca>^AXcW3SbzuEKZ5QkP+p9T79FJd! zb1c2%ogQE+xDcfXXQWw;)@ol@8MRhyZdi<^upo`~zt)irepv1-&!=L&ZQw|FbH2Uk z!)N`I6AN1_s}{%K9~7q3GxNS0Qq@7wbjED+S@H&j5@KFwy(&fE_1P^c;qmmwXAj8A z5zDKxlrTP(T$YAI@~E%dz1gM}s{DpCX~7MfrK6a@quAR>Z= zB1NzOiX8=&V&&Te3;I0wxtI65_dkZ;o}4*t&YU^B%xzLi3M+V3ib{lx95f?z>FVBcjCEBgTv z{nZbM%*uX1AwO(Dl%YRuGX*W11I)osFe1yJODkhZrGea~X@G^+CvTh$09kqiu+eG& zA&IKFGzO#rEDQ_`j0`M{j4YhYOw63T>?|zoy!_nUyxiRUoGi5R{bRZECxoyvGqbX> zar2B4R85Ly8p zrWP2OC~!IiJp&^XGnfv)Ga&#J_Jhd*K;RG<6b_}MM=;RASkyo!Ck!smg`&ec_;O1` zB%={LIb~;zB&B#A+x*njDJgw8dg)xJnTc6^qy}GkeX5Lco-@Iuy&_6;UBAmD@!i?9 zs{_%G-elY6xhYZ2Wz}TCSvK8(b|Auo*v1=o6P{0WPTq2eI2JvqH&Btx`E2rJ zC-s9hwo z1mv~r4XG3CSRxMGp_IN(zp$l^{Jf?Tn-wd?cUZ(^uJ2mUv2$-knnjs*cWvVH7YP`O zpFe%RxmK_T|6tl(fOk4V30_aIlixyVT;I{rkg=e%ZGRT;sjz1p0-NfT6@BBxtdlz` z@JISHY&RVHQhTN_`c*#Sd*J{3CJ~el|s_P4OKZ)-b+b@( zWZzZAclB~NT`86-5E(qgp2=M}pY%*U{X*(w_QP#^vN14c*WIk0qhrigY{P61>6ZYB zD&>W)`HK`?*FyZWYhD7ou#pLM{iNIFAx5Rd#^fb{&!AndSM;+8N$ilaE;eGm zoPf%zJsB+I}|_R+1XnauY5}5tZwU| z208qARXMEi<2in=s~sylUMl_ zJVS3o|F%2>OzqbXMhq4WwvdPAVUgK{uL*Ih*cnl{eTl(q(BPr$mBGUDSNoH*x5wRz8 zg>7%u%kF#9&fB0q7CG7$nK}xyNP>tiRAnz*5iSwsABPD>v3_0H@R3m>L$+fHctp+< z+Gb@HizDc?Zus`*(Ubz@`m{^)Oy37jNG|Yos(^7FpL1-sIp@djDVOYEAn~Bo5Tcyz zcs;}3L5JQq4|}?Zwot7hg6AWSH?614MuN{Yx}f{TGh$c4D@4c3br z7EU^yl&J#yE36c}dw0>t)^GS;1_@uo&!$I;eNwv^9M5Nj3>e8(TA2ajfQrjGTK1oG3U&)8AGbNBFUwQ3s?-?ED!p$eyR+_c& zGVL}=Dg+vCahK+5%f*uwyIbNkwatpN=0}Fn#7Sne3?oUyBJ){qzwIxiPXY6W#}5?W zR}6c4;}h%r=U9va9h@$!E96GN@SAGb>rheqN6apOa6LaA2_VxP%$)dlyw>V1P;6wBmcPCsII{*AAC z)TV33iw*BoeEbyzHjJGt87FM`_Gu^CguM0Jupl`+B5`Y-(Dt_{1gs+&W2#*ICO5qq zH_ZAX3*WU}<>Sdm*%Y*he?JfiSQo60LNmz+C%Dhw?VpH-<1I~d zB^v5SnPlm>0`=w%# z1T0%*rO~J}_BfiPz|XoU&FXtvoskAX09n>zqSe89F;xbf+fxe@ZJtcUpft@nDhke! zRs8)yP6!CW897)W-zjB9#Xl)%8r>6wKV&p$6s2Djn$1P94glfctov^Z>^YVFx2&cA zA0D-BYKXuB1sG{H>b(yDh|=J{1T;xi8k>E2iv1HV$C27UYM-fh80<7@1{#GLQIG~T zTt2>!)pBbzM`*HwpcDWDC-C2+My&#XJ$Q712VH=CAbnvWpaf8FqNpwWSys}h8dS~S z%1VJBjPI^dtK{gA5b%Q<3WQ%+G?@@-=NE(rYv1)8@ln*KX%0Jsw|XIA#_=x1-U(I;(GVRG1l&Or-cm5eXmxaE=creivq17N%0D z9E;EZB>3^a?msM=65?#)?hH2dJ-F21|4y-q3?TkSLHLG5Ir|3uMq|ShK|NwzltrYu zvyHW1SXc<*$5KzNb3Lb_aIu2ew zK@@yyzz^Q@uRSFI|NpE#C0Kh(u=bQ-?J2?9Q-Zap1Zz(T)}9iqJtg?>KP8~eNX5a4 zCjhvB2ZTB$We2bT0SE*903v_{Z|MR-JPJG_X;?TY!ToO}kN^&F0+w&dX)P?@lhZGM zPX+Cd@wNz=|=?bvh*b0 zUbrHGBoXu^+%#-dZNhQHK$3X^h3J%E>x@qb#$yN)2Kr38aXN9K;i1GRUu0Zp2su(G zPEUenTnEIdWEBY{O(ZHX~Oh_bBmqPH@aWpplt_jxkB)+>C8yl+} ztEL=A2~a^{Fc=k8v-rGj$ zNk~u|`dOA;B;bFx6As>?(OMzkRfr+PP$D@h5)21Q<)?8_SC@d-@eiYf`bO!KLVW{> zDna4I09}<|)bDNRs!$tV;RvmlR0Ds||DA*2nEdqcZwKo)??7oC9EIo`1>Tf_H-`GO zZ!-V>yINi9aQ`O!D?GYCSwv&-4qBi3wXPBht%TNe{_(Z0f~vNTs_F_O%aVWCun8lO z{Nw)<8!IILv;k%g!8gkHKeDn?_J1%GHa7n}7@?smf0t#EQI5o43H`Uj7)iZ}R&gXo zhDB5GL{R)!PE)f691t5D9pf;3G&L_RjP;|VNdz5DtT7H_idM&JV$_X|P$;Yk3WrtG zHqtK^dX3TK{Zwh2CFm8WSm`7$U)x z5*A9!?{HsAB$3)dJ&7MZT$#4i4$?xTL;AxRZ7o$C){txZ zm=r*a1a&kt710`6;Gqp37)7)uS`n?K4jv#)TU}8@O%*&)AP&;C)fCYfO+__Tl%g6+ z8$2+I8ffrPLxVKXj+QETfU;WDx(3Jx4>ff)^afM#^wLIQv8E=*I5i_yngGd z{4Hlilz;I(eram|rnrLfry}inpA$H1AX0OWW@`oMPwC%6rlzT_sjjH5iqTZ1L2#I~ zQ0Qfdrcsum8c5ebEz77YQnj>b5FAo)xIhXBL5jMzhAIt$!wKTxV1f`Fel0aHIj9hn z2O)+EwLwV`Qrl7kQ-untt*C)1M1`O(2x+=t0;!|aK`s@7-hyzMqD8~CsSpGD2|}6- zMgzpP&{Vn>m~0>fQ&Js;0c}ztRTra8)x~I2WiZ-Q84QLdgF(@7sx=JF8U{mc5ks?y zp?ZQ=MQJL6X{!jPx}v6{mZG*IMiC4R3Z;lbE27jCQR<3d8iPV8Ek!Wr(V#4NCV(db zc#dd*rwf?v;E4g|`v#P%G0p_7jx$xos;Q$fY8vWl#+qsvO|%gf^ce>xIc+e18?pa( zl868JG1=c|`cL*>)BPt)$D9-i&U)fieq4*GSN~sJS}uZf45Gf8uFBt$S7hifwgnDn zPYLrUg%I^49nFl87A81N6h>2139Sq!`QOU@V`!GI3g9#soGh#SoGq{L7-|$A z8GPw97JR#OX_Cbq{5ih2y`!C}g|!LoRaq8uYZ5sO3NC0AN{*sXf1vN~;fbWbON+$$e!)~6N?k;WhXOhX;`~%RU>R4W$t>fBRGbh>2A3>? zQhg35gc7KD2Z(3HL{pbCf?0!jdJKse3*vV{Tq-0wlmz0`m9=<6iN4_SN^t5LH&RhV zd?1Lcf;bDs*%1fgy5KrNECDNUzZG~CxTX*&3*f@S<7unxAm#Auk>Fb=+DLO^YzQ$b zO35C4DcF}nK;pte!+pu|0I=L=susZYmp3G6CivI9X^JT>=$Rr|OnHtca z<=+bYl>FDoiuRPL?frba1ZljyC=-&J)p&4`rDzH=lKOTPQt8hm{y!_OaBGDhir~Aa zL<$jnBNXWhW*Lbb01h{qK%%`(O(OsH4$KM*|3|eI7O3cQUV{jC`W&pPFFvx-Y95-u5SK5kaZeq`t^PXzq$Ld-%{5yc|0}*H>iMG%efOB+4;-e`s z%S<@+gAQE#jve3u1inN^1dao>Kt0d|Tm;&HPM`-|HG2>k1IB?V;1w_f%mbgnb>rwEtPpO9 z07MKT4N-ugA=(gqhzZ0R;t278_&@?6WJok*3nUe?8&;)22Gz(e?Er*_hHbUE=JW0fGl@BgxsdrZ za~Jaj^G6mg7I_vN%SM)HmMoTPmR6Q~Ebm!aS!GxaSv^^!ShHAuT5a zuUp_3;y2_c@@Mkb@elHU5SNaU)>Yf(N?L(w47{i2seUx;yw>5GxX_KRH+ zdx_*lVv!-pLSzSWMqF6jTs&I5O#HU^X9-ye7l{;!I*A98Op@A?{*wD8+a+hD#H4Jb zwn&|n8kJ_0)|L*GJ|KNvdO=23#!Y6YOq0xWS$viId!b(L?b8mb|xm8xSXE|euI1=WIjhn7d<(Ix1+YOHFeYTMP$tIet_ zs1wx>tB+`KYuIS))M(fEs;Q+((X7>cp(Ul|qjgYgSer-NUVD#r7lsaFgxQX{i210a zsS~ABuk&_;(uUv-CpJ9SmDMHcR_Z?1lhE_kE7Kd-N9z0Nm+Frjh#UAClp9PMN*WRj zs|=rE*JFdQr?78~RE;P`4Mqz%9o$x2t1-;j%sA8dwh5Psi%GG`LsJRU0MnDEGiDlQ ziDs?lbmms(Ip)I_A{Kay;}$cPT9#WaJFHl&oUDqiCavYIDb_7EP#a5|Je&Kr(zayV zCOg2+!YrUKG-cBc+=ADh4_c@Qb$hkzjw7asq zZgf5Fy5MH)mg_d|uHwGcy~jh)gXD4Elit(Sv&wVM%fu_+Yigs$#`KLtn`Ae|ZMv~p zaC6A!OWy3>zTRhj=zQFKPWXKBwev0ao%6HsJLorq$Kem)Ul9xm`w1_Idc<7fGk-n* zT>s|*dI5O>F9Hn$3j$x0aHL|=Y>;`-k)VZOyWr~Jr4YA}x=@BtpU`G97de>R5hfZI z7uFZ95S|`B9-$MFA2CC*qEtmfA~!{zkKzF*Q#YgKqIX13#OTMA#4N_T#-5Gij3dY0 zjF*q!75^;3G~s9>JQ1JRzD0aX@|KCM*sW#TAlv-5wQrZ)p0<4|$t>wuGE;JJ@~srr zl-!gLscxw)X(DM!X^(f9?KqjvmQG0@-nn7tkqmeSDdYAowOvKKzGV_JyLYSZF4+BL z4}MSgUew;gy-QjCS-1CT?mL`~$R=kGx=gS!qc93mX*KWuoo_K3)l-A6u^l1fL) z%*q6d4G&J5LC)wiG3K3jWE`rN?=?uN`pNMn5COjBUf z_<4`>ea)85?JXNx8ZIbbsJ%bz4B&<96@%v8(P^ z?{+wJ+`eXYt*g_t^Xhf%^($R^T^DcY+-UCB?r!SQ>}kBIakJr;#;u0inztM8Xx%yA zi|K9Y)9t(5Z_wX1U_8)y*Wzx^pzUDakjv1>@W$cs5yHsxdm;B`M`K1mk0srwyPx@h z<3Yhg;fLjq)<3En*BrkzVLWkj(rI$+G5+zZCy`ISOr=;s$-n7-(J z>Gg8zRrssVuhXYFrw_i7dvors;oF-t?lV)fl-Z?sd)^DYul}I^p?%JN?%{ms{O5(t zMS;a*AGJST|K#@R+2`0V^j`|UuK(Kd&Fb6zrBHCK*%nw|Nq(X!5-*>LBK5%e(m@m z0PgqzPHVyKKB$H2FtxzxU{C}E_`c%<6bf6({e3M*C|lqE#D zXuYdAdsS}zz~lF6`zz1=?y~8M-6~dYR`Hw9|KRfPn@T+I^^}k}VJf+cM_wXJ{zQ3_ zoDhHrNp0hOsu3k^-p&jyuyc;Y4a?lM3g4I=XotlnrFELu)#P?RCas@kA8cd_72p;M z-jN^dskeUfBU>lArcT#g#j@_rMr@BlVm_hki_C~to;Q;qvz$$RIo6U}4!%Nfab!O~ zR5&Kki4~$t>-WHnR4|pk$Z1?0mk~l5hXqS}0IsD5YU8S7Cj9RmF6^-~70N!EBw3@H zQqL$_7IrHZj^dS?pYQRN*_Eylp04H7UgB@ABqzY|T&vbyfGgFvJ635XTP`9%)I3AM zq!xFA(0wjjP*pdkeWTZ$gmPO(^nH6j_q;&u2i6phpw0%-k9>W;~6b#nj@Gnxn~`WH82z~o|cRSH*ILr ztsc;mRUgPZr@FB`QaNsV&bs3 zb)%i%VY6Zx&rc=>BUA8@r;OaY)d!d+YQ!({iZQyo3BhU3GcUZHD^-qY}hI~S$X0eBtB9^Zd=sNgA`V^ zb{v1cxvmY?yTSG`8kd!RY_3NmFsAy9kZ)CQZ3)D-$l+;j_c^41*}jdTo!JjKkEe*` zeW+sgG(U9D-+xmZzfvPA^ybrnSIRF>*zv&Mj1Bl^O@Y##98*cOTbd!s~E~gq!Pyy#1sGa7Zbi*mYWh2$YenaUv{jC<+OK1u8+S; ze$8M-UjL50y!k=h6+Wz}Wa2@}J^IkNT_Mc&7b&9h3Hf@Yg(n#g_MjnRlqtSfB0a|+ z!*^r5GT-qoEVMdt$PM?~S1sDvx$j`Qhz0*q?y~MF9wEO$3df(>6XjX#G~#p8yy!{M zHSK(ic%8_NneLlj@cf~fJ2RNa5?}Kk=Vw=04mA<9j*5?L&3T(6j#mp>2)Mha+aTce z@O6NtDoM-ns}EO43wv@)a?@qYV&iU|e%y0|6TJOK%yoNNDLhk?2Pz-F2rHC!OQT!d z9+VW~c2zkEf9z1YSYk*mBX&6EW}!tJ_MVr*{fGiYQ33}ekG@6^BSm$$ZjG1ySu2l0 zNwQ_D2D`&$XVyky(yn{Decmalx#7fH-VPUBzupr{9u_3>DtJX@Bv88OhkAEprC-@T zkbmROHNlZ3fQxPK9OHDFk3|T>ql8q?(=%;ejmDqMU-1<4r`7L0;nv(^7_rM%yd~w~ z{p(V>Wi@71ChI>uM)rteoh{6xgO>o$XGa8qQl(gR-d7n}H>>rPdVAGUZyx%T8FTf< z?xp_MsGFz9IwUt+Pn(57ijF=W4??EZ&uf=A&x!jYkKrf0SqfVsWR1MUl~roV7}H14 zMYfM}nZ9}Jb6sc8h2k!|Gh-P%7@k_(pNBE??LQNA_`zw%(e(jr2l@LCp$LMR@k&po z#2mHg%i3H^%uIBwT<^KRIro}!+@w&UVB#Z0N#V27m!Sv9}Tf9eEGd-wN5rQ4v!eMlRyM)wO5R z@N$IJn+CJssshdzXJzT@I@s9wHp|R&6vVPI+Nag+;<=GBfP8Q@eX+3r(CIUrmSlDe z<8y>kvbmnVN&+O=duiZ#r!6YmJzvidQ(v7tsb0l&k;ubgXRTTN_t*Xzz_yPd!w83y~(L0mf=w98cm}i)!l{1 zY0!b}juMs>tdKFm&Mrme(+1WNE*&R{M;$t@kE}%Q*3|5Ql9h+Gc^#2QTxQ1Vnm&UE zXI*&f8g*^#JD45EIz^EME)N4}1H(&OeUUY^z_4W43ZL< zdKbln4>oHTnse3fYsq^2@|~_@O@|cM;Qg%6PNDTeS~soI`X-rlkfzPLP4a1Vw@ZBw z$OwiV-#p|Y)zISM-XxacRugD9(`x#E?FVk&|6=K{{?@lE^`rGVX*< zgjCFSH-!S*Ff(Cbwq3$Y*Zi$_N6o9&({}{L95{hpRy$c6?~2sxd6`LY zm3%~IUX$XRa{r>-ahkY2JRvoYK87oav&CFlxl$%6d&3?jUHMPed<*hh&Efm;ZgWhh z$Lh118P_SWK%RsD&jw#P>2AyCGyZVkb?2qvw?*e>Bd5;r9A(GU$hwGK)T2+p4{|(| zqBBpSq}*$Y>pkPWHJvX4+NuY&tI-|sG~rYyNmZQYS&Y2_xv+PC9_RLsjc1fvG9>qu zVPtSJkH3=0CNTXbQ zMB4Re*EjF%cAEx#Mure9bfZS5=l$Bldvc}W*Y=u$vFPx2zLRFU5yBW6s&uj2xMwcq zVu!k(u0mQtAs(gs)%%uPyt1-VN$)NRi+vr}F5jEsf9J1^^Sv6#99;CeO7Vi~iPL&! zces=)xuHc$(}V42clz7acV7V5_oB~obUi6D8@sRWdP>2$`&G<{Nbc_W0O{v(y-f2x zsT*?>ubk7Xf8M|&KUcl;We=3M!qQwwq1eb*5Sp*Y_)=#|s;bvBjfnN_%W!&k1KI^g z7i93r$FrskL~btfQCzgVa4p71EkXHM{!|yI<(8RQ_+mh_vS>p`vgGFg!t@o-uL4}8 zgOdKKu|u&+q?eca4k3L+g2n^~u584H9JGmSu{fIFe3uE98_dunV#xqKa}I}oottX3 z_4Yjvz9(i#5wWGA7bra~<}TRzTRCwY4#a!+HuD~3bdwC(y}^;ew`J}GPUO{&b*8;d zT4B4$Y4^$OU{?~Z`f+QWPJ6m>;3xd ziCvc|@~<>x;{#GtuZmsMuyA^~drab{$m=XDT~(bxMb?yOy~FgaZGwAWVe|5A5fIeK~I16J6n;$e}Ax;-`yX)9X9V4*R!@Z}xPBSS_<^64=l zBe#48v5P$N7O&oQC!AG<%54po_q;C1ZOD^W9lG#wD%-R)31C#Jv|Ld3i2p>o;ru~q zO!-nV%C!5s$Kha{dZ0!2wvPhsA2|sL=#3sd7&ATwja=6_3 z1ya%Qfna%(Xp!7tll5VtsDb-3S)Kuk15KQplTCu{DhVy6T{9jpD-@*sufOc8q}cM+ znprwt(vA0d7J9&Clk4V&hWqPg7)BxNodh8T-iOs33Q^sA)8$?YBVLTe_H6Xw<&67+ zd~+q}%2lK#QGWZ<@JkM(+3voU-Ws_`Vi+9p{6ZBYnL*qxgfZ8%zDRygP=sHpb_K^| z#dT(`q$|17J__~}LzMb~O*#}|04Bq~U(G(Rr^+J3At-a7Xd#1@|I;U?$KExS8u;!t zHOeIf)wr@c&dYt(2;F(_7(DrXtDk_GdQw$IF;=cnu&X-Xc20lMioYv0aOd5tl^Np9 zFHiex$cAGdUQyhu)uVZx(5+u#4KByO=Df`>{M1Uu(5D9~-EH8EaoXXU z#3cHmmLGBI>?BsY9Vf6{~@*G+M;!awop!ZHU%1H1& z%)ctX(}Uw~S$W=zlK4k{#>? zoEDR^ML5$m(@fjfv7X~Oel~Qs4L6VzoTt!v`y|7_o4)szpU>3-n?~f`7LAX!cT0&Y z2jKWk7Bk;o=97jYJ$KO$SS#|>ax~M~jlN1lrk!^TyTBAR8%;-#Z@JooJ z>Tw1&+alyOn}MKck*xJj1U%N}_&i0LNzsm1!4V>UF*v0lze0+=nnzv^r~H__IG~&{ zGW_-GgW=b^^4@ea&h3=iN~b)x*u3Z4jiZTEXGror=sV=TCE$Ydhgp96G#3R`kqu(i zio)3?hWh+3dZW85J>bsQ=%gOvuV>y+a9Lw7*#wc2Pe_zOY zMwz2Q_Ggo?*vvK>9+k0>D=l`O`*yQXv4#ZBvWAlSii>2dyfZuZA9u8pZSJyu-R^bl R;s=GvM3(ZxPL-v5{|~;{ne6}o literal 0 HcmV?d00001 diff --git a/ThinkPHP/Library/Think/Verify/bgs/8.jpg b/ThinkPHP/Library/Think/Verify/bgs/8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e10cf28115e47d1f5413e76efeccc39cd655b6cc GIT binary patch literal 30188 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 literal 0 HcmV?d00001 diff --git a/ThinkPHP/Library/Think/Verify/ttfs/1.ttf b/ThinkPHP/Library/Think/Verify/ttfs/1.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d4ee155870e0011906a24cf200a8f6a11d2daa0a GIT binary patch literal 57520 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 literal 0 HcmV?d00001 diff --git a/ThinkPHP/Library/Think/Verify/ttfs/2.ttf b/ThinkPHP/Library/Think/Verify/ttfs/2.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3a452b68f3baa612539ad43cd427725e96f44100 GIT binary patch literal 28328 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
h?Dh;`UO$BEWkO}@9qw>ZI2^xAN(lX_pcD;%+W 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 c%R7_2IG>vddcb)xU-zgoC({L12w|r z4v#MIXu52Dmi?wo(jYB?o-O!T=J)OWQgi*V$qWdw_y{z3VnfzoQ^Bo8NC-Nv6tqhw!7ZtLP8;x?iGmW0x|sSh z0iz((<6-c`vB&U+mpn;%aom+FoImt!qoQle@NS*+vBgq*sQR8I&goKToY89k;w2mH zQxDx0N_|p1`RMSv#&nkLS`XHA5^#d$N2V7dFpKrNlv)bZN~Uy5qVQ-?noC9_0m0;@ z)(MH-&ip@|ZrEOy!ik`tdTb<>N@|tL4a1!+R(432UD847aH^c3GI(^D>z<~+qYw?w zRCU{(uLd7Iy!Td>D(I;itVrAI-J?621XXc6bs8-;YYUy?H6U1=8JoUt@9KQKH_}?m z;!@MD>9(qrY9VQldceNf{tx1T=^swSqQYjA#z}H^hr_Ow)J}(0v>+A)A{I1iYPXg2 zRyp#@n2M4oNik838**_$%pG@aRLvp|PN+*v$Y0fKZmRRNrI&dc0!Gbz*ZjV7y*+|) z?x;hjJ7Nl&{QHWVDB}e!mj;FB0pke#Ae{xj+ospi2!;6wHY^2FlEWyeD-!`h&7!kR zb!Ev;ob*ewQ(j3m#2U;7*^W!WE5@|BF3Pci1;Q9^y4eb4k`XljGH6XiZ@7BV=f)b+ z9ss6$_23p!^gOzxVML6=HO#@narpbx%Q|Ibt ztv;d3k7b3nA$#2zWd0{Z4|x+%HLa<_`r@A$PD1HN&K&c-s#(q)^Asa?4fltF+1>v* zXKo^eO&x!5Z0WgAjd{leO&louskbB#YGoqkgia zniL#|Glbm+b3egYgM9!*0H2CKer~?GqahHU)$gmdIBlwZe7d4z;UWuy9``H7a9sGt zeSjT{AN~}uL-4=gl&xmC)HRX;;G5(>V5-370F(_y5tcgqgP8m2vF=Ad{_Ufm3}tX* zW5wf-yk30nTNU@P{ej1dej0Q9-+B<9KX-_k$;^X*oMiFuz{yI|Qt(zr!)}K~pmCob z+iKJbl@;NTLusG9wXzn6NBm%at13CehJ@jK$c>GLNx^oSd%S>!)o(HW-zL zYTp6>g6$*2K!7@xFC5AmYscypO>vLYRCi7=4yOHTL1|dAqIW1-ReXgSB94$Z*<1XW zS~D#kcYE>UacuO%D#lK_OnSAz@&hcALLneNE;64Ow9o$rPq#~6@c24mKrrJ1tGn%a z%Yw~=qpFs3SxVa~*kXV?Bdhx7hoZ%osM})eblaw2a^Pk^_~jFTn+@3o@KTsWucWt2 z#2p}lp0RG3e7PbID1{IKpa2X^7dZsQ%oXY`2^K(#p4{+Rg?VG2HPKeF?LJZTpV`F3 zN^g6s;9s|`X~p$Hwcfb->WKgLJ8sK23@zN&vz)EM0FJ^|;RH`XxNAtJ&tS~i4b+!{B4qfq1AC21=mpNCnzj8CW4cAnCa8g4k6%)FSsFmxfz29U8tl~xv2afbl6~h!x(m|;5G_zUg#NK~ zVVS?eQx!65MYu+-i*|NqYtmNpv&GLecJ{Vs^iTh8s>a~VIK!5RtuncCPn*|~9V}kI zwsGNtnL~_f;6)LPfT0OI#$p0b(*l#AzA6NhQ5Fg9+;*7|z-FG5gaCS&s#918sPfM5 z*t+*v^huAiW2Pkxtl7Aa(lJ%<$kUji6S%S+Gt`j9g{pW=@3fm7ko7f0s)S$^q)MMd zK^!@|GiS4tTupT)ld-l*GL}bKCgLozGG8|B0j>;~Y{^8%HZqf*X@4+~OCq3-=elui zkco#NrBAoCsKi41NT~JHt($wA>C3d+Q}16{)8>f@+TQr;zIW&zDe18r-6qeBtF~9i zXS)pz9*@i5(R%HH27_s8lizorg~4zBr3>^u;S}sy+#U2Ql`^MRXhc|n1b%S0%OqLM z9*-MV8r;@Q*ouPQVwEAwNEB&KP`k_7Jd-n?N|M*hvOwTKWnod6!9Y`_elX1kG3X7m zgJ*-QoonI>yCr4!*7q*!*QNUe;}^fU??taitw4^MP|<2DyvtTPh<$h!F;p+p1-(`? zm>eZe$tOraOtWYbwBz>ogu=zSoDUKGDDV+%f)NO)7s7z4EfBqSN!WPCSX1ltn?0tJ zqS!K)RhiA9+3vb?16d~$LKL4*_~?}4@2Z0)!(L{xQWQ!M;Tu?qio^>h6r%t#%$OA> zjPWw(m}HKxKNmgrYVj|>hp2v<4qW^*#@Wufw-LU$`hrVC^vv$l!n%VI2&6WQOJ(j; zK!#1p05{3@8ejQVM3mR2RZ-m5v$W^pPn#2wpyF;@*kr_Kx7-$-?gwi~DG{MC{|01uUPHFmta&1Gy}2Q5LdHazFzGE+W8WW)3k2GNBk@ zm}!iAC|MS;(2(v=IIMc5LK!zKUbH6Uuv$$?>wF;+QF(QO&<2-d*p+Z;fh=)N2X$*5 zmY~@he63$H=p5emH`dz)RaL?nzopBdv~^kpK~Sk&I*ZxrKikn{!JRRcu#anJlk@W? z^s+KV0|UyDi?KaN_6YA@48Z>WEBply3BN@~TXVq`4h1__c- zORy-Z9hHcfCUN&B%JPF4NRSWS*rjMlcQMy@keWqiRH*2Diy>}UI)7Dtg)XVh)-;30t)rvdv@8f=na&?qHCt=h2Ekt}CM07D_sux1PVf`EV%6vwleq^7(Z zTNjVj2@oCXU5iI5ZgnGtvpx}*{GO(q&5_B!b6~1Cy{|pw*LppwVK%ShQl9{u5t4ME zkVwRB2>Gf_!H6#;B~6k#BKZ<=hiI2eLJZkO@3aJK=cOROjvEK>N zS%J-Nx7n;3Q7sX>PAd|lNx>J-IhLFNtCZ6rEb<|!xs znqUKrd+KC~IEj1ms2!7i%+_SNzTZ!MCU9)o>&uP>j|pREAHMi|!Tm7i7{MIOIusb-Y!B1r=%pXfLm!XZQ4Uht}^TxA{ z<&5JOqK^a~Nk8I$B>LUxG5=@MSd%dJ^g};;==5nkU_HI?mi+?WG8N;imwE3fQej2t zLkzNvu}**#5akI(64LP1@t-`l;{M9sdxc-$H-M0!8r3SlQs{*bv5PK5ktNcNBE*$s zb0J^K4i=oPE>%s5OY<6xMky$0bCKcPLSN3YJl8k9w=0)MeUgICtO`?coWXGa zbR9eyyxiy_#7il9!bqpQI4o^dDb3iPn^2W`?18EBj1zMgRZ0#WF%^tg7?kvUmZgA5 zYeOn2yNg5jDO?P;Y&qc`DGHZyzC8FPW}YD9(2G9@i7 zY_4*7bV1YX9s3N`W>3s%aTia=x+@0uRBBx&uYc&8JtkAoY}AM$yD7S-KHX?EPhDN5 z@|eAWp&ffXQT9^I2^|h~38X-W=^5^$btsTnufpb*QpFTYm4PxtEQ7K9zr53VGevyu z#-HQ9NL-R8tYzW_pM2yr9goYy-#4sL5axf*0 z`ESd=EH@u(+!GeHqNvvSYaPL0*j$@34M*wzC!9{5(qyU%8+DptRaM<-wm1JD)r<%j zdA~4WA{81#y!_mR7TnTYUiJRqIyoZeBX!@ z({;iC=e91kI)}xe(=u7(bh(^P2b5yuN+V|q@iqmjXLv=VrBrNjxvX(R*1%xrvSvcs zK-WOPVmN5ny}(%Qaq5Pm^i&v6OqnWN3u8}Cxpqk9@zk8=7xgu#nFPOsJdY@i6`m(o zL4|S#K^)qDb>Ic9f(ZegLXd;8WJ5APKbx1SxE0HE8}0+AjVT`=*9|_31*F8nH6Y zPi$81vo)G>2EE>()#NNzT=Jdfo{i(Q-PG^^BES@z2UfEjamB>(KmAa@! zf+R+s)l&L@$tw|AN~*gkt2c_Cb?+E;KYDuak?z(bdw>D(MTs9lo*Idpw38i$d__#G zHyJ!8Lj^0l=pd3$DP>^jCZtR>0++YWoC{fUp(aBJ3b&yR25`npo5tlK!VVWLzp(rw z%XlaDR!*Rh87cFLLXRwI;^uaNbZj$6Q3b^=Lnh`G_ z11_Xx4$IVbc>V9o_w;vp^hg?i;tlGCuP@Wnwfw3XtD1DhBZ|Rft@gRZaMj$JroM*I z)m>|Df#>?$yEf5JuUR?J?oZefw!r~Y%%L^b>}YA;JgqY88+5xys-2!tb*TF4b-~zO zw?Y3T{!3k@Tx>1#UPJj5pmPCu61;|j7UxfJ+F`DhQbIOU?{th9V7M}8uT{M-elA)Z z3$5K2qWv$vc=0bMslWJ}lWg>XOIzUUx(m{(hO95tg&an0A})A6IiKI}OQv!a>2!q| zphkS$VK*6d1}OuHefKi5^%(g_EYEL*@eqf*FWl8Hmz;;wS9{HkGNB&5aT6Xmp&HG z2|q!->qwz;dT)1KGVHfo%_1$Nk~LCayVU2F^wWEzVnhmuV3%n1M9M)tL(0keW?3?q z9BZ;n;97NGH^7cUV+aN?TN#PeDDvS+!VrRC2#7dy#^iaXCBm6*U{snVh1SwKfqCT8 z?1PQ*4IL(x$~NSv3pPy~+}N#nj#8Jc*6IstM1yvnc~t9mR%TuG0aKNswmDb3BpTGa zJTXI19}EZTeeni=l_j*0K3J8AE^afrwC42Eo+iC1VK?i7{!oQyG!PDZRT?dTT6ZFb zj3%W?g=Ad8XtqCMjo1^-z>PJeO?XIX0OzkKcNcK@LzTr%tZw3WlYpBf%yXD^&!DuN+X^l*%s8?hqi(GkSyq9xd znKUOqmg2>ib(P#e*&0T=0Ha*^!E1$+;4siQ5JN(ZBlF}#J7ZA~<`quoQ|ShJpH!7J z2+eh=sa5%6F+Q4%#VUe*`CNCStt-ALrHy$iqSKnBDGf#yeJUNxnU$5D6+MZXR%K?W z#;VgLYlbUR((;*^!D^LRqf1v0W)iuT45Om3&HUj1E;6rB<8pwLBUO*(dhqxX+=vEI zA92{3vFLKbIO9$sM02?>5eB1d@5;zkQZdFA4L6eF?A!@;nJtrsMRn-6UH857*nZnr zx_@DLFFF?go$cr0Cv8a4`S{~MEnl|c#TQpBTaMNJzwvhn; zs2%L`c6rsFN@psRp`WKzZ;1!9adSgoyHBTs{D#jt=P(--hU<6F?92(p=K@h=cpM2> z?QS9IY*eT;Uac4j#iBLAN}p|Qrna+vu^-V>mB!#p%nC$|2D?Atv8x<@Ps;2yxQ!l@ zvSGT-;rEB5eQTq23EzPqt z3co~BlCd5vu~F(o)}7s2mDA>OQ@S&`*(`^1azvdu`}wICC7Ncrd|V`9--cLJorQPh zf=DJtu^}vFkJ&2j7>cuD(y$HV#H{AkCcLO?T=v3uUGn*`VUR7CNme9!YMib^KiWR; zu01ZAE7_lzQ@j1_!CCh%F(@tg+whgOi>Dn~;)~?whYQOdpMUkI-iRhn)lN;M@^xNeDtaEUiy#)6A$tq0nQEueK#)qLsW9s*tk$Y1Y&>m5g>;#a zBve;PH5HOsk%|VONU0%RH9CQ2YN9kVuE>;5X<-M5aOISfMLRPMk+U$)5le&t@YZI) zB*6PzPJj`D6GwwE#>Ws{V3-Yg>jt}IPNzckxhaqp z50HYelWT}Za($t$r2p!5IRk3`b=sU-!+b~Ox>Lldaz?el(5*6%6V>?}8MpTH`+l%%#`gQux<{tQdlLXJynT*G3s$c7f?;R)O%f915tVq*oGF`f?hSkJNwy zCT9%C;k|+uW@j21kVX(D5z}WzuU#~W;;w%VF-wr!xx;a*#~bNc-O&@8f3IVIq^?`I zb)0yXe{uAxPc5EAV_&^F9tU}yS~N8U^YF0^g%;axF_qHIuba4vZ2=IF(?(EX-n;J%ZdD$Wrg={+<3a z_LqV?gJ3Dr$t$FYG#TVps9>PA1t4w1xf^wCzRRG7cCq5C4CS@;UUS3(l^b#(ipy9zpmDNM02Jec0cPO2%i+ZNbeA8Aur2YKN zMy=@ePO%70Pewvgb*I}BO{`r$T#*Uc+(TH=5Ovb`kz1H1s|vO8SV%>*;gHQ?c00^z z;(#?{Mj@70GKZwF5XkAB?i}2v#9VQCToyu?GZtCqLi}FARIu&}mH@--9KLQmlx-i0 z)u3_&$wP7H%w@lM-^^LF<58)1Yi{NOK{zDj162dYswxF4fQ7=8nXNU!R+F=-p+C{t z;@h8Br$q|*q<|m=L@D5r z0BF54hk{_W9wDo;R>$n`k|HOobIjr{p%%yx_Zk2ZA_fTa2OLM%0@8W-TEK2n>6kiw zv^9t7@p@0jXLmJ6f(>CymCfpKI||CAzdCBRAp)Ne)}X>Wg<@O!`gP znA08|nV*aXYb$GKEf7sRXx|iitJK^QO(a`K`a3c+{2r@*Mr&}^a`4C_kg`ilQua_G zgL7bv27|HWkbz%GqgHE5ei^kfhYUBRk{aNl(a0{EaT)u)7ysiVWbF4=|8k}tLgN|f z{+|Y!u?*S=g2TGe5%> z?sA`3S)G~8_4CN=j*BHMfFex}RX3YkEJrMF?AhX+vhv~ws|NQb*Y@9%+4$`FkCrW5 zxa=@Jb}=s8%h5z6x8W|BJYX@n7+GD&z`JiiBS%156>7X{>AYk%Z1xRfceeB0ISV>aVRu@{ zmkYy<4fS4RGT{6jt=SaF)+bWYid3|8I2cTOrqZQTEjGAn5RhZC8L>OjP$fkx>ZPoZ z&V+KAid-_`gqu(lyfiVc-L`{IFja=---2+;(cZEPX|i57A(sG~lD^ALV?d`h!2~*r zfuy_|Axvf-L$Gt#oGfdZ;9>z0aP^YJN+AQXB(lUffVG>b{hQH(w8!#!y4#^O&?EjVXvxipwSV_E-+6FW9TWsmD1d)AT3#@#07!f)`&- z;N-5#aaSbIBZA!uv#62&;s>k*e`@da?k@9~Z9&!6o*CDK957FPp7d}B?LSle^T=@6 zQ`a!Ee0nrds=$|mA!jDmOfFrLE5OAsqgpG$ju|3TsX$&H@Bh+x|55b6_z(KmQN^VG zFO0u`$7SO@INpEovT^=4-v0*1c^R-1z%t;E=J~E8g(|q`P@i*o4YA$AtCoP&@%3KLd+;x z;)q;4hFlu?)WJ#h=J9bU%bZ{c*s{C`SopN>gw+n&bFOAkzYT7)Tdi#d- zZxU}Fi$e(4>EMs>axpx`;!4`AW~&|%sn1-ONu%C;VQN!HvfAX`xeV3lhfmf(K4-#e zqe9Chw8LeGfL5s7z!U_)MYqT>s6xgp@>`B6azDd3x&J8o8OB}NzmB{&ssH%+`wx=e z@P4MWJj2JYDUDy?<1>3A&oNr=KZ>1V=tCzm_2Tmh>wH+d|t@}%b2)A91rCbK{=6@VIcEzm12mcBheTq z10u#)(Fm$vSX91TbZfB z+*c3Qzh7h&i8}#h@1Q{r2VR@Y;gBm)e3-*Q?q~ZV_aB7{$l!Km|GLY@VdKm1zkGZK zPr3gfdp~&L(50uv?IMf4#o#YlTBxdw+L_YA4z>%#a&cq{!6m{1WhSXABpJLZnBk&z zshL;Cvcp?sk8xRYUiqI33Ba~WE+&REcp^I(eqbIQXXcm4r~&7o+F^yM)I$8~*~3#u zLW$jD*RGwjpnGw@pbqV5yMM@2vuJtoXGOQPc*G#QTQ@2#32iUz{o%pNmD!d3hYW6W z?ZX?I@)!Tj^Od#!*Qq!7$acVrS4wi+!|^7Zo60c1vj1oahFA8lqhStnxu3yM9*4sa z;~&PJ-o*C~8NL|*DA4Xj;3}(dHga6`Bdgl&bpo;!s)*H+@;gu`hPRDZi?9;sNvshN z3-Ce_;2m?XG3^|Gyq>n?4mf?@wxwrnbgIhG*B>8ky4HD~w)oYdpT}Xec^u zUBY5JKwo|&U){-gWF2alz5*VZVU&}OSoMUM*r}|!8n-8xsj10$q}obJ<;7_d zIpidW5)&-fvGBuw84E0@PGYUKFbE~&6*BvjQ4gG3Y@3<5M;ri@gI5^wK-|mTVzF+% zXQX0d_tqYtUo@|-Z+Lib(r0%EvjG;rtIoP=>Y6J;hK2O~Bklf3t*tSUSdp=HweJ0r zs64EwcKD2bQ#!e`wyLeNrKrOm$n|l*#@>L!*dCJ;gh^7l|Ci(aN6F)tisStg?_YP> z`~Q9X{X5VvjKBZw@&1G4@rm)p+<5;RtRHlR^SH$Gh^01@De&MvP?*7DR?WoHVw>7x zi#Vog98+ki9_k%kI5l1Al{LNlch>PK2zbSs*{saUal(_zC13Gw&yBm zrj|5c*N|@y7a}8_8Bx1(<4~vDqwX-}?KJ-lYIDV-9j{eXcr^`LWvf%Cm_aw@2i!VC zOUr?+9;h0=;LOZCxRz%`omyQY*cmSjTeT{^)@_JpD-D{E%~gHWjJ$nA8YgYrfa$%L zJiJDsgs*WN4KdRUJ^V zYin~I2B9%XRb@k(LmHK}zsPcPn)!X97P8;}t`$t8im7mYDnS7giUA98lr#gtjD@<# zPolX@Jt!*&@JOX;TpAAfBBE%X^War`r?&`WO06~0<@aa-+cz8wy3HC#XEY5Jp_Z!% zy=}oRn+wUENSRNBgZ-JHXZE!tEpXj7#HE?KIoTfDEkCetx@-t~PEdnTTVc^eGrOfoae zUuTUj%=+W)`>M58l>x;y_M9skN=GpxEQNBNN z|GVSwUx$9iE#-bTzC6z5%C5kt7r?>1SY9&<~` zY{BueerF-t=Banpc|)G%hoWW+SfqL|*ypdVX=bHY#V@>U4h5fnM=8(+_h{-i2 z4Dt(0ke4BRWq%p+SN4~wPVQ$Amd7bmom>&Y#vkS5A0(kx3DxI9^%&#f3d%bf!nY-C(Kk zssri3L;=`Prt zX?8<8=ohpD3BT6wvs;}uwdjTWEF}&3{Ap)xce^2L^_k6x3}VI1hGI1%PV8=wY$`MZ zOG|aj@b3AL02g$5eLin6AgRX-ncXh4iBm}_!{bLjtA;fzoS`x1&PE1ew+d&hcv=o4eh&K(}}qi*wxlIMML*)1T;?&y!D zI~pxy+$Q(L{kU*HUYE-|ekZsOo_599okidcw%Tbp?!-GFXr{$g@!rtohQWi2*MEP^Pj+Z zybt1ZwNXMsNZ|@${Lp)Zgkh-#<-Gcf;*j|u7j9vbd%CiZMVhc-gUECFo*?L&N3*As-y#=Q+;oH-BS zOQ!ID7@r-!7VRFilV~rX{gjZo*AX)E7}~RFKSDcC$UFsF0IdNHW6s06<|okF(JUT%*MeW7{hg477PLnQS#%xReP~$IqLZo-ctZlyB~Ox7_)4t1z^7ivmt%d8 z{4464zE94QACTXZtH>MV-|#(UU&YsM-A&#iKO%3U`t%R@X0cny)%eP+jriKFP51(? zNAN8L+sQV34cAUoxm|>aM~oKO|pO!)_#ooO$Z|KLSYp;W0gTAkitG?^_{8OMz>z&>=qkrbW ztijnsbB5=R%$vVp;iAP$Mwc#IzG5YtZ`JBGYp+^|FD<*8J=?fx^OmjKw(rx%8-GPILK6UtdcF5HYH{Nvg=38#P?e;r9edk?w-*fMM_aFPr1D}2HbH_jb z(8G^>;n6QX_NB+aeBz(T$$x(0D^Gs))YqQ+`qSU|=D(bN=3C!>_B+o#|J^e$y!g_~ zue|y_^4hI3eQa5l6%u z2}NR&RHQ!A8jZ!gA2Q9H;B$}Q6JG-^{T-ixrZWBsd&Cn7L?ZkXE&LOde*z!+AOBFS z>^xQAcaYvnAHeT}lYWoWhv=j9asGRP;uquo|C=@$e(wctUkmKM4)}c#*v)X8Z9b#L zn?R#CgGO!zE#40L`!u-|6m~bc2NZc9z7OyiDDnaFS@IzH961hZeTY1a{rLiD@{6F` zFM;;IOiqA?Pl5wH0XlsW6nF}|$LOAM0LBSUlV?Cj-zLw3@}C1IV6^lCsQe}HjaNX| zKp)WZ>m^z~2Ri;CXm%H!2T#r&KjW5RQ{J@jbFi+(-JT6O}zTl0h^a>Q3$v?ju|2&14on*TI|f z(mA|;2<;?V3tBH)BU&D90PPrB2U;KA--hO8W1y`->lId$o#Hn5p|$wZ5H0CL8$>Gz zZ}89div{w(+d8)py{;;bpYL++_jA5mNDUb>R{Kzb*f7j6HB~~|gkWRr*t85%QBX_} zO6-$L(L9M%6J{ne4|xy_1)rvf2EqDJiVp#m`X=PDFFr^V3F<@T^Y>r-eABUUC3~HH z)>(V4{a<^Z%baiTea<2u`j z^nK(@*kN?MH)6}EDAp5uIR7hk`Z6|8c1iVDTKE*Eljxl8U6}uY{3-I!VE0MMNvcmq z`dsffjA!_&_ZhZdV*m5xzd=1;C4Yqclf>^Reic*VIsYgAU*p(_I(&dV#povK1?-JAe}~j9X~%L{fP7`xTeHb zZTD#U3xC_k*j8H}N@~l)37t*8?~evQ;~RYMaL@Ia%X7PT zw*OY|E8eepD|Vy*)S&Ed_OAE;Gk8Bq zp5mGj@Vv9h*$?$UNxS)8um5(RCp+&aZrl4EpB(D$(9(L8K6~}s*LdbR{`daiIM*Gn z7z3UJA)X<)3VHkz%Xut+3VKW>sT-+7F zJ?eu*rH8>V4ET}(VM6R98zlT22p6*t2aNKN8tT}@D7ZqVbOIqsl0fcc-YkL3?RQ*4 zk@>;n;p!^*f*OV?KM_$fs6#$1Ww_+xfLyE+g2+_#IOba};EyBzlTtRSHyA{4)Q73^ zKL6y|;R=uq(_!jDo3a>g>dK&wO^m8hV%RWD(-cP{h95u@y8VtX`iuGbKh0^j!mP~< zR~s>X)AvJ0G7JYF#>Zk60sxAV#3zVplEea<#2MTl(A``>eN`eFW-1M?C}fl@ZHYKQ zHe@P-!<7Lsb7fG+CPwM7QNn59M_HD2xMUs&>bW%Hf)r9C^JB0(fo-hqsZT!6g)1D# zIpd%Fkd@aD$H(%50+={WeS*kD$e0mDq#Pl0Hi@>Y0qsfnbL|8XotFW>Olk1rI7Bwg z`DYEA12K1HP)|5+t76AY5d%V==LAs1J2}XDE{(V#6Y7BZu{PG;f+D(*DqT!iX$Cx# zxDc*Lb`G^Wjv%10)?l1vK5fK6yo53GqynMgGNl-fx?iXSXSI2z`4NZ6{K79BuENj# zBD6B7rzoae12D~8%OEa_f&hy6h7TlMDUG-wh1AIWxaRC6-G%~jT+T}2ie^cfb7hKH z3&tPHo+Hk38U-jBP_isYvZNr(e^SXN7_yGClsMg0iNr6JxATxGPxMj55i-B@OLqk* zfyg4}QbQe^7zJ073s+K>B>@!is~e;%o!jrYp{1<(G1#)SAZvSUS4S$tbtJ6{aK)@s z=MQDi5wW6CfC8m?9;A6%GA}_u^Q?kX?=+GUHK3pRsb4A2H6u+l08Yo#2$^5`mBYot zpe$8jWrEblCPu-P@)g4%NUN$MfFha4fpF0|7o?CHnIAk_6+0Pqr3*J6 zvY-hXhpPc%)=k893OqD{)Fp^n=g zYlSO0mbW#y(p4KydA@$Dr(yAyl^`KQg%A!1Lv@iJWr5mMkn!uMqwL{Tp83Wj#uCc^IFd^`DirC9EOo~6Wx^Y2vae$4$ffv=xbO3Rm2x%2Y~R zo;m-*GMp&SO||4}i7c7H@iIjgPQr=9H38zpm5KP^49A<2dtBLa*^0}_WJ1RfO>k0G zx+&G1bPG~Qjm!@ot%{vYr@nCUGt=_aBP7`}(_M5cEqP2ige!_DQH*4?F55Py(@)yA z=5x=aDMHmdlak>=J(?d{R3v`njV%DNIdUS-IlSv2Ev2hD&ub7i6`YLlvJEAHJD zxVWFrW-|gPnprmyE|woYAnM`B{7^xwVke`noEnnwH&~Asi2CT*&v}a-4t~$u>~j7( z@2|yI;5T`Ht^6ANE^o0V*?`~Sy|g4R!|(A|q9pt9o4g%X{ypO>%mccOel>r{(F*x0 z{2q3cU-N1#ykz_d`0H4!Im&ON3 zsR>_4vDSaq^3RdqXB|S75?_ViL-RR~t{Fe)og=@&FB}d0JbaaL*$*hi3`|OiZ}8SZ zN524H^Df!ZOIX{m{5o8gFIjyq;c?yjBK6-lzRucY>UH89aQWSUufSDmgPO0fj_6JA zD*PREUc}2)??v-+89QsxE8S=}RP6KG3InWD{&hH!rA_#&>SRgPV zr&qs{vL(S8j|c*3f~!5pOdU0CbRTIaJ8v8}p zHM?;ye?=zZg7dr$AB9iw%9}WK=B!_RAKZoUW(fkDIOVD_qwX7cLq|a{e1~g~P8~IC z8hOjqAD<1lKj+F(Q^zg${_GKix#faDr%t`irzCT?MYPaK_a{`AIuiIL5YcaxJ z`4h(c0#}3s{T`mc-=Dtv_On`E?E9o1;&S}S1j?`CAD$7H$G#VYq#atGx+$|G}DeUm$sTcqWznl(B2n5#l2ItFUS?z z$Mi$(IDHC#cf~V)ywAhyTkUCar1nongrMluwy~aCo|vnB&X#H`*)Hvji1FD6+S}}5 z?GV9?q&HqGv|}_&J43c;r|H{(e1-P0*jM|Ct<)Z4d$o7POzi|ati4LV*B%oe&`yXi zW4-7kKpy(#{#ZD&ojW$d5Y7wmD2ldiok zH`87eZ`WQIE41CLmv(|&uYFFw)Ygy#fMb<*GHD0iTjHLU+NW#~=D$~aRV)U)|H1qp zX^)6EX@|sTF#oH97w~NtyK7&vwc6Lxo7!oqDdvAeJI)s2`X}09@w9e?6=g_zP~+HY*U_B~smJ;kopKB8Y~>&Z*n z7vf#oJ*-4KNHVnF=zL9+48W}`#xK?Oke9VDDA7KWuFwvOJ+&kBDeYbIh|r7n)ha;y z!>mSoU4q0)Beet4c;Hg1eJlQ3`-v5v`qU|cwQUNC7pHBmP);~A6Z0OCbre~606WkoS_|LJp_sR zK#LjLOLVsO7Tv1-Kz_&AJG8smquOC|z2Fg}LYjC;2tYR0iazaGu|RuHEC+nq+5xc{ z-V3x>*&6M6b{g+5X|L$-Z-NJ#gcSOakVSXl9iQ>+N%2F>^$y-&$8(&{&ti->w4-#m zc7`q2o@49rx=}j-IeA`et?d^f@0jDb7{N2mwO7O}+}|9}hA_|l;NNffPENcFkny*L zbnrW!zK+-1LaumD`#}7+KIaL{^AhK|_zL)nciKVw0doEXUeB|w`ukJjmD+3UC&)xd zgLJ_T#Q$Z)wVtfP6<7qiUJ%JNd{r*wKzUul^}=D{9pN+K7eOQa$S5+4tfzE<cOc;U(de@VW3S=}Sib;VEZMN=`;jj{a23 zx~H^{1ws1&ulMi@;FY3&6ni1|d~DD856%c@bXz6}XZoBOaHjKzQ$ASq!DJosGR!sy zuUqiC8?Q_Mtru<;Zol-BFhdw4jD>BVDy$R630DZ~g)4|SJSRLaOcSmaP6#K3w}iJr!BfI%VYRRZ^qem&5#|ZC!ct+haDy-&(k0b?J6&#%HzhSKJtH$KJ15uY&&v-4n=~ybENWI<(!8`R zR345*TePfbRaw=#x=q`5*VlCD*r{`uuHCx#=-I1xpT7MryS)E^frADQ89HqEh>@e} zW@`L>#+b3f)S2TJnC4xxa1m~sGx~Kr#HA`^S+c zgUbBhlv!8l?_9?CzpH0YT($b9HITaN@mC8-jwqynO85Nta+kBaPKGc7m%MxbTiLtt zB$vMbLH)T3gDHm6}}=BWyh+Yd3k}c+t)YGC zNV<~lrr)rR>{|AW=n>nA+r_hzAbF$$sg2ZEx?MKOE#zzEZSwnyO{q|>Rdy&ZC_fqk zhR%jrhPw^NjDoR^agOn%@w}wgikgjYc6YN6igSwF6^|`mS-ija z=aR~jD@*P!dAqr<`OxOqHs9U+qtcAh!KEuppD8_87APBDwyW&3P+4eB=pUi4$^+%& z%a@n$Du2EFw{T5(W%xk&n@C>d%E*q$;mG-DS#(NtSM=Q$wiX>Bb(F_T#Ybv z$dT*F4>|nRO+Q;uB`OK??!a=f0{nWPXmTeEQuq`&s z`q?rbro#HrnPd!GlepLE*MCUo^li!A@xqts>ZkmL8?~d_SM)S15z>WhK%zuLVI}If z6aMms?8F^n)g+{ZqM`g~NTH|Swd~uPw#>f3vCQ;N@^jK{H(RDSZ?F$e|4NM*7bs-D zVf0$V@sskqXG~MBlBcNWlFy$yb?syrNti%wwbqV}X79r{RSBIiTOb(qcolCns04!e zY$pM~Pm$d&uLnQ477a>X+=0*4#1oA~BjNHuejrrFzY7pqk(H>|<91Q0I4Je6h>ZAg zTC3{*(K4|=4HvI$8%gqX>amqoR9;o1&M#azY-~{}>%ms_E-m!jpiULXIy~Z_G#hDe zOwTKm(ub4BOvX2yDb2@y&^w%!VmCkTx>czzAJ{7pE-Wc~(~wbk<=Wa%>r|6ri)*1$ z8R*}vu*b~(1L%>AY=^ow#hYb(!jM*M1fHUX$cSxV!vwS76@o$tnEHT~Csf7}V7@@O zJQ6Ac;4)K+i9hjOlzE9%_l4gVpqnP$a?7NNx7;$KWuHDRTlVg~u~YWS{?qTid%D^7 z(KH&4tr^g|^#J}N&nz82V#$&b!@v7>vnNWMxvuhf!c9BOn$Rt`Wgd~O`6caA-Sb9w_9T~Pd2%}T zY~gVan_JX}(6c!ynI4BsE%r>mH#zo%v0G{JWfoO+0Wo-4I#bxD%e|0J{LW?d!Y&Z) z3+-+)oUIiQwdHfrgECDBT7dL9uwJ@px9FNM_GVl9n+LZ|KNeB7tv=s93u{B8@a)Hk zpnI@cf<+LVoB-ZPa=4rZJ><4W%2w@u`c8=+-@Rww{D&)uv~3s3sw%`Y$A!CSj8zEG zND@?{iZ`fmQHQY8*!^2_PaC(~`Ok+oJh<)g$7%l?=bkivG;YD1JtJ?~wqOD05pWR( z(GBz)$Pj-%ta$Atlug2PL;9h2#J8D%`|_+b2dLdYp{8g@Z&=6iFkaA?0`A!b=HX)aN%3P3f zAs^JwjZ_m|u!G#j1QjV7gbs4+!NnOrxo&T?l)Y=-#Z+I@-7{9TA9;B31HaCy?MJR( zIH_o5r*PLntq#4{zO~3YY$P>jlPCFFHz_Ns?z6b6N$*z6o~B_9ED9!2EyIW0X zmz>67$P?VG95A%R9|3=B!W9dKPPcOPFvLL!`XL!GbubFhEm%YtzEBx|fWF+Nr7JJF za(<`c)+1lL+MC&VFi9Jn5monoUCPpjI@ko#iObE#IV>mE*kuV^Wpp%a+z~? zfzz_BkkqWOsXc3((d*1BHj)l1(tA#il|mL9&$5MLPLDb=z{F#g+-z3Qk4p%kli*B` zV2i#3s{?Hm@&Maw*-K*vWrFkF$cnx#y7z3^cY1j-P%pe@+w87Y9T$@K{ARnw;JU-s z(ei%U6}Gd@ny31@boQkcGzY%Lepgaqudy>ZzGsA{Y!^#|jOZNFA%c)_2jk5TDx6Oc zJlzn&3F-Xc;7AGMS0_JIZ5M4bWZwi!POBTcG^^Z6q<)jGeP-3zf@oUjRNJ`rz4|Yn z)2mbUY_+0iRiAz>JN0VOty6>?_t_j(mlyW*WQ|(BEL@UfElH^wTHbHT>rVA`Y@f^J2{bGeZLA%ErbOniT?ekLq%PN}nr?R%BNbrU+n~ciCJDJhsn8Y^ z@xgRay~rIWs1TP)lF*zwH{kpuMZPX^8(nF;u3UV>mAgDChLmQPN4p+gc<+5H-%w4R zO1pLo)zpM~bT92Vro-ebYQ~MPxngn$@}f0KNy%+FswgwFXjCL4TlUypP0G8>Z4HG`4r{Xb~ zGw6;g*(7@W_=l&ZlQKPKId6IR<>U|fecWc#JlJf+4pF}XrDM4JlYc7Hn+(r5{vkKs zw5j>L1^RPLm_)8a?EvR0N{_4{-^J|nzBPSPETxs}{7 zQSyUE8#%3po01>X>Fp0bt$y=L`qLOjJB=}y(@*)BL9Y>JHn@~DRX@3I``H^vQ_^qi zejUc`^dReqSf-HEHX#*p;w8){kJ`A@#DBP%Bh@5G?$63!I%o-bBV%k*@(QzLP7k+l zK9e3cTCxhu3nm1V(oX8or_XL1h3-}7F=eagakNfo@@k= z2?dTuz&=)@?;fQ)b#S`nKxRqF;(Rt0u3)#F$6@MND%IC!W5m3Vhe;{QF8E zVh!^PP648wPeS2bZaO2*&LfP zwB0mXbJbY0Dp=+$HP1QW9XpqV9@~SVLA%GaU)eNPh-@)a$iwL6fgn^p5DZK1sKf7; zO2I`?9B?cT665pS~HEdB_A}Yluod!nED;*SX#_e9mv|ds6rOA4g!o>ejSe#FTxv$6aoib zS9x4^)5|a4w0Ly!V4_?z?3R6J)rW7n?aPw~2T!HHg!a2=VFj>@xpngCkD~+A2z(HZKpuTTXB-`JG&9AKk!@9PJ9MW^p6X%GI8MyR z?pMzzxjh5dKKm_eN+^{%F6L<_BN2V?t$y^H5wVGsCP|JBV|bK8v{$ur>>Uk|qKZrSC?+NoO`ED;}$`2zON{laIeCc*_{+7?2 z57Kcjn=rOJCg@|QDOVW45W_k{V%)>-%lNn_wBF)c@F4&`L;<<`g3A1n9d4i& z4o1Z#=GZj{FCTl;ks~+VFuHgMNn0`WmVIA?GaJ9W>gt1Z-#l)vCiP>JmydG{ z4$=_^>1~$f2}!NRU7*MFsFwf6TIsw*IKpE>-CxABPww*YJf4)U3aVDy06#7 zQSZW0AKLc|piY{9s?WjLaL5aC90Uc-8_st}or;@FB}F3mndi>f*DIYX=*?uGmz2!) zP|>i~xXu|nVYos`BW-jzPix;vcPC^nEEx%ej&Y$2I*kNlNnyW3nyp@@9#PM!vsSn^ zlQ);=kwN4-=fgDkFkQDzQ7gz;L#;vWwB1l^CiBz?O;T4`Z&Ds1y8ssa-LFy*{Bc4C zoZRa8nM9ZuGlU#UIG^pL`)J92NqtT2>)IE4bKgF4`dyf}T61k|wS)XqUCcj{SMi(E zmDexMi;qM1d9E#x9G-vV=J5Q3T$E?iw(4){8gey}iA1KWH>tnx{Px?Oc(HM~9NYfU^_ZARP@S9Hi}S;!O0Av|{Aa>70~mCsd=#`ckn)YJ6w4=r+fhKuY{ z(}TXfHv3b^_}$B_KOWuaw; zuA!bIu8OBw$xZK$=UJ7}woUye4gl4*Yu|})0?!oPc6x*E{1O@o>-c&(zA!h@GCelu zxYCsi4$>RV$9JqTh30Xboy3%Kny!1?vUW}!-P|C#{ET_6VY%g-=zJaTbq00AI!Ht} zQlq0yv}M{`Y(5*n>#i(b>`9>ZC#~b2De=Q2HK<9vSj0mR;?5p1@%>SvC9kYwxBk;R zs#iB#I&$K*gBElvY3f~e<;|TdlZ{lIP&2D6zr~pIGppJTdT{QVnih2Npr)0j;nwLT zCR27=UeluK8y1Dzq-C}aTwdI)*DP;#W=e)Ve{inNUN-5oevz!y(smK())y$@E6@wb zt#0qberG5cW!x((PT1jJl2)DnZMA1U`I#=GnvPcS{X(Tnxo~C|0fvWl9O`8<* zEuF~u&VAvB>`T^0w>kOT<^cCV*x`*z*~Afdg^?-}WnacLY41}5i-KFFn8vmg_Wt0* z-i1%lXKWAA(??r{2b1pPA=?(BezI-AU3U%Gwu4~o*I{c~v1URtCgS6nVF@t@cTE6R zjPQ}hZ@zgj!`SbmXJUgC(_y4!dbb8>$+`E{oR;V``D7cHbN+e1Xnqj#FGBV>$PNp3XX2|vtf^}6zLhq4ZNp7#gC^5wD*-r+& zK>Ht5NcTOea^R=)^hB>-Hg)(ZTj$QoXQYX3xS^Nw28nR^Pr#42WtF<@UP1nlfDP(26(sgA1CZ1(bk}LazSL*irv00?u_ME5ue^dX z7yd&pLJ!yym(T>6LMNdg`VJ-w*I_YfK30`(!}{B!jZl;@J;>G7sV>OPk9wtelz@op zPf`O5xnJry6S5r=qe=PP+8C&!tYF4=5Fo|1wgck0XN%>Z_aPP-oiN%|`sK=DbN4hA)DR;@+P&z;%&17|Ev zCNuRRX}+RfIeRv_Zk9wYW3v@fA(Nq-PMz9>64=I}3l|O@vS<;#Yy0P)Z@=gB&+ob7 z)?2TbbnC6;$+4yrSI#yOb6S3WT6$jon8K{=f`aUq1ba@XGE#*I@ZZ`?R}%0~6F(H%RE8Pl=jXrgYN&a2JSx2n;}-MUTYFQ1A0Y>FKm zdu4Wh8V4*bzd8<8VX-a4)H}y5Gn5#qK~82Y3I3Sd4mOA6i6eOJ5P`mzB)@~$xbkfkfl_e zyXzb|cmJ0QE+1_Q%~8)9oL$CTv3cQ8xlWp$Y;X&Hy2A(Lz?<)BDGQ97`Wd!QlH}y*u>`& zaV8Hi8_I2-6Yq$pHe+9kr#2FV;IC2}(Eku}n+5+{ZgUOK3@#g2pLOcFO+4$IHVwJW zLHB=?+h{-3&@_rXX38o0*oRgrBi)UhCasBYuhsSx5`n+U zX$~W&vHY(&%@Vq-;GH2eyA0BEnxEB`G#3s69P(iDU^O~zSEG)17+q~vW zh<9Vy^H=brGX9~M@~}_&uJJRFP)SS z%z-`#34k_}kf|W08A37kzCEj0Y@ocj@stMRy^Ne@G+@mEtQW@f*9{v*#XM?8U51Zd zE|QaM*qZyR8TnP}vgD>=l$`X=+c!&!-5gJ8VmFJ|9r_+$uuG^+OcyHp%KqM^-B_5A*#7awNE@hFImkcD@dvO%>rI|Lq>DG zxLo<3q|{n!ExUHu=Sgds`ptQ6Lmx#xb4wzh31S_OxV^|{uzbMlFN)VmTPt+fe)YZ= zVh_BasE_O+zdrSs`OK@Z$(8YZCW`VBdPI=>@oJmCD3#ysdV0%yNN3a^$gCP?>=sm; zklR@ID(mzdW+EkLlQXo++Y4CJWJOg^42u3qzZ@gH|LZAE^y@*sQhs{dBe63?v50 zWVn5_nd}=tzT7X*;x2ohVZR}EmwAQxdJC;ZeEgL9jF^M2?+n2YlsG6@sEAi+gHDAZ zdUJWaEb5Fz37Rm_i$Oo^xx`>C>EGIa6|d0Fys=SR!-;er-`;R>t+dipJlXksH zVLw7vDuvBQk_o*RwzQJTDe8lU&$n(m<5CZ#kfzcS;%-05sjf*K34A`%K4WcIsgMc? z5c7G%NWQ@zy|S21b?!Uq3^-zC{1oGz+tOka`WeTj^;F^>IeD)8es&A{Qd&fk4C|a~ zjLl|;6V*nlt=ERpk3>c2fsRhS#ul&jgv>)}fSUVRy-&S-i)eBB8IscR967ikgQ z^Oy!1o4zbnUpoqy#~-AF+-v5fH?+Ky`NpwV{tI3Khcl_o!cq8+6}} zSWg7{uG}&dDp9re;uR7%oR6tT=a!quE>}jnEPc(Ui0{b_pF(6z@e@l*CgjrO>YvRZR<9vl-&T!Nc3-O|0w@S!A#*h;`HJpwKTR^vW?zA^t9%6^Bg^`!IDi+8q1t9 zLebZ4EcFX)f z!V6rAJA@inG+yVbCaC#_P*ID9>5>EA{&b(BSG%5kYW@0ywEqiq;R1T2?PgN3n>l+K zo_W#6>s}8T`}7cN4DEUKtG^Oqou(K_sckryC5n9>JIEHuk{N4`+>X^RzVep83oEoJ zb%#iKc9XI;#d$^k^a zdYfsE^gyeHJZJ4LZMH5V=f_~z5!PsbvaIX-Gh0vh-En?K`*|WsA7R`#kTjRavgZr~ zWTK>{p_F0KYa1E0rVMh=9_LZO!n7lhrI!C!vXlZ@8a)9`SdgXY#Clo!`5i7xFZLQr zt|}`dMLmX``X6M8qK3Vl-dWcdP)F)uJkHbC%yd(X3w1x7Ay7u|&wtw%K(?bTVBmkz z6~H#}t^g|Z((WgdSykLdVDT#$^}D^jNXCd|8@4^z%|`FhyJLw~p)RC5*uR9LxOIRo zDl+QrK{N>HyMy+yknUAfnI2U%y(7?tu0a0c@Kfyo>}@UVZJyrM#TUo|etqQ%Q38w`U+VnBl6ZV! zu9`SxX`*|x+U$y}^+eWn!dqF@sRQ{23*{&#U9wOfyL{)jx6gSxM>o83>Nh*re2^_F zCh3oh=IVeg(+1v{VT^qZq$ppbmns2UzR(O29V%Ip477O5Dwj^M_0Kh}d>2|pn2ktH zTzACoSv+!N+15k5ZhX8$hdw>XjSVc}pSL}{e02KO4|%yOSX z{f|7r@EjW}tFI5livH%|>phHmq4KV zC@x6`+=mGczQ708$K~*!$M

<>7>Y$IK4u@VptX~H6gA&&zv>R}`*L4}7dyPi9nMT8TJm5zA$6bSO zFD5O_>NX~lB^LGJ2|M+U1LazD96Y@KrZz8`cTswm`IVLnmp!C+g|u&J9cOad9d5@o ztBvopL?~7V|}I6EqUuAexMv?73z00)OOS3Q9n~3Gm=UJV*mXENm9D{ z)+GtRJR*AB30)P)Xu`S@~Gcb(UdA(>DGcYO{LZ z^Xx=_KXiK;8KZxPvTRn5hy4A7X+crM3 z@xrIYG1HZO#qkO96C|Dp;4H?wdPRCiAkAzFs~$HnVk`!F;_uiel8n z_bwEp>Q>nob}KIMEStY^Tc18#H$g8v=bWT?DC0M;=8)Sgit%O5pq%Y`5HYE)*sXvk~bZG}m4{~pJ_T9dAbKc!L5b_=J==U8$+QOX0t$ z<_*ycfaD*?tV4%dk0G|$i@MJ$_~tf3Ok@&#`3ssJ@!5$NCqoag&|(sTX3fR;b6krq^R&YrH`gVopDMZHDb?SJKt? zm1dev?}+_KQC$I@4{9^S{-O*oRtm@x&6`5FTtJ-)zqymRV0eO1kVH=>&zaEZNv-G9 z9c19Sb7X+J^W3AVM#!U2F033od+W!qY+1Xcb;oN*EnhxrP`m8bo7$?HD62cp#rrzX zk%8w0LLPaPkY)W!@49-%mf_vHPVCxcVsTOQjxF2HCav8k@e&Sa*!M%zmxN{2`!?N zXf0hp578H?$_y-rHD_TKVN=+2wty{UOVGExf!)rwu_xGJ_8NOj{Es{qyYLpt>*P)H zR(Xf~l>CBxSn(*SN{&*X6f31lM47KFP*y8XC`Xl(%2&!y%5Mf@Tw}b|c)<9a@lE4t zrN3rCcI z!fl5B3L#dKl(0Wq>J2Dm-qK(+o0cPUi((UvFI+<45`z8`;#2%!n4RE1e83-tB*t0l&;|h(r~iorUF0{s}gWLx}(# z|9KVs4thgge^CFk6knBwJlQnIM%1QZNq(-2}m+>9M< zo=|D1ngt693?LdBWOsQs@p*$`58%^53caw&XG0k`vK%J3nK%`Omu zGGHFohv#?5-ZF0>j2pphE)Z@-_j~cD%A=Sv1liC#{V{SOKnFxoe!mVa5JvkOAPfeu zL$HkV288i>qjJ>6i6O)5$ZlN@eCV|fM}R_kPywZ6w^!zmK>$lb+`#&SfRhgqr zdtLa6hA;+~3*9`+UhHl2R1t775G@5Ef{dFRNUn_lY;UMFadV0x@GWygRs2eMuq^0_ zhRfisF%6WWoIeYN0GqH)E*LFG0}1CtHFf*Tl`y_QXe)zRP!vzp9|%DMqX7kgLfz1A zj{G#_spgRT!qGqodQ;BX&t(Av$LpOSx8(7HFW&NCc>u$3ItIc3xGbtu16R0a$#CtQ z=Yb&Sr2YFOT;z`5t*WaQxxXaIqY z-{!}CoB(Ag2F79Fx(@z?e25Ptkg3-2)|}y>Gw{GSOz`5EfF7AE+f7eWIKa5M6*57a0X2;sUui>0w0!U_PfsT>qi$AcO)j zEZ1!l#=-^&M00o=%#am`95{}6P8TxhAs0N%5CF{)?fILd4#p?M{Q(FTkSgaK0rFw+ zxx5;KyCH>9kO*V?WjEv%w#y5;d7v!#&(jJ-g#zoO=3In=y20@PwVe2O*U515931uDT~C|tey*!&yZjiV4e zfDzP&NZ~g=2mBDhFgS%N`6W0OfWlGmxPAQ1g`_f|J1qDRyo1cxE~&FO7{Mc6g+B|@ z1Ql*Q@r=whAOdIOcl&}7T;-w(NOuC!nel$=w86P?o!2~`s40tN-0@PmgU(YOiV=3SQtXaYBWK{@16Kp5yezszrsWD#y%^^Y7g z;1U5SULdEV$uDso@NlStFoZJZ^zqw43O+n%xC`!89|ArFgvSt^RJwqJCS3gkK|ti? z;03`Q7~T>JUh-=?IzSA(;2-(jpf-duBttN~5D_lX+@HhIAOP?NAu`p9HyT34Rwu4I zn|V;R0#`Z7{XtL!W}5%zlE(kQ8y5j4)7sQO2s(cwDYLKwhoXfObV=P&n{!b?TDFqFzl*yAo$ zAW|F%e9ecgCSVM3LvtIR;EENXM~P9QMn%M%Y)*2TjfBbuLhTl6UoTJh_sR){G`iCTVXF(y9{m2S(5(CQD3Yl9^#M6Js)y3{)`~ zWP(K}l7_+=l@!UKU}TZ;ZhPEdLPwxT6v`w?u~AH6lqqo~<4HTgT^L0);&H;vl!_wW z70C!#`BMs$Wqf2rlx2gcn9TseOqoNJDE3s@nFX^;7MW667OfNmm>GsNSuv18L44y42&@zAU9!g!h)w!$g=_tPUtv35@<$6J_UN4C6Emp zj10g4T1v2o$uCheQ6z~ejBvW~+X(uNF+Qgge@q4_3>}h0<|v5Z13R7%M6IGp#7H#XVo2hkn=J<1YrwEgD4$6G$G;FI2B5HG4Ie>8wDzHAm{QJA z3?qs=2MM`h6XwStod5U}&l84cAVQD>JO}~ARJZ_K{Xkd(*}2@}R$S3VhZs0m z_>13#uK}HrWb%=y&4@oiH=xGk2rd9A%mH3={AnJak3H7~>o9oIoD8zFj6yFEBK!dt zUs^RlO9!oN{n3JVqzuAZre+;4_34f)5@8Aliz$BQ3 z|2E*aD6%AzBtyK+Df|Myk`e#(AWFuvTt||@k6qr@OlLEfLuS?#$h&CBs}AO85M)a*~OBYLFb<;#}iIjdKN!q#PuE9rWjyiJWBA z<(e^$vaDa@Y{&N)A5x(|EICqeGgD0ZHE0<(7x=-n7(+A>aGr}3M1%_pvCFS5o#g@WR5@96mKnjGT*f7ZPASTRZl0iK*kuX>| zgAta_Mx|kd#4H}_G1&YN7m=9&i3yN%ae+b1vO-IgH3gVtbcxiM%4ErChh8x& z?3^8zlp3H0;1_VFy1n7E@oBAONDdXTe+GXWHBZLPAGmD7!OtWl|VRqzX zgAAiB!!(Jq!3gu>79BavU~^-bWLWB42go8@Jj|A4vS3pv10W{ailtga7nAI{%w$y@ zMmMn}XM-8$Dr%FAP4gfr0arQ;fK5@&=?0T0>0aDxJ`i9G~X zSuv|)xeT@gSv@8gwbVjrfM{}C>=}53%UQB$x0GVJFU?_Mc7?3gRVKG6-$bNAPRsg=e&#Vs0h!4@eQ^646dEiMQO zz%ux9qmsmctJvLu;~I?U!7$>0v~t^$QW79!3Q0~PGpx`mS1Q*q@I^`j?_t=v$ASHZ z7$JAW6;by>TxcOvaC2MUi8o+W_!XLzam{2!1_#P92I9#=fff;H@8#*>0pF0y5erLfXz*ixJfN#ew zuuGsBfabQ!3Kf?)GWY`Iz|-8Yz;C(`buhC8lkEkE#Sj_Et*-8y0K9H%0SWNY4G|}b z4UP@a`4MfvHba|$4$y}e=Gv> zF~{K>miyoHm%6QHKS0GcjOllbhnQi$edG5yN8Qr3%V2Gzj7@Ks0v~l6eBZQK?#EuJ8CSP(%dJ)@-b7A zBs+nc-IyN0I2lOY3U1zm@-DchKAlg+SPN{jSRFeyD7 z+eb1GB^pJq-KuagGrP+34DODBq-IG0gU#AfN=hyEH?6!=jDG76xkP#N&mY5~WXl)?Qa^V+cw&MKLB zyn}z7AaFWPP}I((RQN3F!Cd+jTsOE*LT;156wWZ2N0j}zHtIlp&xM3bDX~FZ z5ro0DB4ooQ5g8Ch<2nag$HQG%bS?#y+Ch1+#Ug@rTvs`ZAT^+}fyi96@etUGxiL2W z$l}%vOq9{>iDV7@#6>*dG#ji2vu<+~n0UB#Ns`Mk5QxDAipMrwcp1h1B?wq)~T$i0aVACNU(W0~MNYT@_(0fY$s}`^z^pK2TJebXHt0LJ`p`7S;aY!(hpoUR4gE3Yo1kzwO;ZzlC79`OU!l%?A%SrY?JwP_3 z_&B^mGProA3R5EmEhCZ~=?)MDLIXlsV4Dr74M6+28X)*a1gjVnmhOYMgXsnlU}S6% z1qo@fNwo2Z*9u~Q0ykdc(-g~Zs9NlR9< zkp+O;=xeVUPOwgg7l_~RnP#H)64n?8>uZeZ=ug6K^NZIRl`M@E& zyo{zVK%zi|O=;+Z#0o&rosYUkQvLcN))KLL=o>kGwBo<)P4)f7WPoYDH_e#tz4ivI zA2z)?>1Ogwtzoonz+%#)>d{LVA8%H*M+f$mwgvhZ=k}s>v-)Buwv5G=V@Y!4K=GH+ z^5t@fY&47(dk+PE6z3k#qyt4K)=AOxPW1i@zuu4Gyl5jU@ho}z=_N}J99YtIBGxh| zO`TL z+mwI2D867hAwIs$@*+8VNPI2zN%8S#Eqty11bKp-W;uFyZB+64`I3}g84RE_hNXEl z&tb&`MQi;h7d6;Ya#HF|K6dtvw#z9VUft)~mX*OrtIFJMhZkfFo0x-Od^Limqtgr_ zN2W8yofW9gbOtio45{>{muGnGxq-YKpNUy9_uV+r^d`(*4odLz)KGZDq;mbVyzYg+ z8a+yl4DqLnm9smJ37c*Hf~AEvXZyE1Nb;7 zUIj%;NR`9y&^N0%E^LTVSUA;qb?S}|9f!^BbIU$TUXf<;UH(fY@)@JUKAY2Pjr}R_ z+RTu8iY<+A$bVbk?(gB-{UP@r@(dY~r=R1Ef^5{yH{lb9GABE&sT4Ghx{{C@ zlxK+BlVpRIMUA^jJEO|j*wAr1?UkUsZ=USW^3|lg}To|2CpGxH&ab-S?ISubqB1( zm9nMSM8z(akLdvE``kd9MH!!d4Cs!*dI^sa|Q`c z8Mp*tbd$x!&Knm1REg2k94!q;#Zs!COqzM3;Y^TA0y*TTa~ntQkiAJ zdP=T)D?q%b`n1a!P?THTB5&@>J_tWX5asE1ogZ}6iyQS8gJ z2FmBP$#MDAgSqss*s=y$2>hSL-tJWN^Yb&Tu>Z7?Q>>h@7&u%#agx4SX;$Po-$Tx{=P*y&-_8Rg=f%P8!rYjKCjZ1dt$*pW&O(0TCuX%LX6!d;a3Z>i zZ$i*{`YPsXf}Sgm6MFs{!HIF5!F0E&8m6=0fG!=aIq?I!ezL@-3~8SlKRcn}2{X~X zbIc0d4q!eH`qKD(yo0rolfXQ6Yh=2y;nL;hZfwaP(fzq^J-U@SU3<5wv01Fy>A_Lq zNSpSfD!UEnSk@psD{G`P~{?58NJ9=CfH)Gg9B_yz%g}5`@+Es77QM= zV8NhZ1Pf1*NQ2OjjeRRB`tsM2ywo(mKP@#6zUg^2TXdt}wFH#leBjGgeAV&7@n%@8 z!WEb(?h@*|{m}ZrrVO6&;GfO|_|K6)wgL8ea^{s&FFjvt(;4^1OF9GHVH}#u{X9RP z>=g7Oey%h2!Nj+~{ka{Ran(4;_rLJ1{HFMxmq*NVq=neZ&yOji-$MuW&g({wDkFoq zDq_x8Fz4GB&57+0{7kt<=6qg1Jpd=8#ZM354d06@9uW1@1FnKL{?(K;FRqkzc=iLH zKHU>GIw$uLTrJ~=7F;rI{ef)i({a80tEtJ3xOzfA<20VC)7Y=i-YAvp+Uy#lblvWv zNmQMk*wpm3{8u2*mUa3-g%o}!0BCO3BLRMz8$ZPlyC+M~{7*N=4?V;2Y*n0z`q?-9 zL>In=@;jYt;G2+zF?KdEMmjf}bW384gYE^KE0>Hxo=Wf`0Y^W;5kGaP9*%@Lya<$e z2_j!QQgwo?h)v=+P1SMYJ!}_Yh5C5BUGV~DYw9+F$$xi~E!r=y6^=sPo**_iHzHavHY|v|Ig21G0X4+Hd;lY2ST!9Y4#Z zffLjijr-z3XL9efVw1H1-#h{K3%rQt)*a9A0}Bgb(_z@VbwHk?*gcSykFOl{5W*s;bAKihI7 zZq)vVxwwQkJlAL3+%-NGxuwnr9ROahKnCMJ=r8a}_#kprKmQ=Wk8}fSIMS^b&OeBK z8Q+-jS5v0c`<~}9=NsTTrzQ72e>H1@>*O_gkA9Mz98(h~$rU}$V*l3dXBa&HtC=q} zdQ)vb`pVaXkG!wEk*;#C(zn-ll;ePnct^Q<66)MoFL@n3VJBZ@|7QI$KHpgbJ(Bn? zdi^lMKOMgKZ~Y)bRXm1c2p`8eCFvP%vQtgh&nY>8!w3&`3C%HW6PFlb8`i-%P9UU1 z;_yX=GbMODe<4S~+4lU{sSEo#Q5`|D3tYs^x$22==9re)!D(LG=a<$ztdHH%en>V% z@|n6LsN(xJ%}dEOdmbaF;&$*P<|+Q$dAJ!A{f*Bv^ghUAgY#f>qizdPpAqx1*QG8$ zX#}5mAS9;!#Z+VB=CI+}(&7icU7DX@KV^O+^EK$yMDt%vzqCQ;)*v+G%S%05!?{T# zkR=pD%$Td59D9MgmUS1nJ9J&WNo;U_`gom96ty`0FV0OGnYWSLdM;@c}-ciFAw4 z_sh%reD6R98=X%a$@Q>qz7BDJ@R#@{a*{ZTi7D$*OibyB>tds`vUlqAF;3TdpYWG6 zCqzF%-G;G^?Wxbp8=YOercVB;kd8d!KJYc3i*TEbaGjreqUR#_;eaytYbJW*q3~9o zi}3I?_GRM4vQH9zyYVsfC{Z5+$DQ4HXx#xg=S#;R59$uN4vr3hgXba_!BH0lUI1lS zT`qD7G$fEX(~=3fz-hN0TiIb(FCDEen%d1V)1lf`Z!G=@+o3u#K|Ve}9QlSg9DA~O z*Ui#nPt%gL-794XDY>zRvVO6e2* zP~)9j|BWUbtS-F~8rYJ+pv=Os-0K^RGXKe`?50oUpJcg?N?xu(U|l(9WyM%gGO_Kx%M^ z0qJ@~{y3KYv|it(+7pPWkJR}k#DeiN%c}4RhbZ%2a-P?P9f|zpWoc`$Xy8+OB}CIt z6!yj;W`$drn#Sd>4NfxNKRDd#=xNH2Io=UJo0(*gEtl8jC-M0GLN3(kT-*ne*BhMc z6E1&^&qZ?L_E5*`WyoP&e$wb%I4g(O$jD35wqP?IHH|(I&ri0{*n0^%Y<$Ms5@r*5 z`BR|f(M$4^MrW<7jgcdm78KpX&!>L8e%d=O@V@bxN%$iB`Wo_+J>X+JKf!qp!6;I>;Gka|!io*C;O^b?1MMOlz5fM@8)P&RpaY5UtbwP_z11e71Aqgfk^)?ZU zC@zRTsQBbxAcBg!g6NwIxG(5aAAIt`eb-n0&D;k2Aig*-$;p{BH)rlW6K?LA^L-7m zKFZWb+YHXOavk^;HF*ek>f8bSQ2xs<8jur%Z0f1P%?;=qVm-LziQ%*v`DtbAr!+2ipB?bY=HZep$&<-?g)nq-eNMZZakFl#7Zm-Hv&^ej3Ps;IO|h5> zgGz6QpRblYv)b!2VcG94dQPMc^Tz8bV3~H4Zi^E)_ivL_b28mLE3GVFUx2hWB$1p` zAFu5!H)Ytf?5!LPpttpL)1fHY!{G|{k;cez8V6}Jv=MN$7l<_vkTt{SGIrfqd)U>E zhg`Nn(?dIPE2P+63bS3cVD-nJ|7j&fY9vX!&CvFcz>nt~(c7<`#P{E}enO8)45J%G zLIY7U434A`{Hl)xDQ6Vx-HjT}3~vnQk0nDkj@5|q%W(4 z1w*k?tLV-(i9O^gp z8eH`g`ULOsei)Fy>KiEQU-U!Y^;2hc4<6lfgisd){(ynKtNVJQhkB&PdINLub3N5F zooDbr!*ToqrsIO%=>sIdMR4281Xw`8Y$GfTAryt#cF^44Y6on&eW1GUf(Q8;*zUWv jM|<^NA9cOn4XUM5V-N;q&ulFQdE-~ACP=hZO(^{aR9Qcq literal 0 HcmV?d00001 diff --git a/ThinkPHP/Library/Think/Verify/ttfs/6.ttf b/ThinkPHP/Library/Think/Verify/ttfs/6.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7f183e20820646f07ae833b64230629ce815fd36 GIT binary patch literal 28036 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

  • PHP-Script: ' . $_SERVER['PATH_TRANSLATED'] . '
  • Template: ' . $this->filename . '

  • '; + echo '
  • Template
  • '; + echo '
  • Compiled Template
  • '; + echo '
  • Data
  • '; + echo '

    '; + + // Print Template + echo '
    Template: [
    Hide Ouptut]
    '; + echo '
    ';
    +			echo $page;
    +			echo '
    '; + + // Print Compiled Template + if (@include_once ("class.smarttemplateparser.php")) + { + $parser = new SmartTemplateParser($this->filename); + $compiled = $parser->compile(); + echo '

    Compiled Template: [
    Hide Ouptut]
    '; + echo '
    ';
    +				highlight_string($compiled);
    +				echo '
    '; + } + else + { + exit( "SmartTemplate Error: Cannot find class.smarttemplateparser.php; check SmartTemplate installation"); + } + + // Print Data + echo '

    Data: [
    Hide Ouptut]
    '; + echo '
    ';
    +			echo $this->vardump($vars);
    +			echo '
    '; + } + + + /** + * Insert Hide/Show Layer Switch + * + * @param string $suffix Additional Text + * @desc Insert Hide/Show Layer Switch + */ + function toggleview ( $suffix = '') + { + global $spancnt; + + $spancnt++; + if ($suffix) + { + $suffix .= ':'; + } + $ret = '[' . $suffix . 'Hide Block]'; + return $ret; + } + + + /** + * Create Title Text + * + * @param string $value Content + * @desc Create Title Text + */ + function tip ( $value ) + { + if (empty($value)) + { + return "[NULL]"; + } + else + { + $ret = htmlentities(substr($value,0,200)); + return $ret; + } + } + + + /** + * Recursive Variable Display Output + * + * @param mixed $var Content + * @param int $depth Incremented Indent Counter for Recursive Calls + * @return string Variable Content + * @access private + * @desc Recursive Variable Display Output + */ + function vardump($var, $depth = 0) + { + if (is_array($var)) + { + $result = "Array (" . count($var) . ")
    "; + foreach(array_keys($var) as $key) + { + $result .= $this->tab[$depth] . "$key: " . $this->vardump($var[$key], $depth+1); + } + return $result; + } + else + { + $ret = htmlentities($var) . "
    "; + return $ret; + } + } + + + /** + * Splits Template-Style Variable Names into an Array-Name/Key-Name Components + * + * @param string $tag Variale Name used in Template + * @return array Array Name, Key Name + * @access private + * @desc Splits Template-Style Variable Names into an Array-Name/Key-Name Components + */ + function var_name($tag) + { + $parent_level = 0; + while (substr($tag, 0, 7) == 'parent.') + { + $tag = substr($tag, 7); + $parent_level++; + } + if (substr($tag, 0, 4) == 'top.') + { + $ret = array('_stack[0]', substr($tag,4)); + return $ret; + } + elseif ($parent_level) + { + $ret = array('_stack[$_stack_cnt-'.$parent_level.']', $tag); + return $ret; + } + else + { + $ret = array('_obj', $tag); + return $ret; + } + } + + + /** + * Highlight HTML Source + * + * @param string $code HTML Source + * @return string Hightlighte HTML Source + * @access private + * @desc Highlight HTML Source + */ + function highlight_html ( $code ) + { + $code = htmlentities($code); + $code = preg_replace('/([a-zA-Z_]+)=/', '$1=', $code); + $code = preg_replace('/(<[\/a-zA-Z0-9&;]+)/', '$1', $code); + $code = str_replace('<!--', '<!--', $code); + $code = str_replace('-->', '-->', $code); + $code = preg_replace('/[\r\n]+/', "\n", $code); + return $code; + } + } +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplateparser.php b/ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplateparser.php new file mode 100644 index 0000000..e16d2cf --- /dev/null +++ b/ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplateparser.php @@ -0,0 +1,365 @@ +template = fread($hd, filesize($template_filename)); + } + else + { + $this->template = "SmartTemplate Parser Error: File size is zero byte: '$template_filename'"; + } + fclose($hd); + // Extract the name of the template directory + $this->template_dir = dirname($template_filename); + } + else + { + $this->template = "SmartTemplate Parser Error: File not found: '$template_filename'"; + } + } + + /** + * Main Template Parser + * + * @param string $compiled_template_filename Compiled Template Filename + * @desc Creates Compiled PHP Template + */ + function compile( $compiled_template_filename = '' ) + { + if (empty($this->template)) + { + return; + } + /* Quick hack to allow subtemplates */ + if(eregi("/', $this->template, $tvar); + foreach($tvar[1] as $subfile) + { + if(file_exists($this->template_dir . "/$subfile")) + { + $subst = implode('',file($this->template_dir . "/$subfile")); + } + else + { + $subst = 'SmartTemplate Parser Error: Subtemplate not found: \''.$subfile.'\''; + } + $this->template = str_replace("", $subst, $this->template); + } + } + } + // END, ELSE Blocks + $page = preg_replace("//", "", $this->template); + $page = preg_replace("//", "", $page); + $page = str_replace("", "", $page); + + // 'BEGIN - END' Blocks + if (preg_match_all('//', $page, $var)) + { + foreach ($var[1] as $tag) + { + list($parent, $block) = $this->var_name($tag); + $code = "\$$parent"."['$block']));\n" + . "\$_tmp_arr_keys=array_keys(\$$parent"."['$block']);\n" + . "if (\$_tmp_arr_keys[0]!='0')\n" + . "\$$parent"."['$block']=array(0=>\$$parent"."['$block']);\n" + . "\$_stack[\$_stack_cnt++]=\$_obj;\n" + . "foreach (\$$parent"."['$block'] as \$rowcnt=>\$$block) {\n" + . "\$$block"."['ROWCNT']=\$rowcnt;\n" + . "\$$block"."['ALTROW']=\$rowcnt%2;\n" + . "\$$block"."['ROWBIT']=\$rowcnt%2;\n" + . "\$_obj=&\$$block;\n?>"; + $page = str_replace("", $code, $page); + } + } + + // 'IF nnn=mmm' Blocks + if (preg_match_all('//', $page, $var)) + { + foreach ($var[2] as $cnt => $tag) + { + list($parent, $block) = $this->var_name($tag); + $cmp = $var[3][$cnt]; + $val = $var[4][$cnt]; + $else = ($var[1][$cnt] == 'ELSE') ? '} else' : ''; + if ($cmp == '=') + { + $cmp = '=='; + } + + if (preg_match('/"([^"]*)"/',$val,$matches)) + { + $code = ""; + } + elseif (preg_match('/([^"]*)/',$val,$matches)) + { + list($parent_right, $block_right) = $this->var_name($matches[1]); + $code = ""; + } + + $page = str_replace($var[0][$cnt], $code, $page); + } + } + + // 'IF nnn' Blocks + if (preg_match_all('//', $page, $var)) + { + foreach ($var[2] as $cnt => $tag) + { + $else = ($var[1][$cnt] == 'ELSE') ? '} else' : ''; + list($parent, $block) = $this->var_name($tag); + $code = ""; + $page = str_replace($var[0][$cnt], $code, $page); + } + } + + // Replace Scalars + if (preg_match_all('/{([a-zA-Z0-9_. >]+)}/', $page, $var)) + { + foreach ($var[1] as $fulltag) + { + // Determin Command (echo / $obj[n]=) + list($cmd, $tag) = $this->cmd_name($fulltag); + + list($block, $skalar) = $this->var_name($tag); + $code = "\n"; + $page = str_replace('{'.$fulltag.'}', $code, $page); + } + } + + + // ROSI Special: Replace Translations + if (preg_match_all('/<"([a-zA-Z0-9_.]+)">/', $page, $var)) + { + foreach ($var[1] as $tag) + { + list($block, $skalar) = $this->var_name($tag); + $code = "\n"; + $page = str_replace('<"'.$tag.'">', $code, $page); + } + } + + + // Include Extensions + $header = ''; + if (preg_match_all('/{([a-zA-Z0-9_]+):([^}]*)}/', $page, $var)) + { + foreach ($var[2] as $cnt => $tag) + { + // Determin Command (echo / $obj[n]=) + list($cmd, $tag) = $this->cmd_name($tag); + + $extension = $var[1][$cnt]; + if (!isset($this->extension_tagged[$extension])) + { + $header .= "include_once \"smarttemplate_extensions/smarttemplate_extension_$extension.php\";\n"; + $this->extension_tagged[$extension] = true; + } + if (!strlen($tag)) + { + $code = "\n"; + } + elseif (substr($tag, 0, 1) == '"') + { + $code = "\n"; + } + elseif (strpos($tag, ',')) + { + list($tag, $addparam) = explode(',', $tag, 2); + list($block, $skalar) = $this->var_name($tag); + if (preg_match('/^([a-zA-Z_]+)/', $addparam, $match)) + { + $nexttag = $match[1]; + list($nextblock, $nextskalar) = $this->var_name($nexttag); + $addparam = substr($addparam, strlen($nexttag)); + $code = "\n"; + } + else + { + $code = "\n"; + } + } + else + { + list($block, $skalar) = $this->var_name($tag); + $code = "\n"; + } + $page = str_replace($var[0][$cnt], $code, $page); + } + } + + // Add Include Header + if (isset($header) && !empty($header)) + { + $page = "$page"; + } + + // Store Code to Temp Dir + if (strlen($compiled_template_filename)) + { + if ($hd = fopen($compiled_template_filename, "w")) + { + fwrite($hd, $page); + fclose($hd); + return true; + } + else + { + $this->error = "Could not write compiled file."; + return false; + } + } + else + { + return $page; + } + } + + + /** + * Splits Template-Style Variable Names into an Array-Name/Key-Name Components + * {example} : array( "_obj", "example" ) -> $_obj['example'] + * {example.value} : array( "_obj['example']", "value" ) -> $_obj['example']['value'] + * {example.0.value} : array( "_obj['example'][0]", "value" ) -> $_obj['example'][0]['value'] + * {top.example} : array( "_stack[0]", "example" ) -> $_stack[0]['example'] + * {parent.example} : array( "_stack[$_stack_cnt-1]", "example" ) -> $_stack[$_stack_cnt-1]['example'] + * {parent.parent.example} : array( "_stack[$_stack_cnt-2]", "example" ) -> $_stack[$_stack_cnt-2]['example'] + * + * @param string $tag Variale Name used in Template + * @return array Array Name, Key Name + * @access private + * @desc Splits Template-Style Variable Names into an Array-Name/Key-Name Components + */ + function var_name($tag) + { + $parent_level = 0; + while (substr($tag, 0, 7) == 'parent.') + { + $tag = substr($tag, 7); + $parent_level++; + } + if (substr($tag, 0, 4) == 'top.') + { + $obj = '_stack[0]'; + $tag = substr($tag,4); + } + elseif ($parent_level) + { + $obj = '_stack[$_stack_cnt-'.$parent_level.']'; + } + else + { + $obj = '_obj'; + } + while (is_int(strpos($tag, '.'))) + { + list($parent, $tag) = explode('.', $tag, 2); + if (is_numeric($parent)) + { + $obj .= "[" . $parent . "]"; + } + else + { + $obj .= "['" . $parent . "']"; + } + } + $ret = array($obj, $tag); + return $ret; + } + + + /** + * Determine Template Command from Variable Name + * {variable} : array( "echo", "variable" ) -> echo $_obj['variable'] + * {variable > new_name} : array( "_obj['new_name']=", "variable" ) -> $_obj['new_name']= $_obj['variable'] + * + * @param string $tag Variale Name used in Template + * @return array Array Command, Variable + * @access private + * @desc Determine Template Command from Variable Name + */ + function cmd_name($tag) + { + if (preg_match('/^(.+) > ([a-zA-Z0-9_.]+)$/', $tag, $tagvar)) + { + $tag = $tagvar[1]; + list($newblock, $newskalar) = $this->var_name($tagvar[2]); + $cmd = "\$$newblock"."['$newskalar']="; + } + else + { + $cmd = "echo"; + } + $ret = array($cmd, $tag); + return $ret; + } + + /** + * @return int Number of subtemplate included + * @access private + * @desc Count number of subtemplates included in current template + */ + function count_subtemplates() + { + preg_match_all('//', $this->template, $tvar); + $count_subtemplates = count($tvar[1]); + $ret = intval($count_subtemplates); + return $ret; + } + } +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Smarty/Smarty.class.php b/ThinkPHP/Library/Vendor/Smarty/Smarty.class.php new file mode 100644 index 0000000..f2d3408 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Smarty/Smarty.class.php @@ -0,0 +1,1473 @@ + +* @author Uwe Tews +* @author Rodney Rehm +* @package Smarty +* @version 3.1.6 +*/ + +/** +* define shorthand directory separator constant +*/ +if (!defined('DS')) { + define('DS', DIRECTORY_SEPARATOR); +} + +/** +* set SMARTY_DIR to absolute path to Smarty library files. +* Sets SMARTY_DIR only if user application has not already defined it. +*/ +if (!defined('SMARTY_DIR')) { + define('SMARTY_DIR', dirname(__FILE__) . DS); +} + +/** +* set SMARTY_SYSPLUGINS_DIR to absolute path to Smarty internal plugins. +* Sets SMARTY_SYSPLUGINS_DIR only if user application has not already defined it. +*/ +if (!defined('SMARTY_SYSPLUGINS_DIR')) { + define('SMARTY_SYSPLUGINS_DIR', SMARTY_DIR . 'sysplugins' . DS); +} +if (!defined('SMARTY_PLUGINS_DIR')) { + define('SMARTY_PLUGINS_DIR', SMARTY_DIR . 'plugins' . DS); +} +if (!defined('SMARTY_MBSTRING')) { + define('SMARTY_MBSTRING', function_exists('mb_strlen')); +} +if (!defined('SMARTY_RESOURCE_CHAR_SET')) { + // UTF-8 can only be done properly when mbstring is available! + define('SMARTY_RESOURCE_CHAR_SET', SMARTY_MBSTRING ? 'UTF-8' : 'ISO-8859-1'); +} +if (!defined('SMARTY_RESOURCE_DATE_FORMAT')) { + define('SMARTY_RESOURCE_DATE_FORMAT', '%b %e, %Y'); +} + +/** +* register the class autoloader +*/ +if (!defined('SMARTY_SPL_AUTOLOAD')) { + define('SMARTY_SPL_AUTOLOAD', 0); +} + +if (SMARTY_SPL_AUTOLOAD && set_include_path(get_include_path() . PATH_SEPARATOR . SMARTY_SYSPLUGINS_DIR) !== false) { + $registeredAutoLoadFunctions = spl_autoload_functions(); + if (!isset($registeredAutoLoadFunctions['spl_autoload'])) { + spl_autoload_register(); + } +} else { + spl_autoload_register('smartyAutoload'); +} + +/** +* Load always needed external class files +*/ +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_data.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_templatebase.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_template.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_resource.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_resource_file.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_cacheresource.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_cacheresource_file.php'; + +/** +* This is the main Smarty class +* @package Smarty +*/ +class Smarty extends Smarty_Internal_TemplateBase { + + /**#@+ + * constant definitions + */ + + /** + * smarty version + */ + const SMARTY_VERSION = 'Smarty-3.1.6'; + + /** + * define variable scopes + */ + const SCOPE_LOCAL = 0; + const SCOPE_PARENT = 1; + const SCOPE_ROOT = 2; + const SCOPE_GLOBAL = 3; + /** + * define caching modes + */ + const CACHING_OFF = 0; + const CACHING_LIFETIME_CURRENT = 1; + const CACHING_LIFETIME_SAVED = 2; + /** + * define compile check modes + */ + const COMPILECHECK_OFF = 0; + const COMPILECHECK_ON = 1; + const COMPILECHECK_CACHEMISS = 2; + /** + * modes for handling of "" tags in templates. + */ + const PHP_PASSTHRU = 0; //-> print tags as plain text + const PHP_QUOTE = 1; //-> escape tags as entities + const PHP_REMOVE = 2; //-> escape tags as entities + const PHP_ALLOW = 3; //-> escape tags as entities + /** + * filter types + */ + const FILTER_POST = 'post'; + const FILTER_PRE = 'pre'; + const FILTER_OUTPUT = 'output'; + const FILTER_VARIABLE = 'variable'; + /** + * plugin types + */ + const PLUGIN_FUNCTION = 'function'; + const PLUGIN_BLOCK = 'block'; + const PLUGIN_COMPILER = 'compiler'; + const PLUGIN_MODIFIER = 'modifier'; + const PLUGIN_MODIFIERCOMPILER = 'modifiercompiler'; + + /**#@-*/ + + /** + * assigned global tpl vars + */ + public static $global_tpl_vars = array(); + + /** + * error handler returned by set_error_hanlder() in Smarty::muteExpectedErrors() + */ + public static $_previous_error_handler = null; + /** + * contains directories outside of SMARTY_DIR that are to be muted by muteExpectedErrors() + */ + public static $_muted_directories = array(); + + /**#@+ + * variables + */ + + /** + * auto literal on delimiters with whitspace + * @var boolean + */ + public $auto_literal = true; + /** + * display error on not assigned variables + * @var boolean + */ + public $error_unassigned = false; + /** + * look up relative filepaths in include_path + * @var boolean + */ + public $use_include_path = false; + /** + * template directory + * @var array + */ + private $template_dir = array(); + /** + * joined template directory string used in cache keys + * @var string + */ + public $joined_template_dir = null; + /** + * joined config directory string used in cache keys + * @var string + */ + public $joined_config_dir = null; + /** + * default template handler + * @var callable + */ + public $default_template_handler_func = null; + /** + * default config handler + * @var callable + */ + public $default_config_handler_func = null; + /** + * default plugin handler + * @var callable + */ + public $default_plugin_handler_func = null; + /** + * compile directory + * @var string + */ + private $compile_dir = null; + /** + * plugins directory + * @var array + */ + private $plugins_dir = array(); + /** + * cache directory + * @var string + */ + private $cache_dir = null; + /** + * config directory + * @var array + */ + private $config_dir = array(); + /** + * force template compiling? + * @var boolean + */ + public $force_compile = false; + /** + * check template for modifications? + * @var boolean + */ + public $compile_check = true; + /** + * use sub dirs for compiled/cached files? + * @var boolean + */ + public $use_sub_dirs = false; + /** + * allow ambiguous resources (that are made unique by the resource handler) + * @var boolean + */ + public $allow_ambiguous_resources = false; + /** + * caching enabled + * @var boolean + */ + public $caching = false; + /** + * merge compiled includes + * @var boolean + */ + public $merge_compiled_includes = false; + /** + * cache lifetime in seconds + * @var integer + */ + public $cache_lifetime = 3600; + /** + * force cache file creation + * @var boolean + */ + public $force_cache = false; + /** + * Set this if you want different sets of cache files for the same + * templates. + * + * @var string + */ + public $cache_id = null; + /** + * Set this if you want different sets of compiled files for the same + * templates. + * + * @var string + */ + public $compile_id = null; + /** + * template left-delimiter + * @var string + */ + public $left_delimiter = "{"; + /** + * template right-delimiter + * @var string + */ + public $right_delimiter = "}"; + /**#@+ + * security + */ + /** + * class name + * + * This should be instance of Smarty_Security. + * + * @var string + * @see Smarty_Security + */ + public $security_class = 'Smarty_Security'; + /** + * implementation of security class + * + * @var Smarty_Security + */ + public $security_policy = null; + /** + * controls handling of PHP-blocks + * + * @var integer + */ + public $php_handling = self::PHP_PASSTHRU; + /** + * controls if the php template file resource is allowed + * + * @var bool + */ + public $allow_php_templates = false; + /** + * Should compiled-templates be prevented from being called directly? + * + * {@internal + * Currently used by Smarty_Internal_Template only. + * }} + * + * @var boolean + */ + public $direct_access_security = true; + /**#@-*/ + /** + * debug mode + * + * Setting this to true enables the debug-console. + * + * @var boolean + */ + public $debugging = false; + /** + * This determines if debugging is enable-able from the browser. + *
      + *
    • NONE => no debugging control allowed
    • + *
    • URL => enable debugging when SMARTY_DEBUG is found in the URL.
    • + *
    + * @var string + */ + public $debugging_ctrl = 'NONE'; + /** + * Name of debugging URL-param. + * + * Only used when $debugging_ctrl is set to 'URL'. + * The name of the URL-parameter that activates debugging. + * + * @var type + */ + public $smarty_debug_id = 'SMARTY_DEBUG'; + /** + * Path of debug template. + * @var string + */ + public $debug_tpl = null; + /** + * When set, smarty uses this value as error_reporting-level. + * @var int + */ + public $error_reporting = null; + /** + * Internal flag for getTags() + * @var boolean + */ + public $get_used_tags = false; + + /**#@+ + * config var settings + */ + + /** + * Controls whether variables with the same name overwrite each other. + * @var boolean + */ + public $config_overwrite = true; + /** + * Controls whether config values of on/true/yes and off/false/no get converted to boolean. + * @var boolean + */ + public $config_booleanize = true; + /** + * Controls whether hidden config sections/vars are read from the file. + * @var boolean + */ + public $config_read_hidden = false; + + /**#@-*/ + + /**#@+ + * resource locking + */ + + /** + * locking concurrent compiles + * @var boolean + */ + public $compile_locking = true; + /** + * Controls whether cache resources should emply locking mechanism + * @var boolean + */ + public $cache_locking = false; + /** + * seconds to wait for acquiring a lock before ignoring the write lock + * @var float + */ + public $locking_timeout = 10; + + /**#@-*/ + + /** + * global template functions + * @var array + */ + public $template_functions = array(); + /** + * resource type used if none given + * + * Must be an valid key of $registered_resources. + * @var string + */ + public $default_resource_type = 'file'; + /** + * caching type + * + * Must be an element of $cache_resource_types. + * + * @var string + */ + public $caching_type = 'file'; + /** + * internal config properties + * @var array + */ + public $properties = array(); + /** + * config type + * @var string + */ + public $default_config_type = 'file'; + /** + * cached template objects + * @var array + */ + public $template_objects = array(); + /** + * check If-Modified-Since headers + * @var boolean + */ + public $cache_modified_check = false; + /** + * registered plugins + * @var array + */ + public $registered_plugins = array(); + /** + * plugin search order + * @var array + */ + public $plugin_search_order = array('function', 'block', 'compiler', 'class'); + /** + * registered objects + * @var array + */ + public $registered_objects = array(); + /** + * registered classes + * @var array + */ + public $registered_classes = array(); + /** + * registered filters + * @var array + */ + public $registered_filters = array(); + /** + * registered resources + * @var array + */ + public $registered_resources = array(); + /** + * resource handler cache + * @var array + */ + public $_resource_handlers = array(); + /** + * registered cache resources + * @var array + */ + public $registered_cache_resources = array(); + /** + * cache resource handler cache + * @var array + */ + public $_cacheresource_handlers = array(); + /** + * autoload filter + * @var array + */ + public $autoload_filters = array(); + /** + * default modifier + * @var array + */ + public $default_modifiers = array(); + /** + * autoescape variable output + * @var boolean + */ + public $escape_html = false; + /** + * global internal smarty vars + * @var array + */ + public static $_smarty_vars = array(); + /** + * start time for execution time calculation + * @var int + */ + public $start_time = 0; + /** + * default file permissions + * @var int + */ + public $_file_perms = 0644; + /** + * default dir permissions + * @var int + */ + public $_dir_perms = 0771; + /** + * block tag hierarchy + * @var array + */ + public $_tag_stack = array(); + /** + * self pointer to Smarty object + * @var Smarty + */ + public $smarty; + /** + * required by the compiler for BC + * @var string + */ + public $_current_file = null; + /** + * internal flag to enable parser debugging + * @var bool + */ + public $_parserdebug = false; + /** + * Saved parameter of merged templates during compilation + * + * @var array + */ + public $merged_templates_func = array(); + /**#@-*/ + + /** + * Initialize new Smarty object + * + */ + public function __construct() + { + // selfpointer needed by some other class methods + $this->smarty = $this; + if (is_callable('mb_internal_encoding')) { + mb_internal_encoding(SMARTY_RESOURCE_CHAR_SET); + } + $this->start_time = microtime(true); + // set default dirs + $this->setTemplateDir('.' . DS . 'templates' . DS) + ->setCompileDir('.' . DS . 'templates_c' . DS) + ->setPluginsDir(SMARTY_PLUGINS_DIR) + ->setCacheDir('.' . DS . 'cache' . DS) + ->setConfigDir('.' . DS . 'configs' . DS); + + $this->debug_tpl = 'file:' . dirname(__FILE__) . '/debug.tpl'; + if (isset($_SERVER['SCRIPT_NAME'])) { + $this->assignGlobal('SCRIPT_NAME', $_SERVER['SCRIPT_NAME']); + } + } + + + /** + * Class destructor + */ + public function __destruct() + { + // intentionally left blank + } + + /** + * <> set selfpointer on cloned object + */ + public function __clone() + { + $this->smarty = $this; + } + + + /** + * <> Generic getter. + * + * Calls the appropriate getter function. + * Issues an E_USER_NOTICE if no valid getter is found. + * + * @param string $name property name + * @return mixed + */ + public function __get($name) + { + $allowed = array( + 'template_dir' => 'getTemplateDir', + 'config_dir' => 'getConfigDir', + 'plugins_dir' => 'getPluginsDir', + 'compile_dir' => 'getCompileDir', + 'cache_dir' => 'getCacheDir', + ); + + if (isset($allowed[$name])) { + return $this->{$allowed[$name]}(); + } else { + trigger_error('Undefined property: '. get_class($this) .'::$'. $name, E_USER_NOTICE); + } + } + + /** + * <> Generic setter. + * + * Calls the appropriate setter function. + * Issues an E_USER_NOTICE if no valid setter is found. + * + * @param string $name property name + * @param mixed $value parameter passed to setter + */ + public function __set($name, $value) + { + $allowed = array( + 'template_dir' => 'setTemplateDir', + 'config_dir' => 'setConfigDir', + 'plugins_dir' => 'setPluginsDir', + 'compile_dir' => 'setCompileDir', + 'cache_dir' => 'setCacheDir', + ); + + if (isset($allowed[$name])) { + $this->{$allowed[$name]}($value); + } else { + trigger_error('Undefined property: ' . get_class($this) . '::$' . $name, E_USER_NOTICE); + } + } + + /** + * Check if a template resource exists + * + * @param string $resource_name template name + * @return boolean status + */ + public function templateExists($resource_name) + { + // create template object + $save = $this->template_objects; + $tpl = new $this->template_class($resource_name, $this); + // check if it does exists + $result = $tpl->source->exists; + $this->template_objects = $save; + return $result; + } + + /** + * Returns a single or all global variables + * + * @param object $smarty + * @param string $varname variable name or null + * @return string variable value or or array of variables + */ + public function getGlobal($varname = null) + { + if (isset($varname)) { + if (isset(self::$global_tpl_vars[$varname])) { + return self::$global_tpl_vars[$varname]->value; + } else { + return ''; + } + } else { + $_result = array(); + foreach (self::$global_tpl_vars AS $key => $var) { + $_result[$key] = $var->value; + } + return $_result; + } + } + + /** + * Empty cache folder + * + * @param integer $exp_time expiration time + * @param string $type resource type + * @return integer number of cache files deleted + */ + function clearAllCache($exp_time = null, $type = null) + { + // load cache resource and call clearAll + $_cache_resource = Smarty_CacheResource::load($this, $type); + Smarty_CacheResource::invalidLoadedCache($this); + return $_cache_resource->clearAll($this, $exp_time); + } + + /** + * Empty cache for a specific template + * + * @param string $template_name template name + * @param string $cache_id cache id + * @param string $compile_id compile id + * @param integer $exp_time expiration time + * @param string $type resource type + * @return integer number of cache files deleted + */ + public function clearCache($template_name, $cache_id = null, $compile_id = null, $exp_time = null, $type = null) + { + // load cache resource and call clear + $_cache_resource = Smarty_CacheResource::load($this, $type); + Smarty_CacheResource::invalidLoadedCache($this); + return $_cache_resource->clear($this, $template_name, $cache_id, $compile_id, $exp_time); + } + + /** + * Loads security class and enables security + * + * @param string|Smarty_Security $security_class if a string is used, it must be class-name + * @return Smarty current Smarty instance for chaining + * @throws SmartyException when an invalid class name is provided + */ + public function enableSecurity($security_class = null) + { + if ($security_class instanceof Smarty_Security) { + $this->security_policy = $security_class; + return $this; + } elseif (is_object($security_class)) { + throw new SmartyException("Class '" . get_class($security_class) . "' must extend Smarty_Security."); + } + if ($security_class == null) { + $security_class = $this->security_class; + } + if (!class_exists($security_class)) { + throw new SmartyException("Security class '$security_class' is not defined"); + } elseif ($security_class !== 'Smarty_Security' && !is_subclass_of($security_class, 'Smarty_Security')) { + throw new SmartyException("Class '$security_class' must extend Smarty_Security."); + } else { + $this->security_policy = new $security_class($this); + } + + return $this; + } + + /** + * Disable security + * @return Smarty current Smarty instance for chaining + */ + public function disableSecurity() + { + $this->security_policy = null; + + return $this; + } + + /** + * Set template directory + * + * @param string|array $template_dir directory(s) of template sources + * @return Smarty current Smarty instance for chaining + */ + public function setTemplateDir($template_dir) + { + $this->template_dir = array(); + foreach ((array) $template_dir as $k => $v) { + $this->template_dir[$k] = rtrim($v, '/\\') . DS; + } + + $this->joined_template_dir = join(DIRECTORY_SEPARATOR, $this->template_dir); + return $this; + } + + /** + * Add template directory(s) + * + * @param string|array $template_dir directory(s) of template sources + * @param string $key of the array element to assign the template dir to + * @return Smarty current Smarty instance for chaining + * @throws SmartyException when the given template directory is not valid + */ + public function addTemplateDir($template_dir, $key=null) + { + // make sure we're dealing with an array + $this->template_dir = (array) $this->template_dir; + + if (is_array($template_dir)) { + foreach ($template_dir as $k => $v) { + if (is_int($k)) { + // indexes are not merged but appended + $this->template_dir[] = rtrim($v, '/\\') . DS; + } else { + // string indexes are overridden + $this->template_dir[$k] = rtrim($v, '/\\') . DS; + } + } + } elseif ($key !== null) { + // override directory at specified index + $this->template_dir[$key] = rtrim($template_dir, '/\\') . DS; + } else { + // append new directory + $this->template_dir[] = rtrim($template_dir, '/\\') . DS; + } + $this->joined_template_dir = join(DIRECTORY_SEPARATOR, $this->template_dir); + return $this; + } + + /** + * Get template directories + * + * @param mixed index of directory to get, null to get all + * @return array|string list of template directories, or directory of $index + */ + public function getTemplateDir($index=null) + { + if ($index !== null) { + return isset($this->template_dir[$index]) ? $this->template_dir[$index] : null; + } + + return (array)$this->template_dir; + } + + /** + * Set config directory + * + * @param string|array $template_dir directory(s) of configuration sources + * @return Smarty current Smarty instance for chaining + */ + public function setConfigDir($config_dir) + { + $this->config_dir = array(); + foreach ((array) $config_dir as $k => $v) { + $this->config_dir[$k] = rtrim($v, '/\\') . DS; + } + + $this->joined_config_dir = join(DIRECTORY_SEPARATOR, $this->config_dir); + return $this; + } + + /** + * Add config directory(s) + * + * @param string|array $config_dir directory(s) of config sources + * @param string key of the array element to assign the config dir to + * @return Smarty current Smarty instance for chaining + */ + public function addConfigDir($config_dir, $key=null) + { + // make sure we're dealing with an array + $this->config_dir = (array) $this->config_dir; + + if (is_array($config_dir)) { + foreach ($config_dir as $k => $v) { + if (is_int($k)) { + // indexes are not merged but appended + $this->config_dir[] = rtrim($v, '/\\') . DS; + } else { + // string indexes are overridden + $this->config_dir[$k] = rtrim($v, '/\\') . DS; + } + } + } elseif( $key !== null ) { + // override directory at specified index + $this->config_dir[$key] = rtrim($config_dir, '/\\') . DS; + } else { + // append new directory + $this->config_dir[] = rtrim($config_dir, '/\\') . DS; + } + + $this->joined_config_dir = join(DIRECTORY_SEPARATOR, $this->config_dir); + return $this; + } + + /** + * Get config directory + * + * @param mixed index of directory to get, null to get all + * @return array|string configuration directory + */ + public function getConfigDir($index=null) + { + if ($index !== null) { + return isset($this->config_dir[$index]) ? $this->config_dir[$index] : null; + } + + return (array)$this->config_dir; + } + + /** + * Set plugins directory + * + * @param string|array $plugins_dir directory(s) of plugins + * @return Smarty current Smarty instance for chaining + */ + public function setPluginsDir($plugins_dir) + { + $this->plugins_dir = array(); + foreach ((array)$plugins_dir as $k => $v) { + $this->plugins_dir[$k] = rtrim($v, '/\\') . DS; + } + + return $this; + } + + /** + * Adds directory of plugin files + * + * @param object $smarty + * @param string $ |array $ plugins folder + * @return Smarty current Smarty instance for chaining + */ + public function addPluginsDir($plugins_dir) + { + // make sure we're dealing with an array + $this->plugins_dir = (array) $this->plugins_dir; + + if (is_array($plugins_dir)) { + foreach ($plugins_dir as $k => $v) { + if (is_int($k)) { + // indexes are not merged but appended + $this->plugins_dir[] = rtrim($v, '/\\') . DS; + } else { + // string indexes are overridden + $this->plugins_dir[$k] = rtrim($v, '/\\') . DS; + } + } + } else { + // append new directory + $this->plugins_dir[] = rtrim($plugins_dir, '/\\') . DS; + } + + $this->plugins_dir = array_unique($this->plugins_dir); + return $this; + } + + /** + * Get plugin directories + * + * @return array list of plugin directories + */ + public function getPluginsDir() + { + return (array)$this->plugins_dir; + } + + /** + * Set compile directory + * + * @param string $compile_dir directory to store compiled templates in + * @return Smarty current Smarty instance for chaining + */ + public function setCompileDir($compile_dir) + { + $this->compile_dir = rtrim($compile_dir, '/\\') . DS; + if (!isset(Smarty::$_muted_directories[$this->compile_dir])) { + Smarty::$_muted_directories[$this->compile_dir] = null; + } + return $this; + } + + /** + * Get compiled directory + * + * @return string path to compiled templates + */ + public function getCompileDir() + { + return $this->compile_dir; + } + + /** + * Set cache directory + * + * @param string $cache_dir directory to store cached templates in + * @return Smarty current Smarty instance for chaining + */ + public function setCacheDir($cache_dir) + { + $this->cache_dir = rtrim($cache_dir, '/\\') . DS; + if (!isset(Smarty::$_muted_directories[$this->cache_dir])) { + Smarty::$_muted_directories[$this->cache_dir] = null; + } + return $this; + } + + /** + * Get cache directory + * + * @return string path of cache directory + */ + public function getCacheDir() + { + return $this->cache_dir; + } + + /** + * Set default modifiers + * + * @param array|string $modifiers modifier or list of modifiers to set + * @return Smarty current Smarty instance for chaining + */ + public function setDefaultModifiers($modifiers) + { + $this->default_modifiers = (array) $modifiers; + return $this; + } + + /** + * Add default modifiers + * + * @param array|string $modifiers modifier or list of modifiers to add + * @return Smarty current Smarty instance for chaining + */ + public function addDefaultModifiers($modifiers) + { + if (is_array($modifiers)) { + $this->default_modifiers = array_merge($this->default_modifiers, $modifiers); + } else { + $this->default_modifiers[] = $modifiers; + } + + return $this; + } + + /** + * Get default modifiers + * + * @return array list of default modifiers + */ + public function getDefaultModifiers() + { + return $this->default_modifiers; + } + + + /** + * Set autoload filters + * + * @param array $filters filters to load automatically + * @param string $type "pre", "output", … specify the filter type to set. Defaults to none treating $filters' keys as the appropriate types + * @return Smarty current Smarty instance for chaining + */ + public function setAutoloadFilters($filters, $type=null) + { + if ($type !== null) { + $this->autoload_filters[$type] = (array) $filters; + } else { + $this->autoload_filters = (array) $filters; + } + + return $this; + } + + /** + * Add autoload filters + * + * @param array $filters filters to load automatically + * @param string $type "pre", "output", … specify the filter type to set. Defaults to none treating $filters' keys as the appropriate types + * @return Smarty current Smarty instance for chaining + */ + public function addAutoloadFilters($filters, $type=null) + { + if ($type !== null) { + if (!empty($this->autoload_filters[$type])) { + $this->autoload_filters[$type] = array_merge($this->autoload_filters[$type], (array) $filters); + } else { + $this->autoload_filters[$type] = (array) $filters; + } + } else { + foreach ((array) $filters as $key => $value) { + if (!empty($this->autoload_filters[$key])) { + $this->autoload_filters[$key] = array_merge($this->autoload_filters[$key], (array) $value); + } else { + $this->autoload_filters[$key] = (array) $value; + } + } + } + + return $this; + } + + /** + * Get autoload filters + * + * @param string $type type of filter to get autoloads for. Defaults to all autoload filters + * @return array array( 'type1' => array( 'filter1', 'filter2', … ) ) or array( 'filter1', 'filter2', …) if $type was specified + */ + public function getAutoloadFilters($type=null) + { + if ($type !== null) { + return isset($this->autoload_filters[$type]) ? $this->autoload_filters[$type] : array(); + } + + return $this->autoload_filters; + } + + /** + * return name of debugging template + * + * @return string + */ + public function getDebugTemplate() + { + return $this->debug_tpl; + } + + /** + * set the debug template + * + * @param string $tpl_name + * @return Smarty current Smarty instance for chaining + * @throws SmartyException if file is not readable + */ + public function setDebugTemplate($tpl_name) + { + if (!is_readable($tpl_name)) { + throw new SmartyException("Unknown file '{$tpl_name}'"); + } + $this->debug_tpl = $tpl_name; + + return $this; + } + + /** + * creates a template object + * + * @param string $template the resource handle of the template file + * @param mixed $cache_id cache id to be used with this template + * @param mixed $compile_id compile id to be used with this template + * @param object $parent next higher level of Smarty variables + * @param boolean $do_clone flag is Smarty object shall be cloned + * @return object template object + */ + public function createTemplate($template, $cache_id = null, $compile_id = null, $parent = null, $do_clone = true) + { + if (!empty($cache_id) && (is_object($cache_id) || is_array($cache_id))) { + $parent = $cache_id; + $cache_id = null; + } + if (!empty($parent) && is_array($parent)) { + $data = $parent; + $parent = null; + } else { + $data = null; + } + // default to cache_id and compile_id of Smarty object + $cache_id = $cache_id === null ? $this->cache_id : $cache_id; + $compile_id = $compile_id === null ? $this->compile_id : $compile_id; + // already in template cache? + if ($this->allow_ambiguous_resources) { + $_templateId = Smarty_Resource::getUniqueTemplateName($this, $template) . $cache_id . $compile_id; + } else { + $_templateId = $this->joined_template_dir . '#' . $template . $cache_id . $compile_id; + } + if (isset($_templateId[150])) { + $_templateId = sha1($_templateId); + } + if ($do_clone) { + if (isset($this->template_objects[$_templateId])) { + // return cached template object + $tpl = clone $this->template_objects[$_templateId]; + $tpl->smarty = clone $tpl->smarty; + $tpl->parent = $parent; + $tpl->tpl_vars = array(); + $tpl->config_vars = array(); + } else { + $tpl = new $this->template_class($template, clone $this, $parent, $cache_id, $compile_id); + } + } else { + if (isset($this->template_objects[$_templateId])) { + // return cached template object + $tpl = $this->template_objects[$_templateId]; + $tpl->parent = $parent; + $tpl->tpl_vars = array(); + $tpl->config_vars = array(); + } else { + $tpl = new $this->template_class($template, $this, $parent, $cache_id, $compile_id); + } + } + // fill data if present + if (!empty($data) && is_array($data)) { + // set up variable values + foreach ($data as $_key => $_val) { + $tpl->tpl_vars[$_key] = new Smarty_variable($_val); + } + } + return $tpl; + } + + + /** + * Takes unknown classes and loads plugin files for them + * class name format: Smarty_PluginType_PluginName + * plugin filename format: plugintype.pluginname.php + * + * @param string $plugin_name class plugin name to load + * @param bool $check check if already loaded + * @return string |boolean filepath of loaded file or false + */ + public function loadPlugin($plugin_name, $check = true) + { + // if function or class exists, exit silently (already loaded) + if ($check && (is_callable($plugin_name) || class_exists($plugin_name, false))) { + return true; + } + // Plugin name is expected to be: Smarty_[Type]_[Name] + $_name_parts = explode('_', $plugin_name, 3); + // class name must have three parts to be valid plugin + // count($_name_parts) < 3 === !isset($_name_parts[2]) + if (!isset($_name_parts[2]) || strtolower($_name_parts[0]) !== 'smarty') { + throw new SmartyException("plugin {$plugin_name} is not a valid name format"); + return false; + } + // if type is "internal", get plugin from sysplugins + if (strtolower($_name_parts[1]) == 'internal') { + $file = SMARTY_SYSPLUGINS_DIR . strtolower($plugin_name) . '.php'; + if (file_exists($file)) { + require_once($file); + return $file; + } else { + return false; + } + } + // plugin filename is expected to be: [type].[name].php + $_plugin_filename = "{$_name_parts[1]}.{$_name_parts[2]}.php"; + + // loop through plugin dirs and find the plugin + foreach($this->getPluginsDir() as $_plugin_dir) { + $names = array( + $_plugin_dir . $_plugin_filename, + $_plugin_dir . strtolower($_plugin_filename), + ); + foreach ($names as $file) { + if (file_exists($file)) { + require_once($file); + return $file; + } + if ($this->use_include_path && !preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $_plugin_dir)) { + // try PHP include_path + if (($file = Smarty_Internal_Get_Include_Path::getIncludePath($file)) !== false) { + require_once($file); + return $file; + } + } + } + } + // no plugin loaded + return false; + } + + /** + * Compile all template files + * + * @param string $extension file extension + * @param bool $force_compile force all to recompile + * @param int $time_limit + * @param int $max_errors + * @return integer number of template files recompiled + */ + public function compileAllTemplates($extention = '.tpl', $force_compile = false, $time_limit = 0, $max_errors = null) + { + return Smarty_Internal_Utility::compileAllTemplates($extention, $force_compile, $time_limit, $max_errors, $this); + } + + /** + * Compile all config files + * + * @param string $extension file extension + * @param bool $force_compile force all to recompile + * @param int $time_limit + * @param int $max_errors + * @return integer number of template files recompiled + */ + public function compileAllConfig($extention = '.conf', $force_compile = false, $time_limit = 0, $max_errors = null) + { + return Smarty_Internal_Utility::compileAllConfig($extention, $force_compile, $time_limit, $max_errors, $this); + } + + /** + * Delete compiled template file + * + * @param string $resource_name template name + * @param string $compile_id compile id + * @param integer $exp_time expiration time + * @return integer number of template files deleted + */ + public function clearCompiledTemplate($resource_name = null, $compile_id = null, $exp_time = null) + { + return Smarty_Internal_Utility::clearCompiledTemplate($resource_name, $compile_id, $exp_time, $this); + } + + + /** + * Return array of tag/attributes of all tags used by an template + * + * @param object $templae template object + * @return array of tag/attributes + */ + public function getTags(Smarty_Internal_Template $template) + { + return Smarty_Internal_Utility::getTags($template); + } + + /** + * Run installation test + * + * @param array $errors Array to write errors into, rather than outputting them + * @return boolean true if setup is fine, false if something is wrong + */ + public function testInstall(&$errors=null) + { + return Smarty_Internal_Utility::testInstall($this, $errors); + } + + /** + * Error Handler to mute expected messages + * + * @link http://php.net/set_error_handler + * @param integer $errno Error level + * @return boolean + */ + public static function mutingErrorHandler($errno, $errstr, $errfile, $errline, $errcontext) + { + $_is_muted_directory = false; + + // add the SMARTY_DIR to the list of muted directories + if (!isset(Smarty::$_muted_directories[SMARTY_DIR])) { + $smarty_dir = realpath(SMARTY_DIR); + Smarty::$_muted_directories[SMARTY_DIR] = array( + 'file' => $smarty_dir, + 'length' => strlen($smarty_dir), + ); + } + + // walk the muted directories and test against $errfile + foreach (Smarty::$_muted_directories as $key => &$dir) { + if (!$dir) { + // resolve directory and length for speedy comparisons + $file = realpath($key); + $dir = array( + 'file' => $file, + 'length' => strlen($file), + ); + } + if (!strncmp($errfile, $dir['file'], $dir['length'])) { + $_is_muted_directory = true; + break; + } + } + + // pass to next error handler if this error did not occur inside SMARTY_DIR + // or the error was within smarty but masked to be ignored + if (!$_is_muted_directory || ($errno && $errno & error_reporting())) { + if (Smarty::$_previous_error_handler) { + return call_user_func(Smarty::$_previous_error_handler, $errno, $errstr, $errfile, $errline, $errcontext); + } else { + return false; + } + } + } + + /** + * Enable error handler to mute expected messages + * + * @return void + */ + public static function muteExpectedErrors() + { + /* + error muting is done because some people implemented custom error_handlers using + http://php.net/set_error_handler and for some reason did not understand the following paragraph: + + It is important to remember that the standard PHP error handler is completely bypassed for the + error types specified by error_types unless the callback function returns FALSE. + error_reporting() settings will have no effect and your error handler will be called regardless - + however you are still able to read the current value of error_reporting and act appropriately. + Of particular note is that this value will be 0 if the statement that caused the error was + prepended by the @ error-control operator. + + Smarty deliberately uses @filemtime() over file_exists() and filemtime() in some places. Reasons include + - @filemtime() is almost twice as fast as using an additional file_exists() + - between file_exists() and filemtime() a possible race condition is opened, + which does not exist using the simple @filemtime() approach. + */ + $error_handler = array('Smarty', 'mutingErrorHandler'); + $previous = set_error_handler($error_handler); + + // avoid dead loops + if ($previous !== $error_handler) { + Smarty::$_previous_error_handler = $previous; + } + } + + /** + * Disable error handler muting expected messages + * + * @return void + */ + public static function unmuteExpectedErrors() + { + restore_error_handler(); + } +} + +/** +* Smarty exception class +* @package Smarty +*/ +class SmartyException extends Exception { +} + +/** +* Smarty compiler exception class +* @package Smarty +*/ +class SmartyCompilerException extends SmartyException { +} + +/** +* Autoloader +*/ +function smartyAutoload($class) +{ + $_class = strtolower($class); + $_classes = array( + 'smarty_config_source' => true, + 'smarty_config_compiled' => true, + 'smarty_security' => true, + 'smarty_cacheresource' => true, + 'smarty_cacheresource_custom' => true, + 'smarty_cacheresource_keyvaluestore' => true, + 'smarty_resource' => true, + 'smarty_resource_custom' => true, + 'smarty_resource_uncompiled' => true, + 'smarty_resource_recompiled' => true, + ); + + if (!strncmp($_class, 'smarty_internal_', 16) || isset($_classes[$_class])) { + include SMARTY_SYSPLUGINS_DIR . $_class . '.php'; + } +} + +?> diff --git a/ThinkPHP/Library/Vendor/Smarty/SmartyBC.class.php b/ThinkPHP/Library/Vendor/Smarty/SmartyBC.class.php new file mode 100644 index 0000000..c060a25 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Smarty/SmartyBC.class.php @@ -0,0 +1,460 @@ + + * @author Uwe Tews + * @author Rodney Rehm + * @package Smarty + */ +/** + * @ignore + */ +require(dirname(__FILE__) . '/Smarty.class.php'); + +/** + * Smarty Backward Compatability Wrapper Class + * + * @package Smarty + */ +class SmartyBC extends Smarty { + + /** + * Smarty 2 BC + * @var string + */ + public $_version = self::SMARTY_VERSION; + + /** + * Initialize new SmartyBC object + * + * @param array $options options to set during initialization, e.g. array( 'forceCompile' => false ) + */ + public function __construct(array $options=array()) + { + parent::__construct($options); + // register {php} tag + $this->registerPlugin('block', 'php', 'smarty_php_tag'); + } + + /** + * wrapper for assign_by_ref + * + * @param string $tpl_var the template variable name + * @param mixed &$value the referenced value to assign + */ + public function assign_by_ref($tpl_var, &$value) + { + $this->assignByRef($tpl_var, $value); + } + + /** + * wrapper for append_by_ref + * + * @param string $tpl_var the template variable name + * @param mixed &$value the referenced value to append + * @param boolean $merge flag if array elements shall be merged + */ + public function append_by_ref($tpl_var, &$value, $merge = false) + { + $this->appendByRef($tpl_var, $value, $merge); + } + + /** + * clear the given assigned template variable. + * + * @param string $tpl_var the template variable to clear + */ + public function clear_assign($tpl_var) + { + $this->clearAssign($tpl_var); + } + + /** + * Registers custom function to be used in templates + * + * @param string $function the name of the template function + * @param string $function_impl the name of the PHP function to register + * @param bool $cacheable + * @param mixed $cache_attrs + */ + public function register_function($function, $function_impl, $cacheable=true, $cache_attrs=null) + { + $this->registerPlugin('function', $function, $function_impl, $cacheable, $cache_attrs); + } + + /** + * Unregisters custom function + * + * @param string $function name of template function + */ + public function unregister_function($function) + { + $this->unregisterPlugin('function', $function); + } + + /** + * Registers object to be used in templates + * + * @param string $object name of template object + * @param object $object_impl the referenced PHP object to register + * @param array $allowed list of allowed methods (empty = all) + * @param boolean $smarty_args smarty argument format, else traditional + * @param array $block_functs list of methods that are block format + */ + public function register_object($object, $object_impl, $allowed = array(), $smarty_args = true, $block_methods = array()) + { + settype($allowed, 'array'); + settype($smarty_args, 'boolean'); + $this->registerObject($object, $object_impl, $allowed, $smarty_args, $block_methods); + } + + /** + * Unregisters object + * + * @param string $object name of template object + */ + public function unregister_object($object) + { + $this->unregisterObject($object); + } + + /** + * Registers block function to be used in templates + * + * @param string $block name of template block + * @param string $block_impl PHP function to register + * @param bool $cacheable + * @param mixed $cache_attrs + */ + public function register_block($block, $block_impl, $cacheable=true, $cache_attrs=null) + { + $this->registerPlugin('block', $block, $block_impl, $cacheable, $cache_attrs); + } + + /** + * Unregisters block function + * + * @param string $block name of template function + */ + public function unregister_block($block) + { + $this->unregisterPlugin('block', $block); + } + + /** + * Registers compiler function + * + * @param string $function name of template function + * @param string $function_impl name of PHP function to register + * @param bool $cacheable + */ + public function register_compiler_function($function, $function_impl, $cacheable=true) + { + $this->registerPlugin('compiler', $function, $function_impl, $cacheable); + } + + /** + * Unregisters compiler function + * + * @param string $function name of template function + */ + public function unregister_compiler_function($function) + { + $this->unregisterPlugin('compiler', $function); + } + + /** + * Registers modifier to be used in templates + * + * @param string $modifier name of template modifier + * @param string $modifier_impl name of PHP function to register + */ + public function register_modifier($modifier, $modifier_impl) + { + $this->registerPlugin('modifier', $modifier, $modifier_impl); + } + + /** + * Unregisters modifier + * + * @param string $modifier name of template modifier + */ + public function unregister_modifier($modifier) + { + $this->unregisterPlugin('modifier', $modifier); + } + + /** + * Registers a resource to fetch a template + * + * @param string $type name of resource + * @param array $functions array of functions to handle resource + */ + public function register_resource($type, $functions) + { + $this->registerResource($type, $functions); + } + + /** + * Unregisters a resource + * + * @param string $type name of resource + */ + public function unregister_resource($type) + { + $this->unregisterResource($type); + } + + /** + * Registers a prefilter function to apply + * to a template before compiling + * + * @param callable $function + */ + public function register_prefilter($function) + { + $this->registerFilter('pre', $function); + } + + /** + * Unregisters a prefilter function + * + * @param callable $function + */ + public function unregister_prefilter($function) + { + $this->unregisterFilter('pre', $function); + } + + /** + * Registers a postfilter function to apply + * to a compiled template after compilation + * + * @param callable $function + */ + public function register_postfilter($function) + { + $this->registerFilter('post', $function); + } + + /** + * Unregisters a postfilter function + * + * @param callable $function + */ + public function unregister_postfilter($function) + { + $this->unregisterFilter('post', $function); + } + + /** + * Registers an output filter function to apply + * to a template output + * + * @param callable $function + */ + public function register_outputfilter($function) + { + $this->registerFilter('output', $function); + } + + /** + * Unregisters an outputfilter function + * + * @param callable $function + */ + public function unregister_outputfilter($function) + { + $this->unregisterFilter('output', $function); + } + + /** + * load a filter of specified type and name + * + * @param string $type filter type + * @param string $name filter name + */ + public function load_filter($type, $name) + { + $this->loadFilter($type, $name); + } + + /** + * clear cached content for the given template and cache id + * + * @param string $tpl_file name of template file + * @param string $cache_id name of cache_id + * @param string $compile_id name of compile_id + * @param string $exp_time expiration time + * @return boolean + */ + public function clear_cache($tpl_file = null, $cache_id = null, $compile_id = null, $exp_time = null) + { + return $this->clearCache($tpl_file, $cache_id, $compile_id, $exp_time); + } + + /** + * clear the entire contents of cache (all templates) + * + * @param string $exp_time expire time + * @return boolean + */ + public function clear_all_cache($exp_time = null) + { + return $this->clearCache(null, null, null, $exp_time); + } + + /** + * test to see if valid cache exists for this template + * + * @param string $tpl_file name of template file + * @param string $cache_id + * @param string $compile_id + * @return boolean + */ + public function is_cached($tpl_file, $cache_id = null, $compile_id = null) + { + return $this->isCached($tpl_file, $cache_id, $compile_id); + } + + /** + * clear all the assigned template variables. + */ + public function clear_all_assign() + { + $this->clearAllAssign(); + } + + /** + * clears compiled version of specified template resource, + * or all compiled template files if one is not specified. + * This function is for advanced use only, not normally needed. + * + * @param string $tpl_file + * @param string $compile_id + * @param string $exp_time + * @return boolean results of {@link smarty_core_rm_auto()} + */ + public function clear_compiled_tpl($tpl_file = null, $compile_id = null, $exp_time = null) + { + return $this->clearCompiledTemplate($tpl_file, $compile_id, $exp_time); + } + + /** + * Checks whether requested template exists. + * + * @param string $tpl_file + * @return boolean + */ + public function template_exists($tpl_file) + { + return $this->templateExists($tpl_file); + } + + /** + * Returns an array containing template variables + * + * @param string $name + * @return array + */ + public function get_template_vars($name=null) + { + return $this->getTemplateVars($name); + } + + /** + * Returns an array containing config variables + * + * @param string $name + * @return array + */ + public function get_config_vars($name=null) + { + return $this->getConfigVars($name); + } + + /** + * load configuration values + * + * @param string $file + * @param string $section + * @param string $scope + */ + public function config_load($file, $section = null, $scope = 'global') + { + $this->ConfigLoad($file, $section, $scope); + } + + /** + * return a reference to a registered object + * + * @param string $name + * @return object + */ + public function get_registered_object($name) + { + return $this->getRegisteredObject($name); + } + + /** + * clear configuration values + * + * @param string $var + */ + public function clear_config($var = null) + { + $this->clearConfig($var); + } + + /** + * trigger Smarty error + * + * @param string $error_msg + * @param integer $error_type + */ + public function trigger_error($error_msg, $error_type = E_USER_WARNING) + { + trigger_error("Smarty error: $error_msg", $error_type); + } + +} + +/** + * Smarty {php}{/php} block function + * + * @param array $params parameter list + * @param string $content contents of the block + * @param object $template template object + * @param boolean &$repeat repeat flag + * @return string content re-formatted + */ +function smarty_php_tag($params, $content, $template, &$repeat) +{ + eval($content); + return ''; +} + +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Smarty/debug.tpl b/ThinkPHP/Library/Vendor/Smarty/debug.tpl new file mode 100644 index 0000000..12eef0f --- /dev/null +++ b/ThinkPHP/Library/Vendor/Smarty/debug.tpl @@ -0,0 +1,133 @@ +{capture name='_smarty_debug' assign=debug_output} + + + + Smarty Debug Console + + + + +

    Smarty Debug Console - {if isset($template_name)}{$template_name|debug_print_var nofilter}{else}Total Time {$execution_time|string_format:"%.5f"}{/if}

    + +{if !empty($template_data)} +

    included templates & config files (load time in seconds)

    + +
    +{foreach $template_data as $template} + {$template.name} + + (compile {$template['compile_time']|string_format:"%.5f"}) (render {$template['render_time']|string_format:"%.5f"}) (cache {$template['cache_time']|string_format:"%.5f"}) + +
    +{/foreach} +
    +{/if} + +

    assigned template variables

    + + + {foreach $assigned_vars as $vars} + + + + {/foreach} +
    ${$vars@key|escape:'html'}{$vars|debug_print_var nofilter}
    + +

    assigned config file variables (outer template scope)

    + + + {foreach $config_vars as $vars} + + + + {/foreach} + +
    {$vars@key|escape:'html'}{$vars|debug_print_var nofilter}
    + + +{/capture} + diff --git a/ThinkPHP/Library/Vendor/Smarty/plugins/block.textformat.php b/ThinkPHP/Library/Vendor/Smarty/plugins/block.textformat.php new file mode 100644 index 0000000..bdd8067 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Smarty/plugins/block.textformat.php @@ -0,0 +1,113 @@ + + * Name: textformat
    + * Purpose: format text a certain way with preset styles + * or custom wrap/indent settings
    + * Params: + *
    + * - style         - string (email)
    + * - indent        - integer (0)
    + * - wrap          - integer (80)
    + * - wrap_char     - string ("\n")
    + * - indent_char   - string (" ")
    + * - wrap_boundary - boolean (true)
    + * 
    + * + * @link http://www.smarty.net/manual/en/language.function.textformat.php {textformat} + * (Smarty online manual) + * @param array $params parameters + * @param string $content contents of the block + * @param Smarty_Internal_Template $template template object + * @param boolean &$repeat repeat flag + * @return string content re-formatted + * @author Monte Ohrt + */ +function smarty_block_textformat($params, $content, $template, &$repeat) +{ + if (is_null($content)) { + return; + } + + $style = null; + $indent = 0; + $indent_first = 0; + $indent_char = ' '; + $wrap = 80; + $wrap_char = "\n"; + $wrap_cut = false; + $assign = null; + + foreach ($params as $_key => $_val) { + switch ($_key) { + case 'style': + case 'indent_char': + case 'wrap_char': + case 'assign': + $$_key = (string)$_val; + break; + + case 'indent': + case 'indent_first': + case 'wrap': + $$_key = (int)$_val; + break; + + case 'wrap_cut': + $$_key = (bool)$_val; + break; + + default: + trigger_error("textformat: unknown attribute '$_key'"); + } + } + + if ($style == 'email') { + $wrap = 72; + } + // split into paragraphs + $_paragraphs = preg_split('![\r\n]{2}!', $content); + $_output = ''; + + + foreach ($_paragraphs as &$_paragraph) { + if (!$_paragraph) { + continue; + } + // convert mult. spaces & special chars to single space + $_paragraph = preg_replace(array('!\s+!u', '!(^\s+)|(\s+$)!u'), array(' ', ''), $_paragraph); + // indent first line + if ($indent_first > 0) { + $_paragraph = str_repeat($indent_char, $indent_first) . $_paragraph; + } + // wordwrap sentences + if (SMARTY_MBSTRING /* ^phpunit */&&empty($_SERVER['SMARTY_PHPUNIT_DISABLE_MBSTRING'])/* phpunit$ */) { + require_once(SMARTY_PLUGINS_DIR . 'shared.mb_wordwrap.php'); + $_paragraph = smarty_mb_wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut); + } else { + $_paragraph = wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut); + } + // indent lines + if ($indent > 0) { + $_paragraph = preg_replace('!^!m', str_repeat($indent_char, $indent), $_paragraph); + } + } + $_output = implode($wrap_char . $wrap_char, $_paragraphs); + + if ($assign) { + $template->assign($assign, $_output); + } else { + return $_output; + } +} + +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Smarty/plugins/function.counter.php b/ThinkPHP/Library/Vendor/Smarty/plugins/function.counter.php new file mode 100644 index 0000000..3906bad --- /dev/null +++ b/ThinkPHP/Library/Vendor/Smarty/plugins/function.counter.php @@ -0,0 +1,78 @@ + + * Name: counter
    + * Purpose: print out a counter value + * + * @author Monte Ohrt + * @link http://www.smarty.net/manual/en/language.function.counter.php {counter} + * (Smarty online manual) + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * @return string|null + */ +function smarty_function_counter($params, $template) +{ + static $counters = array(); + + $name = (isset($params['name'])) ? $params['name'] : 'default'; + if (!isset($counters[$name])) { + $counters[$name] = array( + 'start'=>1, + 'skip'=>1, + 'direction'=>'up', + 'count'=>1 + ); + } + $counter =& $counters[$name]; + + if (isset($params['start'])) { + $counter['start'] = $counter['count'] = (int)$params['start']; + } + + if (!empty($params['assign'])) { + $counter['assign'] = $params['assign']; + } + + if (isset($counter['assign'])) { + $template->assign($counter['assign'], $counter['count']); + } + + if (isset($params['print'])) { + $print = (bool)$params['print']; + } else { + $print = empty($counter['assign']); + } + + if ($print) { + $retval = $counter['count']; + } else { + $retval = null; + } + + if (isset($params['skip'])) { + $counter['skip'] = $params['skip']; + } + + if (isset($params['direction'])) { + $counter['direction'] = $params['direction']; + } + + if ($counter['direction'] == "down") + $counter['count'] -= $counter['skip']; + else + $counter['count'] += $counter['skip']; + + return $retval; + +} + +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Smarty/plugins/function.cycle.php b/ThinkPHP/Library/Vendor/Smarty/plugins/function.cycle.php new file mode 100644 index 0000000..1778ffb --- /dev/null +++ b/ThinkPHP/Library/Vendor/Smarty/plugins/function.cycle.php @@ -0,0 +1,106 @@ + + * Name: cycle
    + * Date: May 3, 2002
    + * Purpose: cycle through given values
    + * Params: + *
    + * - name      - name of cycle (optional)
    + * - values    - comma separated list of values to cycle, or an array of values to cycle
    + *               (this can be left out for subsequent calls)
    + * - reset     - boolean - resets given var to true
    + * - print     - boolean - print var or not. default is true
    + * - advance   - boolean - whether or not to advance the cycle
    + * - delimiter - the value delimiter, default is ","
    + * - assign    - boolean, assigns to template var instead of printed.
    + * 
    + * Examples:
    + *
    + * {cycle values="#eeeeee,#d0d0d0d"}
    + * {cycle name=row values="one,two,three" reset=true}
    + * {cycle name=row}
    + * 
    + * + * @link http://www.smarty.net/manual/en/language.function.cycle.php {cycle} + * (Smarty online manual) + * @author Monte Ohrt + * @author credit to Mark Priatel + * @author credit to Gerard + * @author credit to Jason Sweat + * @version 1.3 + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * @return string|null + */ + +function smarty_function_cycle($params, $template) +{ + static $cycle_vars; + + $name = (empty($params['name'])) ? 'default' : $params['name']; + $print = (isset($params['print'])) ? (bool)$params['print'] : true; + $advance = (isset($params['advance'])) ? (bool)$params['advance'] : true; + $reset = (isset($params['reset'])) ? (bool)$params['reset'] : false; + + if (!isset($params['values'])) { + if(!isset($cycle_vars[$name]['values'])) { + trigger_error("cycle: missing 'values' parameter"); + return; + } + } else { + if(isset($cycle_vars[$name]['values']) + && $cycle_vars[$name]['values'] != $params['values'] ) { + $cycle_vars[$name]['index'] = 0; + } + $cycle_vars[$name]['values'] = $params['values']; + } + + if (isset($params['delimiter'])) { + $cycle_vars[$name]['delimiter'] = $params['delimiter']; + } elseif (!isset($cycle_vars[$name]['delimiter'])) { + $cycle_vars[$name]['delimiter'] = ','; + } + + if(is_array($cycle_vars[$name]['values'])) { + $cycle_array = $cycle_vars[$name]['values']; + } else { + $cycle_array = explode($cycle_vars[$name]['delimiter'],$cycle_vars[$name]['values']); + } + + if(!isset($cycle_vars[$name]['index']) || $reset ) { + $cycle_vars[$name]['index'] = 0; + } + + if (isset($params['assign'])) { + $print = false; + $template->assign($params['assign'], $cycle_array[$cycle_vars[$name]['index']]); + } + + if($print) { + $retval = $cycle_array[$cycle_vars[$name]['index']]; + } else { + $retval = null; + } + + if($advance) { + if ( $cycle_vars[$name]['index'] >= count($cycle_array) -1 ) { + $cycle_vars[$name]['index'] = 0; + } else { + $cycle_vars[$name]['index']++; + } + } + + return $retval; +} + +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Smarty/plugins/function.fetch.php b/ThinkPHP/Library/Vendor/Smarty/plugins/function.fetch.php new file mode 100644 index 0000000..cde98d2 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Smarty/plugins/function.fetch.php @@ -0,0 +1,216 @@ + + * Name: fetch
    + * Purpose: fetch file, web or ftp data and display results + * + * @link http://www.smarty.net/manual/en/language.function.fetch.php {fetch} + * (Smarty online manual) + * @author Monte Ohrt + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * @return string|null if the assign parameter is passed, Smarty assigns the result to a template variable + */ +function smarty_function_fetch($params, $template) +{ + if (empty($params['file'])) { + trigger_error("[plugin] fetch parameter 'file' cannot be empty",E_USER_NOTICE); + return; + } + + $content = ''; + if (isset($template->smarty->security_policy) && !preg_match('!^(http|ftp)://!i', $params['file'])) { + if(!$template->smarty->security_policy->isTrustedResourceDir($params['file'])) { + return; + } + + // fetch the file + if($fp = @fopen($params['file'],'r')) { + while(!feof($fp)) { + $content .= fgets ($fp,4096); + } + fclose($fp); + } else { + trigger_error('[plugin] fetch cannot read file \'' . $params['file'] . '\'',E_USER_NOTICE); + return; + } + } else { + // not a local file + if(preg_match('!^http://!i',$params['file'])) { + // http fetch + if($uri_parts = parse_url($params['file'])) { + // set defaults + $host = $server_name = $uri_parts['host']; + $timeout = 30; + $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*"; + $agent = "Smarty Template Engine ". Smarty::SMARTY_VERSION; + $referer = ""; + $uri = !empty($uri_parts['path']) ? $uri_parts['path'] : '/'; + $uri .= !empty($uri_parts['query']) ? '?' . $uri_parts['query'] : ''; + $_is_proxy = false; + if(empty($uri_parts['port'])) { + $port = 80; + } else { + $port = $uri_parts['port']; + } + if(!empty($uri_parts['user'])) { + $user = $uri_parts['user']; + } + if(!empty($uri_parts['pass'])) { + $pass = $uri_parts['pass']; + } + // loop through parameters, setup headers + foreach($params as $param_key => $param_value) { + switch($param_key) { + case "file": + case "assign": + case "assign_headers": + break; + case "user": + if(!empty($param_value)) { + $user = $param_value; + } + break; + case "pass": + if(!empty($param_value)) { + $pass = $param_value; + } + break; + case "accept": + if(!empty($param_value)) { + $accept = $param_value; + } + break; + case "header": + if(!empty($param_value)) { + if(!preg_match('![\w\d-]+: .+!',$param_value)) { + trigger_error("[plugin] invalid header format '".$param_value."'",E_USER_NOTICE); + return; + } else { + $extra_headers[] = $param_value; + } + } + break; + case "proxy_host": + if(!empty($param_value)) { + $proxy_host = $param_value; + } + break; + case "proxy_port": + if(!preg_match('!\D!', $param_value)) { + $proxy_port = (int) $param_value; + } else { + trigger_error("[plugin] invalid value for attribute '".$param_key."'",E_USER_NOTICE); + return; + } + break; + case "agent": + if(!empty($param_value)) { + $agent = $param_value; + } + break; + case "referer": + if(!empty($param_value)) { + $referer = $param_value; + } + break; + case "timeout": + if(!preg_match('!\D!', $param_value)) { + $timeout = (int) $param_value; + } else { + trigger_error("[plugin] invalid value for attribute '".$param_key."'",E_USER_NOTICE); + return; + } + break; + default: + trigger_error("[plugin] unrecognized attribute '".$param_key."'",E_USER_NOTICE); + return; + } + } + if(!empty($proxy_host) && !empty($proxy_port)) { + $_is_proxy = true; + $fp = fsockopen($proxy_host,$proxy_port,$errno,$errstr,$timeout); + } else { + $fp = fsockopen($server_name,$port,$errno,$errstr,$timeout); + } + + if(!$fp) { + trigger_error("[plugin] unable to fetch: $errstr ($errno)",E_USER_NOTICE); + return; + } else { + if($_is_proxy) { + fputs($fp, 'GET ' . $params['file'] . " HTTP/1.0\r\n"); + } else { + fputs($fp, "GET $uri HTTP/1.0\r\n"); + } + if(!empty($host)) { + fputs($fp, "Host: $host\r\n"); + } + if(!empty($accept)) { + fputs($fp, "Accept: $accept\r\n"); + } + if(!empty($agent)) { + fputs($fp, "User-Agent: $agent\r\n"); + } + if(!empty($referer)) { + fputs($fp, "Referer: $referer\r\n"); + } + if(isset($extra_headers) && is_array($extra_headers)) { + foreach($extra_headers as $curr_header) { + fputs($fp, $curr_header."\r\n"); + } + } + if(!empty($user) && !empty($pass)) { + fputs($fp, "Authorization: BASIC ".base64_encode("$user:$pass")."\r\n"); + } + + fputs($fp, "\r\n"); + while(!feof($fp)) { + $content .= fgets($fp,4096); + } + fclose($fp); + $csplit = preg_split("!\r\n\r\n!",$content,2); + + $content = $csplit[1]; + + if(!empty($params['assign_headers'])) { + $template->assign($params['assign_headers'],preg_split("!\r\n!",$csplit[0])); + } + } + } else { + trigger_error("[plugin fetch] unable to parse URL, check syntax",E_USER_NOTICE); + return; + } + } else { + // ftp fetch + if($fp = @fopen($params['file'],'r')) { + while(!feof($fp)) { + $content .= fgets ($fp,4096); + } + fclose($fp); + } else { + trigger_error('[plugin] fetch cannot read file \'' . $params['file'] .'\'',E_USER_NOTICE); + return; + } + } + + } + + + if (!empty($params['assign'])) { + $template->assign($params['assign'],$content); + } else { + return $content; + } +} + +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Smarty/plugins/function.html_checkboxes.php b/ThinkPHP/Library/Vendor/Smarty/plugins/function.html_checkboxes.php new file mode 100644 index 0000000..4251369 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Smarty/plugins/function.html_checkboxes.php @@ -0,0 +1,216 @@ + + * Type: function
    + * Name: html_checkboxes
    + * Date: 24.Feb.2003
    + * Purpose: Prints out a list of checkbox input types
    + * Examples: + *
    + * {html_checkboxes values=$ids output=$names}
    + * {html_checkboxes values=$ids name='box' separator='
    ' output=$names} + * {html_checkboxes values=$ids checked=$checked separator='
    ' output=$names} + *
    + * Params: + *
    + * - name       (optional) - string default "checkbox"
    + * - values     (required) - array
    + * - options    (optional) - associative array
    + * - checked    (optional) - array default not set
    + * - separator  (optional) - ie 
    or   + * - output (optional) - the output next to each checkbox + * - assign (optional) - assign the output as an array to this variable + * - escape (optional) - escape the content (not value), defaults to true + *
    + * + * @link http://www.smarty.net/manual/en/language.function.html.checkboxes.php {html_checkboxes} + * (Smarty online manual) + * @author Christopher Kvarme + * @author credits to Monte Ohrt + * @version 1.0 + * @param array $params parameters + * @param object $template template object + * @return string + * @uses smarty_function_escape_special_chars() + */ +function smarty_function_html_checkboxes($params, $template) +{ + require_once(SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php'); + + $name = 'checkbox'; + $values = null; + $options = null; + $selected = array(); + $separator = ''; + $escape = true; + $labels = true; + $label_ids = false; + $output = null; + + $extra = ''; + + foreach($params as $_key => $_val) { + switch($_key) { + case 'name': + case 'separator': + $$_key = (string) $_val; + break; + + case 'escape': + case 'labels': + case 'label_ids': + $$_key = (bool) $_val; + break; + + case 'options': + $$_key = (array) $_val; + break; + + case 'values': + case 'output': + $$_key = array_values((array) $_val); + break; + + case 'checked': + case 'selected': + if (is_array($_val)) { + $selected = array(); + foreach ($_val as $_sel) { + if (is_object($_sel)) { + if (method_exists($_sel, "__toString")) { + $_sel = smarty_function_escape_special_chars((string) $_sel->__toString()); + } else { + trigger_error("html_checkboxes: selected attribute contains an object of class '". get_class($_sel) ."' without __toString() method", E_USER_NOTICE); + continue; + } + } else { + $_sel = smarty_function_escape_special_chars((string) $_sel); + } + $selected[$_sel] = true; + } + } elseif (is_object($_val)) { + if (method_exists($_val, "__toString")) { + $selected = smarty_function_escape_special_chars((string) $_val->__toString()); + } else { + trigger_error("html_checkboxes: selected attribute is an object of class '". get_class($_val) ."' without __toString() method", E_USER_NOTICE); + } + } else { + $selected = smarty_function_escape_special_chars((string) $_val); + } + break; + + case 'checkboxes': + trigger_error('html_checkboxes: the use of the "checkboxes" attribute is deprecated, use "options" instead', E_USER_WARNING); + $options = (array) $_val; + break; + + case 'assign': + break; + + default: + if(!is_array($_val)) { + $extra .= ' '.$_key.'="'.smarty_function_escape_special_chars($_val).'"'; + } else { + trigger_error("html_checkboxes: extra attribute '$_key' cannot be an array", E_USER_NOTICE); + } + break; + } + } + + if (!isset($options) && !isset($values)) + return ''; /* raise error here? */ + + $_html_result = array(); + + if (isset($options)) { + foreach ($options as $_key=>$_val) { + $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels, $label_ids, $escape); + } + } else { + foreach ($values as $_i=>$_key) { + $_val = isset($output[$_i]) ? $output[$_i] : ''; + $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels, $label_ids, $escape); + } + } + + if(!empty($params['assign'])) { + $template->assign($params['assign'], $_html_result); + } else { + return implode("\n", $_html_result); + } + +} + +function smarty_function_html_checkboxes_output($name, $value, $output, $selected, $extra, $separator, $labels, $label_ids, $escape=true) { + $_output = ''; + + if (is_object($value)) { + if (method_exists($value, "__toString")) { + $value = (string) $value->__toString(); + } else { + trigger_error("html_options: value is an object of class '". get_class($value) ."' without __toString() method", E_USER_NOTICE); + return ''; + } + } else { + $value = (string) $value; + } + + if (is_object($output)) { + if (method_exists($output, "__toString")) { + $output = (string) $output->__toString(); + } else { + trigger_error("html_options: output is an object of class '". get_class($output) ."' without __toString() method", E_USER_NOTICE); + return ''; + } + } else { + $output = (string) $output; + } + + if ($labels) { + if ($label_ids) { + $_id = smarty_function_escape_special_chars(preg_replace('![^\w\-\.]!u', '_', $name . '_' . $value)); + $_output .= '

    NiFR; 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 +// +---------------------------------------------------------------------- +namespace Think; +/** + * ThinkPHP 视图类 + */ +class View { + /** + * 模板输出变量 + * @var tVar + * @access protected + */ + protected $tVar = array(); + + /** + * 模板主题 + * @var theme + * @access protected + */ + protected $theme = ''; + + /** + * 模板变量赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function assign($name,$value=''){ + if(is_array($name)) { + $this->tVar = array_merge($this->tVar,$name); + }else { + $this->tVar[$name] = $value; + } + } + + /** + * 取得模板变量的值 + * @access public + * @param string $name + * @return mixed + */ + public function get($name=''){ + if('' === $name) { + return $this->tVar; + } + return isset($this->tVar[$name])?$this->tVar[$name]:false; + } + + /** + * 加载模板和页面输出 可以返回输出内容 + * @access public + * @param string $templateFile 模板文件名 + * @param string $charset 模板输出字符集 + * @param string $contentType 输出类型 + * @param string $content 模板输出内容 + * @param string $prefix 模板缓存前缀 + * @return mixed + */ + public function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') { + G('viewStartTime'); + // 视图开始标签 + Hook::listen('view_begin',$templateFile); + // 解析并获取模板内容 + $content = $this->fetch($templateFile,$content,$prefix); + // 输出模板内容 + $this->render($content,$charset,$contentType); + // 视图结束标签 + Hook::listen('view_end'); + } + + /** + * 输出内容文本可以包括Html + * @access private + * @param string $content 输出内容 + * @param string $charset 模板输出字符集 + * @param string $contentType 输出类型 + * @return mixed + */ + private function render($content,$charset='',$contentType=''){ + if(empty($charset)) $charset = C('DEFAULT_CHARSET'); + if(empty($contentType)) $contentType = C('TMPL_CONTENT_TYPE'); + // 网页字符编码 + header('Content-Type:'.$contentType.'; charset='.$charset); + header('Cache-control: '.C('HTTP_CACHE_CONTROL')); // 页面缓存控制 + header('X-Powered-By:ThinkPHP'); + // 输出模板文件 + echo $content; + } + + /** + * 解析和获取模板内容 用于输出 + * @access public + * @param string $templateFile 模板文件名 + * @param string $content 模板输出内容 + * @param string $prefix 模板缓存前缀 + * @return string + */ + public function fetch($templateFile='',$content='',$prefix='') { + if(empty($content)) { + $templateFile = $this->parseTemplate($templateFile); + // 模板文件不存在直接返回 + if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile); + }else{ + defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath()); + } + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板 + $_content = $content; + // 模板阵列变量分解成为独立变量 + extract($this->tVar, EXTR_OVERWRITE); + // 直接载入PHP模板 + empty($_content)?include $templateFile:eval('?>'.$_content); + }else{ + // 视图解析标签 + $params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix); + Hook::listen('view_parse',$params); + } + // 获取并清空缓存 + $content = ob_get_clean(); + // 内容过滤标签 + Hook::listen('view_filter',$content); + // 输出模板文件 + return $content; + } + + /** + * 自动定位模板文件 + * @access protected + * @param string $template 模板文件规则 + * @return string + */ + public function parseTemplate($template='') { + if(is_file($template)) { + return $template; + } + $depr = C('TMPL_FILE_DEPR'); + $template = str_replace(':', $depr, $template); + + // 获取当前模块 + $module = MODULE_NAME; + if(strpos($template,'@')){ // 跨模块调用模版文件 + list($module,$template) = explode('@',$template); + } + // 获取当前主题的模版路径 + defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath($module)); + + // 分析模板文件规则 + if('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = CONTROLLER_NAME . $depr . ACTION_NAME; + }elseif(false === strpos($template, $depr)){ + $template = CONTROLLER_NAME . $depr . $template; + } + $file = THEME_PATH.$template.C('TMPL_TEMPLATE_SUFFIX'); + if(C('TMPL_LOAD_DEFAULTTHEME') && THEME_NAME != C('DEFAULT_THEME') && !is_file($file)){ + // 找不到当前主题模板的时候定位默认主题中的模板 + $file = dirname(THEME_PATH).'/'.C('DEFAULT_THEME').'/'.$template.C('TMPL_TEMPLATE_SUFFIX'); + } + return $file; + } + + /** + * 获取当前的模板路径 + * @access protected + * @param string $module 模块名 + * @return string + */ + protected function getThemePath($module=MODULE_NAME){ + // 获取当前主题名称 + $theme = $this->getTemplateTheme(); + // 获取当前主题的模版路径 + $tmplPath = C('VIEW_PATH'); // 模块设置独立的视图目录 + if(!$tmplPath){ + // 定义TMPL_PATH 则改变全局的视图目录到模块之外 + $tmplPath = defined('TMPL_PATH')? TMPL_PATH.$module.'/' : APP_PATH.$module.'/'.C('DEFAULT_V_LAYER').'/'; + } + return $tmplPath.$theme; + } + + /** + * 设置当前输出的模板主题 + * @access public + * @param mixed $theme 主题名称 + * @return View + */ + public function theme($theme){ + $this->theme = $theme; + return $this; + } + + /** + * 获取当前的模板主题 + * @access private + * @return string + */ + private function getTemplateTheme() { + if($this->theme) { // 指定模板主题 + $theme = $this->theme; + }else{ + /* 获取模板主题名称 */ + $theme = C('DEFAULT_THEME'); + if(C('TMPL_DETECT_THEME')) {// 自动侦测模板主题 + $t = C('VAR_TEMPLATE'); + if (isset($_GET[$t])){ + $theme = $_GET[$t]; + }elseif(cookie('think_template')){ + $theme = cookie('think_template'); + } + if(!in_array($theme,explode(',',C('THEME_LIST')))){ + $theme = C('DEFAULT_THEME'); + } + cookie('think_template',$theme,864000); + } + } + defined('THEME_NAME') || define('THEME_NAME', $theme); // 当前模板主题名称 + return $theme?$theme . '/':''; + } + +} \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Boris/Boris.php b/ThinkPHP/Library/Vendor/Boris/Boris.php new file mode 100644 index 0000000..e2319cd --- /dev/null +++ b/ThinkPHP/Library/Vendor/Boris/Boris.php @@ -0,0 +1,174 @@ + ', $historyFile = null) { + $this->setPrompt($prompt); + $this->_historyFile = $historyFile + ? $historyFile + : sprintf('%s/.boris_history', getenv('HOME')) + ; + $this->_inspector = new ColoredInspector(); + } + + /** + * Add a new hook to run in the context of the REPL when it starts. + * + * @param mixed $hook + * + * The hook is either a string of PHP code to eval(), or a Closure accepting + * the EvalWorker object as its first argument and the array of defined + * local variables in the second argument. + * + * If the hook is a callback and needs to set any local variables in the + * REPL's scope, it should invoke $worker->setLocal($var_name, $value) to + * do so. + * + * Hooks are guaranteed to run in the order they were added and the state + * set by each hook is available to the next hook (either through global + * resources, such as classes and interfaces, or through the 2nd parameter + * of the callback, if any local variables were set. + * + * @example Contrived example where one hook sets the date and another + * prints it in the REPL. + * + * $boris->onStart(function($worker, $vars){ + * $worker->setLocal('date', date('Y-m-d')); + * }); + * + * $boris->onStart('echo "The date is $date\n";'); + */ + public function onStart($hook) { + $this->_startHooks[] = $hook; + } + + /** + * Add a new hook to run in the context of the REPL when a fatal error occurs. + * + * @param mixed $hook + * + * The hook is either a string of PHP code to eval(), or a Closure accepting + * the EvalWorker object as its first argument and the array of defined + * local variables in the second argument. + * + * If the hook is a callback and needs to set any local variables in the + * REPL's scope, it should invoke $worker->setLocal($var_name, $value) to + * do so. + * + * Hooks are guaranteed to run in the order they were added and the state + * set by each hook is available to the next hook (either through global + * resources, such as classes and interfaces, or through the 2nd parameter + * of the callback, if any local variables were set. + * + * @example An example if your project requires some database connection cleanup: + * + * $boris->onFailure(function($worker, $vars){ + * DB::reset(); + * }); + */ + public function onFailure($hook){ + $this->_failureHooks[] = $hook; + } + + /** + * Set a local variable, or many local variables. + * + * @example Setting a single variable + * $boris->setLocal('user', $bob); + * + * @example Setting many variables at once + * $boris->setLocal(array('user' => $bob, 'appContext' => $appContext)); + * + * This method can safely be invoked repeatedly. + * + * @param array|string $local + * @param mixed $value, optional + */ + public function setLocal($local, $value = null) { + if (!is_array($local)) { + $local = array($local => $value); + } + + $this->_exports = array_merge($this->_exports, $local); + } + + /** + * Sets the Boris prompt text + * + * @param string $prompt + */ + public function setPrompt($prompt) { + $this->_prompt = $prompt; + } + + /** + * Set an Inspector object for Boris to output return values with. + * + * @param object $inspector any object the responds to inspect($v) + */ + public function setInspector($inspector) { + $this->_inspector = $inspector; + } + + /** + * Start the REPL (display the readline prompt). + * + * This method never returns. + */ + public function start() { + declare(ticks = 1); + pcntl_signal(SIGINT, SIG_IGN, true); + + if (!$pipes = stream_socket_pair( + STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP)) { + throw new \RuntimeException('Failed to create socket pair'); + } + + $pid = pcntl_fork(); + + if ($pid > 0) { + if (function_exists('setproctitle')) { + setproctitle('boris (master)'); + } + + fclose($pipes[0]); + $client = new ReadlineClient($pipes[1]); + $client->start($this->_prompt, $this->_historyFile); + } elseif ($pid < 0) { + throw new \RuntimeException('Failed to fork child process'); + } else { + if (function_exists('setproctitle')) { + setproctitle('boris (worker)'); + } + + fclose($pipes[1]); + $worker = new EvalWorker($pipes[0]); + $worker->setLocal($this->_exports); + $worker->setStartHooks($this->_startHooks); + $worker->setFailureHooks($this->_failureHooks); + $worker->setInspector($this->_inspector); + $worker->start(); + } + } +} diff --git a/ThinkPHP/Library/Vendor/Boris/CLIOptionsHandler.php b/ThinkPHP/Library/Vendor/Boris/CLIOptionsHandler.php new file mode 100644 index 0000000..2aa3ab3 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Boris/CLIOptionsHandler.php @@ -0,0 +1,85 @@ + $value) { + switch ($option) { + /* + * Sets files to load at startup, may be used multiple times, + * i.e: boris -r test.php,foo/bar.php -r ba/foo.php --require hey.php + */ + case 'r': + case 'require': + $this->_handleRequire($boris, $value); + break; + + /* + * Show Usage info + */ + case 'h': + case 'help': + $this->_handleUsageInfo(); + break; + + /* + * Show version + */ + case 'v': + case 'version': + $this->_handleVersion(); + break; + } + } + } + + // -- Private Methods + + private function _handleRequire($boris, $paths) { + $require = array_reduce( + (array) $paths, + function($acc, $v) { return array_merge($acc, explode(',', $v)); }, + array() + ); + + $boris->onStart(function($worker, $scope) use($require) { + foreach($require as $path) { + require $path; + } + + $worker->setLocal(get_defined_vars()); + }); + } + + private function _handleUsageInfo() { + echo << + * @author Chris Corbyn + * + * Copyright © 2013-2014 Rob Morris. + */ + +namespace Boris; + +/** + * Identifies data types in data structures and syntax highlights them. + */ +class ColoredInspector implements Inspector { + static $TERM_COLORS = array( + 'black' => "\033[0;30m", + 'white' => "\033[1;37m", + 'none' => "\033[1;30m", + 'dark_grey' => "\033[1;30m", + 'light_grey' => "\033[0;37m", + 'dark_red' => "\033[0;31m", + 'light_red' => "\033[1;31m", + 'dark_green' => "\033[0;32m", + 'light_green' => "\033[1;32m", + 'dark_yellow' => "\033[0;33m", + 'light_yellow' => "\033[1;33m", + 'dark_blue' => "\033[0;34m", + 'light_blue' => "\033[1;34m", + 'dark_purple' => "\033[0;35m", + 'light_purple' => "\033[1;35m", + 'dark_cyan' => "\033[0;36m", + 'light_cyan' => "\033[1;36m", + ); + + private $_fallback; + private $_colorMap = array(); + + /** + * Initialize a new ColoredInspector, using $colorMap. + * + * The colors should be an associative array with the keys: + * + * - 'integer' + * - 'float' + * - 'keyword' + * - 'string' + * - 'boolean' + * - 'default' + * + * And the values, one of the following colors: + * + * - 'none' + * - 'black' + * - 'white' + * - 'dark_grey' + * - 'light_grey' + * - 'dark_red' + * - 'light_red' + * - 'dark_green' + * - 'light_green' + * - 'dark_yellow' + * - 'light_yellow' + * - 'dark_blue' + * - 'light_blue' + * - 'dark_purple' + * - 'light_purple' + * - 'dark_cyan' + * - 'light_cyan' + * + * An empty $colorMap array effectively means 'none' for all types. + * + * @param array $colorMap + */ + public function __construct($colorMap = null) { + $this->_fallback = new DumpInspector(); + + if (isset($colorMap)) { + $this->_colorMap = $colorMap; + } else { + $this->_colorMap = $this->_defaultColorMap(); + } + } + + public function inspect($variable) { + return preg_replace( + '/^/m', + $this->_colorize('comment', '// '), + $this->_dump($variable) + ); + } + + /** + * Returns an associative array of an object's properties. + * + * This method is public so that subclasses may override it. + * + * @param object $value + * @return array + * */ + public function objectVars($value) { + return get_object_vars($value); + } + + // -- Private Methods + + public function _dump($value) { + $tests = array( + 'is_null' => '_dumpNull', + 'is_string' => '_dumpString', + 'is_bool' => '_dumpBoolean', + 'is_integer' => '_dumpInteger', + 'is_float' => '_dumpFloat', + 'is_array' => '_dumpArray', + 'is_object' => '_dumpObject' + ); + + foreach ($tests as $predicate => $outputMethod) { + if (call_user_func($predicate, $value)) + return call_user_func(array($this, $outputMethod), $value); + } + + return $this->_fallback->inspect($value); + } + + private function _dumpNull($value) { + return $this->_colorize('keyword', 'NULL'); + } + + private function _dumpString($value) { + return $this->_colorize('string', var_export($value, true)); + } + + private function _dumpBoolean($value) { + return $this->_colorize('bool', var_export($value, true)); + } + + private function _dumpInteger($value) { + return $this->_colorize('integer', var_export($value, true)); + } + + private function _dumpFloat($value) { + return $this->_colorize('float', var_export($value, true)); + } + + private function _dumpArray($value) { + return $this->_dumpStructure('array', $value); + } + + private function _dumpObject($value) { + return $this->_dumpStructure( + sprintf('object(%s)', get_class($value)), + $this->objectVars($value) + ); + } + + private function _dumpStructure($type, $value) { + return $this->_astToString($this->_buildAst($type, $value)); + } + + public function _buildAst($type, $value, $seen = array()) { + // FIXME: Improve this AST so it doesn't require access to dump() or colorize() + if ($this->_isSeen($value, $seen)) { + return $this->_colorize('default', '*** RECURSION ***'); + } else { + $nextSeen = array_merge($seen, array($value)); + } + + if (is_object($value)) { + $vars = $this->objectVars($value); + } else { + $vars = $value; + } + + $self = $this; + + return array( + 'name' => $this->_colorize('keyword', $type), + 'children' => empty($vars) ? array() : array_combine( + array_map(array($this, '_dump'), array_keys($vars)), + array_map( + function($v) use($self, $nextSeen) { + if (is_object($v)) { + return $self->_buildAst( + sprintf('object(%s)', get_class($v)), + $v, + $nextSeen + ); + } elseif (is_array($v)) { + return $self->_buildAst('array', $v, $nextSeen); + } else { + return $self->_dump($v); + } + }, + array_values($vars) + ) + ) + ); + } + + public function _astToString($node, $indent = 0) { + $children = $node['children']; + $self = $this; + + return implode( + "\n", + array( + sprintf('%s(', $node['name']), + implode( + ",\n", + array_map( + function($k) use($self, $children, $indent) { + if (is_array($children[$k])) { + return sprintf( + '%s%s => %s', + str_repeat(' ', ($indent + 1) * 2), + $k, + $self->_astToString($children[$k], $indent + 1) + ); + } else { + return sprintf( + '%s%s => %s', + str_repeat(' ', ($indent + 1) * 2), + $k, + $children[$k] + ); + } + }, + array_keys($children) + ) + ), + sprintf('%s)', str_repeat(' ', $indent * 2)) + ) + ); + } + + private function _defaultColorMap() { + return array( + 'integer' => 'light_green', + 'float' => 'light_yellow', + 'string' => 'light_red', + 'bool' => 'light_purple', + 'keyword' => 'light_cyan', + 'comment' => 'dark_grey', + 'default' => 'none' + ); + } + + private function _colorize($type, $value) { + if (!empty($this->_colorMap[$type])) { + $colorName = $this->_colorMap[$type]; + } else { + $colorName = $this->_colorMap['default']; + } + + return sprintf( + "%s%s\033[0m", + static::$TERM_COLORS[$colorName], + $value + ); + } + + private function _isSeen($value, $seen) { + foreach ($seen as $v) { + if ($v === $value) + return true; + } + + return false; + } +} diff --git a/ThinkPHP/Library/Vendor/Boris/Config.php b/ThinkPHP/Library/Vendor/Boris/Config.php new file mode 100644 index 0000000..f03d3b7 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Boris/Config.php @@ -0,0 +1,85 @@ +_cascade = $cascade; + $this->_searchPaths = $searchPaths; + } + + /** + * Searches for configuration files in the available + * search paths, and applies them to the provided + * boris instance. + * + * Returns true if any configuration files were found. + * + * @param Boris\Boris $boris + * @return bool + */ + public function apply(Boris $boris) { + $applied = false; + + foreach($this->_searchPaths as $path) { + if (is_readable($path)) { + $this->_loadInIsolation($path, $boris); + + $applied = true; + $this->_files[] = $path; + + if (!$this->_cascade) { + break; + } + } + } + + return $applied; + } + + /** + * Returns an array of files that were loaded + * for this Config + * + * @return array + */ + public function loadedFiles() { + return $this->_files; + } + + // -- Private Methods + + private function _loadInIsolation($path, $boris) { + require $path; + } +} diff --git a/ThinkPHP/Library/Vendor/Boris/DumpInspector.php b/ThinkPHP/Library/Vendor/Boris/DumpInspector.php new file mode 100644 index 0000000..96ab38a --- /dev/null +++ b/ThinkPHP/Library/Vendor/Boris/DumpInspector.php @@ -0,0 +1,16 @@ +_socket = $socket; + $this->_inspector = new DumpInspector(); + stream_set_blocking($socket, 0); + } + + /** + * Set local variables to be placed in the workers's scope. + * + * @param array|string $local + * @param mixed $value, if $local is a string + */ + public function setLocal($local, $value = null) { + if (!is_array($local)) { + $local = array($local => $value); + } + + $this->_exports = array_merge($this->_exports, $local); + } + + /** + * Set hooks to run inside the worker before it starts looping. + * + * @param array $hooks + */ + public function setStartHooks($hooks) { + $this->_startHooks = $hooks; + } + + /** + * Set hooks to run inside the worker after a fatal error is caught. + * + * @param array $hooks + */ + public function setFailureHooks($hooks) { + $this->_failureHooks = $hooks; + } + + /** + * Set an Inspector object for Boris to output return values with. + * + * @param object $inspector any object the responds to inspect($v) + */ + public function setInspector($inspector) { + $this->_inspector = $inspector; + } + + /** + * Start the worker. + * + * This method never returns. + */ + public function start() { + $__scope = $this->_runHooks($this->_startHooks); + extract($__scope); + + $this->_write($this->_socket, self::READY); + + /* Note the naming of the local variables due to shared scope with the user here */ + for (;;) { + declare(ticks = 1); + // don't exit on ctrl-c + pcntl_signal(SIGINT, SIG_IGN, true); + + $this->_cancelled = false; + + $__input = $this->_transform($this->_read($this->_socket)); + + if ($__input === null) { + continue; + } + + $__response = self::DONE; + + $this->_ppid = posix_getpid(); + $this->_pid = pcntl_fork(); + + if ($this->_pid < 0) { + throw new \RuntimeException('Failed to fork child labourer'); + } elseif ($this->_pid > 0) { + // kill the child on ctrl-c + pcntl_signal(SIGINT, array($this, 'cancelOperation'), true); + pcntl_waitpid($this->_pid, $__status); + + if (!$this->_cancelled && $__status != (self::ABNORMAL_EXIT << 8)) { + $__response = self::EXITED; + } else { + $this->_runHooks($this->_failureHooks); + $__response = self::FAILED; + } + } else { + // user exception handlers normally cause a clean exit, so Boris will exit too + if (!$this->_exceptionHandler = + set_exception_handler(array($this, 'delegateExceptionHandler'))) { + restore_exception_handler(); + } + + // undo ctrl-c signal handling ready for user code execution + pcntl_signal(SIGINT, SIG_DFL, true); + $__pid = posix_getpid(); + + $__result = eval($__input); + + if (posix_getpid() != $__pid) { + // whatever the user entered caused a forked child + // (totally valid, but we don't want that child to loop and wait for input) + exit(0); + } + + if (preg_match('/\s*return\b/i', $__input)) { + fwrite(STDOUT, sprintf("%s\n", $this->_inspector->inspect($__result))); + } + $this->_expungeOldWorker(); + } + + $this->_write($this->_socket, $__response); + + if ($__response == self::EXITED) { + exit(0); + } + } + } + + /** + * While a child process is running, terminate it immediately. + */ + public function cancelOperation() { + printf("Cancelling...\n"); + $this->_cancelled = true; + posix_kill($this->_pid, SIGKILL); + pcntl_signal_dispatch(); + } + + /** + * If any user-defined exception handler is present, call it, but be sure to exit correctly. + */ + public function delegateExceptionHandler($ex) { + call_user_func($this->_exceptionHandler, $ex); + exit(self::ABNORMAL_EXIT); + } + + // -- Private Methods + + private function _runHooks($hooks) { + extract($this->_exports); + + foreach ($hooks as $__hook) { + if (is_string($__hook)) { + eval($__hook); + } elseif (is_callable($__hook)) { + call_user_func($__hook, $this, get_defined_vars()); + } else { + throw new \RuntimeException( + sprintf( + 'Hooks must be closures or strings of PHP code. Got [%s].', + gettype($__hook) + ) + ); + } + + // hooks may set locals + extract($this->_exports); + } + + return get_defined_vars(); + } + + private function _expungeOldWorker() { + posix_kill($this->_ppid, SIGTERM); + pcntl_signal_dispatch(); + } + + private function _write($socket, $data) { + if (!fwrite($socket, $data)) { + throw new \RuntimeException('Socket error: failed to write data'); + } + } + + private function _read($socket) + { + $read = array($socket); + $except = array($socket); + + if ($this->_select($read, $except) > 0) { + if ($read) { + return stream_get_contents($read[0]); + } else if ($except) { + throw new \UnexpectedValueException("Socket error: closed"); + } + } + } + + private function _select(&$read, &$except) { + $write = null; + set_error_handler(function(){return true;}, E_WARNING); + $result = stream_select($read, $write, $except, 10); + restore_error_handler(); + return $result; + } + + private function _transform($input) { + if ($input === null) { + return null; + } + + $transforms = array( + 'exit' => 'exit(0)' + ); + + foreach ($transforms as $from => $to) { + $input = preg_replace('/^\s*' . preg_quote($from, '/') . '\s*;?\s*$/', $to . ';', $input); + } + + return $input; + } +} diff --git a/ThinkPHP/Library/Vendor/Boris/ExportInspector.php b/ThinkPHP/Library/Vendor/Boris/ExportInspector.php new file mode 100644 index 0000000..2ac226b --- /dev/null +++ b/ThinkPHP/Library/Vendor/Boris/ExportInspector.php @@ -0,0 +1,14 @@ +_socket = $socket; + } + + /** + * Start the client with an prompt and readline history path. + * + * This method never returns. + * + * @param string $prompt + * @param string $historyFile + */ + public function start($prompt, $historyFile) { + readline_read_history($historyFile); + + declare(ticks = 1); + pcntl_signal(SIGCHLD, SIG_IGN); + pcntl_signal(SIGINT, array($this, 'clear'), true); + + // wait for the worker to finish executing hooks + if (fread($this->_socket, 1) != EvalWorker::READY) { + throw new \RuntimeException('EvalWorker failed to start'); + } + + $parser = new ShallowParser(); + $buf = ''; + $lineno = 1; + + for (;;) { + $this->_clear = false; + $line = readline( + sprintf( + '[%d] %s', + $lineno, + ($buf == '' + ? $prompt + : str_pad('*> ', strlen($prompt), ' ', STR_PAD_LEFT)) + ) + ); + + if ($this->_clear) { + $buf = ''; + continue; + } + + if (false === $line) { + $buf = 'exit(0);'; // ctrl-d acts like exit + } + + if (strlen($line) > 0) { + readline_add_history($line); + } + + $buf .= sprintf("%s\n", $line); + + if ($statements = $parser->statements($buf)) { + ++$lineno; + + $buf = ''; + foreach ($statements as $stmt) { + if (false === $written = fwrite($this->_socket, $stmt)) { + throw new \RuntimeException('Socket error: failed to write data'); + } + + if ($written > 0) { + $status = fread($this->_socket, 1); + if ($status == EvalWorker::EXITED) { + readline_write_history($historyFile); + echo "\n"; + exit(0); + } elseif ($status == EvalWorker::FAILED) { + break; + } + } + } + } + } + } + + /** + * Clear the input buffer. + */ + public function clear() { + // FIXME: I'd love to have this send \r to readline so it puts the user on a blank line + $this->_clear = true; + } +} diff --git a/ThinkPHP/Library/Vendor/Boris/ShallowParser.php b/ThinkPHP/Library/Vendor/Boris/ShallowParser.php new file mode 100644 index 0000000..624724b --- /dev/null +++ b/ThinkPHP/Library/Vendor/Boris/ShallowParser.php @@ -0,0 +1,233 @@ + ')', + '{' => '}', + '[' => ']', + '"' => '"', + "'" => "'", + '//' => "\n", + '#' => "\n", + '/*' => '*/', + '<<<' => '_heredoc_special_case_' + ); + + private $_initials; + + public function __construct() { + $this->_initials = '/^(' . implode('|', array_map(array($this, 'quote'), array_keys($this->_pairs))) . ')/'; + } + + /** + * Break the $buffer into chunks, with one for each highest-level construct possible. + * + * If the buffer is incomplete, returns an empty array. + * + * @param string $buffer + * + * @return array + */ + public function statements($buffer) { + $result = $this->_createResult($buffer); + + while (strlen($result->buffer) > 0) { + $this->_resetResult($result); + + if ($result->state == '<<<') { + if (!$this->_initializeHeredoc($result)) { + continue; + } + } + + $rules = array('_scanEscapedChar', '_scanRegion', '_scanStateEntrant', '_scanWsp', '_scanChar'); + + foreach ($rules as $method) { + if ($this->$method($result)) { + break; + } + } + + if ($result->stop) { + break; + } + } + + if (!empty($result->statements) && trim($result->stmt) === '' && strlen($result->buffer) == 0) { + $this->_combineStatements($result); + $this->_prepareForDebug($result); + return $result->statements; + } + } + + public function quote($token) { + return preg_quote($token, '/'); + } + + // -- Private Methods + + private function _createResult($buffer) { + $result = new \stdClass(); + $result->buffer = $buffer; + $result->stmt = ''; + $result->state = null; + $result->states = array(); + $result->statements = array(); + $result->stop = false; + + return $result; + } + + private function _resetResult($result) { + $result->stop = false; + $result->state = end($result->states); + $result->terminator = $result->state + ? '/^(.*?' . preg_quote($this->_pairs[$result->state], '/') . ')/s' + : null + ; + } + + private function _combineStatements($result) { + $combined = array(); + + foreach ($result->statements as $scope) { + if (trim($scope) == ';' || substr(trim($scope), -1) != ';') { + $combined[] = ((string) array_pop($combined)) . $scope; + } else { + $combined[] = $scope; + } + } + + $result->statements = $combined; + } + + private function _prepareForDebug($result) { + $result->statements []= $this->_prepareDebugStmt(array_pop($result->statements)); + } + + private function _initializeHeredoc($result) { + if (preg_match('/^([\'"]?)([a-z_][a-z0-9_]*)\\1/i', $result->buffer, $match)) { + $docId = $match[2]; + $result->stmt .= $match[0]; + $result->buffer = substr($result->buffer, strlen($match[0])); + + $result->terminator = '/^(.*?\n' . $docId . ');?\n/s'; + + return true; + } else { + return false; + } + } + + private function _scanWsp($result) { + if (preg_match('/^\s+/', $result->buffer, $match)) { + if (!empty($result->statements) && $result->stmt === '') { + $result->statements[] = array_pop($result->statements) . $match[0]; + } else { + $result->stmt .= $match[0]; + } + $result->buffer = substr($result->buffer, strlen($match[0])); + + return true; + } else { + return false; + } + } + + private function _scanEscapedChar($result) { + if (($result->state == '"' || $result->state == "'") + && preg_match('/^[^' . $result->state . ']*?\\\\./s', $result->buffer, $match)) { + + $result->stmt .= $match[0]; + $result->buffer = substr($result->buffer, strlen($match[0])); + + return true; + } else { + return false; + } + } + + private function _scanRegion($result) { + if (in_array($result->state, array('"', "'", '<<<', '//', '#', '/*'))) { + if (preg_match($result->terminator, $result->buffer, $match)) { + $result->stmt .= $match[1]; + $result->buffer = substr($result->buffer, strlen($match[1])); + array_pop($result->states); + } else { + $result->stop = true; + } + + return true; + } else { + return false; + } + } + + private function _scanStateEntrant($result) { + if (preg_match($this->_initials, $result->buffer, $match)) { + $result->stmt .= $match[0]; + $result->buffer = substr($result->buffer, strlen($match[0])); + $result->states[] = $match[0]; + + return true; + } else { + return false; + } + } + + private function _scanChar($result) { + $chr = substr($result->buffer, 0, 1); + $result->stmt .= $chr; + $result->buffer = substr($result->buffer, 1); + if ($result->state && $chr == $this->_pairs[$result->state]) { + array_pop($result->states); + } + + if (empty($result->states) && ($chr == ';' || $chr == '}')) { + if (!$this->_isLambda($result->stmt) || $chr == ';') { + $result->statements[] = $result->stmt; + $result->stmt = ''; + } + } + + return true; + } + + private function _isLambda($input) { + return preg_match( + '/^([^=]*?=\s*)?function\s*\([^\)]*\)\s*(use\s*\([^\)]*\)\s*)?\s*\{.*\}\s*;?$/is', + trim($input) + ); + } + + private function _isReturnable($input) { + $input = trim($input); + if (substr($input, -1) == ';' && substr($input, 0, 1) != '{') { + return $this->_isLambda($input) || !preg_match( + '/^(' . + 'echo|print|exit|die|goto|global|include|include_once|require|require_once|list|' . + 'return|do|for|foreach|while|if|function|namespace|class|interface|abstract|switch|' . + 'declare|throw|try|unset' . + ')\b/i', + $input + ); + } else { + return false; + } + } + + private function _prepareDebugStmt($input) { + if ($this->_isReturnable($input) && !preg_match('/^\s*return/i', $input)) { + $input = sprintf('return %s', $input); + } + + return $input; + } +} diff --git a/ThinkPHP/Library/Vendor/EaseTemplate/template.core.php b/ThinkPHP/Library/Vendor/EaseTemplate/template.core.php new file mode 100644 index 0000000..630c38d --- /dev/null +++ b/ThinkPHP/Library/Vendor/EaseTemplate/template.core.php @@ -0,0 +1,970 @@ +Power by Ease Template!');}"; + var $Compile = array(); + var $Analysis = array(); + var $Emc = array(); + + /** + * 声明模板用法 + */ + function ETCoreStart( + $set = array( + 'ID' =>'1', //缓存ID + 'TplType' =>'htm', //模板格式 + 'CacheDir' =>'cache', //缓存目录 + 'TemplateDir'=>'template' , //模板存放目录 + 'AutoImage' =>'on' , //自动解析图片目录开关 on表示开放 off表示关闭 + 'LangDir' =>'language' , //语言文件存放的目录 + 'Language' =>'default' , //语言的默认文件 + 'Copyright' =>'off' , //版权保护 + 'MemCache' =>'' , //Memcache服务器地址例如:127.0.0.1:11211 + ) + ){ + + $this->TplID = (defined('TemplateID')?TemplateID:( ((int)@$set['ID']<=1)?1:(int)$set['ID']) ).'_'; + + $this->CacheDir = (defined('NewCache')?NewCache:( (trim($set['CacheDir']) != '')?$set['CacheDir']:'cache') ).'/'; + + $this->TemplateDir = (defined('NewTemplate')?NewTemplate:( (trim($set['TemplateDir']) != '')?$set['TemplateDir']:'template') ).'/'; + + $this->Ext = (@$set['TplType'] != '')?$set['TplType']:'htm'; + + $this->AutoImage = (@$set['AutoImage']=='off')?0:1; + + $this->Copyright = (@$set['Copyright']=='off')?0:1; + + $this->Server = (is_array($GLOBALS['_SERVER']))?$GLOBALS['_SERVER']:$_SERVER; + $this->version = (trim($_GET['EaseTemplateVer']))?die('Ease Templae E3!'):''; + + //载入语言文件 + $this->LangDir = (defined('LangDir')?LangDir:( ((@$set['LangDir']!='language' && @$set['LangDir'])?$set['LangDir']:'language') )).'/'; + if(is_dir($this->LangDir)){ + $this->Language = (defined('Language')?Language:( (($set['Language']!='default' && $set['Language'])?$set['Language']:'default') )); + if(@is_file($this->LangDir.$this->Language.'.php')){ + $lang = array(); + @include_once $this->LangDir.$this->Language.'.php'; + $this->LangData = $lang; + } + }else{ + $this->Language = 'default'; + } + + + //缓存目录检测以及运行模式 + if(@ereg(':',$set['MemCache'])){ + $this->RunType = 'MemCache'; + $memset = explode(":",$set['MemCache']); + $this->Emc = memcache_connect($memset[0], $memset[1]) OR die("Could not connect!"); + }else{ + $this->RunType = (@substr(@sprintf('%o', @fileperms($this->CacheDir)), -3)==777 && is_dir($this->CacheDir))?'Cache':'Replace'; + } + + $CompileBasic = array( + '/(\{\s*|)/eis', + + '//is', + '//is', + '//is', + '//is', + '//is', + '//', + '//is', + + '/(\{\s*|)/eis', + '/(\{\s*|)/eis', + '/(\{\s*|)/eis', + '/(\{\s*|)/eis', + '/(\{\s*|)\s*(.+?)\s*(\{|)/is', + '/(\{\s*|)/is', + '/\{([a-zA-Z0-9_\'\"\[\]\$]{1,100})\}/', + ); + $this->Compile = (is_array($this->Compile))?array_merge($this->Compile,$CompileBasic):$CompileBasic; + + $AnalysisBasic = array( + '$this->inc_php("\\2")', + + '";if($ET_Del==true){echo"', + '";if(\\2){echo"', + '";}elseif(\\2){echo"', + '";}else{echo"', + '";}echo"', + '";\$_i=0;foreach((array)\\1 AS \\3){\$_i++;echo"', + '";\$_i=0;while(\\1){\$_i++;echo"', + + '$this->lang("\\2")', + '$this->Row("\\2")', + '$this->Color("\\2")', + '$this->Dirs("\\2")', + '";\\3;echo"', + '";\\2;echo"', + '";echo \$\\1;echo"', + ); + $this->Analysis = (is_array($this->Analysis))?array_merge($this->Analysis,$AnalysisBasic):$AnalysisBasic; + + } + + + /** + * 设置数值 + * set_var(变量名或是数组,设置数值[数组不设置此值]); + */ + function set_var( + $name, + $value = '' + ){ + if (is_array($name)){ + $this->ThisValue = @array_merge($this->ThisValue,$name); + }else{ + $this->ThisValue[$name] = $value; + } + } + + + /** + * 设置模板文件 + * set_file(文件名,设置目录); + */ + function set_file( + $FileName, + $NewDir = '' + ){ + //当前模板名 + $this->ThisFile = $FileName.'.'.$this->Ext; + + //目录地址检测 + $this->FileDir[$this->ThisFile] = (trim($NewDir) != '')?$NewDir.'/':$this->TemplateDir; + + $this->IncFile[$FileName] = $this->FileDir[$this->ThisFile].$this->ThisFile; + + if(!is_file($this->IncFile[$FileName]) && $this->Copyright==1){ + die('Sorry, The file '.$this->IncFile[$FileName].' does not exist.'); + } + + + //bug 系统 + $this->IncList[] = $this->ThisFile; + } + + //解析替换程序 + function ParseCode( + $FileList = '', + $CacheFile = '' + ){ + //模板数据 + $ShowTPL = ''; + //解析续载 + if (@is_array($FileList) && $FileList!='include_page'){ + foreach ($FileList AS $K=>$V) { + $ShowTPL .= $this->reader($V.$K); + } + }else{ + + + //如果指定文件地址则载入 + $SourceFile = ($FileList!='')?$FileList:$this->FileDir[$this->ThisFile].$this->ThisFile; + + if(!is_file($SourceFile) && $this->Copyright==1){ + die('Sorry, The file '.$SourceFile.' does not exist.'); + } + + $ShowTPL = $this->reader($SourceFile); + } + + //引用模板处理 + $ShowTPL = $this->inc_preg($ShowTPL); + + //检测run方法 + $run = 0; + if (eregi("run:",$ShowTPL)){ + $run = 1; + //Fix = + $ShowTPL = preg_replace('/(\{|)\s*=/','{run:}echo ',$ShowTPL); + $ShowTPL = preg_replace('/(\{|)\s*(.+?)\s*(\{|)/is', '(T_T)\\3;(T_T!)',$ShowTPL); + } + + //Fix XML + if (eregi("/is', '\\1', $ShowTPL); + } + + //修复代码中\n换行错误 + $ShowTPL = str_replace('\\','\\\\',$ShowTPL); + //修复双引号问题 + $ShowTPL = str_replace('"','\"',$ShowTPL); + + //编译运算 + $ShowTPL = @preg_replace($this->Compile, $this->Analysis, $ShowTPL); + + //分析图片地址 + $ShowTPL = $this->ImgCheck($ShowTPL); + + //Fix 模板中金钱符号 + $ShowTPL = str_replace('$','\$',$ShowTPL); + + //修复php运行错误 + $ShowTPL = @preg_replace("/\";(.+?)echo\"/e", '$this->FixPHP(\'\\1\')', $ShowTPL); + + //Fix Run 2 + if ($run==1){ + $ShowTPL = preg_replace("/\(T_T\)(.+?)\(T_T!\)/ise", '$this->FixPHP(\'\\1\')', $ShowTPL); + } + + //还原xml + $ShowTPL = (strrpos($ShowTPL,''))?@preg_replace('/ET>(.+?)<\/ET/is', '?\\1?', $ShowTPL):$ShowTPL; + + //修复"问题 + $ShowTPL = str_replace('echo ""','echo "\"',$ShowTPL); + + + //从数组中将变量导入到当前的符号表 + @extract($this->Value()); + ob_start(); + ob_implicit_flush(0); + @eval('echo "'.$ShowTPL.'";'); + $contents = ob_get_contents(); + ob_end_clean(); + + //Cache htm + if($this->HtmID){ + $this->writer($this->HtmDir.$this->HtmID,$this->Hacker."?>".$contents); + } + + + //编译模板 + if ($this->RunType=='Cache'){ + $this->CompilePHP($ShowTPL,$CacheFile); + } + + + //错误检查 + if(strlen($contents)<=0){ + //echo $ShowTPL; + die('
    Sorry, Error or complicated syntax error exists in '.$SourceFile.' file.'); + } + + return $contents; + } + + + /** + * 多语言 + */ + function lang( + $str = '' + ){ + if (is_dir($this->LangDir)){ + + //采用MD5效验 + $id = md5($str); + + //不存在数据则写入 + if($this->LangData[$id]=='' && $this->Language=='default'){ + + //语言包文件 + if (@is_file($this->LangDir.$this->Language.'.php')){ + unset($lang); + @include($this->LangDir.$this->Language.'.php'); + } + + + //如果检测到有数据则输出 + if ($lang[$id]){ + $out = str_replace('\\','\\\\',$lang[$id]); + return str_replace('"','\"',$out); + } + + + //修复'多\问题 + $str = str_replace("\\'","'",$str); + + + //语言文件过大时采取建立新文件 + if(strlen($docs)>400){ + $this->writer($this->LangDir.$this->Language.'.'.$id.'.php',''); + $docs= substr($str,0,40); //简要说明 + $docs = str_replace('\"','"',$docs); + $docs = str_replace('\\\\','\\',$docs); + $str = 'o(O_O)o.ET Lang.o(*_*)o'; //语言新文件 + }else{ + $docs = str_replace('\"','"',$str); + $docs = str_replace('\\\\','\\',$docs); + } + + //文件安全处理 + $data = (!is_file($this->LangDir.'default.php'))?"Language."\n*/\n\n\n":''; + + + if (trim($str)){ + //写入数据 + $data .= "/**".date("Y.m.d",time())."\n"; + $data.= $docs."\n"; + $data.= "*/\n"; + $data.= '$lang["'.$id.'"] = "'.$str.'";'."\n\n"; + $this->writer($this->LangDir.'default.php',$data,'a+'); + } + } + + //单独语言文件包 + if($this->LangData[$id]=='o(O_O)o.ET Lang.o(*_*)o'){ + unset($etl); + include($this->LangDir.$this->Language.".".$id.".php"); + $this->LangData[$id] = $etl; + } + + $out = ($this->LangData[$id])?$this->LangData[$id]:$str; + + //输出部分要做处理 + if(($this->RunType=='Replace' || $this->RunType!='Replace') && $data==''){ + $out = str_replace('\\','\\\\',$out); + $out = str_replace('"','\"',$out); + } + + return $out; + }else{ + return $str; + } + } + + /** + * inc引用函数 + */ + function inc_preg( + $content + ){ + return preg_replace('/<\!--\s*\#include\s*file\s*=(\"|\')([a-zA-Z0-9_\.\|]{1,100})(\"|\')\s*-->/eis', '$this->inc("\\2")', preg_replace('/(\{\s*|)/eis', '$this->inc("\\2")', $content)); + } + + + /** + * 引用函数运算 + */ + function inc( + $Files = '' + ){ + if($Files){ + if (!strrpos($Files,$this->Ext)){ + $Files = $Files.".".$this->Ext; + } + $FileLs = $this->TemplateDir.$Files; + $contents =$this->ParseCode($FileLs,$Files); + + if($this->RunType=='Cache'){ + //引用模板 + $this->IncList[] = $Files; + $cache_file = $this->CacheDir.$this->TplID.$Files.".".$this->Language.".php"; + return " +{inc_php:".$cache_file."} +{run:@eval('echo \"'.\$EaseTemplate3_Cache.'\";')} +"; + }elseif($this->RunType=='MemCache'){ + //cache date + memcache_set($this->Emc,$Files.'_date', time()) OR die("Failed to save data at the server."); + memcache_set($this->Emc,$Files, $contents) OR die("Failed to save data at the server"); + return "".$contents; + }else{ + //引用模板 + $this->IncList[] = $Files; + return $contents; + } + } + } + + + /** + * 编译解析处理 + */ + function CompilePHP( + $content='', + $cachename = '' + ){ + if ($content){ + //如果没有安全文件则自动创建 + if($this->RunType=='Cache' && !is_file($this->CacheDir.'index.htm')){ + $Ease_name = 'Ease Template!'; + $Ease_base = "$Ease_name
    $Ease_name"; + $this->writer($this->CacheDir.'index.htm',$Ease_base); + $this->writer($this->CacheDir.'index.html',$Ease_base); + $this->writer($this->CacheDir.'default.htm',$Ease_base); + } + + + //编译记录 + $content = str_replace("\\","\\\\",$content); + $content = str_replace("'","\'",$content); + $content = str_replace('echo"";',"",$content); //替换多余数据 + + $wfile = ($cachename)?$cachename:$this->ThisFile; + $this->writer($this->FileName($wfile,$this->TplID) ,$this->Hacker.'$EaseTemplate3_Cache = \''.$content.'\';'); + } + } + + + //修复PHP执行时产生的错误 + function FixPHP( + $content='' + ){ + $content = str_replace('\\\\','\\',$content); + return '";'.str_replace('\\"','"',str_replace('\$','$',$content)).'echo"'; + } + + + /** + * 检测缓存是否要更新 + * filename 缓存文件名 + * settime 指定事件则提供更新,只用于memcache + */ + function FileUpdate($filname,$settime=0){ + + //检测设置模板文件 + if (is_array($this->IncFile)){ + unset($k,$v); + $update = 0; + $settime = ($settime>0)?$settime:@filemtime($filname); + foreach ($this->IncFile AS $k=>$v) { + if (@filemtime($v)>$settime){$update = 1;} + } + //更新缓存 + if($update==1){ + return false; + }else { + return $filname; + } + + }else{ + return $filname; + } + } + + + /** + * 输出运算 + * Filename 连载编译输出文件名 + */ + function output( + $Filename = '' + ){ + switch($this->RunType){ + + //Mem编译模式 + case'MemCache': + if ($Filename=='include_page'){ + //直接输出文件 + return $this->reader($this->FileDir[$this->ThisFile].$this->ThisFile); + }else{ + + $FileNames = ($Filename)?$Filename:$this->ThisFile; + $CacheFile = $this->FileName($FileNames,$this->TplID); + + //检测记录时间 + $updateT = memcache_get($this->Emc,$CacheFile.'_date'); + $update = $this->FileUpdate($CacheFile,$updateT); + + $CacheData = memcache_get($this->Emc,$CacheFile); + + if(trim($CacheData) && $update){ + //获得列表文件 + unset($ks,$vs); + preg_match_all('/<\!-- ET\_inc\_cache\[(.+?)\] -->/',$CacheData, $IncFile); + if (is_array($IncFile[1])){ + foreach ($IncFile[1] AS $ks=>$vs) { + $this->IncList[] = $vs; + $listDate = memcache_get($this->Emc,$vs.'_date'); + + echo @filemtime($this->TemplateDir.$vs).' - '.$listDate.'
    '; + + //更新inc缓存 + if (@filemtime($this->TemplateDir.$vs)>$listDate){ + $update = 1; + $this->inc($vs); + } + } + + //更新数据 + if ($update == 1){ + $CacheData = $this->ParseCode($this->FileList,$Filename); + //cache date + @memcache_set($this->Emc,$CacheFile.'_date', time()) OR die("Failed to save data at the server."); + @memcache_set($this->Emc,$CacheFile, $CacheData) OR die("Failed to save data at the server."); + } + } + //Close + memcache_close($this->Emc); + return $CacheData; + }else{ + if ($Filename){ + $CacheData = $this->ParseCode($this->FileList,$Filename); + //cache date + @memcache_set($this->Emc,$CacheFile.'_date', time()) OR die("Failed to save data at the server."); + @memcache_set($this->Emc,$CacheFile, $CacheData) OR die("Failed to save data at the server."); + //Close + memcache_close($this->Emc); + return $CacheData; + }else{ + $CacheData = $this->ParseCode(); + //cache date + @memcache_set($this->Emc,$CacheFile.'_date', time()) OR die("Failed to save data at the server."); + @memcache_set($this->Emc,$CacheFile, $CacheData) OR die("Failed to save data at the server2"); + //Close + memcache_close($this->Emc); + return $CacheData; + } + } + } + break; + + + //编译模式 + case'Cache': + if ($Filename=='include_page'){ + //直接输出文件 + return $this->reader($this->FileDir[$this->ThisFile].$this->ThisFile); + }else{ + + $FileNames = ($Filename)?$Filename:$this->ThisFile; + $CacheFile = $this->FileName($FileNames,$this->TplID); + + $CacheFile = $this->FileUpdate($CacheFile); + + if (@is_file($CacheFile)){ + @extract($this->Value()); + ob_start(); + ob_implicit_flush(0); + include $CacheFile; + + //获得列表文件 + if($EaseTemplate3_Cache!=''){ + unset($ks,$vs); + preg_match_all('/<\!-- ET\_inc\_cache\[(.+?)\] -->/',$EaseTemplate3_Cache, $IncFile); + + if (is_array($IncFile[1])){ + foreach ($IncFile[1] AS $ks=>$vs) { + $this->IncList[] = $vs; + //更新inc缓存 + if (@filemtime($this->TemplateDir.$vs)>@filemtime($this->CacheDir.$this->TplID.$vs.'.'.$this->Language.'.php')){ + $this->inc($vs); + } + } + } + + @eval('echo "'.$EaseTemplate3_Cache.'";'); + $contents = ob_get_contents(); + ob_end_clean(); + return $contents; + } + }else{ + if ($Filename){ + return $this->ParseCode($this->FileList,$Filename); + }else{ + return $this->ParseCode(); + } + } + } + break; + + + //替换引擎 + default: + if($Filename){ + if ($Filename=='include_page'){ + //直接输出文件 + return $this->reader($this->FileDir[$this->ThisFile].$this->ThisFile); + }else { + return $this->ParseCode($this->FileList); + } + }else{ + return $this->ParseCode(); + } + } + } + + + /** + * 连载函数 + */ + function n(){ + //连载模板 + $this->FileList[$this->ThisFile] = $this->FileDir[$this->ThisFile]; + } + + + /** + * 输出模板内容 + * Filename 连载编译输出文件名 + */ + function r( + $Filename = '' + ){ + return $this->output($Filename); + } + + + /** + * 打印模板内容 + * Filename 连载编译输出文件名 + */ + function p( + $Filename = '' + ){ + echo $this->output($Filename); + } + + + /** + * 分析图片地址 + */ + function ImgCheck( + $content + ){ + //Check Image Dir + if($this->AutoImage==1){ + $NewFileDir = $this->FileDir[$this->ThisFile]; + + //FIX img + if(is_array($this->ImgDir)){ + foreach($this->ImgDir AS $rep){ + $rep = trim($rep); + //检测是否执行替换 + if(strrpos($content,$rep."/")){ + if(substr($rep,-1)=='/'){ + $rep = substr($rep,0,strlen($rep)-1); + } + $content = str_replace($rep.'/',$NewFileDir.$rep.'/',$content); + } + } + } + + //FIX Dir + $NewFileDirs = $NewFileDir.$NewFileDir; + if(strrpos($content,$NewFileDirs)){ + $content = str_replace($NewFileDirs,$NewFileDir,$content); + } + } + return $content; + } + + + /** + * 获得所有设置与公共变量 + */ + function Value(){ + return (is_array($this->ThisValue))?array_merge($this->ThisValue,$GLOBALS):$GLOBALS; + } + + + /** + * 清除设置 + */ + function clear(){ + $this->RunType = 'Replace'; + } + + + /** + * 静态文件写入 + */ + function htm_w( + $w_dir = '', + $w_filename = '', + $w_content = '' + ){ + + $dvs = ''; + if($w_dir && $w_filename && $w_content){ + //目录检测数量 + $w_dir_ex = explode('/',$w_dir); + $w_new_dir = ''; //处理后的写入目录 + unset($dvs,$fdk,$fdv,$w_dir_len); + foreach((array)$w_dir_ex AS $dvs){ + if(trim($dvs) && $dvs!='..'){ + $w_dir_len .= '../'; + $w_new_dir .= $dvs.'/'; + if (!@is_dir($w_new_dir)) @mkdir($w_new_dir, 0777); + } + } + + + //获得需要更改的目录数 + foreach((array)$this->FileDir AS $fdk=>$fdv){ + $w_content = str_replace($fdv,$w_dir_len.str_replace('../','',$fdv),$w_content); + } + + $this->writer($w_dir.$w_filename,$w_content); + } + } + + + /** + * 改变静态刷新时间 + */ + function htm_time($times=0){ + if((int)$times>0){ + $this->HtmTime = (int)$times; + } + } + + + /** + * 静态文件存放的绝对目录 + */ + function htm_dir($Name = ''){ + if(trim($Name)){ + $this->HtmDir = trim($Name).'/'; + } + } + + + /** + * 产生静态文件输出 + */ + function HtmCheck( + $Name = '' + ){ + $this->HtmID = md5(trim($Name)? trim($Name).'.php' : $this->Server['REQUEST_URI'].'.php' ); + //检测时间 + if(is_file($this->HtmDir.$this->HtmID) && (time() - @filemtime($this->HtmDir.$this->HtmID)<=$this->HtmTime)){ + ob_start(); + ob_implicit_flush(0); + include $this->HtmDir.$this->HtmID; + $HtmContent = ob_get_contents(); + ob_end_clean(); + return $HtmContent; + } + } + + + /** + * 打印静态内容 + */ + function htm_p( + $Name = '' + ){ + $output = $this->HtmCheck($Name); + if ($output){ + die($this->HtmCheck($Name)); + } + } + + + /** + * 输出静态内容 + */ + function htm_r( + $Name = '' + ){ + return $this->HtmCheck($Name); + } + + + + + + /** + * 解析文件 + */ + function FileName( + $name, + $id = '1' + ){ + $extdir = explode("/",$name); + $dircnt = @count($extdir) - 1; + $extdir[$dircnt] = $id.$extdir[$dircnt]; + + return $this->CacheDir.implode("_",$extdir).".".$this->Language.'.php'; + } + + + /** + * 检测引入文件 + */ + function inc_php( + $url = '' + ){ + $parse = parse_url($url); + unset($vals,$code_array); + foreach((array)explode('&',$parse['query']) AS $vals){ + $code_array .= preg_replace('/(.+)=(.+)/',"\$_GET['\\1']= \$\\1 ='\\2';",$vals); + } + return '";'.$code_array.' @include(\''.$parse['path'].'\');echo"'; + } + + + /** + * 换行函数 + * Row(换行数,换行颜色); + * Row("5,#ffffff:#e1e1e1"); + */ + function Row( + $Num = '' + ){ + $Num = trim($Num); + if($Num != ''){ + $Nums = explode(",",$Num); + $Numr = ((int)$Nums[0]>0)?(int)$Nums[0]:2; + $input = (trim($Nums[1]) == '')?'':$Nums[1]; + + if(trim($Nums[1]) != ''){ + $Co = explode(":",$Nums[1]); + $OutStr = "if(\$_i%$Numr===0){\$row_count++;echo(\$row_count%2===0)?'':'';}"; + }else{ + $OutStr = "if(\$_i%$Numr===0){echo '$input';}"; + } + return '";'.$OutStr.'echo "'; + } + } + + + /** + * 间隔变色 + * Color(两组颜色代码); + * Color('#FFFFFF,#DCDCDC'); + */ + function Color( + $color = '' + ){ + if($color != ''){ + $OutStr = preg_replace("/(.+),(.+)/","_i%2===0)?'\\1':'\\2';",$color); + if(strrpos($OutStr,"%2")){ + return '";echo(\$'.$OutStr.'echo "'; + } + } + } + + + /** + * 映射图片地址 + */ + function Dirs( + $adds = '' + ){ + $adds_ary = explode(",",$adds); + if(is_array($adds_ary)){ + $this->ImgDir = (is_array($this->ImgDir))?@array_merge($adds_ary, $this->ImgDir):$adds_ary; + } + } + + + /** + * 读取函数 + * reader(文件名); + */ + function reader( + $filename + ){ + $get_fun = @get_defined_functions(); + return (in_array('file_get_contents',$get_fun['internal']))?@file_get_contents($filename):@implode("", @file($filename)); + } + + + /** + * 写入函数 + * writer(文件名,写入数据, 写入数据方式); + */ + function writer( + $filename, + $data = '', + $mode='w' + ){ + if(trim($filename)){ + $file = @fopen($filename, $mode); + $filedata = @fwrite($file, $data); + @fclose($file); + } + if(!is_file($filename)){ + die('Sorry,'.$filename.' file write in failed!'); + } + } + + + /** + * 引入模板系统 + * 察看当前使用的模板以及调试信息 + */ + function inc_list(){ + if(is_array($this->IncList)){ + $EXTS = explode("/",$this->Server['REQUEST_URI']); + $Last = count($EXTS) -1; + //处理清除工作 START + if(strrpos($EXTS[$Last],'Ease_Templatepage=Clear') && trim($EXTS[$Last]) != ''){ + $dir_name = $this->CacheDir; + if(file_exists($dir_name)){ + $handle=@opendir($dir_name); + while($tmp_file=@readdir($handle)){ + if(@file_exists($dir_name.$tmp_file)){ + @unlink($dir_name.$tmp_file); + } + } + @closedir($handle); + } + $GoURL = urldecode(preg_replace("/.+?REFERER=(.+?)!!!/","\\1",$EXTS[$Last])); + + die(''); + } + //处理清除工作 END + + $list_file = array(); + $file_nums = count($this->IncList); + $AllSize = 0; + foreach($this->IncList AS $Ks=>$Vs){ + $FSize[$Ks] = @filesize($this->TemplateDir.$Vs); + $AllSize += $FSize[$Ks]; + } + + foreach($this->IncList AS $K=>$V){ + $File_Size = @round($FSize[$K] / 1024 * 100) / 100 . 'KB'; + $Fwidth = @floor(100*$FSize[$K]/$AllSize); + $list_file[] = "".$this->TemplateDir.$V." + ".$File_Size." +
    "; + } + + //连接地址 + $BackURL = preg_replace("/.+\//","\\1",$this->Server['REQUEST_URI']); + $NowPAGE = 'http://'.$this->Server['HTTP_HOST'].$this->Server['SCRIPT_NAME']; + $clear_link = $NowPAGE."?Ease_Templatepage=Clear&REFERER=".urlencode($BackURL)."!!!"; + $sf13 = ' style="font-size:13px;color:#666666"'; + echo '
    + +'.implode("",$list_file)."
    Include Templates (Num:'.count($this-> IncList).')'; + +if($this->RunType=='Cache'){ + echo '[Clear Cache]'; +} + +echo '
    +Cache File ID: '.substr($this->TplID,0,-1).' +Index: '.((count($this->FileList)==0)?'False':'True').' +Format: '.$this->Ext.' +Cache: '.($this->RunType=='MemCache'?'Memcache Engine':($this->RunType == 'Replace'?'Replace Engine':$this->CacheDir)).' +Template: '.$this->TemplateDir.' +
    "; + } + } + +} + +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/EaseTemplate/template.ease.php b/ThinkPHP/Library/Vendor/EaseTemplate/template.ease.php new file mode 100644 index 0000000..9366b29 --- /dev/null +++ b/ThinkPHP/Library/Vendor/EaseTemplate/template.ease.php @@ -0,0 +1,42 @@ +'1', //缓存ID + 'TplType' =>'htm', //模板格式 + 'CacheDir' =>'cache', //缓存目录 + 'TemplateDir'=>'template' , //模板存放目录 + 'AutoImage' =>'on' , //自动解析图片目录开关 on表示开放 off表示关闭 + 'LangDir' =>'language' , //语言文件存放的目录 + 'Language' =>'default' , //语言的默认文件 + 'Copyright' =>'off' , //版权保护 + 'MemCache' =>'' , //Memcache服务器地址例如:127.0.0.1:11211 + ) + ){ + + parent::ETCoreStart($set); + } + +} +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseClassManager.php b/ThinkPHP/Library/Vendor/Hprose/HproseClassManager.php new file mode 100644 index 0000000..aabea87 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseClassManager.php @@ -0,0 +1,53 @@ + * + * * +\**********************************************************/ + +class HproseClassManager { + private static $classCache1 = array(); + private static $classCache2 = array(); + public static function register($class, $alias) { + self::$classCache1[$alias] = $class; + self::$classCache2[$class] = $alias; + } + public static function getClassAlias($class) { + if (array_key_exists($class, self::$classCache2)) { + return self::$classCache2[$class]; + } + $alias = str_replace('\\', '_', $class); + self::register($class, $alias); + return $alias; + } + public static function getClass($alias) { + if (array_key_exists($alias, self::$classCache1)) { + return self::$classCache1[$alias]; + } + if (!class_exists($alias)) { + $class = str_replace('_', '\\', $alias); + if (class_exists($class)) { + self::register($class, $alias); + return $class; + } + eval("class " . $alias . " { }"); + } + return $alias; + } +} +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseClient.php b/ThinkPHP/Library/Vendor/Hprose/HproseClient.php new file mode 100644 index 0000000..53c01ec --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseClient.php @@ -0,0 +1,134 @@ + * + * * +\**********************************************************/ + +require_once('HproseCommon.php'); +require_once('HproseIO.php'); + +abstract class HproseClient { + protected $url; + private $filter; + private $simple; + protected abstract function send($request); + public function __construct($url = '') { + $this->useService($url); + $this->filter = NULL; + $this->simple = false; + } + public function useService($url = '', $namespace = '') { + if ($url) { + $this->url = $url; + } + return new HproseProxy($this, $namespace); + } + public function invoke($functionName, &$arguments = array(), $byRef = false, $resultMode = HproseResultMode::Normal, $simple = NULL) { + if ($simple === NULL) $simple = $this->simple; + $stream = new HproseStringStream(HproseTags::TagCall); + $hproseWriter = ($simple ? new HproseSimpleWriter($stream) : new HproseWriter($stream)); + $hproseWriter->writeString($functionName); + if (count($arguments) > 0 || $byRef) { + $hproseWriter->reset(); + $hproseWriter->writeList($arguments); + if ($byRef) { + $hproseWriter->writeBoolean(true); + } + } + $stream->write(HproseTags::TagEnd); + $request = $stream->toString(); + if ($this->filter) $request = $this->filter->outputFilter($request); + $stream->close(); + $response = $this->send($request); + if ($this->filter) $response = $this->filter->inputFilter($response); + if ($resultMode == HproseResultMode::RawWithEndTag) { + return $response; + } + if ($resultMode == HproseResultMode::Raw) { + return substr($response, 0, -1); + } + $stream = new HproseStringStream($response); + $hproseReader = new HproseReader($stream); + $result = NULL; + while (($tag = $hproseReader->checkTags( + array(HproseTags::TagResult, + HproseTags::TagArgument, + HproseTags::TagError, + HproseTags::TagEnd))) !== HproseTags::TagEnd) { + switch ($tag) { + case HproseTags::TagResult: + if ($resultMode == HproseResultMode::Serialized) { + $result = $hproseReader->readRaw()->toString(); + } + else { + $hproseReader->reset(); + $result = &$hproseReader->unserialize(); + } + break; + case HproseTags::TagArgument: + $hproseReader->reset(); + $args = &$hproseReader->readList(true); + for ($i = 0; $i < count($arguments); $i++) { + $arguments[$i] = &$args[$i]; + } + break; + case HproseTags::TagError: + $hproseReader->reset(); + throw new HproseException($hproseReader->readString(true)); + break; + } + } + return $result; + } + public function getFilter() { + return $this->filter; + } + public function setFilter($filter) { + $this->filter = $filter; + } + public function getSimpleMode() { + return $this->simple; + } + public function setSimpleMode($simple = true) { + $this->simple = $simple; + } + public function __call($function, $arguments) { + return $this->invoke($function, $arguments); + } + public function __get($name) { + return new HproseProxy($this, $name . '_'); + } +} + +class HproseProxy { + private $client; + private $namespace; + public function __construct($client, $namespace = '') { + $this->client = $client; + $this->namespace = $namespace; + } + public function __call($function, $arguments) { + $function = $this->namespace . $function; + return $this->client->invoke($function, $arguments); + } + public function __get($name) { + return new HproseProxy($this->client, $this->namespace . $name . '_'); + } +} +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseCommon.php b/ThinkPHP/Library/Vendor/Hprose/HproseCommon.php new file mode 100644 index 0000000..33d102c --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseCommon.php @@ -0,0 +1,816 @@ + * + * * +\**********************************************************/ + +class HproseResultMode { + const Normal = 0; + const Serialized = 1; + const Raw = 2; + const RawWithEndTag = 3; +} + +class HproseException extends Exception {} + +interface HproseFilter { + function inputFilter($data); + function outputFilter($data); +} + +class HproseDate { + public $year; + public $month; + public $day; + public $utc = false; + public function __construct() { + $args_num = func_num_args(); + $args = func_get_args(); + switch ($args_num) { + case 0: + $time = getdate(); + $this->year = $time['year']; + $this->month = $time['mon']; + $this->day = $time['mday']; + break; + case 1: + $time = false; + if (is_int($args[0])) { + $time = getdate($args[0]); + } + elseif (is_string($args[0])) { + $time = getdate(strtotime($args[0])); + } + if (is_array($time)) { + $this->year = $time['year']; + $this->month = $time['mon']; + $this->day = $time['mday']; + } + elseif ($args[0] instanceof HproseDate) { + $this->year = $args[0]->year; + $this->month = $args[0]->month; + $this->day = $args[0]->day; + } + else { + throw new HproseException('Unexpected arguments'); + } + break; + case 4: + $this->utc = $args[3]; + case 3: + if (!self::isValidDate($args[0], $args[1], $args[2])) { + throw new HproseException('Unexpected arguments'); + } + $this->year = $args[0]; + $this->month = $args[1]; + $this->day = $args[2]; + break; + default: + throw new HproseException('Unexpected arguments'); + } + } + public function addDays($days) { + if (!is_int($days)) return false; + $year = $this->year; + if ($days == 0) return true; + if ($days >= 146097 || $days <= -146097) { + $remainder = $days % 146097; + if ($remainder < 0) { + $remainder += 146097; + } + $years = 400 * (int)(($days - $remainder) / 146097); + $year += $years; + if ($year < 1 || $year > 9999) return false; + $days = $remainder; + } + if ($days >= 36524 || $days <= -36524) { + $remainder = $days % 36524; + if ($remainder < 0) { + $remainder += 36524; + } + $years = 100 * (int)(($days - $remainder) / 36524); + $year += $years; + if ($year < 1 || $year > 9999) return false; + $days = $remainder; + } + if ($days >= 1461 || $days <= -1461) { + $remainder = $days % 1461; + if ($remainder < 0) { + $remainder += 1461; + } + $years = 4 * (int)(($days - $remainder) / 1461); + $year += $years; + if ($year < 1 || $year > 9999) return false; + $days = $remainder; + } + $month = $this->month; + while ($days >= 365) { + if ($year >= 9999) return false; + if ($month <= 2) { + if ((($year % 4) == 0) ? (($year % 100) == 0) ? (($year % 400) == 0) : true : false) { + $days -= 366; + } + else { + $days -= 365; + } + $year++; + } + else { + $year++; + if ((($year % 4) == 0) ? (($year % 100) == 0) ? (($year % 400) == 0) : true : false) { + $days -= 366; + } + else { + $days -= 365; + } + } + } + while ($days < 0) { + if ($year <= 1) return false; + if ($month <= 2) { + $year--; + if ((($year % 4) == 0) ? (($year % 100) == 0) ? (($year % 400) == 0) : true : false) { + $days += 366; + } + else { + $days += 365; + } + } + else { + if ((($year % 4) == 0) ? (($year % 100) == 0) ? (($year % 400) == 0) : true : false) { + $days += 366; + } + else { + $days += 365; + } + $year--; + } + } + $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year); + $day = $this->day; + while ($day + $days > $daysInMonth) { + $days -= $daysInMonth - $day + 1; + $month++; + if ($month > 12) { + if ($year >= 9999) return false; + $year++; + $month = 1; + } + $day = 1; + $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year); + } + $day += $days; + $this->year = $year; + $this->month = $month; + $this->day = $day; + return true; + } + public function addMonths($months) { + if (!is_int($months)) return false; + if ($months == 0) return true; + $month = $this->month + $months; + $months = ($month - 1) % 12 + 1; + if ($months < 1) { + $months += 12; + } + $years = (int)(($month - $months) / 12); + if ($this->addYears($years)) { + $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $months, $this->year); + if ($this->day > $daysInMonth) { + $months++; + $this->day -= $daysInMonth; + } + $this->month = (int)$months; + return true; + } + else { + return false; + } + } + public function addYears($years) { + if (!is_int($years)) return false; + if ($years == 0) return true; + $year = $this->year + $years; + if ($year < 1 || $year > 9999) return false; + $this->year = $year; + return true; + } + public function timestamp() { + if ($this->utc) { + return gmmktime(0, 0, 0, $this->month, $this->day, $this->year); + } + else { + return mktime(0, 0, 0, $this->month, $this->day, $this->year); + } + } + public function toString($fullformat = true) { + $format = ($fullformat ? '%04d-%02d-%02d': '%04d%02d%02d'); + $str = sprintf($format, $this->year, $this->month, $this->day); + if ($this->utc) { + $str .= 'Z'; + } + return $str; + } + public function __toString() { + return $this->toString(); + } + + public static function isLeapYear($year) { + return (($year % 4) == 0) ? (($year % 100) == 0) ? (($year % 400) == 0) : true : false; + } + public static function daysInMonth($year, $month) { + if (($month < 1) || ($month > 12)) { + return false; + } + return cal_days_in_month(CAL_GREGORIAN, $month, $year); + } + public static function isValidDate($year, $month, $day) { + if (($year >= 1) && ($year <= 9999)) { + return checkdate($month, $day, $year); + } + return false; + } + + public function dayOfWeek() { + $num = func_num_args(); + if ($num == 3) { + $args = func_get_args(); + $y = $args[0]; + $m = $args[1]; + $d = $args[2]; + } + else { + $y = $this->year; + $m = $this->month; + $d = $this->day; + } + $d += $m < 3 ? $y-- : $y - 2; + return ((int)(23 * $m / 9) + $d + 4 + (int)($y / 4) - (int)($y / 100) + (int)($y / 400)) % 7; + } + public function dayOfYear() { + static $daysToMonth365 = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365); + static $daysToMonth366 = array(0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366); + $num = func_num_args(); + if ($num == 3) { + $args = func_get_args(); + $y = $args[0]; + $m = $args[1]; + $d = $args[2]; + } + else { + $y = $this->year; + $m = $this->month; + $d = $this->day; + } + $days = self::isLeapYear($y) ? $daysToMonth365 : $daysToMonth366; + return $days[$m - 1] + $d; + } +} + +class HproseTime { + public $hour; + public $minute; + public $second; + public $microsecond = 0; + public $utc = false; + public function __construct() { + $args_num = func_num_args(); + $args = func_get_args(); + switch ($args_num) { + case 0: + $time = getdate(); + $timeofday = gettimeofday(); + $this->hour = $time['hours']; + $this->minute = $time['minutes']; + $this->second = $time['seconds']; + $this->microsecond = $timeofday['usec']; + break; + case 1: + $time = false; + if (is_int($args[0])) { + $time = getdate($args[0]); + } + elseif (is_string($args[0])) { + $time = getdate(strtotime($args[0])); + } + if (is_array($time)) { + $this->hour = $time['hours']; + $this->minute = $time['minutes']; + $this->second = $time['seconds']; + } + elseif ($args[0] instanceof HproseTime) { + $this->hour = $args[0]->hour; + $this->minute = $args[0]->minute; + $this->second = $args[0]->second; + $this->microsecond = $args[0]->microsecond; + } + else { + throw new HproseException('Unexpected arguments'); + } + break; + case 5: + $this->utc = $args[4]; + case 4: + if (($args[3] < 0) || ($args[3] > 999999)) { + throw new HproseException('Unexpected arguments'); + } + $this->microsecond = $args[3]; + case 3: + if (!self::isValidTime($args[0], $args[1], $args[2])) { + throw new HproseException('Unexpected arguments'); + } + $this->hour = $args[0]; + $this->minute = $args[1]; + $this->second = $args[2]; + break; + default: + throw new HproseException('Unexpected arguments'); + } + } + public function timestamp() { + if ($this->utc) { + return gmmktime($this->hour, $this->minute, $this->second) + + ($this->microsecond / 1000000); + } + else { + return mktime($this->hour, $this->minute, $this->second) + + ($this->microsecond / 1000000); + } + } + public function toString($fullformat = true) { + if ($this->microsecond == 0) { + $format = ($fullformat ? '%02d:%02d:%02d': '%02d%02d%02d'); + $str = sprintf($format, $this->hour, $this->minute, $this->second); + } + if ($this->microsecond % 1000 == 0) { + $format = ($fullformat ? '%02d:%02d:%02d.%03d': '%02d%02d%02d.%03d'); + $str = sprintf($format, $this->hour, $this->minute, $this->second, (int)($this->microsecond / 1000)); + } + else { + $format = ($fullformat ? '%02d:%02d:%02d.%06d': '%02d%02d%02d.%06d'); + $str = sprintf($format, $this->hour, $this->minute, $this->second, $this->microsecond); + } + if ($this->utc) { + $str .= 'Z'; + } + return $str; + } + public function __toString() { + return $this->toString(); + } + public static function isValidTime($hour, $minute, $second, $microsecond = 0) { + return !(($hour < 0) || ($hour > 23) || + ($minute < 0) || ($minute > 59) || + ($second < 0) || ($second > 59) || + ($microsecond < 0) || ($microsecond > 999999)); + } +} + +class HproseDateTime extends HproseDate { + public $hour; + public $minute; + public $second; + public $microsecond = 0; + public function __construct() { + $args_num = func_num_args(); + $args = func_get_args(); + switch ($args_num) { + case 0: + $time = getdate(); + $timeofday = gettimeofday(); + $this->year = $time['year']; + $this->month = $time['mon']; + $this->day = $time['mday']; + $this->hour = $time['hours']; + $this->minute = $time['minutes']; + $this->second = $time['seconds']; + $this->microsecond = $timeofday['usec']; + break; + case 1: + $time = false; + if (is_int($args[0])) { + $time = getdate($args[0]); + } + elseif (is_string($args[0])) { + $time = getdate(strtotime($args[0])); + } + if (is_array($time)) { + $this->year = $time['year']; + $this->month = $time['mon']; + $this->day = $time['mday']; + $this->hour = $time['hours']; + $this->minute = $time['minutes']; + $this->second = $time['seconds']; + } + elseif ($args[0] instanceof HproseDate) { + $this->year = $args[0]->year; + $this->month = $args[0]->month; + $this->day = $args[0]->day; + $this->hour = 0; + $this->minute = 0; + $this->second = 0; + } + elseif ($args[0] instanceof HproseTime) { + $this->year = 1970; + $this->month = 1; + $this->day = 1; + $this->hour = $args[0]->hour; + $this->minute = $args[0]->minute; + $this->second = $args[0]->second; + $this->microsecond = $args[0]->microsecond; + } + elseif ($args[0] instanceof HproseDateTime) { + $this->year = $args[0]->year; + $this->month = $args[0]->month; + $this->day = $args[0]->day; + $this->hour = $args[0]->hour; + $this->minute = $args[0]->minute; + $this->second = $args[0]->second; + $this->microsecond = $args[0]->microsecond; + } + else { + throw new HproseException('Unexpected arguments'); + } + break; + case 2: + if (($args[0] instanceof HproseDate) && ($args[1] instanceof HproseTime)) { + $this->year = $args[0]->year; + $this->month = $args[0]->month; + $this->day = $args[0]->day; + $this->hour = $args[1]->hour; + $this->minute = $args[1]->minute; + $this->second = $args[1]->second; + $this->microsecond = $args[1]->microsecond; + } + else { + throw new HproseException('Unexpected arguments'); + } + break; + case 3: + if (!self::isValidDate($args[0], $args[1], $args[2])) { + throw new HproseException('Unexpected arguments'); + } + $this->year = $args[0]; + $this->month = $args[1]; + $this->day = $args[2]; + $this->hour = 0; + $this->minute = 0; + $this->second = 0; + break; + case 8: + $this->utc = $args[7]; + case 7: + if (($args[6] < 0) || ($args[6] > 999999)) { + throw new HproseException('Unexpected arguments'); + } + $this->microsecond = $args[6]; + case 6: + if (!self::isValidDate($args[0], $args[1], $args[2])) { + throw new HproseException('Unexpected arguments'); + } + if (!self::isValidTime($args[3], $args[4], $args[5])) { + throw new HproseException('Unexpected arguments'); + } + $this->year = $args[0]; + $this->month = $args[1]; + $this->day = $args[2]; + $this->hour = $args[3]; + $this->minute = $args[4]; + $this->second = $args[5]; + break; + default: + throw new HproseException('Unexpected arguments'); + } + } + + public function addMicroseconds($microseconds) { + if (!is_int($microseconds)) return false; + if ($microseconds == 0) return true; + $microsecond = $this->microsecond + $microseconds; + $microseconds = $microsecond % 1000000; + if ($microseconds < 0) { + $microseconds += 1000000; + } + $seconds = (int)(($microsecond - $microseconds) / 1000000); + if ($this->addSeconds($seconds)) { + $this->microsecond = (int)$microseconds; + return true; + } + else { + return false; + } + } + + public function addSeconds($seconds) { + if (!is_int($seconds)) return false; + if ($seconds == 0) return true; + $second = $this->second + $seconds; + $seconds = $second % 60; + if ($seconds < 0) { + $seconds += 60; + } + $minutes = (int)(($second - $seconds) / 60); + if ($this->addMinutes($minutes)) { + $this->second = (int)$seconds; + return true; + } + else { + return false; + } + } + public function addMinutes($minutes) { + if (!is_int($minutes)) return false; + if ($minutes == 0) return true; + $minute = $this->minute + $minutes; + $minutes = $minute % 60; + if ($minutes < 0) { + $minutes += 60; + } + $hours = (int)(($minute - $minutes) / 60); + if ($this->addHours($hours)) { + $this->minute = (int)$minutes; + return true; + } + else { + return false; + } + } + public function addHours($hours) { + if (!is_int($hours)) return false; + if ($hours == 0) return true; + $hour = $this->hour + $hours; + $hours = $hour % 24; + if ($hours < 0) { + $hours += 24; + } + $days = (int)(($hour - $hours) / 24); + if ($this->addDays($days)) { + $this->hour = (int)$hours; + return true; + } + else { + return false; + } + } + public function after($when) { + if (!($when instanceof HproseDateTime)) { + $when = new HproseDateTime($when); + } + if ($this->utc != $when->utc) return ($this->timestamp() > $when->timestamp()); + if ($this->year < $when->year) return false; + if ($this->year > $when->year) return true; + if ($this->month < $when->month) return false; + if ($this->month > $when->month) return true; + if ($this->day < $when->day) return false; + if ($this->day > $when->day) return true; + if ($this->hour < $when->hour) return false; + if ($this->hour > $when->hour) return true; + if ($this->minute < $when->minute) return false; + if ($this->minute > $when->minute) return true; + if ($this->second < $when->second) return false; + if ($this->second > $when->second) return true; + if ($this->microsecond < $when->microsecond) return false; + if ($this->microsecond > $when->microsecond) return true; + return false; + } + public function before($when) { + if (!($when instanceof HproseDateTime)) { + $when = new HproseDateTime($when); + } + if ($this->utc != $when->utc) return ($this->timestamp() < $when->timestamp()); + if ($this->year < $when->year) return true; + if ($this->year > $when->year) return false; + if ($this->month < $when->month) return true; + if ($this->month > $when->month) return false; + if ($this->day < $when->day) return true; + if ($this->day > $when->day) return false; + if ($this->hour < $when->hour) return true; + if ($this->hour > $when->hour) return false; + if ($this->minute < $when->minute) return true; + if ($this->minute > $when->minute) return false; + if ($this->second < $when->second) return true; + if ($this->second > $when->second) return false; + if ($this->microsecond < $when->microsecond) return true; + if ($this->microsecond > $when->microsecond) return false; + return false; + } + public function equals($when) { + if (!($when instanceof HproseDateTime)) { + $when = new HproseDateTime($when); + } + if ($this->utc != $when->utc) return ($this->timestamp() == $when->timestamp()); + return (($this->year == $when->year) && + ($this->month == $when->month) && + ($this->day == $when->day) && + ($this->hour == $when->hour) && + ($this->minute == $when->minute) && + ($this->second == $when->second) && + ($this->microsecond == $when->microsecond)); + } + public function timestamp() { + if ($this->utc) { + return gmmktime($this->hour, + $this->minute, + $this->second, + $this->month, + $this->day, + $this->year) + + ($this->microsecond / 1000000); + } + else { + return mktime($this->hour, + $this->minute, + $this->second, + $this->month, + $this->day, + $this->year) + + ($this->microsecond / 1000000); + } + } + public function toString($fullformat = true) { + if ($this->microsecond == 0) { + $format = ($fullformat ? '%04d-%02d-%02dT%02d:%02d:%02d' + : '%04d%02d%02dT%02d%02d%02d'); + $str = sprintf($format, + $this->year, $this->month, $this->day, + $this->hour, $this->minute, $this->second); + } + if ($this->microsecond % 1000 == 0) { + $format = ($fullformat ? '%04d-%02d-%02dT%02d:%02d:%02d.%03d' + : '%04d%02d%02dT%02d%02d%02d.%03d'); + $str = sprintf($format, + $this->year, $this->month, $this->day, + $this->hour, $this->minute, $this->second, + (int)($this->microsecond / 1000)); + } + else { + $format = ($fullformat ? '%04d-%02d-%02dT%02d:%02d:%02d.%06d' + : '%04d%02d%02dT%02d%02d%02d.%06d'); + $str = sprintf($format, + $this->year, $this->month, $this->day, + $this->hour, $this->minute, $this->second, + $this->microsecond); + } + if ($this->utc) { + $str .= 'Z'; + } + return $str; + } + public function __toString() { + return $this->toString(); + } + public static function isValidTime($hour, $minute, $second, $microsecond = 0) { + return HproseTime::isValidTime($hour, $minute, $second, $microsecond); + } +} + +/* + integer is_utf8(string $s) + if $s is UTF-8 String, return 1 else 0 + */ +if (function_exists('mb_detect_encoding')) { + function is_utf8($s) { + return mb_detect_encoding($s, 'UTF-8', true) === 'UTF-8'; + } +} +elseif (function_exists('iconv')) { + function is_utf8($s) { + return iconv('UTF-8', 'UTF-8//IGNORE', $s) === $s; + } +} +else { + function is_utf8($s) { + $len = strlen($s); + for($i = 0; $i < $len; ++$i){ + $c = ord($s{$i}); + switch ($c >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + break; + case 12: + case 13: + if ((ord($s{++$i}) >> 6) != 0x2) return false; + break; + case 14: + if ((ord($s{++$i}) >> 6) != 0x2) return false; + if ((ord($s{++$i}) >> 6) != 0x2) return false; + break; + case 15: + $b = $s{++$i}; + if ((ord($b) >> 6) != 0x2) return false; + if ((ord($s{++$i}) >> 6) != 0x2) return false; + if ((ord($s{++$i}) >> 6) != 0x2) return false; + if (((($c & 0xf) << 2) | (($b >> 4) & 0x3)) > 0x10) return false; + break; + default: + return false; + } + } + return true; + } +} + +/* + integer ustrlen(string $s) + $s must be a UTF-8 String, return the Unicode code unit (not code point) length + */ +if (function_exists('iconv')) { + function ustrlen($s) { + return strlen(iconv('UTF-8', 'UTF-16LE', $s)) >> 1; + } +} +elseif (function_exists('mb_convert_encoding')) { + function ustrlen($s) { + return strlen(mb_convert_encoding($s, "UTF-16LE", "UTF-8")) >> 1; + } +} +else { + function ustrlen($s) { + $pos = 0; + $length = strlen($s); + $len = $length; + while ($pos < $length) { + $a = ord($s{$pos++}); + if ($a < 0x80) { + continue; + } + elseif (($a & 0xE0) == 0xC0) { + ++$pos; + --$len; + } + elseif (($a & 0xF0) == 0xE0) { + $pos += 2; + $len -= 2; + } + elseif (($a & 0xF8) == 0xF0) { + $pos += 3; + $len -= 2; + } + } + return $len; + } +} + +/* + bool is_list(array $a) + if $a is list, return true else false + */ +function is_list(array $a) { + $count = count($a); + if ($count === 0) return true; + return !array_diff_key($a, array_fill(0, $count, NULL)); +} + +/* + mixed array_ref_search(mixed &$value, array $array) + if $value ref in $array, return the index else false +*/ +function array_ref_search(&$value, &$array) { + if (!is_array($value)) return array_search($value, $array, true); + $temp = $value; + foreach ($array as $i => &$ref) { + if (($ref === ($value = 1)) && ($ref === ($value = 0))) { + $value = $temp; + return $i; + } + } + $value = $temp; + return false; +} + +/* + string spl_object_hash(object $obj) + This function returns a unique identifier for the object. + This id can be used as a hash key for storing objects or for identifying an object. +*/ +if (!function_exists('spl_object_hash')) { + function spl_object_hash($object) { + ob_start(); + var_dump($object); + preg_match('[#(\d+)]', ob_get_clean(), $match); + return $match[1]; + } +} +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseFormatter.php b/ThinkPHP/Library/Vendor/Hprose/HproseFormatter.php new file mode 100644 index 0000000..b72fe4b --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseFormatter.php @@ -0,0 +1,40 @@ + * + * * +\**********************************************************/ + +require_once('HproseIOStream.php'); +require_once('HproseReader.php'); +require_once('HproseWriter.php'); + +class HproseFormatter { + public static function serialize(&$var, $simple = false) { + $stream = new HproseStringStream(); + $hproseWriter = ($simple ? new HproseSimpleWriter($stream) : new HproseWriter($stream)); + $hproseWriter->serialize($var); + return $stream->toString(); + } + public static function &unserialize($data, $simple = false) { + $stream = new HproseStringStream($data); + $hproseReader = ($simple ? new HproseSimpleReader($stream) : new HproseReader($stream)); + return $hproseReader->unserialize(); + } +} +?> diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseHttpClient.php b/ThinkPHP/Library/Vendor/Hprose/HproseHttpClient.php new file mode 100644 index 0000000..3472454 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseHttpClient.php @@ -0,0 +1,314 @@ + * + * * +\**********************************************************/ + +require_once('HproseCommon.php'); +require_once('HproseIO.php'); +require_once('HproseClient.php'); + +abstract class HproseBaseHttpClient extends HproseClient { + protected $host; + protected $path; + protected $secure; + protected $proxy; + protected $header; + protected $timeout; + protected $keepAlive; + protected $keepAliveTimeout; + protected static $cookieManager = array(); + static function hproseKeepCookieInSession() { + $_SESSION['HPROSE_COOKIE_MANAGER'] = self::$cookieManager; + } + public static function keepSession() { + if (array_key_exists('HPROSE_COOKIE_MANAGER', $_SESSION)) { + self::$cookieManager = $_SESSION['HPROSE_COOKIE_MANAGER']; + } + register_shutdown_function(array('HproseBaseHttpClient', 'hproseKeepCookieInSession')); + } + protected function setCookie($headers) { + foreach ($headers as $header) { + @list($name, $value) = explode(':', $header, 2); + if (strtolower($name) == 'set-cookie' || + strtolower($name) == 'set-cookie2') { + $cookies = explode(';', trim($value)); + $cookie = array(); + list($name, $value) = explode('=', trim($cookies[0]), 2); + $cookie['name'] = $name; + $cookie['value'] = $value; + for ($i = 1; $i < count($cookies); $i++) { + list($name, $value) = explode('=', trim($cookies[$i]), 2); + $cookie[strtoupper($name)] = $value; + } + // Tomcat can return SetCookie2 with path wrapped in " + if (array_key_exists('PATH', $cookie)) { + $cookie['PATH'] = trim($cookie['PATH'], '"'); + } + else { + $cookie['PATH'] = '/'; + } + if (array_key_exists('EXPIRES', $cookie)) { + $cookie['EXPIRES'] = strtotime($cookie['EXPIRES']); + } + if (array_key_exists('DOMAIN', $cookie)) { + $cookie['DOMAIN'] = strtolower($cookie['DOMAIN']); + } + else { + $cookie['DOMAIN'] = $this->host; + } + $cookie['SECURE'] = array_key_exists('SECURE', $cookie); + if (!array_key_exists($cookie['DOMAIN'], self::$cookieManager)) { + self::$cookieManager[$cookie['DOMAIN']] = array(); + } + self::$cookieManager[$cookie['DOMAIN']][$cookie['name']] = $cookie; + } + } + } + protected abstract function formatCookie($cookies); + protected function getCookie() { + $cookies = array(); + foreach (self::$cookieManager as $domain => $cookieList) { + if (strpos($this->host, $domain) !== false) { + $names = array(); + foreach ($cookieList as $cookie) { + if (array_key_exists('EXPIRES', $cookie) && (time() > $cookie['EXPIRES'])) { + $names[] = $cookie['name']; + } + elseif (strpos($this->path, $cookie['PATH']) === 0) { + if ((($this->secure && $cookie['SECURE']) || + !$cookie['SECURE']) && !is_null($cookie['value'])) { + $cookies[] = $cookie['name'] . '=' . $cookie['value']; + } + } + } + foreach ($names as $name) { + unset(self::$cookieManager[$domain][$name]); + } + } + } + return $this->formatCookie($cookies); + } + public function __construct($url = '') { + parent::__construct($url); + $this->header = array('Content-type' => 'application/hprose'); + } + public function useService($url = '', $namespace = '') { + $serviceProxy = parent::useService($url, $namespace); + if ($url) { + $url = parse_url($url); + $this->secure = (strtolower($url['scheme']) == 'https'); + $this->host = strtolower($url['host']); + $this->path = $url['path']; + $this->timeout = 30000; + $this->keepAlive = false; + $this->keepAliveTimeout = 300; + } + return $serviceProxy; + } + public function setHeader($name, $value) { + $lname = strtolower($name); + if ($lname != 'content-type' && + $lname != 'content-length' && + $lname != 'host') { + if ($value) { + $this->header[$name] = $value; + } + else { + unset($this->header[$name]); + } + } + } + public function setProxy($proxy = NULL) { + $this->proxy = $proxy; + } + public function setTimeout($timeout) { + $this->timeout = $timeout; + } + public function getTimeout() { + return $this->timeout; + } + public function setKeepAlive($keepAlive = true) { + $this->keepAlive = $keepAlive; + } + public function getKeepAlive() { + return $this->keeepAlive; + } + public function setKeepAliveTimeout($timeout) { + $this->keepAliveTimeout = $timeout; + } + public function getKeepAliveTimeout() { + return $this->keepAliveTimeout; + } +} + +if (class_exists('SaeFetchurl')) { + class HproseHttpClient extends HproseBaseHttpClient { + protected function formatCookie($cookies) { + if (count($cookies) > 0) { + return implode('; ', $cookies); + } + return ''; + } + protected function send($request) { + $f = new SaeFetchurl(); + $cookie = $this->getCookie(); + if ($cookie != '') { + $f->setHeader("Cookie", $cookie); + } + if ($this->keepAlive) { + $f->setHeader("Connection", "keep-alive"); + $f->setHeader("Keep-Alive", $this->keepAliveTimeout); + } + else { + $f->setHeader("Connection", "close"); + } + foreach ($this->header as $name => $value) { + $f->setHeader($name, $value); + } + $f->setMethod("post"); + $f->setPostData($request); + $f->setConnectTimeout($this->timeout); + $f->setSendTimeout($this->timeout); + $f->setReadTimeout($this->timeout); + $response = $f->fetch($this->url); + if ($f->errno()) { + throw new HproseException($f->errno() . ": " . $f->errmsg()); + } + $http_response_header = $f->responseHeaders(false); + $this->setCookie($http_response_header); + return $response; + } + } +} +elseif (function_exists('curl_init')) { + class HproseHttpClient extends HproseBaseHttpClient { + private $curl; + protected function formatCookie($cookies) { + if (count($cookies) > 0) { + return "Cookie: " . implode('; ', $cookies); + } + return ''; + } + public function __construct($url = '') { + parent::__construct($url); + $this->curl = curl_init(); + } + protected function send($request) { + curl_setopt($this->curl, CURLOPT_URL, $this->url); + curl_setopt($this->curl, CURLOPT_HEADER, TRUE); + curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($this->curl, CURLOPT_POST, TRUE); + curl_setopt($this->curl, CURLOPT_POSTFIELDS, $request); + $headers_array = array($this->getCookie(), + "Content-Length: " . strlen($request)); + if ($this->keepAlive) { + $headers_array[] = "Connection: keep-alive"; + $headers_array[] = "Keep-Alive: " . $this->keepAliveTimeout; + } + else { + $headers_array[] = "Connection: close"; + } + foreach ($this->header as $name => $value) { + $headers_array[] = $name . ": " . $value; + } + curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers_array); + if ($this->proxy) { + curl_setopt($this->curl, CURLOPT_PROXY, $this->proxy); + } + if (defined(CURLOPT_TIMEOUT_MS)) { + curl_setopt($this->curl, CURLOPT_TIMEOUT_MS, $this->timeout); + } + else { + curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout / 1000); + } + $response = curl_exec($this->curl); + $errno = curl_errno($this->curl); + if ($errno) { + throw new HproseException($errno . ": " . curl_error($this->curl)); + } + do { + list($response_headers, $response) = explode("\r\n\r\n", $response, 2); + $http_response_header = explode("\r\n", $response_headers); + $http_response_firstline = array_shift($http_response_header); + if (preg_match('@^HTTP/[0-9]\.[0-9]\s([0-9]{3})\s(.*)@', + $http_response_firstline, $matches)) { + $response_code = $matches[1]; + $response_status = trim($matches[2]); + } + else { + $response_code = "500"; + $response_status = "Unknown Error."; + } + } while (substr($response_code, 0, 1) == "1"); + if ($response_code != '200') { + throw new HproseException($response_code . ": " . $response_status); + } + $this->setCookie($http_response_header); + return $response; + } + public function __destruct() { + curl_close($this->curl); + } + } +} +else { + class HproseHttpClient extends HproseBaseHttpClient { + protected function formatCookie($cookies) { + if (count($cookies) > 0) { + return "Cookie: " . implode('; ', $cookies) . "\r\n"; + } + return ''; + } + public function __errorHandler($errno, $errstr, $errfile, $errline) { + throw new Exception($errstr, $errno); + } + protected function send($request) { + $opts = array ( + 'http' => array ( + 'method' => 'POST', + 'header'=> $this->getCookie() . + "Content-Length: " . strlen($request) . "\r\n" . + ($this->keepAlive ? + "Connection: keep-alive\r\n" . + "Keep-Alive: " . $this->keepAliveTimeout . "\r\n" : + "Connection: close\r\n"), + 'content' => $request, + 'timeout' => $this->timeout / 1000.0, + ), + ); + foreach ($this->header as $name => $value) { + $opts['http']['header'] .= "$name: $value\r\n"; + } + if ($this->proxy) { + $opts['http']['proxy'] = $this->proxy; + $opts['http']['request_fulluri'] = true; + } + $context = stream_context_create($opts); + set_error_handler(array(&$this, '__errorHandler')); + $response = file_get_contents($this->url, false, $context); + restore_error_handler(); + $this->setCookie($http_response_header); + return $response; + } + } +} + +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseHttpServer.php b/ThinkPHP/Library/Vendor/Hprose/HproseHttpServer.php new file mode 100644 index 0000000..86443ee --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseHttpServer.php @@ -0,0 +1,483 @@ + * + * * +\**********************************************************/ + +require_once('HproseCommon.php'); +require_once('HproseIO.php'); + +class HproseHttpServer { + private $errorTable = array(E_ERROR => 'Error', + E_WARNING => 'Warning', + E_PARSE => 'Parse Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Run-time Notice', + E_RECOVERABLE_ERROR => 'Error'); + private $functions; + private $funcNames; + private $resultModes; + private $simpleModes; + private $debug; + private $crossDomain; + private $P3P; + private $get; + private $input; + private $output; + private $error; + private $filter; + private $simple; + public $onBeforeInvoke; + public $onAfterInvoke; + public $onSendHeader; + public $onSendError; + public function __construct() { + $this->functions = array(); + $this->funcNames = array(); + $this->resultModes = array(); + $this->simpleModes = array(); + $this->debug = false; + $this->crossDomain = false; + $this->P3P = false; + $this->get = true; + $this->filter = NULL; + $this->simple = false; + $this->error_types = E_ALL & ~E_NOTICE; + $this->onBeforeInvoke = NULL; + $this->onAfterInvoke = NULL; + $this->onSendHeader = NULL; + $this->onSendError = NULL; + } + /* + __filterHandler & __errorHandler must be public, + however we should never call them directly. + */ + public function __filterHandler($data) { + if (preg_match('/.*? error<\/b>:(.*?)
    debug) { + $error = preg_replace('/<.*?>/', '', $match[1]); + } + else { + $error = preg_replace('/ in .*<\/b>$/', '', $match[1]); + } + $data = HproseTags::TagError . + HproseFormatter::serialize(trim($error), true) . + HproseTags::TagEnd; + } + if ($this->filter) $data = $this->filter->outputFilter($data); + return $data; + } + public function __errorHandler($errno, $errstr, $errfile, $errline) { + if ($this->debug) { + $errstr .= " in $errfile on line $errline"; + } + $this->error = $this->errorTable[$errno] . ": " . $errstr; + $this->sendError(); + return true; + } + private function sendHeader() { + if ($this->onSendHeader) { + call_user_func($this->onSendHeader); + } + header("Content-Type: text/plain"); + if ($this->P3P) { + header('P3P: CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi ' . + 'CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL ' . + 'UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV"'); + } + if ($this->crossDomain) { + if (array_key_exists('HTTP_ORIGIN', $_SERVER) && $_SERVER['HTTP_ORIGIN'] != "null") { + header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']); + header("Access-Control-Allow-Credentials: true"); + } + else { + header('Access-Control-Allow-Origin: *'); + } + } + } + private function sendError() { + if ($this->onSendError) { + call_user_func($this->onSendError, $this->error); + } + ob_clean(); + $this->output->write(HproseTags::TagError); + $writer = new HproseSimpleWriter($this->output); + $writer->writeString($this->error); + $this->output->write(HproseTags::TagEnd); + ob_end_flush(); + } + private function doInvoke() { + $simpleReader = new HproseSimpleReader($this->input); + do { + $functionName = $simpleReader->readString(true); + $aliasName = strtolower($functionName); + $resultMode = HproseResultMode::Normal; + if (array_key_exists($aliasName, $this->functions)) { + $function = $this->functions[$aliasName]; + $resultMode = $this->resultModes[$aliasName]; + $simple = $this->simpleModes[$aliasName]; + } + elseif (array_key_exists('*', $this->functions)) { + $function = $this->functions['*']; + $resultMode = $this->resultModes['*']; + $simple = $this->resultModes['*']; + } + else { + throw new HproseException("Can't find this function " . $functionName . "()."); + } + if ($simple === NULL) $simple = $this->simple; + $writer = ($simple ? new HproseSimpleWriter($this->output) : new HproseWriter($this->output)); + $args = array(); + $byref = false; + $tag = $simpleReader->checkTags(array(HproseTags::TagList, + HproseTags::TagEnd, + HproseTags::TagCall)); + if ($tag == HproseTags::TagList) { + $reader = new HproseReader($this->input); + $args = &$reader->readList(); + $tag = $reader->checkTags(array(HproseTags::TagTrue, + HproseTags::TagEnd, + HproseTags::TagCall)); + if ($tag == HproseTags::TagTrue) { + $byref = true; + $tag = $reader->checkTags(array(HproseTags::TagEnd, + HproseTags::TagCall)); + } + } + if ($this->onBeforeInvoke) { + call_user_func($this->onBeforeInvoke, $functionName, $args, $byref); + } + if (array_key_exists('*', $this->functions) && ($function === $this->functions['*'])) { + $arguments = array($functionName, &$args); + } + elseif ($byref) { + $arguments = array(); + for ($i = 0; $i < count($args); $i++) { + $arguments[$i] = &$args[$i]; + } + } + else { + $arguments = $args; + } + $result = call_user_func_array($function, $arguments); + if ($this->onAfterInvoke) { + call_user_func($this->onAfterInvoke, $functionName, $args, $byref, $result); + } + // some service functions/methods may echo content, we need clean it + ob_clean(); + if ($resultMode == HproseResultMode::RawWithEndTag) { + $this->output->write($result); + return; + } + elseif ($resultMode == HproseResultMode::Raw) { + $this->output->write($result); + } + else { + $this->output->write(HproseTags::TagResult); + if ($resultMode == HproseResultMode::Serialized) { + $this->output->write($result); + } + else { + $writer->reset(); + $writer->serialize($result); + } + if ($byref) { + $this->output->write(HproseTags::TagArgument); + $writer->reset(); + $writer->writeList($args); + } + } + } while ($tag == HproseTags::TagCall); + $this->output->write(HproseTags::TagEnd); + ob_end_flush(); + } + private function doFunctionList() { + $functions = array_values($this->funcNames); + $writer = new HproseSimpleWriter($this->output); + $this->output->write(HproseTags::TagFunctions); + $writer->writeList($functions); + $this->output->write(HproseTags::TagEnd); + ob_end_flush(); + } + private function getDeclaredOnlyMethods($class) { + $all = get_class_methods($class); + if ($parent_class = get_parent_class($class)) { + $inherit = get_class_methods($parent_class); + $result = array_diff($all, $inherit); + } + else { + $result = $all; + } + return $result; + } + public function addMissingFunction($function, $resultMode = HproseResultMode::Normal, $simple = NULL) { + $this->addFunction($function, '*', $resultMode, $simple); + } + public function addFunction($function, $alias = NULL, $resultMode = HproseResultMode::Normal, $simple = NULL) { + if (is_callable($function)) { + if ($alias === NULL) { + if (is_string($function)) { + $alias = $function; + } + else { + $alias = $function[1]; + } + } + if (is_string($alias)) { + $aliasName = strtolower($alias); + $this->functions[$aliasName] = $function; + $this->funcNames[$aliasName] = $alias; + $this->resultModes[$aliasName] = $resultMode; + $this->simpleModes[$aliasName] = $simple; + } + else { + throw new HproseException('Argument alias is not a string'); + } + } + else { + throw new HproseException('Argument function is not a callable variable'); + } + } + public function addFunctions($functions, $aliases = NULL, $resultMode = HproseResultMode::Normal, $simple = NULL) { + $aliases_is_null = ($aliases === NULL); + $count = count($functions); + if (!$aliases_is_null && $count != count($aliases)) { + throw new HproseException('The count of functions is not matched with aliases'); + } + for ($i = 0; $i < $count; $i++) { + $function = $functions[$i]; + if ($aliases_is_null) { + $this->addFunction($function, NULL, $resultMode, $simple); + } + else { + $this->addFunction($function, $aliases[$i], $resultMode, $simple); + } + } + } + public function addMethod($methodname, $belongto, $alias = NULL, $resultMode = HproseResultMode::Normal, $simple = NULL) { + if ($alias === NULL) { + $alias = $methodname; + } + if (is_string($belongto)) { + $this->addFunction(array($belongto, $methodname), $alias, $resultMode, $simple); + } + else { + $this->addFunction(array(&$belongto, $methodname), $alias, $resultMode, $simple); + } + } + public function addMethods($methods, $belongto, $aliases = NULL, $resultMode = HproseResultMode::Normal, $simple = NULL) { + $aliases_is_null = ($aliases === NULL); + $count = count($methods); + if (is_string($aliases)) { + $aliasPrefix = $aliases; + $aliases = array(); + foreach ($methods as $name) { + $aliases[] = $aliasPrefix . '_' . $name; + } + } + if (!$aliases_is_null && $count != count($aliases)) { + throw new HproseException('The count of methods is not matched with aliases'); + } + for ($i = 0; $i < $count; $i++) { + $method = $methods[$i]; + if (is_string($belongto)) { + $function = array($belongto, $method); + } + else { + $function = array(&$belongto, $method); + } + if ($aliases_is_null) { + $this->addFunction($function, $method, $resultMode, $simple); + } + else { + $this->addFunction($function, $aliases[$i], $resultMode, $simple); + } + } + } + public function addInstanceMethods($object, $class = NULL, $aliasPrefix = NULL, $resultMode = HproseResultMode::Normal, $simple = NULL) { + if ($class === NULL) $class = get_class($object); + $this->addMethods($this->getDeclaredOnlyMethods($class), $object, $aliasPrefix, $resultMode, $simple); + } + public function addClassMethods($class, $execclass = NULL, $aliasPrefix = NULL, $resultMode = HproseResultMode::Normal, $simple = NULL) { + if ($execclass === NULL) $execclass = $class; + $this->addMethods($this->getDeclaredOnlyMethods($class), $execclass, $aliasPrefix, $resultMode, $simple); + } + public function add() { + $args_num = func_num_args(); + $args = func_get_args(); + switch ($args_num) { + case 1: { + if (is_callable($args[0])) { + return $this->addFunction($args[0]); + } + elseif (is_array($args[0])) { + return $this->addFunctions($args[0]); + } + elseif (is_object($args[0])) { + return $this->addInstanceMethods($args[0]); + } + elseif (is_string($args[0])) { + return $this->addClassMethods($args[0]); + } + break; + } + case 2: { + if (is_callable($args[0]) && is_string($args[1])) { + return $this->addFunction($args[0], $args[1]); + } + elseif (is_string($args[0])) { + if (is_string($args[1]) && !is_callable(array($args[1], $args[0]))) { + if (class_exists($args[1])) { + return $this->addClassMethods($args[0], $args[1]); + } + else { + return $this->addClassMethods($args[0], NULL, $args[1]); + } + } + return $this->addMethod($args[0], $args[1]); + } + elseif (is_array($args[0])) { + if (is_array($args[1])) { + return $this->addFunctions($args[0], $args[1]); + } + else { + return $this->addMethods($args[0], $args[1]); + } + } + elseif (is_object($args[0])) { + return $this->addInstanceMethods($args[0], $args[1]); + } + break; + } + case 3: { + if (is_callable($args[0]) && is_null($args[1]) && is_string($args[2])) { + return $this->addFunction($args[0], $args[2]); + } + elseif (is_string($args[0]) && is_string($args[2])) { + if (is_string($args[1]) && !is_callable(array($args[0], $args[1]))) { + return $this->addClassMethods($args[0], $args[1], $args[2]); + } + else { + return $this->addMethod($args[0], $args[1], $args[2]); + } + } + elseif (is_array($args[0])) { + if (is_null($args[1]) && is_array($args[2])) { + return $this->addFunctions($args[0], $args[2]); + } + else { + return $this->addMethods($args[0], $args[1], $args[2]); + } + } + elseif (is_object($args[0])) { + return $this->addInstanceMethods($args[0], $args[1], $args[2]); + } + break; + } + throw new HproseException('Wrong arguments'); + } + } + public function isDebugEnabled() { + return $this->debug; + } + public function setDebugEnabled($enable = true) { + $this->debug = $enable; + } + public function isCrossDomainEnabled() { + return $this->crossDomain; + } + public function setCrossDomainEnabled($enable = true) { + $this->crossDomain = $enable; + } + public function isP3PEnabled() { + return $this->P3P; + } + public function setP3PEnabled($enable = true) { + $this->P3P = $enable; + } + public function isGetEnabled() { + return $this->get; + } + public function setGetEnabled($enable = true) { + $this->get = $enable; + } + public function getFilter() { + return $this->filter; + } + public function setFilter($filter) { + $this->filter = $filter; + } + public function getSimpleMode() { + return $this->simple; + } + public function setSimpleMode($simple = true) { + $this->simple = $simple; + } + public function getErrorTypes() { + return $this->error_types; + } + public function setErrorTypes($error_types) { + $this->error_types = $error_types; + } + public function handle() { + if (!isset($HTTP_RAW_POST_DATA)) $HTTP_RAW_POST_DATA = file_get_contents("php://input"); + if ($this->filter) $HTTP_RAW_POST_DATA = $this->filter->inputFilter($HTTP_RAW_POST_DATA); + $this->input = new HproseStringStream($HTTP_RAW_POST_DATA); + $this->output = new HproseFileStream(fopen('php://output', 'wb')); + set_error_handler(array(&$this, '__errorHandler'), $this->error_types); + ob_start(array(&$this, "__filterHandler")); + ob_implicit_flush(0); + ob_clean(); + $this->sendHeader(); + if (($_SERVER['REQUEST_METHOD'] == 'GET') and $this->get) { + return $this->doFunctionList(); + } + elseif ($_SERVER['REQUEST_METHOD'] == 'POST') { + try { + switch ($this->input->getc()) { + case HproseTags::TagCall: return $this->doInvoke(); + case HproseTags::TagEnd: return $this->doFunctionList(); + default: throw new HproseException("Wrong Request: \r\n" . $HTTP_RAW_POST_DATA); + } + } + catch (Exception $e) { + $this->error = $e->getMessage(); + if ($this->debug) { + $this->error .= "\nfile: " . $e->getFile() . + "\nline: " . $e->getLine() . + "\ntrace: " . $e->getTraceAsString(); + } + $this->sendError(); + } + } + $this->input->close(); + $this->output->close(); + } + public function start() { + $this->handle(); + } +} +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseIO.php b/ThinkPHP/Library/Vendor/Hprose/HproseIO.php new file mode 100644 index 0000000..cadbba7 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseIO.php @@ -0,0 +1,29 @@ + * + * * +\**********************************************************/ + +require_once('HproseTags.php'); +require_once('HproseClassManager.php'); +require_once('HproseReader.php'); +require_once('HproseWriter.php'); +require_once('HproseFormatter.php'); + +?> diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseIOStream.php b/ThinkPHP/Library/Vendor/Hprose/HproseIOStream.php new file mode 100644 index 0000000..72c0fb5 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseIOStream.php @@ -0,0 +1,349 @@ + * + * * +\**********************************************************/ + +abstract class HproseAbstractStream { + public abstract function close(); + public abstract function getc(); + public abstract function read($length); + public abstract function readuntil($char); + public abstract function seek($offset, $whence = SEEK_SET); + public abstract function mark(); + public abstract function unmark(); + public abstract function reset(); + public abstract function skip($n); + public abstract function eof(); + public abstract function write($string, $length = -1); +} + +class HproseStringStream extends HproseAbstractStream { + protected $buffer; + protected $pos; + protected $mark; + protected $length; + public function __construct($string = '') { + $this->buffer = $string; + $this->pos = 0; + $this->mark = -1; + $this->length = strlen($string); + } + public function close() { + $this->buffer = NULL; + $this->pos = 0; + $this->mark = -1; + $this->length = 0; + } + public function length() { + return $this->length; + } + public function getc() { + return $this->buffer{$this->pos++}; + } + public function read($length) { + $s = substr($this->buffer, $this->pos, $length); + $this->skip($length); + return $s; + } + public function readuntil($tag) { + $pos = strpos($this->buffer, $tag, $this->pos); + if ($pos !== false) { + $s = substr($this->buffer, $this->pos, $pos - $this->pos); + $this->pos = $pos + strlen($tag); + } + else { + $s = substr($this->buffer, $this->pos); + $this->pos = $this->length; + } + return $s; + } + public function seek($offset, $whence = SEEK_SET) { + switch ($whence) { + case SEEK_SET: + $this->pos = $offset; + break; + case SEEK_CUR: + $this->pos += $offset; + break; + case SEEK_END: + $this->pos = $this->length + $offset; + break; + } + $this->mark = -1; + return 0; + } + public function mark() { + $this->mark = $this->pos; + } + public function unmark() { + $this->mark = -1; + } + public function reset() { + if ($this->mark != -1) { + $this->pos = $this->mark; + } + } + public function skip($n) { + $this->pos += $n; + } + public function eof() { + return ($this->pos >= $this->length); + } + public function write($string, $length = -1) { + if ($length == -1) { + $this->buffer .= $string; + $length = strlen($string); + } + else { + $this->buffer .= substr($string, 0, $length); + } + $this->length += $length; + } + public function toString() { + return $this->buffer; + } +} + +class HproseFileStream extends HproseAbstractStream { + protected $fp; + protected $buf; + protected $unmark; + protected $pos; + protected $length; + public function __construct($fp) { + $this->fp = $fp; + $this->buf = ""; + $this->unmark = true; + $this->pos = -1; + $this->length = 0; + } + public function close() { + return fclose($this->fp); + } + public function getc() { + if ($this->pos == -1) { + return fgetc($this->fp); + } + elseif ($this->pos < $this->length) { + return $this->buf{$this->pos++}; + } + elseif ($this->unmark) { + $this->buf = ""; + $this->pos = -1; + $this->length = 0; + return fgetc($this->fp); + } + elseif (($c = fgetc($this->fp)) !== false) { + $this->buf .= $c; + $this->pos++; + $this->length++; + } + return $c; + } + public function read($length) { + if ($this->pos == -1) { + return fread($this->fp, $length); + } + elseif ($this->pos < $this->length) { + $len = $this->length - $this->pos; + if ($len < $length) { + $s = fread($this->fp, $length - $len); + $this->buf .= $s; + $this->length += strlen($s); + } + $s = substr($this->buf, $this->pos, $length); + $this->pos += strlen($s); + } + elseif ($this->unmark) { + $this->buf = ""; + $this->pos = -1; + $this->length = 0; + return fread($this->fp, $length); + } + elseif (($s = fread($this->fp, $length)) !== "") { + $this->buf .= $s; + $len = strlen($s); + $this->pos += $len; + $this->length += $len; + } + return $s; + } + public function readuntil($char) { + $s = ''; + while ((($c = $this->getc()) != $char) && $c !== false) $s .= $c; + return $s; + } + public function seek($offset, $whence = SEEK_SET) { + if (fseek($this->fp, $offset, $whence) == 0) { + $this->buf = ""; + $this->unmark = true; + $this->pos = -1; + $this->length = 0; + return 0; + } + return -1; + } + public function mark() { + $this->unmark = false; + if ($this->pos == -1) { + $this->buf = ""; + $this->pos = 0; + $this->length = 0; + } + elseif ($this->pos > 0) { + $this->buf = substr($this->buf, $this->pos); + $this->length -= $this->pos; + $this->pos = 0; + } + } + public function unmark() { + $this->unmark = true; + } + public function reset() { + $this->pos = 0; + } + public function skip($n) { + $this->read($n); + } + public function eof() { + if (($this->pos != -1) && ($this->pos < $this->length)) return false; + return feof($this->fp); + } + public function write($string, $length = -1) { + if ($length == -1) $length = strlen($string); + return fwrite($this->fp, $string, $length); + } +} + +class HproseProcStream extends HproseAbstractStream { + protected $process; + protected $pipes; + protected $buf; + protected $unmark; + protected $pos; + protected $length; + public function __construct($process, $pipes) { + $this->process = $process; + $this->pipes = $pipes; + $this->buf = ""; + $this->unmark = true; + $this->pos = -1; + $this->length = 0; + } + public function close() { + fclose($this->pipes[0]); + fclose($this->pipes[1]); + proc_close($this->process); + } + public function getc() { + if ($this->pos == -1) { + return fgetc($this->pipes[1]); + } + elseif ($this->pos < $this->length) { + return $this->buf{$this->pos++}; + } + elseif ($this->unmark) { + $this->buf = ""; + $this->pos = -1; + $this->length = 0; + return fgetc($this->pipes[1]); + } + elseif (($c = fgetc($this->pipes[1])) !== false) { + $this->buf .= $c; + $this->pos++; + $this->length++; + } + return $c; + } + public function read($length) { + if ($this->pos == -1) { + return fread($this->pipes[1], $length); + } + elseif ($this->pos < $this->length) { + $len = $this->length - $this->pos; + if ($len < $length) { + $s = fread($this->pipes[1], $length - $len); + $this->buf .= $s; + $this->length += strlen($s); + } + $s = substr($this->buf, $this->pos, $length); + $this->pos += strlen($s); + } + elseif ($this->unmark) { + $this->buf = ""; + $this->pos = -1; + $this->length = 0; + return fread($this->pipes[1], $length); + } + elseif (($s = fread($this->pipes[1], $length)) !== "") { + $this->buf .= $s; + $len = strlen($s); + $this->pos += $len; + $this->length += $len; + } + return $s; + } + public function readuntil($char) { + $s = ''; + while ((($c = $this->getc()) != $char) && $c !== false) $s .= $c; + return $s; + } + public function seek($offset, $whence = SEEK_SET) { + if (fseek($this->pipes[1], $offset, $whence) == 0) { + $this->buf = ""; + $this->unmark = true; + $this->pos = -1; + $this->length = 0; + return 0; + } + return -1; + } + public function mark() { + $this->unmark = false; + if ($this->pos == -1) { + $this->buf = ""; + $this->pos = 0; + $this->length = 0; + } + elseif ($this->pos > 0) { + $this->buf = substr($this->buf, $this->pos); + $this->length -= $this->pos; + $this->pos = 0; + } + } + public function unmark() { + $this->unmark = true; + } + public function reset() { + $this->pos = 0; + } + public function skip($n) { + $this->read($n); + } + public function eof() { + if (($this->pos != -1) && ($this->pos < $this->length)) return false; + return feof($this->pipes[1]); + } + public function write($string, $length = -1) { + if ($length == -1) $length = strlen($string); + return fwrite($this->pipes[0], $string, $length); + } +} +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseReader.php b/ThinkPHP/Library/Vendor/Hprose/HproseReader.php new file mode 100644 index 0000000..da223a8 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseReader.php @@ -0,0 +1,672 @@ + * + * * +\**********************************************************/ + +require_once('HproseCommon.php'); +require_once('HproseTags.php'); +require_once('HproseClassManager.php'); + +class HproseRawReader { + public $stream; + function __construct(&$stream) { + $this->stream = &$stream; + } + public function readRaw($ostream = NULL, $tag = NULL) { + if (is_null($ostream)) { + $ostream = new HproseStringStream(); + } + if (is_null($tag)) { + $tag = $this->stream->getc(); + } + switch ($tag) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case HproseTags::TagNull: + case HproseTags::TagEmpty: + case HproseTags::TagTrue: + case HproseTags::TagFalse: + case HproseTags::TagNaN: + $ostream->write($tag); + break; + case HproseTags::TagInfinity: + $ostream->write($tag); + $ostream->write($this->stream->getc()); + break; + case HproseTags::TagInteger: + case HproseTags::TagLong: + case HproseTags::TagDouble: + case HproseTags::TagRef: + $this->readNumberRaw($ostream, $tag); + break; + case HproseTags::TagDate: + case HproseTags::TagTime: + $this->readDateTimeRaw($ostream, $tag); + break; + case HproseTags::TagUTF8Char: + $this->readUTF8CharRaw($ostream, $tag); + break; + case HproseTags::TagBytes: + $this->readBytesRaw($ostream, $tag); + break; + case HproseTags::TagString: + $this->readStringRaw($ostream, $tag); + break; + case HproseTags::TagGuid: + $this->readGuidRaw($ostream, $tag); + break; + case HproseTags::TagList: + case HproseTags::TagMap: + case HproseTags::TagObject: + $this->readComplexRaw($ostream, $tag); + break; + case HproseTags::TagClass: + $this->readComplexRaw($ostream, $tag); + $this->readRaw($ostream); + break; + case HproseTags::TagError: + $ostream->write($tag); + $this->readRaw($ostream); + break; + case false: + throw new HproseException("No byte found in stream"); + default: + throw new HproseException("Unexpected serialize tag '" + $tag + "' in stream"); + } + return $ostream; + } + + private function readNumberRaw($ostream, $tag) { + $s = $tag . + $this->stream->readuntil(HproseTags::TagSemicolon) . + HproseTags::TagSemicolon; + $ostream->write($s); + } + + private function readDateTimeRaw($ostream, $tag) { + $s = $tag; + do { + $tag = $this->stream->getc(); + $s .= $tag; + } while ($tag != HproseTags::TagSemicolon && + $tag != HproseTags::TagUTC); + $ostream->write($s); + } + + private function readUTF8CharRaw($ostream, $tag) { + $s = $tag; + $tag = $this->stream->getc(); + $s .= $tag; + $a = ord($tag); + if (($a & 0xE0) == 0xC0) { + $s .= $this->stream->getc(); + } + elseif (($a & 0xF0) == 0xE0) { + $s .= $this->stream->read(2); + } + elseif ($a > 0x7F) { + throw new HproseException("bad utf-8 encoding"); + } + $ostream->write($s); + } + + private function readBytesRaw($ostream, $tag) { + $len = $this->stream->readuntil(HproseTags::TagQuote); + $s = $tag . $len . HproseTags::TagQuote . $this->stream->read((int)$len) . HproseTags::TagQuote; + $this->stream->skip(1); + $ostream->write($s); + } + + private function readStringRaw($ostream, $tag) { + $len = $this->stream->readuntil(HproseTags::TagQuote); + $s = $tag . $len . HproseTags::TagQuote; + $len = (int)$len; + $this->stream->mark(); + $utf8len = 0; + for ($i = 0; $i < $len; ++$i) { + switch (ord($this->stream->getc()) >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: { + // 0xxx xxxx + $utf8len++; + break; + } + case 12: + case 13: { + // 110x xxxx 10xx xxxx + $this->stream->skip(1); + $utf8len += 2; + break; + } + case 14: { + // 1110 xxxx 10xx xxxx 10xx xxxx + $this->stream->skip(2); + $utf8len += 3; + break; + } + case 15: { + // 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx + $this->stream->skip(3); + $utf8len += 4; + ++$i; + break; + } + default: { + throw new HproseException('bad utf-8 encoding'); + } + } + } + $this->stream->reset(); + $this->stream->unmark(); + $s .= $this->stream->read($utf8len) . HproseTags::TagQuote; + $this->stream->skip(1); + $ostream->write($s); + } + + private function readGuidRaw($ostream, $tag) { + $s = $tag . $this->stream->read(38); + $ostream->write($s); + } + + private function readComplexRaw($ostream, $tag) { + $s = $tag . + $this->stream->readuntil(HproseTags::TagOpenbrace) . + HproseTags::TagOpenbrace; + $ostream->write($s); + while (($tag = $this->stream->getc()) != HproseTags::TagClosebrace) { + $this->readRaw($ostream, $tag); + } + $ostream->write($tag); + } +} + +class HproseSimpleReader extends HproseRawReader { + private $classref; + function __construct(&$stream) { + parent::__construct($stream); + $this->classref = array(); + } + public function &unserialize($tag = NULL) { + if (is_null($tag)) { + $tag = $this->stream->getc(); + } + $result = NULL; + switch ($tag) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + $result = (int)$tag; break; + case HproseTags::TagInteger: $result = $this->readInteger(); break; + case HproseTags::TagLong: $result = $this->readLong(); break; + case HproseTags::TagDouble: $result = $this->readDouble(); break; + case HproseTags::TagNull: break; + case HproseTags::TagEmpty: $result = ''; break; + case HproseTags::TagTrue: $result = true; break; + case HproseTags::TagFalse: $result = false; break; + case HproseTags::TagNaN: $result = log(-1); break; + case HproseTags::TagInfinity: $result = $this->readInfinity(); break; + case HproseTags::TagDate: $result = $this->readDate(); break; + case HproseTags::TagTime: $result = $this->readTime(); break; + case HproseTags::TagBytes: $result = $this->readBytes(); break; + case HproseTags::TagUTF8Char: $result = $this->readUTF8Char(); break; + case HproseTags::TagString: $result = $this->readString(); break; + case HproseTags::TagGuid: $result = $this->readGuid(); break; + case HproseTags::TagList: $result = &$this->readList(); break; + case HproseTags::TagMap: $result = &$this->readMap(); break; + case HproseTags::TagClass: $this->readClass(); $result = &$this->unserialize(); break; + case HproseTags::TagObject: $result = $this->readObject(); break; + case HproseTags::TagError: throw new HproseException($this->readString(true)); + case false: throw new HproseException('No byte found in stream'); + default: throw new HproseException("Unexpected serialize tag '$tag' in stream"); + } + return $result; + } + public function checkTag($expectTag, $tag = NULL) { + if (is_null($tag)) $tag = $this->stream->getc(); + if ($tag != $expectTag) { + throw new HproseException("Tag '$expectTag' expected, but '$tag' found in stream"); + } + } + public function checkTags($expectTags, $tag = NULL) { + if (is_null($tag)) $tag = $this->stream->getc(); + if (!in_array($tag, $expectTags)) { + $expectTags = implode('', $expectTags); + throw new HproseException("Tag '$expectTags' expected, but '$tag' found in stream"); + } + return $tag; + } + public function readInteger($includeTag = false) { + if ($includeTag) { + $tag = $this->stream->getc(); + if (($tag >= '0') && ($tag <= '9')) { + return (int)$tag; + } + $this->checkTag(HproseTags::TagInteger, $tag); + } + return (int)($this->stream->readuntil(HproseTags::TagSemicolon)); + } + public function readLong($includeTag = false) { + if ($includeTag) { + $tag = $this->stream->getc(); + if (($tag >= '0') && ($tag <= '9')) { + return $tag; + } + $this->checkTag(HproseTags::TagLong, $tag); + } + return $this->stream->readuntil(HproseTags::TagSemicolon); + } + public function readDouble($includeTag = false) { + if ($includeTag) { + $tag = $this->stream->getc(); + if (($tag >= '0') && ($tag <= '9')) { + return (double)$tag; + } + $this->checkTag(HproseTags::TagDouble, $tag); + } + return (double)($this->stream->readuntil(HproseTags::TagSemicolon)); + } + public function readNaN() { + $this->checkTag(HproseTags::TagNaN); + return log(-1); + } + public function readInfinity($includeTag = false) { + if ($includeTag) $this->checkTag(HproseTags::TagInfinity); + return (($this->stream->getc() == HproseTags::TagNeg) ? log(0) : -log(0)); + } + public function readNull() { + $this->checkTag(HproseTags::TagNull); + return NULL; + } + public function readEmpty() { + $this->checkTag(HproseTags::TagEmpty); + return ''; + } + public function readBoolean() { + $tag = $this->checkTags(array(HproseTags::TagTrue, HproseTags::TagFalse)); + return ($tag == HproseTags::TagTrue); + } + public function readDate($includeTag = false) { + if ($includeTag) $this->checkTag(HproseTags::TagDate); + $year = (int)($this->stream->read(4)); + $month = (int)($this->stream->read(2)); + $day = (int)($this->stream->read(2)); + $tag = $this->stream->getc(); + if ($tag == HproseTags::TagTime) { + $hour = (int)($this->stream->read(2)); + $minute = (int)($this->stream->read(2)); + $second = (int)($this->stream->read(2)); + $microsecond = 0; + $tag = $this->stream->getc(); + if ($tag == HproseTags::TagPoint) { + $microsecond = (int)($this->stream->read(3)) * 1000; + $tag = $this->stream->getc(); + if (($tag >= '0') && ($tag <= '9')) { + $microsecond += (int)($tag) * 100 + (int)($this->stream->read(2)); + $tag = $this->stream->getc(); + if (($tag >= '0') && ($tag <= '9')) { + $this->stream->skip(2); + $tag = $this->stream->getc(); + } + } + } + if ($tag == HproseTags::TagUTC) { + $date = new HproseDateTime($year, $month, $day, + $hour, $minute, $second, + $microsecond, true); + } + else { + $date = new HproseDateTime($year, $month, $day, + $hour, $minute, $second, + $microsecond); + } + } + elseif ($tag == HproseTags::TagUTC) { + $date = new HproseDate($year, $month, $day, true); + } + else { + $date = new HproseDate($year, $month, $day); + } + return $date; + } + public function readTime($includeTag = false) { + if ($includeTag) $this->checkTag(HproseTags::TagTime); + $hour = (int)($this->stream->read(2)); + $minute = (int)($this->stream->read(2)); + $second = (int)($this->stream->read(2)); + $microsecond = 0; + $tag = $this->stream->getc(); + if ($tag == HproseTags::TagPoint) { + $microsecond = (int)($this->stream->read(3)) * 1000; + $tag = $this->stream->getc(); + if (($tag >= '0') && ($tag <= '9')) { + $microsecond += (int)($tag) * 100 + (int)($this->stream->read(2)); + $tag = $this->stream->getc(); + if (($tag >= '0') && ($tag <= '9')) { + $this->stream->skip(2); + $tag = $this->stream->getc(); + } + } + } + if ($tag == HproseTags::TagUTC) { + $time = new HproseTime($hour, $minute, $second, $microsecond, true); + } + else { + $time = new HproseTime($hour, $minute, $second, $microsecond); + } + return $time; + } + public function readBytes($includeTag = false) { + if ($includeTag) $this->checkTag(HproseTags::TagBytes); + $count = (int)($this->stream->readuntil(HproseTags::TagQuote)); + $bytes = $this->stream->read($count); + $this->stream->skip(1); + return $bytes; + } + public function readUTF8Char($includeTag = false) { + if ($includeTag) $this->checkTag(HproseTags::TagUTF8Char); + $c = $this->stream->getc(); + $s = $c; + $a = ord($c); + if (($a & 0xE0) == 0xC0) { + $s .= $this->stream->getc(); + } + elseif (($a & 0xF0) == 0xE0) { + $s .= $this->stream->read(2); + } + elseif ($a > 0x7F) { + throw new HproseException("bad utf-8 encoding"); + } + return $s; + } + public function readString($includeTag = false) { + if ($includeTag) $this->checkTag(HproseTags::TagString); + $len = (int)$this->stream->readuntil(HproseTags::TagQuote); + $this->stream->mark(); + $utf8len = 0; + for ($i = 0; $i < $len; ++$i) { + switch (ord($this->stream->getc()) >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: { + // 0xxx xxxx + $utf8len++; + break; + } + case 12: + case 13: { + // 110x xxxx 10xx xxxx + $this->stream->skip(1); + $utf8len += 2; + break; + } + case 14: { + // 1110 xxxx 10xx xxxx 10xx xxxx + $this->stream->skip(2); + $utf8len += 3; + break; + } + case 15: { + // 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx + $this->stream->skip(3); + $utf8len += 4; + ++$i; + break; + } + default: { + throw new HproseException('bad utf-8 encoding'); + } + } + } + $this->stream->reset(); + $this->stream->unmark(); + $s = $this->stream->read($utf8len); + $this->stream->skip(1); + return $s; + } + public function readGuid($includeTag = false) { + if ($includeTag) $this->checkTag(HproseTags::TagGuid); + $this->stream->skip(1); + $s = $this->stream->read(36); + $this->stream->skip(1); + return $s; + } + protected function &readListBegin() { + $list = array(); + return $list; + } + protected function &readListEnd(&$list) { + $count = (int)$this->stream->readuntil(HproseTags::TagOpenbrace); + for ($i = 0; $i < $count; ++$i) { + $list[] = &$this->unserialize(); + } + $this->stream->skip(1); + return $list; + } + public function &readList($includeTag = false) { + if ($includeTag) $this->checkTag(HproseTags::TagList); + $list = &$this->readListBegin(); + return $this->readListEnd($list); + } + protected function &readMapBegin() { + $map = array(); + return $map; + } + protected function &readMapEnd(&$map) { + $count = (int)$this->stream->readuntil(HproseTags::TagOpenbrace); + for ($i = 0; $i < $count; ++$i) { + $key = &$this->unserialize(); + $map[$key] = &$this->unserialize(); + } + $this->stream->skip(1); + return $map; + } + public function &readMap($includeTag = false) { + if ($includeTag) $this->checkTag(HproseTags::TagMap); + $map = &$this->readMapBegin(); + return $this->readMapEnd($map); + } + protected function readObjectBegin() { + list($classname, $fields) = $this->classref[(int)$this->stream->readuntil(HproseTags::TagOpenbrace)]; + $object = new $classname; + return array($object, $fields); + } + protected function readObjectEnd($object, $fields) { + $count = count($fields); + if (class_exists('ReflectionClass')) { + $reflector = new ReflectionClass($object); + for ($i = 0; $i < $count; ++$i) { + $field = $fields[$i]; + if ($reflector->hasProperty($field)) { + $property = $reflector->getProperty($field); + $property->setAccessible(true); + $property->setValue($object, $this->unserialize()); + } + else { + $object->$field = &$this->unserialize(); + } + } + } + else { + for ($i = 0; $i < $count; ++$i) { + $object->$fields[$i] = &$this->unserialize(); + } + } + $this->stream->skip(1); + return $object; + } + public function readObject($includeTag = false) { + if ($includeTag) { + $tag = $this->checkTags(array(HproseTags::TagClass, HproseTags::TagObject)); + if ($tag == HproseTags::TagClass) { + $this->readClass(); + return $this->readObject(true); + } + } + list($object, $fields) = $this->readObjectBegin(); + return $this->readObjectEnd($object, $fields); + } + protected function readClass() { + $classname = HproseClassManager::getClass(self::readString()); + $count = (int)$this->stream->readuntil(HproseTags::TagOpenbrace); + $fields = array(); + for ($i = 0; $i < $count; ++$i) { + $fields[] = $this->readString(true); + } + $this->stream->skip(1); + $this->classref[] = array($classname, $fields); + } + public function reset() { + $this->classref = array(); + } +} + +class HproseReader extends HproseSimpleReader { + private $ref; + function __construct(&$stream) { + parent::__construct($stream); + $this->ref = array(); + } + public function &unserialize($tag = NULL) { + if (is_null($tag)) { + $tag = $this->stream->getc(); + } + if ($tag == HproseTags::TagRef) { + return $this->readRef(); + } + return parent::unserialize($tag); + } + public function readDate($includeTag = false) { + if ($includeTag) { + $tag = $this->checkTags(array(HproseTags::TagDate, HproseTags::TagRef)); + if ($tag == HproseTags::TagRef) return $this->readRef(); + } + $date = parent::readDate(); + $this->ref[] = $date; + return $date; + } + public function readTime($includeTag = false) { + if ($includeTag) { + $tag = $this->checkTags(array(HproseTags::TagTime, HproseTags::TagRef)); + if ($tag == HproseTags::TagRef) return $this->readRef(); + } + $time = parent::readTime(); + $this->ref[] = $time; + return $time; + } + public function readBytes($includeTag = false) { + if ($includeTag) { + $tag = $this->checkTags(array(HproseTags::TagBytes, HproseTags::TagRef)); + if ($tag == HproseTags::TagRef) return $this->readRef(); + } + $bytes = parent::readBytes(); + $this->ref[] = $bytes; + return $bytes; + } + public function readString($includeTag = false) { + if ($includeTag) { + $tag = $this->checkTags(array(HproseTags::TagString, HproseTags::TagRef)); + if ($tag == HproseTags::TagRef) return $this->readRef(); + } + $str = parent::readString(); + $this->ref[] = $str; + return $str; + } + public function readGuid($includeTag = false) { + if ($includeTag) { + $tag = $this->checkTags(array(HproseTags::TagGuid, HproseTags::TagRef)); + if ($tag == HproseTags::TagRef) return $this->readRef(); + } + $guid = parent::readGuid(); + $this->ref[] = $guid; + return $guid; + } + public function &readList($includeTag = false) { + if ($includeTag) { + $tag = $this->checkTags(array(HproseTags::TagList, HproseTags::TagRef)); + if ($tag == HproseTags::TagRef) return $this->readRef(); + } + $list = &$this->readListBegin(); + $this->ref[] = &$list; + return $this->readListEnd($list); + } + public function &readMap($includeTag = false) { + if ($includeTag) { + $tag = $this->checkTags(array(HproseTags::TagMap, HproseTags::TagRef)); + if ($tag == HproseTags::TagRef) return $this->readRef(); + } + $map = &$this->readMapBegin(); + $this->ref[] = &$map; + return $this->readMapEnd($map); + } + public function readObject($includeTag = false) { + if ($includeTag) { + $tag = $this->checkTags(array(HproseTags::TagClass, HproseTags::TagObject, HproseTags::TagRef)); + if ($tag == HproseTags::TagRef) return $this->readRef(); + if ($tag == HproseTags::TagClass) { + $this->readClass(); + return $this->readObject(true); + } + } + list($object, $fields) = $this->readObjectBegin(); + $this->ref[] = $object; + return $this->readObjectEnd($object, $fields); + } + private function &readRef() { + $ref = &$this->ref[(int)$this->stream->readuntil(HproseTags::TagSemicolon)]; + if (gettype($ref) == 'array') { + $result = &$ref; + } + else { + $result = $ref; + } + return $result; + } + public function reset() { + parent::reset(); + $this->ref = array(); + } +} +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseTags.php b/ThinkPHP/Library/Vendor/Hprose/HproseTags.php new file mode 100644 index 0000000..c1d6430 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseTags.php @@ -0,0 +1,62 @@ + * + * * +\**********************************************************/ + +class HproseTags { + /* Serialize Tags */ + const TagInteger = 'i'; + const TagLong = 'l'; + const TagDouble = 'd'; + const TagNull = 'n'; + const TagEmpty = 'e'; + const TagTrue = 't'; + const TagFalse = 'f'; + const TagNaN = 'N'; + const TagInfinity = 'I'; + const TagDate = 'D'; + const TagTime = 'T'; + const TagUTC = 'Z'; + const TagBytes = 'b'; + const TagUTF8Char = 'u'; + const TagString = 's'; + const TagGuid = 'g'; + const TagList = 'a'; + const TagMap = 'm'; + const TagClass = 'c'; + const TagObject = 'o'; + const TagRef = 'r'; + /* Serialize Marks */ + const TagPos = '+'; + const TagNeg = '-'; + const TagSemicolon = ';'; + const TagOpenbrace = '{'; + const TagClosebrace = '}'; + const TagQuote = '"'; + const TagPoint = '.'; + /* Protocol Tags */ + const TagFunctions = 'F'; + const TagCall = 'C'; + const TagResult = 'R'; + const TagArgument = 'A'; + const TagError = 'E'; + const TagEnd = 'z'; +} +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/Hprose/HproseWriter.php b/ThinkPHP/Library/Vendor/Hprose/HproseWriter.php new file mode 100644 index 0000000..6ec9d79 --- /dev/null +++ b/ThinkPHP/Library/Vendor/Hprose/HproseWriter.php @@ -0,0 +1,301 @@ + * + * * +\**********************************************************/ + +require_once('HproseCommon.php'); +require_once('HproseTags.php'); +require_once('HproseClassManager.php'); + +class HproseSimpleWriter { + public $stream; + private $classref; + private $fieldsref; + function __construct(&$stream) { + $this->stream = &$stream; + $this->classref = array(); + $this->fieldsref = array(); + } + public function serialize(&$var) { + if ((!isset($var)) || ($var === NULL)) { + $this->writeNull(); + } + elseif (is_scalar($var)) { + if (is_int($var)) { + $this->writeInteger($var); + } + elseif (is_bool($var)) { + $this->writeBoolean($var); + } + elseif (is_float($var)) { + $this->writeDouble($var); + } + elseif (is_string($var)) { + if ($var === '') { + $this->writeEmpty(); + } + elseif ((strlen($var) < 4) && is_utf8($var) && (ustrlen($var) == 1)) { + $this->writeUTF8Char($var); + } + elseif (is_utf8($var)) { + $this->writeString($var, true); + } + else { + $this->writeBytes($var, true); + } + } + } + elseif (is_array($var)) { + if (is_list($var)) { + $this->writeList($var, true); + } + else { + $this->writeMap($var, true); + } + } + elseif (is_object($var)) { + if ($var instanceof stdClass) { + $this->writeStdObject($var, true); + } + elseif (($var instanceof HproseDate) || ($var instanceof HproseDateTime)) { + $this->writeDate($var, true); + } + elseif ($var instanceof HproseTime) { + $this->writeTime($var, true); + } + else { + $this->writeObject($var, true); + } + } + else { + throw new HproseException('Not support to serialize this data'); + } + } + public function writeInteger($integer) { + if ($integer >= 0 && $integer <= 9) { + $this->stream->write((string)$integer); + } + else { + $this->stream->write(HproseTags::TagInteger . $integer . HproseTags::TagSemicolon); + } + } + public function writeLong($long) { + if ($long >= '0' && $long <= '9') { + $this->stream->write($long); + } + else { + $this->stream->write(HproseTags::TagLong . $long . HproseTags::TagSemicolon); + } + } + public function writeDouble($double) { + if (is_nan($double)) { + $this->writeNaN(); + } + elseif (is_infinite($double)) { + $this->writeInfinity($double > 0); + } + else { + $this->stream->write(HproseTags::TagDouble . $double . HproseTags::TagSemicolon); + } + } + public function writeNaN() { + $this->stream->write(HproseTags::TagNaN); + } + public function writeInfinity($positive = true) { + $this->stream->write(HproseTags::TagInfinity . ($positive ? HproseTags::TagPos : HproseTags::TagNeg)); + } + public function writeNull() { + $this->stream->write(HproseTags::TagNull); + } + public function writeEmpty() { + $this->stream->write(HproseTags::TagEmpty); + } + public function writeBoolean($bool) { + $this->stream->write($bool ? HproseTags::TagTrue : HproseTags::TagFalse); + } + public function writeDate($date, $checkRef = false) { + if ($date->utc) { + $this->stream->write(HproseTags::TagDate . $date->toString(false)); + } + else { + $this->stream->write(HproseTags::TagDate . $date->toString(false) . HproseTags::TagSemicolon); + } + } + public function writeTime($time, $checkRef = false) { + if ($time->utc) { + $this->stream->write(HproseTags::TagTime . $time->toString(false)); + } + else { + $this->stream->write(HproseTags::TagTime . $time->toString(false) . HproseTags::TagSemicolon); + } + } + public function writeBytes($bytes, $checkRef = false) { + $len = strlen($bytes); + $this->stream->write(HproseTags::TagBytes); + if ($len > 0) $this->stream->write((string)$len); + $this->stream->write(HproseTags::TagQuote . $bytes . HproseTags::TagQuote); + } + public function writeUTF8Char($char) { + $this->stream->write(HproseTags::TagUTF8Char . $char); + } + public function writeString($str, $checkRef = false) { + $len = ustrlen($str); + $this->stream->write(HproseTags::TagString); + if ($len > 0) $this->stream->write((string)$len); + $this->stream->write(HproseTags::TagQuote . $str . HproseTags::TagQuote); + } + public function writeList(&$list, $checkRef = false) { + $count = count($list); + $this->stream->write(HproseTags::TagList); + if ($count > 0) $this->stream->write((string)$count); + $this->stream->write(HproseTags::TagOpenbrace); + for ($i = 0; $i < $count; ++$i) { + $this->serialize($list[$i]); + } + $this->stream->write(HproseTags::TagClosebrace); + } + public function writeMap(&$map, $checkRef = false) { + $count = count($map); + $this->stream->write(HproseTags::TagMap); + if ($count > 0) $this->stream->write((string)$count); + $this->stream->write(HproseTags::TagOpenbrace); + foreach ($map as $key => &$value) { + $this->serialize($key); + $this->serialize($value); + } + $this->stream->write(HproseTags::TagClosebrace); + } + public function writeStdObject($obj, $checkRef = false) { + $map = (array)$obj; + self::writeMap($map); + } + protected function writeObjectBegin($obj) { + $class = get_class($obj); + $alias = HproseClassManager::getClassAlias($class); + $fields = array_keys((array)$obj); + if (array_key_exists($alias, $this->classref)) { + $index = $this->classref[$alias]; + } + else { + $index = $this->writeClass($alias, $fields); + } + return $index; + } + protected function writeObjectEnd($obj, $index) { + $fields = $this->fieldsref[$index]; + $count = count($fields); + $this->stream->write(HproseTags::TagObject . $index . HproseTags::TagOpenbrace); + $array = (array)$obj; + for ($i = 0; $i < $count; ++$i) { + $this->serialize($array[$fields[$i]]); + } + $this->stream->write(HproseTags::TagClosebrace); + } + public function writeObject($obj, $checkRef = false) { + $this->writeObjectEnd($obj, $this->writeObjectBegin($obj)); + } + protected function writeClass($alias, $fields) { + $len = ustrlen($alias); + $this->stream->write(HproseTags::TagClass . $len . + HproseTags::TagQuote . $alias . HproseTags::TagQuote); + $count = count($fields); + if ($count > 0) $this->stream->write((string)$count); + $this->stream->write(HproseTags::TagOpenbrace); + for ($i = 0; $i < $count; ++$i) { + $field = $fields[$i]; + if ($field{0} === "\0") { + $field = substr($field, strpos($field, "\0", 1) + 1); + } + $this->writeString($field); + } + $this->stream->write(HproseTags::TagClosebrace); + $index = count($this->fieldsref); + $this->classref[$alias] = $index; + $this->fieldsref[$index] = $fields; + return $index; + } + public function reset() { + $this->classref = array(); + $this->fieldsref = array(); + } +} +class HproseWriter extends HproseSimpleWriter { + private $ref; + private $arrayref; + function __construct(&$stream) { + parent::__construct($stream); + $this->ref = array(); + $this->arrayref = array(); + } + private function writeRef(&$obj, $checkRef, $writeBegin, $writeEnd) { + if (is_string($obj)) { + $key = 's_' . $obj; + } + elseif (is_array($obj)) { + if (($i = array_ref_search($obj, $this->arrayref)) === false) { + $i = count($this->arrayref); + $this->arrayref[$i] = &$obj; + } + $key = 'a_' . $i; + } + else { + $key = 'o_' . spl_object_hash($obj); + } + if ($checkRef && array_key_exists($key, $this->ref)) { + $this->stream->write(HproseTags::TagRef . $this->ref[$key] . HproseTags::TagSemicolon); + } + else { + $result = $writeBegin ? call_user_func_array($writeBegin, array(&$obj)) : false; + $index = count($this->ref); + $this->ref[$key] = $index; + call_user_func_array($writeEnd, array(&$obj, $result)); + } + } + public function writeDate($date, $checkRef = false) { + $this->writeRef($date, $checkRef, NULL, array(&$this, 'parent::writeDate')); + } + public function writeTime($time, $checkRef = false) { + $this->writeRef($time, $checkRef, NULL, array(&$this, 'parent::writeTime')); + } + public function writeBytes($bytes, $checkRef = false) { + $this->writeRef($bytes, $checkRef, NULL, array(&$this, 'parent::writeBytes')); + } + public function writeString($str, $checkRef = false) { + $this->writeRef($str, $checkRef, NULL, array(&$this, 'parent::writeString')); + } + public function writeList(&$list, $checkRef = false) { + $this->writeRef($list, $checkRef, NULL, array(&$this, 'parent::writeList')); + } + public function writeMap(&$map, $checkRef = false) { + $this->writeRef($map, $checkRef, NULL, array(&$this, 'parent::writeMap')); + } + public function writeStdObject($obj, $checkRef = false) { + $this->writeRef($obj, $checkRef, NULL, array(&$this, 'parent::writeStdObject')); + } + public function writeObject($obj, $checkRef = false) { + $this->writeRef($obj, $checkRef, array(&$this, 'writeObjectBegin'), array(&$this, 'writeObjectEnd')); + } + public function reset() { + parent::reset(); + $this->ref = array(); + $this->arrayref = array(); + } +} +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/README.txt b/ThinkPHP/Library/Vendor/README.txt new file mode 100644 index 0000000..88cafc0 --- /dev/null +++ b/ThinkPHP/Library/Vendor/README.txt @@ -0,0 +1 @@ +第三方类库包目录 \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplate.php b/ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplate.php new file mode 100644 index 0000000..e245e38 --- /dev/null +++ b/ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplate.php @@ -0,0 +1,392 @@ +assign( 'TITLE', 'TemplateDemo - Userlist' ); + * $page->assign( 'user', DB_read_all( 'select * from ris_user' ) ); + * $page->output(); + * + * Usage Example II: + * + * $data = array( + * 'TITLE' => 'TemplateDemo - Userlist', + * 'user' => DB_read_all( 'select * from ris_user' ) + * ); + * $page = new SmartTemplate( "template.html" ); + * $page->output( $data ); + * + * + * @author Philipp v. Criegern philipp@criegern.com + * @author Manuel 'EndelWar' Dalla Lana endelwar@aregar.it + * @version 1.2.1 03.07.2006 + * + * CVS ID: $Id: class.smarttemplate.php 2504 2011-12-28 07:35:29Z liu21st $ + */ + class SmartTemplate + { + /** + * Whether to store compiled php code or not (for debug purpose) + * + * @access public + */ + var $reuse_code = true; + + /** + * Directory where all templates are stored + * Can be overwritten by global configuration array $_CONFIG['template_dir'] + * + * @access public + */ + var $template_dir = 'templates/'; + + /** + * Where to store compiled templates + * Can be overwritten by global configuration array $_CONFIG['smarttemplate_compiled'] + * + * @access public + */ + var $temp_dir = 'templates_c/'; + + /** + * Temporary folder for output cache storage + * Can be overwritten by global configuration array $_CONFIG['smarttemplate_cache'] + * + * @access public + */ + var $cache_dir = 'templates_c/'; + + /** + * Default Output Cache Lifetime in Seconds + * Can be overwritten by global configuration array $_CONFIG['cache_lifetime'] + * + * @access public + */ + var $cache_lifetime = 600; + + /** + * Temporary file for output cache storage + * + * @access private + */ + var $cache_filename; + + /** + * The template filename + * + * @access private + */ + var $tpl_file; + + /** + * The compiled template filename + * + * @access private + */ + var $cpl_file; + + /** + * Template content array + * + * @access private + */ + var $data = array(); + + /** + * Parser Class + * + * @access private + */ + var $parser; + + /** + * Debugger Class + * + * @access private + */ + var $debugger; + + /** + * SmartTemplate Constructor + * + * @access public + * @param string $template_filename Template Filename + */ + function SmartTemplate ( $template_filename = '' ) + { + global $_CONFIG; + + if (!empty($_CONFIG['smarttemplate_compiled'])) + { + $this->temp_dir = $_CONFIG['smarttemplate_compiled']; + } + if (!empty($_CONFIG['smarttemplate_cache'])) + { + $this->cache_dir = $_CONFIG['smarttemplate_cache']; + } + if (is_numeric($_CONFIG['cache_lifetime'])) + { + $this->cache_lifetime = $_CONFIG['cache_lifetime']; + } + if (!empty($_CONFIG['template_dir']) && is_file($_CONFIG['template_dir'] . '/' . $template_filename)) + { + $this->template_dir = $_CONFIG['template_dir']; + } + $this->tpl_file = $template_filename; + } + + // DEPRECATED METHODS + // Methods used in older parser versions, soon will be removed + function set_templatefile ($template_filename) { $this->tpl_file = $template_filename; } + function add_value ($name, $value ) { $this->assign($name, $value); } + function add_array ($name, $value ) { $this->append($name, $value); } + + + /** + * Assign Template Content + * + * Usage Example: + * $page->assign( 'TITLE', 'My Document Title' ); + * $page->assign( 'userlist', array( + * array( 'ID' => 123, 'NAME' => 'John Doe' ), + * array( 'ID' => 124, 'NAME' => 'Jack Doe' ), + * ); + * + * @access public + * @param string $name Parameter Name + * @param mixed $value Parameter Value + * @desc Assign Template Content + */ + function assign ( $name, $value = '' ) + { + if (is_array($name)) + { + foreach ($name as $k => $v) + { + $this->data[$k] = $v; + } + } + else + { + $this->data[$name] = $value; + } + } + + + /** + * Assign Template Content + * + * Usage Example: + * $page->append( 'userlist', array( 'ID' => 123, 'NAME' => 'John Doe' ) ); + * $page->append( 'userlist', array( 'ID' => 124, 'NAME' => 'Jack Doe' ) ); + * + * @access public + * @param string $name Parameter Name + * @param mixed $value Parameter Value + * @desc Assign Template Content + */ + function append ( $name, $value ) + { + if (is_array($value)) + { + $this->data[$name][] = $value; + } + elseif (!is_array($this->data[$name])) + { + $this->data[$name] .= $value; + } + } + + + /** + * Parser Wrapper + * Returns Template Output as a String + * + * @access public + * @param array $_top Content Array + * @return string Parsed Template + * @desc Output Buffer Parser Wrapper + */ + function result ( $_top = '' ) + { + ob_start(); + $this->output( $_top ); + $result = ob_get_contents(); + ob_end_clean(); + return $result; + } + + + /** + * Execute parsed Template + * Prints Parsing Results to Standard Output + * + * @access public + * @param array $_top Content Array + * @desc Execute parsed Template + */ + function output ( $_top = '' ) + { + global $_top; + + // Make sure that folder names have a trailing '/' + if (strlen($this->template_dir) && substr($this->template_dir, -1) != '/') + { + $this->template_dir .= '/'; + } + if (strlen($this->temp_dir) && substr($this->temp_dir, -1) != '/') + { + $this->temp_dir .= '/'; + } + // Prepare Template Content + if (!is_array($_top)) + { + if (strlen($_top)) + { + $this->tpl_file = $_top; + } + $_top = $this->data; + } + $_obj = &$_top; + $_stack_cnt = 0; + $_stack[$_stack_cnt++] = $_obj; + + // Check if template is already compiled + $cpl_file_name = preg_replace('/[:\/.\\\\]/', '_', $this->tpl_file); + if (strlen($cpl_file_name) > 0) + { + $this->cpl_file = $this->temp_dir . $cpl_file_name . '.php'; + $compile_template = true; + if ($this->reuse_code) + { + if (is_file($this->cpl_file)) + { + if ($this->mtime($this->cpl_file) > $this->mtime($this->template_dir . $this->tpl_file)) + { + $compile_template = false; + } + } + } + if ($compile_template) + { + if (@include_once("class.smarttemplateparser.php")) + { + $this->parser = new SmartTemplateParser($this->template_dir . $this->tpl_file); + if (!$this->parser->compile($this->cpl_file)) + { + exit( "SmartTemplate Parser Error: " . $this->parser->error ); + } + } + else + { + exit( "SmartTemplate Error: Cannot find class.smarttemplateparser.php; check SmartTemplate installation"); + } + } + // Execute Compiled Template + include($this->cpl_file); + } + else + { + exit( "SmartTemplate Error: You must set a template file name"); + } + // Delete Global Content Array in order to allow multiple use of SmartTemplate class in one script + unset ($_top); + } + + + /** + * Debug Template + * + * @access public + * @param array $_top Content Array + * @desc Debug Template + */ + function debug ( $_top = '' ) + { + // Prepare Template Content + if (!$_top) + { + $_top = $this->data; + } + if (@include_once("class.smarttemplatedebugger.php")) + { + $this->debugger = new SmartTemplateDebugger($this->template_dir . $this->tpl_file); + $this->debugger->start($_top); + } + else + { + exit( "SmartTemplate Error: Cannot find class.smarttemplatedebugger.php; check SmartTemplate installation"); + } + } + + + /** + * Start Ouput Content Buffering + * + * Usage Example: + * $page = new SmartTemplate('template.html'); + * $page->use_cache(); + * ... + * + * @access public + * @desc Output Cache + */ + function use_cache ( $key = '' ) + { + if (empty($_POST)) + { + $this->cache_filename = $this->cache_dir . 'cache_' . md5($_SERVER['REQUEST_URI'] . serialize($key)) . '.ser'; + if (($_SERVER['HTTP_CACHE_CONTROL'] != 'no-cache') && ($_SERVER['HTTP_PRAGMA'] != 'no-cache') && @is_file($this->cache_filename)) + { + if ((time() - filemtime($this->cache_filename)) < $this->cache_lifetime) + { + readfile($this->cache_filename); + exit; + } + } + ob_start( array( &$this, 'cache_callback' ) ); + } + } + + + /** + * Output Buffer Callback Function + * + * @access private + * @param string $output + * @return string $output + */ + function cache_callback ( $output ) + { + if ($hd = @fopen($this->cache_filename, 'w')) + { + fputs($hd, $output); + fclose($hd); + } + return $output; + } + + + /** + * Determine Last Filechange Date (if File exists) + * + * @access private + * @param string $filename + * @return mixed + * @desc Determine Last Filechange Date + */ + function mtime ( $filename ) + { + if (@is_file($filename)) + { + $ret = filemtime($filename); + return $ret; + } + } + } +?> \ No newline at end of file diff --git a/ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplatedebugger.php b/ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplatedebugger.php new file mode 100644 index 0000000..8a2ebf4 --- /dev/null +++ b/ThinkPHP/Library/Vendor/SmartTemplate/class.smarttemplatedebugger.php @@ -0,0 +1,456 @@ +filename = $template_filename; + + // Load Template + if ($hd = @fopen($template_filename, "r")) + { + $this->template = fread($hd, filesize($template_filename)); + fclose($hd); + } + else + { + $this->template = "SmartTemplate Debugger Error: File not found: '$template_filename'"; + } + $this->tab[0] = ''; + for ($i=1; $i < 10; $i++) { + $this->tab[$i] = str_repeat(' ', $i); + } + } + + + /** + * Main Template Parser + * + * @param string $compiled_template_filename Compiled Template Filename + * @desc Creates Compiled PHP Template + */ + function start ( $vars ) + { + $page = $this->template; + + $page = preg_replace("/()/", "\n$1\n", $page); + $page = preg_replace("/()/", "\n$1\n", $page); + $page = preg_replace("/()/", "\n$1\n", $page); + $page = preg_replace("/()/", "\n$1\n", $page); + $page = preg_replace("/()/", "\n$1\n", $page); + + $page = $this->highlight_html($page); + + $rows = explode("\n", $page); + $page_arr = array(); + $level = 0; + $blocklvl = 0; + $rowcnt = 0; + $spancnt = 0; + $offset = 22; + $lvl_block = array(); + $lvl_row = array(); + $lvl_typ = array(); + foreach ($rows as $row) + { + if ($row = trim($row)) + { + $closespan = false; + if (substr($row, $offset, 12) == '<!-- END ') + { + if ($level < 1) + { + $level++; + $error[$rowcnt] = "END Without BEGIN"; + } + elseif ($lvl_typ[$level] != 'BEGIN') + { + $error[$lvl_row[$level]] = "IF without ENDIF"; + $error[$rowcnt] = "END Without BEGIN"; + } + $blocklvl--; + $level--; + $closespan = true; + } + if (substr($row, $offset, 14) == '<!-- ENDIF ') + { + if ($level < 1) + { + $level++; + $error[$rowcnt] = "ENDIF Without IF"; + } + elseif ($lvl_typ[$level] != 'IF') + { + $error[$lvl_row[$level]] = "BEGIN without END"; + $error[$rowcnt] = "ENDIF Without IF"; + } + $closespan = true; + $level--; + } + if ($closespan) + { + $page_arr[$rowcnt-1] .= ''; + } + $this_row = $this->tab[$level] . $row; + if (substr($row, $offset, 12) == '<!-- ELSE') + { + if ($level < 1) + { + $error[$rowcnt] = "ELSE Without IF"; + } + elseif ($lvl_typ[$level] != 'IF') + { + $error[$rowcnt] = "ELSE Without IF"; + } + else + { + $this_row = $this->tab[$level-1] . $row; + } + } + if (substr($row, $offset, 14) == '<!-- BEGIN ') + { + if ($blocklvl == 0) + { + if ($lp = strpos($row, '-->')) + { + if ($blockname = trim(substr($row, $offset + 14, $lp -$offset -14))) + { + if ($nr = count($vars[$blockname])) + { + $this_row .= $this->toggleview("$nr Entries"); + } + else + { + $this_row .= $this->toggleview("Emtpy"); + } + } + } + } + else + { + $this_row .= $this->toggleview('['); + } + $blocklvl++; + $level++; + $lvl_row[$level] = $rowcnt; + $lvl_typ[$level] = 'BEGIN'; + } + elseif (substr($row, $offset, 11) == '<!-- IF ') + { + $level++; + $lvl_row[$level] = $rowcnt; + $lvl_typ[$level] = 'IF'; + $this_row .= $this->toggleview(); + } + $page_arr[] = $this_row; + $lvl_block[$rowcnt] = $blocklvl; + $rowcnt++; + } + } + if ($level > 0) + { + $error[$lvl_row[$level]] = "Block not closed"; + } + + $page = join("\n", $page_arr); + $rows = explode("\n", $page); + $cnt = count($rows); + + for ($i = 0; $i < $cnt; $i++) + { + // Add Errortext + if (isset($error)) + { + if ($err = $error[$i]) + { + $rows[$i] = '' . $rows[$i] . ' ERROR: ' . $err . '!'; + } + } + + // Replace Scalars + if (preg_match_all('/{([a-zA-Z0-9_. &;]+)}/', $rows[$i], $var)) + { + foreach ($var[1] as $tag) + { + $fulltag = $tag; + if ($delim = strpos($tag, ' > ')) + { + $tag = substr($tag, 0, $delim); + } + if (substr($tag, 0, 4) == 'top.') + { + $title = $this->tip($vars[substr($tag, 4)]); + } + elseif ($lvl_block[$i] == 0) + { + $title = $this->tip($vars[$tag]); + } + else + { + $title = '[BLOCK?]'; + } + $code = '{' . $fulltag . '}'; + $rows[$i] = str_replace('{'.$fulltag.'}', $code, $rows[$i]); + } + } + + // Replace Extensions + if (preg_match_all('/{([a-zA-Z0-9_]+):([^}]*)}/', $rows[$i], $var)) + { + foreach ($var[2] as $tmpcnt => $tag) + { + $fulltag = $tag; + if ($delim = strpos($tag, ' > ')) + { + $tag = substr($tag, 0, $delim); + } + if (strpos($tag, ',')) + { + list($tag, $addparam) = explode(',', $tag, 2); + } + $extension = $var[1][$tmpcnt]; + + if (substr($tag, 0, 4) == 'top.') + { + $title = $this->tip($vars[substr($tag, 4)]); + } + elseif ($lvl_block[$i] == 0) + { + $title = $this->tip($vars[$tag]); + } + else + { + $title = '[BLOCK?]'; + } + $code = '{' . $extension . ':' . $fulltag . '}'; + $rows[$i] = str_replace('{'.$extension . ':' . $fulltag .'}', $code, $rows[$i]); + } + } + + // 'IF nnn' Blocks + if (preg_match_all('/<!-- IF ([a-zA-Z0-9_.]+) -->/', $rows[$i], $var)) + { + foreach ($var[1] as $tag) + { + if (substr($tag, 0, 4) == 'top.') + { + $title = $this->tip($vars[substr($tag, 4)]); + } + elseif ($lvl_block[$i] == 0) + { + $title = $this->tip($vars[$tag]); + } + else + { + $title = '[BLOCK?]'; + } + $code = '<!-- IF ' . $tag . ' -->'; + $rows[$i] = str_replace("<!-- IF $tag -->", $code, $rows[$i]); + if ($title == '[NULL]') + { + $rows[$i] = str_replace('Hide', 'Show', $rows[$i]); + $rows[$i] = str_replace('block', 'none', $rows[$i]); + } + } + } + } + $page = join("
    ", $rows); + + // Print Header + echo ''; + + // Print Index + echo ''; + echo 'SmartTemplate Debugger
    '; + echo '