From ea366396380e001739f09e8d241b46a4dde29f0d Mon Sep 17 00:00:00 2001 From: Deon George Date: Fri, 9 Nov 2012 23:18:50 +1100 Subject: [PATCH] Upgraded to KH 3.3.0 --- .htaccess | 4 +- application/bootstrap.php | 39 +- includes/kohana/.travis.yml | 24 + includes/kohana/LICENSE.md | 2 +- includes/kohana/README.md | 20 +- includes/kohana/composer.json | 5 + includes/kohana/composer.lock | 413 ++ includes/kohana/composer.phar | Bin 0 -> 628852 bytes includes/kohana/install.php | 24 +- includes/kohana/modules/auth/classes/Auth.php | 3 + .../kohana/modules/auth/classes/Auth/File.php | 3 + .../{kohana/auth.php => Kohana/Auth.php} | 32 +- .../auth/file.php => Kohana/Auth/File.php} | 24 +- includes/kohana/modules/auth/classes/auth.php | 3 - .../kohana/modules/auth/classes/auth/file.php | 3 - includes/kohana/modules/auth/config/auth.php | 5 +- .../kohana/modules/auth/config/userguide.php | 23 + .../kohana/modules/auth/guide/auth/config.md | 13 + .../modules/auth/guide/auth/driver/develop.md | 79 + .../modules/auth/guide/auth/driver/file.md | 19 + .../kohana/modules/auth/guide/auth/edit.md | 0 .../kohana/modules/auth/guide/auth/index.md | 19 + .../kohana/modules/auth/guide/auth/login.md | 62 + .../kohana/modules/auth/guide/auth/menu.md | 9 +- .../modules/auth/guide/auth/register.md | 0 .../kohana/modules/auth/guide/auth/roles.md | 0 .../kohana/modules/auth/guide/auth/user.md | 0 includes/kohana/modules/cache/README.md | 12 +- .../cache/classes/{cache.php => Cache.php} | 0 .../classes/{cache/apc.php => Cache/Apc.php} | 0 .../eaccelerator.php => Cache/Arithmetic.php} | 2 +- .../classes/Cache/Exception.php} | 2 +- .../{cache/file.php => Cache/File.php} | 0 .../cache/classes/Cache/GarbageCollect.php | 3 + .../memcache.php => Cache/Memcache.php} | 0 .../memcachetag.php => Cache/MemcacheTag.php} | 0 .../{cache/sqlite.php => Cache/Sqlite.php} | 0 .../classes/Cache/Tagging.php} | 2 +- .../wincache.php => Cache/Wincache.php} | 0 .../gd.php => cache/classes/HTTP/Cache.php} | 2 +- .../{kohana/cache.php => Kohana/Cache.php} | 156 +- .../cache/apc.php => Kohana/Cache/Apc.php} | 93 +- .../cache/classes/Kohana/Cache/Arithmetic.php | 39 + .../Cache/Exception.php} | 2 +- .../cache/file.php => Kohana/Cache/File.php} | 189 +- .../Cache/GarbageCollect.php} | 2 +- .../Cache/Memcache.php} | 112 +- .../Cache/MemcacheTag.php} | 36 +- .../sqlite.php => Kohana/Cache/Sqlite.php} | 78 +- .../tagging.php => Kohana/Cache/Tagging.php} | 19 +- .../Cache/Wincache.php} | 58 +- .../cache/classes/Kohana/HTTP/Cache.php | 503 ++ .../modules/cache/classes/cache/xcache.php | 3 - .../classes/kohana/cache/eaccelerator.php | 133 - .../cache/classes/kohana/cache/xcache.php | 84 - .../kohana/modules/cache/config/cache.php | 44 +- .../kohana/modules/cache/config/userguide.php | 2 +- .../kohana/modules/cache/guide/cache.usage.md | 16 +- .../modules/cache/guide/cache/config.md | 30 +- .../kohana/modules/cache/guide/cache/index.md | 12 +- .../kohana/modules/cache/guide/cache/usage.md | 16 +- .../tests/cache/CacheBasicMethodsTest.php | 299 ++ .../modules/cache/tests/cache/CacheTest.php | 242 + .../modules/cache/tests/cache/FileTest.php | 98 + .../cache/tests/cache/KohanaCacheTest.php | 91 - .../modules/cache/tests/cache/SqliteTest.php | 44 + .../cache/tests/cache/WincacheTest.php | 39 + .../cache/tests/cache/arithmetic/ApcTest.php | 75 + .../arithmetic/CacheArithmeticMethods.php | 173 + .../tests/cache/arithmetic/MemcacheTest.php | 103 + .../tests/cache/request/client/CacheTest.php | 265 ++ .../kohana/modules/cache/tests/phpunit.xml | 5 +- .../arrcallback.php => Bench/ArrCallback.php} | 0 .../AutoLinkEmails.php} | 0 .../datespan.php => Bench/DateSpan.php} | 0 .../ExplodeLimit.php} | 0 .../gruberurl.php => Bench/GruberURL.php} | 0 .../ltrimdigits.php => Bench/LtrimDigits.php} | 0 .../mddobaseurl.php => Bench/MDDoBaseURL.php} | 0 .../MDDoImageURL.php} | 0 .../MDDoIncludeViews.php} | 0 .../StripNullBytes.php} | 0 .../Transliterate.php} | 0 .../{bench/urlsite.php => Bench/URLSite.php} | 0 .../UserFuncArray.php} | 0 .../validcolor.php => Bench/ValidColor.php} | 0 .../validurl.php => Bench/ValidURL.php} | 0 .../classes/{codebench.php => Codebench.php} | 0 .../Codebench.php} | 6 +- .../codebench.php => Kohana/Codebench.php} | 2 +- .../modules/codebench/config/userguide.php | 2 +- includes/kohana/modules/codebench/init.php | 2 +- .../modules/codebench/views/codebench.php | 4 +- .../database/classes/Config/Database.php | 12 + .../classes/Config/Database/Reader.php | 15 + .../classes/Config/Database/Writer.php | 15 + .../kohana/modules/database/classes/DB.php | 3 + .../modules/database/classes/Database.php | 3 + .../exception.php => Database/Exception.php} | 2 +- .../Expression.php} | 2 +- .../database/classes/Database/MySQL.php | 3 + .../result.php => Database/MySQL/Result.php} | 2 +- .../modules/database/classes/Database/PDO.php | 3 + .../database/classes/Database/Query.php | 3 + .../Query/Builder.php} | 2 +- .../Query/Builder/Delete.php} | 2 +- .../Query/Builder/Insert.php} | 2 +- .../Query/Builder/Join.php} | 2 +- .../Query/Builder/Select.php} | 2 +- .../Query/Builder/Update.php} | 2 +- .../Query/Builder/Where.php} | 2 +- .../result.php => Database/Result.php} | 2 +- .../cached.php => Database/Result/Cached.php} | 2 +- .../classes/Kohana/Config/Database.php | 15 + .../classes/Kohana/Config/Database/Reader.php | 66 + .../classes/Kohana/Config/Database/Writer.php | 110 + .../classes/{kohana/db.php => Kohana/DB.php} | 26 +- .../database.php => Kohana/Database.php} | 123 +- .../Database/Exception.php} | 2 +- .../classes/Kohana/Database/Expression.php | 138 + .../mysql.php => Kohana/Database/MySQL.php} | 29 +- .../Database/MySQL/Result.php} | 2 +- .../pdo.php => Kohana/Database/PDO.php} | 65 +- .../query.php => Kohana/Database/Query.php} | 68 +- .../Database/Query/Builder.php} | 83 +- .../Database/Query/Builder/Delete.php} | 16 +- .../Database/Query/Builder/Insert.php} | 24 +- .../Database/Query/Builder/Join.php} | 25 +- .../Database/Query/Builder/Select.php} | 77 +- .../Database/Query/Builder/Update.php} | 22 +- .../Database/Query/Builder/Where.php} | 54 +- .../result.php => Kohana/Database/Result.php} | 21 +- .../Database/Result/Cached.php} | 2 +- .../Model/Database.php} | 21 +- .../Session/Database.php} | 14 +- .../database.php => Model/Database.php} | 2 +- .../database/classes/Session/Database.php | 3 + .../database/classes/database/mysql.php | 3 - .../database/classes/database/query.php | 3 - .../kohana/modules/database/classes/db.php | 3 - .../classes/kohana/config/database.php | 97 - .../classes/kohana/database/expression.php | 62 - .../database/classes/session/database.php | 3 - .../modules/database/config/database.php | 11 +- .../modules/database/config/session.php | 2 +- .../modules/database/config/userguide.php | 4 +- .../modules/database/guide/database/config.md | 10 +- .../database/guide/database/examples.md | 8 +- .../modules/database/guide/database/menu.md | 2 +- .../modules/database/guide/database/query.md | 2 +- .../database/guide/database/query/builder.md | 18 +- .../query/{prepared.md => parameterized.md} | 8 +- .../database/guide/database/results.md | 4 +- .../kohana/modules/image/classes/Image.php | 3 + .../kohana/modules/image/classes/Image/GD.php | 3 + .../modules/image/classes/Image/Imagick.php | 3 + .../{kohana/image.php => Kohana/Image.php} | 120 +- .../image/gd.php => Kohana/Image/GD.php} | 91 +- .../image/classes/Kohana/Image/Imagick.php | 334 ++ .../kohana/modules/image/config/userguide.php | 4 +- .../modules/image/guide/image/examples.md | 7 + .../image/guide/image/examples/crop.md | 141 + .../image/guide/image/examples/dynamic.md | 108 + .../image/guide/image/examples/upload.md | 139 + .../kohana/modules/image/guide/image/index.md | 21 + .../kohana/modules/image/guide/image/menu.md | 5 +- .../kohana/modules/image/guide/image/using.md | 112 + .../modules/image/media/guide/image/Thumbs.db | Bin 0 -> 19968 bytes .../image/media/guide/image/crop_form.jpg | Bin 0 -> 15262 bytes .../image/media/guide/image/crop_orig.jpg | Bin 0 -> 31360 bytes .../image/media/guide/image/crop_result.jpg | Bin 0 -> 15771 bytes .../image/media/guide/image/dynamic-400.jpg | Bin 0 -> 27609 bytes .../image/media/guide/image/dynamic-600.jpg | Bin 0 -> 45517 bytes .../image/media/guide/image/upload_form.jpg | Bin 0 -> 12478 bytes .../image/media/guide/image/upload_result.jpg | Bin 0 -> 18469 bytes .../modules/image/tests/kohana/ImageTest.php | 36 + .../modules/image/tests/test_data/test_image | Bin 0 -> 3455 bytes includes/kohana/modules/minion/README.md | 62 + .../minion/classes/Kohana/Minion/CLI.php | 315 ++ .../classes/Kohana/Minion/Exception.php | 64 + .../Kohana/Minion/Exception/InvalidTask.php | 18 + .../minion/classes/Kohana/Minion/Task.php | 364 ++ .../classes/Minion/CLI.php} | 2 +- .../classes/Minion/Exception.php} | 2 +- .../classes/Minion/Exception/InvalidTask.php | 3 + .../modules/minion/classes/Minion/Task.php | 8 + .../modules/minion/classes/Task/Help.php | 28 + .../modules/minion/config/userguide.php | 13 + .../modules/minion/guide/minion/index.md | 3 + .../modules/minion/guide/minion/menu.md | 3 + .../modules/minion/guide/minion/setup.md | 32 + .../modules/minion/guide/minion/tasks.md | 71 + .../modules/minion/messages/validation.php | 5 + includes/kohana/modules/minion/minion | 4 + includes/kohana/modules/minion/miniond | 68 + .../modules/minion/tests/minion/task.php | 70 + .../minion/views/minion/error/validation.php | 10 + .../minion/views/minion/help/error.php | 7 + .../modules/minion/views/minion/help/list.php | 17 + .../modules/minion/views/minion/help/task.php | 17 + .../kohana/modules/orm/auth-schema-mysql.sql | 6 +- .../modules/orm/auth-schema-postgresql.sql | 2 +- .../kohana/modules/orm/classes/Auth/ORM.php | 3 + .../auth/orm.php => Kohana/Auth/ORM.php} | 66 +- .../{kohana/orm.php => Kohana/ORM.php} | 1257 +++-- .../ORM/Validation/Exception.php} | 82 +- .../auth/role.php => Model/Auth/Role.php} | 8 +- .../auth/user.php => Model/Auth/User.php} | 56 +- .../token.php => Model/Auth/User/Token.php} | 17 +- .../{model/role.php => Model/Role.php} | 2 +- .../{model/user.php => Model/User.php} | 2 +- .../user/token.php => Model/User/Token.php} | 2 +- includes/kohana/modules/orm/classes/ORM.php | 3 + .../Validation/Exception.php} | 2 +- .../kohana/modules/orm/classes/auth/orm.php | 3 - includes/kohana/modules/orm/classes/orm.php | 3 - .../kohana/modules/orm/config/userguide.php | 4 +- .../modules/orm/guide/orm/examples/simple.md | 8 +- .../orm/guide/orm/examples/validation.md | 4 +- .../kohana/modules/orm/guide/orm/filters.md | 25 +- includes/kohana/modules/orm/guide/orm/menu.md | 1 + .../modules/orm/guide/orm/relationships.md | 10 +- .../kohana/modules/orm/guide/orm/upgrading.md | 16 + .../kohana/modules/orm/guide/orm/using.md | 29 +- .../modules/orm/guide/orm/validation.md | 8 +- .../kohana/modules/unittest/README.markdown | 30 +- .../kohana/modules/unittest/bootstrap.php | 56 +- .../unittest/bootstrap_all_modules.php | 20 + .../Unittest/Database/TestCase.php} | 68 +- .../Unittest/Helpers.php} | 6 +- .../Unittest/TestCase.php} | 39 +- .../classes/Kohana/Unittest/TestSuite.php | 80 + .../tests.php => Kohana/Unittest/Tests.php} | 161 +- .../classes/Unittest/Database/TestCase.php | 17 + .../helpers.php => Unittest/Helpers.php} | 0 .../unittest/classes/Unittest/TestCase.php | 3 + .../unittest/classes/Unittest/TestSuite.php | 3 + .../tests.php => Unittest/Tests.php} | 0 .../unittest/classes/controller/unittest.php | 352 -- .../classes/kohana/unittest/runner.php | 313 -- .../unittest/classes/unittest/runner.php | 3 - .../unittest/classes/unittest/testcase.php | 3 - .../modules/unittest/config/unittest.php | 19 - .../modules/unittest/config/userguide.php | 22 +- .../modules/unittest/guide/unittest/index.md | 3 +- .../modules/unittest/guide/unittest/menu.md | 8 +- .../unittest/guide/unittest/testing.md | 14 +- .../guide/unittest/testing_workflows.md | 10 - includes/kohana/modules/unittest/init.php | 16 - includes/kohana/modules/unittest/tests.php | 6 +- .../modules/unittest/views/unittest/index.php | 66 - .../unittest/views/unittest/layout.php | 255 - .../unittest/views/unittest/results.php | 83 - includes/kohana/modules/userguide/README.md | 40 +- .../classes/Controller/Userguide.php | 3 + .../classes/{kodoc.php => Kodoc.php} | 0 .../{kodoc/class.php => Kodoc/Class.php} | 0 .../markdown.php => Kodoc/Markdown.php} | 0 .../{kodoc/method.php => Kodoc/Method.php} | 0 .../param.php => Kodoc/Method/Param.php} | 0 .../{kodoc/missing.php => Kodoc/Missing.php} | 0 .../property.php => Kodoc/Property.php} | 0 .../Controller/Userguide.php} | 148 +- .../{kohana/kodoc.php => Kohana/Kodoc.php} | 284 +- .../class.php => Kohana/Kodoc/Class.php} | 109 +- .../Kodoc/Markdown.php} | 49 +- .../method.php => Kohana/Kodoc/Method.php} | 4 +- .../Kodoc/Method/Param.php} | 2 +- .../missing.php => Kohana/Kodoc/Missing.php} | 0 .../Kodoc/Property.php} | 21 +- .../modules/userguide/config/userguide.php | 14 +- .../userguide/guide/userguide/adding.md | 14 + .../userguide/guide/userguide/config.md | 12 +- .../userguide/guide/userguide/contributing.md | 57 +- .../userguide/guide/userguide/markdown.md | 6 +- includes/kohana/modules/userguide/i18n/de.php | 6 - includes/kohana/modules/userguide/i18n/es.php | 6 - includes/kohana/modules/userguide/i18n/fr.php | 7 - includes/kohana/modules/userguide/i18n/he.php | 6 - includes/kohana/modules/userguide/i18n/nl.php | 6 - includes/kohana/modules/userguide/i18n/ru.php | 7 - includes/kohana/modules/userguide/i18n/zh.php | 28 - includes/kohana/modules/userguide/init.php | 28 +- .../userguide/media/guide/css/kodoc.css | 78 +- .../modules/userguide/media/guide/js/kodoc.js | 8 +- .../modules/userguide/tests/KodocTest.php | 368 ++ .../tests/userguide/ControllerTest.php | 45 + .../userguide/views/userguide/api/class.php | 51 +- .../userguide/views/userguide/api/method.php | 4 +- .../userguide/views/userguide/api/toc.php | 96 +- .../userguide/views/userguide/error.php | 2 +- .../userguide/views/userguide/template.php | 30 +- includes/kohana/system/classes/Arr.php | 3 + includes/kohana/system/classes/Config.php | 3 + .../kohana/system/classes/Config/File.php | 3 + .../kohana/system/classes/Config/Group.php | 4 + includes/kohana/system/classes/Controller.php | 3 + .../template.php => Controller/Template.php} | 2 +- includes/kohana/system/classes/Cookie.php | 3 + includes/kohana/system/classes/Date.php | 3 + includes/kohana/system/classes/Debug.php | 3 + includes/kohana/system/classes/Encrypt.php | 3 + includes/kohana/system/classes/Feed.php | 3 + includes/kohana/system/classes/File.php | 3 + includes/kohana/system/classes/Form.php | 3 + includes/kohana/system/classes/Fragment.php | 3 + includes/kohana/system/classes/HTML.php | 3 + includes/kohana/system/classes/HTTP.php | 3 + .../kohana/system/classes/HTTP/Exception.php | 3 + .../system/classes/HTTP/Exception/300.php | 3 + .../system/classes/HTTP/Exception/301.php | 3 + .../system/classes/HTTP/Exception/302.php | 3 + .../system/classes/HTTP/Exception/303.php | 3 + .../system/classes/HTTP/Exception/304.php | 3 + .../system/classes/HTTP/Exception/305.php | 3 + .../system/classes/HTTP/Exception/307.php | 3 + .../exception => HTTP/Exception}/400.php | 2 +- .../exception => HTTP/Exception}/401.php | 2 +- .../exception => HTTP/Exception}/402.php | 2 +- .../exception => HTTP/Exception}/403.php | 2 +- .../exception => HTTP/Exception}/404.php | 2 +- .../exception => HTTP/Exception}/405.php | 2 +- .../exception => HTTP/Exception}/406.php | 2 +- .../exception => HTTP/Exception}/407.php | 2 +- .../exception => HTTP/Exception}/408.php | 2 +- .../exception => HTTP/Exception}/409.php | 2 +- .../exception => HTTP/Exception}/410.php | 2 +- .../exception => HTTP/Exception}/411.php | 2 +- .../exception => HTTP/Exception}/412.php | 2 +- .../exception => HTTP/Exception}/413.php | 2 +- .../exception => HTTP/Exception}/414.php | 2 +- .../exception => HTTP/Exception}/415.php | 2 +- .../exception => HTTP/Exception}/416.php | 2 +- .../exception => HTTP/Exception}/417.php | 2 +- .../exception => HTTP/Exception}/500.php | 2 +- .../exception => HTTP/Exception}/501.php | 2 +- .../exception => HTTP/Exception}/502.php | 2 +- .../exception => HTTP/Exception}/503.php | 2 +- .../exception => HTTP/Exception}/504.php | 2 +- .../exception => HTTP/Exception}/505.php | 2 +- .../classes/HTTP/Exception/Expected.php | 3 + .../classes/HTTP/Exception/Redirect.php | 3 + .../kohana/system/classes/HTTP/Header.php | 3 + .../kohana/system/classes/HTTP/Message.php | 3 + .../kohana/system/classes/HTTP/Request.php | 3 + .../kohana/system/classes/HTTP/Response.php | 3 + includes/kohana/system/classes/I18n.php | 3 + includes/kohana/system/classes/Inflector.php | 3 + includes/kohana/system/classes/Kohana.php | 3 + .../{kohana/arr.php => Kohana/Arr.php} | 204 +- .../kohana/system/classes/Kohana/Config.php | 192 + .../system/classes/Kohana/Config/File.php | 15 + .../Config/File/Reader.php} | 38 +- .../system/classes/Kohana/Config/Group.php | 130 + .../system/classes/Kohana/Config/Reader.php | 25 + .../system/classes/Kohana/Config/Source.php | 14 + .../system/classes/Kohana/Config/Writer.php | 27 + .../system/classes/Kohana/Controller.php | 145 + .../Controller/Template.php} | 10 +- .../{kohana/cookie.php => Kohana/Cookie.php} | 20 +- .../{kohana/core.php => Kohana/Core.php} | 240 +- .../{kohana/date.php => Kohana/Date.php} | 83 +- .../{kohana/debug.php => Kohana/Debug.php} | 51 +- .../encrypt.php => Kohana/Encrypt.php} | 18 +- .../system/classes/Kohana/Exception.php | 3 + .../{kohana/feed.php => Kohana/Feed.php} | 33 +- .../{kohana/file.php => Kohana/File.php} | 26 +- .../{kohana/form.php => Kohana/Form.php} | 104 +- .../fragment.php => Kohana/Fragment.php} | 18 +- .../{kohana/html.php => Kohana/HTML.php} | 155 +- .../{kohana/http.php => Kohana/HTTP.php} | 67 +- .../system/classes/Kohana/HTTP/Exception.php | 72 + .../classes/Kohana/HTTP/Exception/300.php | 10 + .../classes/Kohana/HTTP/Exception/301.php | 10 + .../classes/Kohana/HTTP/Exception/302.php | 10 + .../classes/Kohana/HTTP/Exception/303.php | 10 + .../classes/Kohana/HTTP/Exception/304.php | 10 + .../classes/Kohana/HTTP/Exception/305.php | 41 + .../classes/Kohana/HTTP/Exception/307.php | 10 + .../HTTP/Exception}/400.php | 2 +- .../classes/Kohana/HTTP/Exception/401.php | 38 + .../HTTP/Exception}/402.php | 2 +- .../HTTP/Exception}/403.php | 2 +- .../HTTP/Exception}/404.php | 2 +- .../classes/Kohana/HTTP/Exception/405.php | 41 + .../HTTP/Exception}/406.php | 2 +- .../HTTP/Exception}/407.php | 2 +- .../HTTP/Exception}/408.php | 2 +- .../HTTP/Exception}/409.php | 2 +- .../HTTP/Exception}/410.php | 2 +- .../HTTP/Exception}/411.php | 2 +- .../HTTP/Exception}/412.php | 2 +- .../HTTP/Exception}/413.php | 2 +- .../HTTP/Exception}/414.php | 2 +- .../HTTP/Exception}/415.php | 2 +- .../HTTP/Exception}/416.php | 2 +- .../HTTP/Exception}/417.php | 2 +- .../HTTP/Exception}/500.php | 2 +- .../HTTP/Exception}/501.php | 2 +- .../HTTP/Exception}/502.php | 2 +- .../HTTP/Exception}/503.php | 2 +- .../HTTP/Exception}/504.php | 2 +- .../HTTP/Exception}/505.php | 2 +- .../Kohana/HTTP/Exception/Expected.php | 82 + .../Kohana/HTTP/Exception/Redirect.php | 51 + .../system/classes/Kohana/HTTP/Header.php | 949 ++++ .../HTTP/Message.php} | 6 +- .../request.php => Kohana/HTTP/Request.php} | 8 +- .../response.php => Kohana/HTTP/Response.php} | 6 +- .../{kohana/i18n.php => Kohana/I18n.php} | 18 +- .../inflector.php => Kohana/Inflector.php} | 30 +- .../classes/Kohana/Kohana/Exception.php | 282 ++ .../{kohana/log.php => Kohana/Log.php} | 71 +- .../log/file.php => Kohana/Log/File.php} | 13 +- .../log/stderr.php => Kohana/Log/StdErr.php} | 14 +- .../log/stdout.php => Kohana/Log/StdOut.php} | 15 +- .../log/syslog.php => Kohana/Log/Syslog.php} | 27 +- .../system/classes/Kohana/Log/Writer.php | 95 + .../{kohana/model.php => Kohana/Model.php} | 6 +- .../{kohana/num.php => Kohana/Num.php} | 16 +- .../profiler.php => Kohana/Profiler.php} | 22 +- .../request.php => Kohana/Request.php} | 748 ++- .../system/classes/Kohana/Request/Client.php | 424 ++ .../classes/Kohana/Request/Client/Curl.php | 135 + .../Kohana/Request/Client/External.php | 207 + .../classes/Kohana/Request/Client/HTTP.php | 121 + .../Kohana/Request/Client/Internal.php | 128 + .../Request/Client/Recursion/Exception.php | 10 + .../classes/Kohana/Request/Client/Stream.php | 109 + .../Request/Exception.php} | 4 +- .../response.php => Kohana/Response.php} | 290 +- .../{kohana/route.php => Kohana/Route.php} | 246 +- .../security.php => Kohana/Security.php} | 12 +- .../session.php => Kohana/Session.php} | 129 +- .../cookie.php => Kohana/Session/Cookie.php} | 12 +- .../classes/Kohana/Session/Exception.php | 11 + .../native.php => Kohana/Session/Native.php} | 18 +- .../{kohana/text.php => Kohana/Text.php} | 158 +- .../{kohana/url.php => Kohana/URL.php} | 33 +- .../{kohana/utf8.php => Kohana/UTF8.php} | 190 +- .../system/classes/Kohana/UTF8/Exception.php | 9 + .../{kohana/upload.php => Kohana/Upload.php} | 94 +- .../{kohana/valid.php => Kohana/Valid.php} | 129 +- .../validation.php => Kohana/Validation.php} | 160 +- .../classes/Kohana/Validation/Exception.php | 29 + .../{kohana/view.php => Kohana/View.php} | 71 +- .../View/Exception.php} | 4 +- includes/kohana/system/classes/Log.php | 3 + includes/kohana/system/classes/Log/File.php | 3 + includes/kohana/system/classes/Log/StdErr.php | 3 + includes/kohana/system/classes/Log/StdOut.php | 3 + includes/kohana/system/classes/Log/Syslog.php | 3 + includes/kohana/system/classes/Log/Writer.php | 3 + includes/kohana/system/classes/Model.php | 3 + includes/kohana/system/classes/Num.php | 3 + includes/kohana/system/classes/Profiler.php | 3 + includes/kohana/system/classes/Request.php | 3 + .../client.php => Request/Client.php} | 2 +- .../system/classes/Request/Client/Curl.php | 3 + .../classes/Request/Client/External.php | 3 + .../system/classes/Request/Client/HTTP.php | 3 + .../Client/Internal.php} | 2 +- .../Request/Client/Recursion/Exception.php | 10 + .../system/classes/Request/Client/Stream.php | 3 + .../system/classes/Request/Exception.php | 9 + includes/kohana/system/classes/Response.php | 3 + includes/kohana/system/classes/Route.php | 3 + includes/kohana/system/classes/Security.php | 3 + includes/kohana/system/classes/Session.php | 3 + .../kohana/system/classes/Session/Cookie.php | 3 + .../system/classes/Session/Exception.php | 9 + .../kohana/system/classes/Session/Native.php | 3 + includes/kohana/system/classes/Text.php | 3 + includes/kohana/system/classes/URL.php | 3 + includes/kohana/system/classes/UTF8.php | 3 + .../kohana/system/classes/UTF8/Exception.php | 9 + includes/kohana/system/classes/Upload.php | 3 + includes/kohana/system/classes/Valid.php | 3 + includes/kohana/system/classes/Validation.php | 3 + .../Exception.php} | 2 +- includes/kohana/system/classes/View.php | 3 + .../kohana/system/classes/View/Exception.php | 9 + includes/kohana/system/classes/arr.php | 3 - includes/kohana/system/classes/cli.php | 3 - includes/kohana/system/classes/config.php | 3 - .../kohana/system/classes/config/file.php | 3 - .../kohana/system/classes/config/reader.php | 3 - includes/kohana/system/classes/controller.php | 3 - .../kohana/system/classes/controller/rest.php | 3 - includes/kohana/system/classes/cookie.php | 3 - includes/kohana/system/classes/date.php | 3 - includes/kohana/system/classes/debug.php | 3 - includes/kohana/system/classes/encrypt.php | 3 - includes/kohana/system/classes/feed.php | 3 - includes/kohana/system/classes/file.php | 3 - includes/kohana/system/classes/form.php | 3 - includes/kohana/system/classes/fragment.php | 3 - includes/kohana/system/classes/html.php | 3 - includes/kohana/system/classes/http.php | 3 - .../kohana/system/classes/http/exception.php | 3 - .../kohana/system/classes/http/header.php | 3 - .../system/classes/http/header/value.php | 3 - .../system/classes/http/interaction.php | 3 - .../kohana/system/classes/http/request.php | 3 - .../kohana/system/classes/http/response.php | 3 - includes/kohana/system/classes/i18n.php | 3 - includes/kohana/system/classes/inflector.php | 3 - includes/kohana/system/classes/kohana.php | 3 - includes/kohana/system/classes/kohana/cli.php | 75 - .../kohana/system/classes/kohana/config.php | 157 - .../system/classes/kohana/config/reader.php | 115 - .../system/classes/kohana/controller.php | 75 - .../system/classes/kohana/controller/rest.php | 96 - .../system/classes/kohana/exception.php | 3 - .../system/classes/kohana/http/exception.php | 34 - .../classes/kohana/http/exception/401.php | 10 - .../classes/kohana/http/exception/405.php | 10 - .../system/classes/kohana/http/header.php | 310 -- .../classes/kohana/http/header/value.php | 219 - .../classes/kohana/kohana/exception.php | 206 - .../system/classes/kohana/log/writer.php | 49 - .../system/classes/kohana/request/client.php | 328 -- .../kohana/request/client/external.php | 427 -- .../kohana/request/client/internal.php | 179 - .../classes/kohana/validation/exception.php | 29 - includes/kohana/system/classes/log.php | 3 - includes/kohana/system/classes/log/file.php | 3 - includes/kohana/system/classes/log/stderr.php | 3 - includes/kohana/system/classes/log/stdout.php | 3 - includes/kohana/system/classes/log/syslog.php | 3 - includes/kohana/system/classes/log/writer.php | 3 - includes/kohana/system/classes/model.php | 3 - includes/kohana/system/classes/num.php | 3 - includes/kohana/system/classes/profiler.php | 3 - includes/kohana/system/classes/request.php | 3 - .../classes/request/client/external.php | 3 - includes/kohana/system/classes/response.php | 3 - includes/kohana/system/classes/route.php | 3 - includes/kohana/system/classes/security.php | 3 - includes/kohana/system/classes/session.php | 3 - .../kohana/system/classes/session/cookie.php | 3 - .../kohana/system/classes/session/native.php | 3 - includes/kohana/system/classes/text.php | 3 - includes/kohana/system/classes/upload.php | 3 - includes/kohana/system/classes/url.php | 3 - includes/kohana/system/classes/utf8.php | 3 - includes/kohana/system/classes/valid.php | 3 - includes/kohana/system/classes/validation.php | 3 - includes/kohana/system/classes/view.php | 3 - .../kohana/system/config/credit_cards.php | 2 +- includes/kohana/system/config/curl.php | 3 +- includes/kohana/system/config/encrypt.php | 2 +- includes/kohana/system/config/inflector.php | 57 +- includes/kohana/system/config/mimes.php | 5 +- includes/kohana/system/config/session.php | 2 +- includes/kohana/system/config/user_agents.php | 20 +- includes/kohana/system/config/userguide.php | 4 +- .../kohana/system/guide/kohana/autoloading.md | 17 +- .../kohana/system/guide/kohana/bootstrap.md | 22 +- includes/kohana/system/guide/kohana/config.md | 193 + .../kohana/system/guide/kohana/conventions.md | 17 +- includes/kohana/system/guide/kohana/errors.md | 44 +- .../kohana/system/guide/kohana/extension.md | 18 +- includes/kohana/system/guide/kohana/files.md | 10 +- .../system/guide/kohana/files/classes.md | 4 +- .../system/guide/kohana/files/config.md | 34 +- .../system/guide/kohana/files/messages.md | 2 +- includes/kohana/system/guide/kohana/flow.md | 4 +- includes/kohana/system/guide/kohana/index.md | 10 +- .../kohana/system/guide/kohana/install.md | 23 +- includes/kohana/system/guide/kohana/menu.md | 4 +- includes/kohana/system/guide/kohana/mvc.md | 2 +- .../system/guide/kohana/mvc/controllers.md | 37 +- .../kohana/system/guide/kohana/requests.md | 87 +- .../kohana/system/guide/kohana/routing.md | 84 +- .../system/guide/kohana/security/deploying.md | 30 - .../guide/kohana/security/encryption.md | 108 +- .../guide/kohana/security/validation.md | 72 +- includes/kohana/system/guide/kohana/tips.md | 8 + .../guide/kohana/tutorials/clean-urls.md | 15 +- .../guide/kohana/tutorials/error-pages.md | 219 +- .../system/guide/kohana/tutorials/git.md | 20 +- .../guide/kohana/tutorials/hello-world.md | 4 +- .../guide/kohana/tutorials/library-kohana.md | 219 + .../guide/kohana/tutorials/sharing-kohana.md | 8 +- .../kohana/system/guide/kohana/upgrading.md | 111 +- includes/kohana/system/i18n/en.php | 2 +- includes/kohana/system/i18n/es.php | 2 +- includes/kohana/system/i18n/fr.php | 2 +- .../tests/validation/error_type_check.php | 7 + .../kohana/system/messages/validation.php | 6 +- .../kohana/system/tests/kohana/ArrTest.php | 174 +- .../kohana/system/tests/kohana/CLITest.php | 168 - .../tests/kohana/Config/File/ReaderTest.php | 94 + .../system/tests/kohana/Config/GroupTest.php | 192 + .../kohana/system/tests/kohana/ConfigTest.php | 331 +- .../kohana/system/tests/kohana/CookieTest.php | 21 +- .../kohana/system/tests/kohana/CoreTest.php | 51 +- .../kohana/system/tests/kohana/DateTest.php | 20 +- .../kohana/system/tests/kohana/DebugTest.php | 37 +- .../system/tests/kohana/ExceptionTest.php | 11 +- .../kohana/system/tests/kohana/FeedTest.php | 16 +- .../kohana/system/tests/kohana/FileTest.php | 10 +- .../kohana/system/tests/kohana/FormTest.php | 41 +- .../kohana/system/tests/kohana/HTMLTest.php | 125 +- .../kohana/system/tests/kohana/HTTPTest.php | 87 + .../tests/kohana/Http/Header/ValueTest.php | 164 - .../system/tests/kohana/Http/HeaderTest.php | 1526 +++++- .../kohana/system/tests/kohana/I18nTest.php | 34 +- .../system/tests/kohana/InflectorTest.php | 11 +- .../kohana/system/tests/kohana/LogTest.php | 5 +- .../kohana/system/tests/kohana/ModelTest.php | 5 +- .../kohana/system/tests/kohana/NumTest.php | 15 +- .../system/tests/kohana/RequestTest.php | 423 +- .../system/tests/kohana/ResponseTest.php | 62 +- .../kohana/system/tests/kohana/RouteTest.php | 239 +- .../system/tests/kohana/SecurityTest.php | 19 +- .../system/tests/kohana/SessionTest.php | 7 +- .../kohana/system/tests/kohana/TextTest.php | 46 +- .../kohana/system/tests/kohana/URLTest.php | 13 +- .../kohana/system/tests/kohana/UTF8Test.php | 7 +- .../kohana/system/tests/kohana/UploadTest.php | 7 +- .../kohana/system/tests/kohana/ValidTest.php | 191 +- .../system/tests/kohana/ValidationTest.php | 197 +- .../kohana/system/tests/kohana/ViewTest.php | 15 +- .../tests/kohana/request/ClientTest.php | 488 ++ .../kohana/request/client/ExternalTest.php | 191 + .../kohana/request/client/InternalTest.php | 68 + .../tests/kohana/request/client/internal.php | 89 - .../tests/test_data/callback_routes.php | 32 +- includes/kohana/system/utf8/from_unicode.php | 16 +- includes/kohana/system/utf8/ltrim.php | 4 +- includes/kohana/system/utf8/ord.php | 24 +- includes/kohana/system/utf8/rtrim.php | 4 +- includes/kohana/system/utf8/str_ireplace.php | 4 +- includes/kohana/system/utf8/str_pad.php | 8 +- includes/kohana/system/utf8/str_split.php | 4 +- includes/kohana/system/utf8/strcasecmp.php | 4 +- includes/kohana/system/utf8/strcspn.php | 4 +- includes/kohana/system/utf8/stristr.php | 4 +- includes/kohana/system/utf8/strlen.php | 4 +- includes/kohana/system/utf8/strpos.php | 4 +- includes/kohana/system/utf8/strrev.php | 4 +- includes/kohana/system/utf8/strrpos.php | 4 +- includes/kohana/system/utf8/strspn.php | 4 +- includes/kohana/system/utf8/strtolower.php | 4 +- includes/kohana/system/utf8/strtoupper.php | 4 +- includes/kohana/system/utf8/substr.php | 4 +- .../kohana/system/utf8/substr_replace.php | 4 +- includes/kohana/system/utf8/to_unicode.php | 9 +- .../system/utf8/transliterate_to_ascii.php | 4 +- includes/kohana/system/utf8/trim.php | 4 +- includes/kohana/system/utf8/ucfirst.php | 4 +- includes/kohana/system/utf8/ucwords.php | 4 +- includes/kohana/system/views/kohana/error.php | 2 +- .../system/views/kohana/generate_logo.php | 2 +- includes/kohana/system/views/kohana/logo.php | 2 +- .../kohana/system/views/profiler/stats.php | 2 +- includes/kohana/vendor/autoload.php | 7 + includes/kohana/vendor/bin/phpunit | 7 + .../kohana/vendor/composer/ClassLoader.php | 207 + .../vendor/composer/autoload_classmap.php | 351 ++ .../vendor/composer/autoload_namespaces.php | 10 + .../kohana/vendor/composer/autoload_real.php | 39 + .../kohana/vendor/composer/include_paths.php | 17 + .../kohana/vendor/composer/installed.json | 410 ++ .../phpunit/php-code-coverage/CONTRIBUTING.md | 5 + .../php-code-coverage/ChangeLog.markdown | 46 + .../vendor/phpunit/php-code-coverage/LICENSE | 33 + .../php-code-coverage/PHP/CodeCoverage.php | 796 ++++ .../PHP/CodeCoverage/Autoload.php | 90 + .../PHP/CodeCoverage/Autoload.php.in | 70 + .../PHP/CodeCoverage/Driver.php | 70 + .../PHP/CodeCoverage/Driver/Xdebug.php | 97 + .../PHP/CodeCoverage/Exception.php | 59 + .../PHP/CodeCoverage/Filter.php | 343 ++ .../PHP/CodeCoverage/Report/Clover.php | 346 ++ .../PHP/CodeCoverage/Report/Factory.php | 280 ++ .../PHP/CodeCoverage/Report/HTML.php | 221 + .../PHP/CodeCoverage/Report/HTML/Renderer.php | 269 ++ .../Report/HTML/Renderer/Dashboard.php | 256 + .../Report/HTML/Renderer/Directory.php | 132 + .../Report/HTML/Renderer/File.php | 583 +++ .../Renderer/Template/coverage_bar.html.dist | 3 + .../Template/css/bootstrap-responsive.min.css | 9 + .../Renderer/Template/css/bootstrap.min.css | 9 + .../HTML/Renderer/Template/css/style.css | 76 + .../Renderer/Template/dashboard.html.dist | 117 + .../Renderer/Template/directory.html.dist | 58 + .../Template/directory_item.html.dist | 13 + .../HTML/Renderer/Template/file.html.dist | 65 + .../Renderer/Template/file_item.html.dist | 14 + .../img/glyphicons-halflings-white.png | Bin 0 -> 8777 bytes .../Template/img/glyphicons-halflings.png | Bin 0 -> 12799 bytes .../Renderer/Template/js/bootstrap.min.js | 6 + .../HTML/Renderer/Template/js/highcharts.js | 245 + .../HTML/Renderer/Template/js/jquery.min.js | 2 + .../Renderer/Template/method_item.html.dist | 11 + .../PHP/CodeCoverage/Report/Node.php | 380 ++ .../CodeCoverage/Report/Node/Directory.php | 512 ++ .../PHP/CodeCoverage/Report/Node/File.php | 721 +++ .../PHP/CodeCoverage/Report/Node/Iterator.php | 148 + .../PHP/CodeCoverage/Report/PHP.php | 74 + .../PHP/CodeCoverage/Report/Text.php | 278 ++ .../PHP/CodeCoverage/Util.php | 268 ++ .../Util/InvalidArgumentHelper.php | 80 + .../PHP/CodeCoverage/Version.php | 92 + .../phpunit/php-code-coverage/README.markdown | 43 + .../Tests/PHP/CodeCoverage/FilterTest.php | 298 ++ .../PHP/CodeCoverage/Report/CloverTest.php | 96 + .../PHP/CodeCoverage/Report/FactoryTest.php | 263 ++ .../Tests/PHP/CodeCoverage/UtilTest.php | 194 + .../Tests/PHP/CodeCoverageTest.php | 499 ++ .../php-code-coverage/Tests/TestCase.php | 266 ++ .../Tests/_files/BankAccount-clover.xml | 26 + .../Tests/_files/BankAccount.php | 33 + .../Tests/_files/BankAccountTest.php | 70 + .../_files/CoverageClassExtendedTest.php | 12 + .../Tests/_files/CoverageClassTest.php | 12 + .../Tests/_files/CoverageFunctionTest.php | 11 + .../CoverageMethodOneLineAnnotationTest.php | 12 + .../Tests/_files/CoverageMethodTest.php | 12 + .../Tests/_files/CoverageNoneTest.php | 9 + .../Tests/_files/CoverageNotPrivateTest.php | 12 + .../Tests/_files/CoverageNotProtectedTest.php | 12 + .../Tests/_files/CoverageNotPublicTest.php | 12 + .../Tests/_files/CoverageNothingTest.php | 13 + .../Tests/_files/CoveragePrivateTest.php | 12 + .../Tests/_files/CoverageProtectedTest.php | 12 + .../Tests/_files/CoveragePublicTest.php | 12 + .../CoverageTwoDefaultClassAnnotations.php | 19 + .../Tests/_files/CoveredClass.php | 36 + .../Tests/_files/CoveredFunction.php | 4 + .../NamespaceCoverageClassExtendedTest.php | 12 + .../_files/NamespaceCoverageClassTest.php | 12 + ...NamespaceCoverageCoversClassPublicTest.php | 16 + .../NamespaceCoverageCoversClassTest.php | 21 + .../_files/NamespaceCoverageMethodTest.php | 12 + .../NamespaceCoverageNotPrivateTest.php | 12 + .../NamespaceCoverageNotProtectedTest.php | 12 + .../_files/NamespaceCoverageNotPublicTest.php | 12 + .../_files/NamespaceCoveragePrivateTest.php | 12 + .../_files/NamespaceCoverageProtectedTest.php | 12 + .../_files/NamespaceCoveragePublicTest.php | 12 + .../Tests/_files/NamespaceCoveredClass.php | 38 + .../_files/NotExistingCoveredElementTest.php | 24 + .../Tests/_files/ignored-lines-clover.xml | 17 + .../Tests/_files/source_with_ignore.php | 38 + .../Tests/_files/source_with_namespace.php | 20 + .../source_with_oneline_annotations.php | 13 + .../Tests/_files/source_without_ignore.php | 4 + .../Tests/_files/source_without_namespace.php | 18 + .../phpunit/php-code-coverage/build.xml | 162 + .../ControlSignatureSniff.php | 22 + .../Whitespace/ConcatenationSpacingSniff.php | 22 + .../php-code-coverage/build/PHPCS/ruleset.xml | 35 + .../phpunit/php-code-coverage/build/phpmd.xml | 27 + .../phpunit/php-code-coverage/composer.json | 41 + .../phpunit/php-code-coverage/package.xml | 133 + .../php-code-coverage/phpunit.xml.dist | 29 + .../php-code-coverage/scripts/auto_append.php | 5 + .../scripts/auto_prepend.php | 10 + .../php-file-iterator/ChangeLog.markdown | 26 + .../php-file-iterator/File/Iterator.php | 196 + .../File/Iterator/Autoload.php | 66 + .../File/Iterator/Autoload.php.in | 64 + .../File/Iterator/Facade.php | 161 + .../File/Iterator/Factory.php | 120 + .../vendor/phpunit/php-file-iterator/LICENSE | 33 + .../phpunit/php-file-iterator/README.markdown | 23 + .../phpunit/php-file-iterator/build.xml | 161 + .../ControlSignatureSniff.php | 22 + .../Whitespace/ConcatenationSpacingSniff.php | 22 + .../php-file-iterator/build/PHPCS/ruleset.xml | 35 + .../phpunit/php-file-iterator/build/phpmd.xml | 27 + .../phpunit/php-file-iterator/composer.json | 33 + .../php-file-iterator/package-composer.json | 19 + .../phpunit/php-file-iterator/package.xml | 65 + .../php-text-template/ChangeLog.markdown | 19 + .../vendor/phpunit/php-text-template/LICENSE | 33 + .../phpunit/php-text-template/README.markdown | 23 + .../php-text-template/Text/Template.php | 153 + .../Text/Template/Autoload.php | 65 + .../Text/Template/Autoload.php.in | 65 + .../phpunit/php-text-template/build.xml | 161 + .../ControlSignatureSniff.php | 22 + .../Whitespace/ConcatenationSpacingSniff.php | 22 + .../php-text-template/build/PHPCS/ruleset.xml | 35 + .../phpunit/php-text-template/build/phpmd.xml | 27 + .../phpunit/php-text-template/composer.json | 32 + .../php-text-template/package-composer.json | 19 + .../phpunit/php-text-template/package.xml | 59 + .../phpunit/php-timer/ChangeLog.markdown | 29 + .../kohana/vendor/phpunit/php-timer/LICENSE | 33 + .../vendor/phpunit/php-timer/PHP/Timer.php | 159 + .../phpunit/php-timer/PHP/Timer/Autoload.php | 66 + .../php-timer/PHP/Timer/Autoload.php.in | 66 + .../vendor/phpunit/php-timer/README.markdown | 23 + .../phpunit/php-timer/Tests/TimerTest.php | 108 + .../kohana/vendor/phpunit/php-timer/build.xml | 160 + .../ControlSignatureSniff.php | 22 + .../Whitespace/ConcatenationSpacingSniff.php | 22 + .../phpunit/php-timer/build/PHPCS/ruleset.xml | 35 + .../vendor/phpunit/php-timer/build/phpmd.xml | 27 + .../vendor/phpunit/php-timer/composer.json | 32 + .../phpunit/php-timer/package-composer.json | 19 + .../vendor/phpunit/php-timer/package.xml | 59 + .../vendor/phpunit/php-timer/phpunit.xml.dist | 26 + .../php-token-stream/ChangeLog.markdown | 35 + .../vendor/phpunit/php-token-stream/LICENSE | 33 + .../phpunit/php-token-stream/PHP/Token.php | 717 +++ .../php-token-stream/PHP/Token/Stream.php | 568 +++ .../PHP/Token/Stream/Autoload.php | 226 + .../PHP/Token/Stream/Autoload.php.in | 65 + .../PHP/Token/Stream/CachingFactory.php | 85 + .../phpunit/php-token-stream/README.markdown | 23 + .../Tests/Token/ClassTest.php | 122 + .../Tests/Token/FunctionTest.php | 160 + .../Tests/Token/IncludeTest.php | 117 + .../Tests/Token/InterfaceTest.php | 236 + .../Tests/Token/NamespaceTest.php | 124 + .../php-token-stream/Tests/TokenTest.php | 85 + .../_files/classExtendsNamespacedClass.php | 10 + .../Tests/_files/classInNamespace.php | 6 + .../Tests/_files/classInScopedNamespace.php | 9 + .../php-token-stream/Tests/_files/issue19.php | 3 + ...tipleNamespacesWithOneClassUsingBraces.php | 12 + ...espacesWithOneClassUsingNonBraceSyntax.php | 14 + .../php-token-stream/Tests/_files/source.php | 32 + .../php-token-stream/Tests/_files/source2.php | 6 + .../php-token-stream/Tests/_files/source3.php | 14 + .../php-token-stream/Tests/_files/source4.php | 30 + .../vendor/phpunit/php-token-stream/build.xml | 162 + .../ControlSignatureSniff.php | 22 + .../Whitespace/ConcatenationSpacingSniff.php | 22 + .../php-token-stream/build/PHPCS/ruleset.xml | 35 + .../phpunit/php-token-stream/build/phpmd.xml | 27 + .../phpunit/php-token-stream/composer.json | 33 + .../php-token-stream/package-composer.json | 19 + .../phpunit/php-token-stream/package.xml | 70 + .../phpunit/php-token-stream/phpunit.xml.dist | 27 + .../phpunit-mock-objects/CONTRIBUTING.md | 5 + .../phpunit-mock-objects/ChangeLog.markdown | 17 + .../phpunit/phpunit-mock-objects/LICENSE | 33 + .../PHPUnit/Framework/MockObject/Autoload.php | 100 + .../Framework/MockObject/Autoload.php.in | 65 + .../Framework/MockObject/Builder/Identity.php | 70 + .../MockObject/Builder/InvocationMocker.php | 193 + .../Framework/MockObject/Builder/Match.php | 66 + .../MockObject/Builder/MethodNameMatch.php | 68 + .../MockObject/Builder/Namespace.php | 79 + .../MockObject/Builder/ParametersMatch.php | 89 + .../Framework/MockObject/Builder/Stub.php | 66 + .../Framework/MockObject/Generator.php | 811 ++++ .../Generator/mocked_class.tpl.dist | 60 + .../Generator/mocked_clone.tpl.dist | 5 + .../Generator/mocked_object_method.tpl.dist | 22 + .../Generator/mocked_static_method.tpl.dist | 22 + .../MockObject/Generator/trait_class.tpl.dist | 4 + .../Generator/unmocked_clone.tpl.dist | 6 + .../MockObject/Generator/wsdl_class.tpl.dist | 9 + .../MockObject/Generator/wsdl_method.tpl.dist | 4 + .../Framework/MockObject/Invocation.php | 58 + .../MockObject/Invocation/Object.php | 75 + .../MockObject/Invocation/Static.php | 191 + .../Framework/MockObject/InvocationMocker.php | 201 + .../Framework/MockObject/Invokable.php | 79 + .../PHPUnit/Framework/MockObject/Matcher.php | 308 ++ .../MockObject/Matcher/AnyInvokedCount.php | 72 + .../MockObject/Matcher/AnyParameters.php | 74 + .../MockObject/Matcher/Invocation.php | 88 + .../MockObject/Matcher/InvokedAtIndex.php | 127 + .../MockObject/Matcher/InvokedAtLeastOnce.php | 85 + .../MockObject/Matcher/InvokedCount.php | 143 + .../MockObject/Matcher/InvokedRecorder.php | 107 + .../MockObject/Matcher/MethodName.php | 102 + .../MockObject/Matcher/Parameters.php | 160 + .../Matcher/StatelessInvocation.php | 96 + .../Framework/MockObject/MockBuilder.php | 291 ++ .../Framework/MockObject/MockObject.php | 94 + .../PHPUnit/Framework/MockObject/Stub.php | 71 + .../MockObject/Stub/ConsecutiveCalls.php | 87 + .../Framework/MockObject/Stub/Exception.php | 80 + .../MockObject/Stub/MatcherCollection.php | 66 + .../Framework/MockObject/Stub/Return.php | 78 + .../MockObject/Stub/ReturnArgument.php | 78 + .../MockObject/Stub/ReturnCallback.php | 94 + .../Framework/MockObject/Stub/ReturnSelf.php | 76 + .../MockObject/Stub/ReturnValueMap.php | 87 + .../Framework/MockObject/Verifiable.php | 65 + .../Tests/GeneratorTest.php | 89 + .../Tests/MockBuilderTest.php | 152 + .../MockObject/Invocation/ObjectTest.php | 82 + .../MockObject/Invocation/StaticTest.php | 52 + .../Tests/MockObject/class.phpt | 137 + .../MockObject/class_call_parent_clone.phpt | 91 + .../class_call_parent_constructor.phpt | 90 + .../class_dont_call_parent_clone.phpt | 90 + .../class_dont_call_parent_constructor.phpt | 90 + ...ing_interface_call_parent_constructor.phpt | 95 + ...nterface_dont_call_parent_constructor.phpt | 95 + .../Tests/MockObject/class_partial.phpt | 116 + .../Tests/MockObject/interface.phpt | 110 + .../invocation_object_clone_object.phpt | 139 + .../invocation_static_clone_object.phpt | 139 + .../Tests/MockObject/namespaced_class.phpt | 140 + .../namespaced_class_call_parent_clone.phpt | 93 + ...espaced_class_call_parent_constructor.phpt | 92 + ...mespaced_class_dont_call_parent_clone.phpt | 92 + ...ed_class_dont_call_parent_constructor.phpt | 92 + ...ing_interface_call_parent_constructor.phpt | 97 + ...nterface_dont_call_parent_constructor.phpt | 97 + .../MockObject/namespaced_class_partial.phpt | 118 + .../MockObject/namespaced_interface.phpt | 112 + .../Tests/MockObject/nonexistent_class.phpt | 87 + .../nonexistent_class_with_namespace.phpt | 95 + ...ith_namespace_starting_with_separator.phpt | 95 + .../Tests/MockObject/wsdl_class.phpt | 36 + .../MockObject/wsdl_class_namespace.phpt | 38 + .../Tests/MockObject/wsdl_class_partial.phpt | 29 + .../Tests/MockObjectTest.php | 552 +++ .../Tests/_files/AbstractMockTestClass.php | 5 + .../Tests/_files/AnInterface.php | 5 + .../Tests/_files/FunctionCallback.php | 9 + .../Tests/_files/GoogleSearch.wsdl | 198 + .../Tests/_files/MethodCallback.php | 21 + .../Tests/_files/Mockable.php | 28 + .../Tests/_files/PartialMockTestClass.php | 18 + .../Tests/_files/SomeClass.php | 13 + .../Tests/_files/StaticMockTestClass.php | 12 + .../phpunit/phpunit-mock-objects/build.xml | 162 + .../ControlSignatureSniff.php | 22 + .../Whitespace/ConcatenationSpacingSniff.php | 22 + .../build/PHPCS/ruleset.xml | 35 + .../phpunit-mock-objects/build/phpmd.xml | 27 + .../phpunit-mock-objects/composer.json | 41 + .../package-composer.json | 19 + .../phpunit/phpunit-mock-objects/package.xml | 199 + .../phpunit-mock-objects/phpunit.xml.dist | 31 + .../vendor/phpunit/phpunit/CONTRIBUTING.md | 55 + .../vendor/phpunit/phpunit/ChangeLog.md | 82 + .../kohana/vendor/phpunit/phpunit/LICENSE | 33 + .../phpunit/phpunit/PHPUnit/Autoload.php | 208 + .../phpunit/phpunit/PHPUnit/Autoload.php.in | 91 + .../PHPUnit/Extensions/GroupTestSuite.php | 99 + .../PHPUnit/Extensions/PhptTestCase.php | 269 ++ .../Extensions/PhptTestCase/Logger.php | 62 + .../PHPUnit/Extensions/PhptTestSuite.php | 82 + .../PHPUnit/Extensions/RepeatedTest.php | 155 + .../PHPUnit/Extensions/TestDecorator.php | 149 + .../PHPUnit/Extensions/TicketListener.php | 224 + .../phpunit/PHPUnit/Framework/Assert.php | 2832 +++++++++++ .../PHPUnit/Framework/Assert/Functions.php | 1972 ++++++++ .../PHPUnit/Framework/Assert/Functions.php.in | 44 + .../Framework/AssertionFailedError.php | 68 + .../phpunit/PHPUnit/Framework/Comparator.php | 97 + .../PHPUnit/Framework/Comparator/Array.php | 177 + .../Framework/Comparator/DOMDocument.php | 114 + .../PHPUnit/Framework/Comparator/Double.php | 101 + .../Framework/Comparator/Exception.php | 92 + .../Framework/Comparator/MockObject.php | 86 + .../PHPUnit/Framework/Comparator/Numeric.php | 116 + .../PHPUnit/Framework/Comparator/Object.php | 145 + .../PHPUnit/Framework/Comparator/Resource.php | 97 + .../PHPUnit/Framework/Comparator/Scalar.php | 136 + .../Framework/Comparator/SplObjectStorage.php | 114 + .../PHPUnit/Framework/Comparator/Type.php | 105 + .../PHPUnit/Framework/ComparatorFactory.php | 156 + .../PHPUnit/Framework/ComparisonFailure.php | 166 + .../phpunit/PHPUnit/Framework/Constraint.php | 180 + .../PHPUnit/Framework/Constraint/And.php | 164 + .../Framework/Constraint/ArrayHasKey.php | 114 + .../Framework/Constraint/Attribute.php | 129 + .../PHPUnit/Framework/Constraint/Callback.php | 116 + .../Constraint/ClassHasAttribute.php | 124 + .../Constraint/ClassHasStaticAttribute.php | 98 + .../Framework/Constraint/Composite.php | 114 + .../PHPUnit/Framework/Constraint/Count.php | 128 + .../Framework/Constraint/Exception.php | 129 + .../Framework/Constraint/ExceptionCode.php | 109 + .../Framework/Constraint/ExceptionMessage.php | 109 + .../Framework/Constraint/FileExists.php | 102 + .../Framework/Constraint/GreaterThan.php | 96 + .../Framework/Constraint/IsAnything.php | 102 + .../PHPUnit/Framework/Constraint/IsEmpty.php | 104 + .../PHPUnit/Framework/Constraint/IsEqual.php | 215 + .../PHPUnit/Framework/Constraint/IsFalse.php | 82 + .../Framework/Constraint/IsIdentical.php | 172 + .../Framework/Constraint/IsInstanceOf.php | 121 + .../PHPUnit/Framework/Constraint/IsNull.php | 82 + .../PHPUnit/Framework/Constraint/IsTrue.php | 82 + .../PHPUnit/Framework/Constraint/IsType.php | 190 + .../Framework/Constraint/JsonMatches.php | 126 + .../JsonMatches/ErrorMessageProvider.php | 106 + .../PHPUnit/Framework/Constraint/LessThan.php | 96 + .../PHPUnit/Framework/Constraint/Not.php | 203 + .../Constraint/ObjectHasAttribute.php | 77 + .../PHPUnit/Framework/Constraint/Or.php | 157 + .../Framework/Constraint/PCREMatch.php | 105 + .../PHPUnit/Framework/Constraint/SameSize.php | 73 + .../Framework/Constraint/StringContains.php | 122 + .../Framework/Constraint/StringEndsWith.php | 96 + .../Framework/Constraint/StringMatches.php | 134 + .../Framework/Constraint/StringStartsWith.php | 96 + .../Constraint/TraversableContains.php | 152 + .../Constraint/TraversableContainsOnly.php | 135 + .../PHPUnit/Framework/Constraint/Xor.php | 162 + .../phpunit/PHPUnit/Framework/Error.php | 75 + .../PHPUnit/Framework/Error/Deprecated.php | 65 + .../PHPUnit/Framework/Error/Notice.php | 65 + .../PHPUnit/Framework/Error/Warning.php | 65 + .../phpunit/PHPUnit/Framework/Exception.php | 59 + .../Framework/ExpectationFailedException.php | 82 + .../PHPUnit/Framework/IncompleteTest.php | 60 + .../PHPUnit/Framework/IncompleteTestError.php | 60 + .../phpunit/PHPUnit/Framework/OutputError.php | 60 + .../Framework/Process/TestCaseMethod.tpl.dist | 51 + .../PHPUnit/Framework/SelfDescribing.php | 65 + .../phpunit/PHPUnit/Framework/SkippedTest.php | 59 + .../PHPUnit/Framework/SkippedTestError.php | 60 + .../Framework/SkippedTestSuiteError.php | 60 + .../PHPUnit/Framework/SyntheticError.php | 121 + .../phpunit/PHPUnit/Framework/Test.php | 66 + .../phpunit/PHPUnit/Framework/TestCase.php | 1880 ++++++++ .../phpunit/PHPUnit/Framework/TestFailure.php | 179 + .../PHPUnit/Framework/TestListener.php | 126 + .../phpunit/PHPUnit/Framework/TestResult.php | 956 ++++ .../phpunit/PHPUnit/Framework/TestSuite.php | 950 ++++ .../Framework/TestSuite/DataProvider.php | 70 + .../phpunit/PHPUnit/Framework/Warning.php | 125 + .../phpunit/PHPUnit/Runner/BaseTestRunner.php | 189 + .../Runner/StandardTestSuiteLoader.php | 153 + .../PHPUnit/Runner/TestSuiteLoader.php | 71 + .../phpunit/PHPUnit/Runner/Version.php | 100 + .../phpunit/PHPUnit/TextUI/Command.php | 897 ++++ .../phpunit/PHPUnit/TextUI/ResultPrinter.php | 664 +++ .../phpunit/PHPUnit/TextUI/TestRunner.php | 814 ++++ .../phpunit/phpunit/PHPUnit/Util/Class.php | 363 ++ .../phpunit/PHPUnit/Util/Configuration.php | 1026 ++++ .../PHPUnit/Util/DeprecatedFeature.php | 102 + .../PHPUnit/Util/DeprecatedFeature/Logger.php | 201 + .../phpunit/phpunit/PHPUnit/Util/Diff.php | 292 ++ .../phpunit/PHPUnit/Util/ErrorHandler.php | 132 + .../phpunit/PHPUnit/Util/Fileloader.php | 107 + .../phpunit/PHPUnit/Util/Filesystem.php | 81 + .../phpunit/phpunit/PHPUnit/Util/Filter.php | 146 + .../phpunit/phpunit/PHPUnit/Util/Getopt.php | 207 + .../phpunit/PHPUnit/Util/GlobalState.php | 427 ++ .../PHPUnit/Util/InvalidArgumentHelper.php | 80 + .../phpunit/phpunit/PHPUnit/Util/Log/JSON.php | 254 + .../phpunit/PHPUnit/Util/Log/JUnit.php | 482 ++ .../phpunit/phpunit/PHPUnit/Util/Log/TAP.php | 255 + .../phpunit/phpunit/PHPUnit/Util/PHP.php | 332 ++ .../phpunit/PHPUnit/Util/PHP/Default.php | 67 + .../phpunit/PHPUnit/Util/PHP/Windows.php | 90 + .../phpunit/phpunit/PHPUnit/Util/Printer.php | 208 + .../phpunit/phpunit/PHPUnit/Util/String.php | 118 + .../phpunit/phpunit/PHPUnit/Util/Test.php | 601 +++ .../PHPUnit/Util/TestDox/NamePrettifier.php | 177 + .../PHPUnit/Util/TestDox/ResultPrinter.php | 347 ++ .../Util/TestDox/ResultPrinter/HTML.php | 123 + .../Util/TestDox/ResultPrinter/Text.php | 95 + .../PHPUnit/Util/TestSuiteIterator.php | 148 + .../phpunit/phpunit/PHPUnit/Util/Type.php | 303 ++ .../phpunit/phpunit/PHPUnit/Util/XML.php | 916 ++++ .../kohana/vendor/phpunit/phpunit/README.md | 71 + .../Tests/Extensions/RepeatedTestTest.php | 108 + .../phpunit/Tests/Framework/AssertTest.php | 4158 +++++++++++++++++ .../Tests/Framework/ComparatorTest.php | 159 + .../JsonMatches/ErrorMessageProviderTest.php | 122 + .../Framework/Constraint/JsonMatchesTest.php | 90 + .../Tests/Framework/ConstraintTest.php | 3554 ++++++++++++++ .../phpunit/Tests/Framework/SuiteTest.php | 185 + .../phpunit/Tests/Framework/TestCaseTest.php | 439 ++ .../Tests/Framework/TestImplementorTest.php | 79 + .../Tests/Framework/TestListenerTest.php | 145 + .../phpunit/Tests/Regression/1021.phpt | 19 + .../Tests/Regression/1021/Issue1021Test.php | 23 + .../phpunit/phpunit/Tests/Regression/523.phpt | 19 + .../Tests/Regression/523/Issue523Test.php | 13 + .../phpunit/phpunit/Tests/Regression/578.phpt | 37 + .../Tests/Regression/578/Issue578Test.php | 20 + .../phpunit/phpunit/Tests/Regression/684.phpt | 26 + .../Tests/Regression/684/Issue684Test.php | 4 + .../phpunit/phpunit/Tests/Regression/783.phpt | 21 + .../Tests/Regression/783/ChildSuite.php | 15 + .../phpunit/Tests/Regression/783/OneTest.php | 10 + .../Tests/Regression/783/ParentSuite.php | 13 + .../phpunit/Tests/Regression/783/TwoTest.php | 10 + .../phpunit/Tests/Regression/GitHub/244.phpt | 40 + .../Regression/GitHub/244/Issue244Test.php | 55 + .../phpunit/Tests/Regression/GitHub/322.phpt | 28 + .../Regression/GitHub/322/Issue322Test.php | 17 + .../Regression/GitHub/322/phpunit322.xml | 11 + .../phpunit/Tests/Regression/GitHub/433.phpt | 33 + .../Regression/GitHub/433/Issue433Test.php | 21 + .../phpunit/Tests/Regression/GitHub/445.phpt | 34 + .../Regression/GitHub/445/Issue445Test.php | 21 + .../phpunit/Tests/Regression/GitHub/503.phpt | 34 + .../Regression/GitHub/503/Issue503Test.php | 11 + .../phpunit/Tests/Regression/GitHub/581.phpt | 43 + .../Regression/GitHub/581/Issue581Test.php | 10 + .../phpunit/Tests/Regression/GitHub/74.phpt | 31 + .../Regression/GitHub/74/Issue74Test.php | 9 + .../Regression/GitHub/74/NewException.php | 4 + .../Tests/Runner/BaseTestRunnerTest.php | 65 + .../Tests/TextUI/abstract-test-class.phpt | 29 + .../Tests/TextUI/concrete-test-class.phpt | 22 + .../dataprovider-log-xml-isolation.phpt | 49 + .../Tests/TextUI/dataprovider-log-xml.phpt | 50 + .../Tests/TextUI/dataprovider-testdox.phpt | 19 + .../phpunit/phpunit/Tests/TextUI/debug.phpt | 28 + .../Tests/TextUI/default-isolation.phpt | 22 + .../phpunit/phpunit/Tests/TextUI/default.phpt | 21 + .../Tests/TextUI/dependencies-isolation.phpt | 42 + .../phpunit/Tests/TextUI/dependencies.phpt | 42 + .../Tests/TextUI/dependencies2-isolation.phpt | 23 + .../phpunit/Tests/TextUI/dependencies2.phpt | 22 + .../Tests/TextUI/dependencies3-isolation.phpt | 23 + .../phpunit/Tests/TextUI/dependencies3.phpt | 21 + .../phpunit/Tests/TextUI/empty-testcase.phpt | 29 + .../phpunit/Tests/TextUI/exception-stack.phpt | 55 + .../Tests/TextUI/exclude-group-isolation.phpt | 24 + .../phpunit/Tests/TextUI/exclude-group.phpt | 23 + .../Tests/TextUI/failure-isolation.phpt | 144 + .../phpunit/phpunit/Tests/TextUI/failure.phpt | 144 + .../phpunit/Tests/TextUI/fatal-isolation.phpt | 35 + .../phpunit/phpunit/Tests/TextUI/fatal.phpt | 17 + .../Tests/TextUI/filter-class-isolation.phpt | 24 + .../phpunit/Tests/TextUI/filter-class.phpt | 23 + .../Tests/TextUI/filter-method-isolation.phpt | 24 + .../phpunit/Tests/TextUI/filter-method.phpt | 23 + .../Tests/TextUI/filter-no-results.phpt | 23 + .../phpunit/Tests/TextUI/group-isolation.phpt | 24 + .../phpunit/phpunit/Tests/TextUI/group.phpt | 23 + .../phpunit/phpunit/Tests/TextUI/help.phpt | 67 + .../phpunit/phpunit/Tests/TextUI/help2.phpt | 68 + .../phpunit/Tests/TextUI/list-groups.phpt | 22 + .../phpunit/Tests/TextUI/log-json.phpt | 77 + .../phpunit/phpunit/Tests/TextUI/log-tap.phpt | 28 + .../phpunit/phpunit/Tests/TextUI/log-xml.phpt | 31 + .../Tests/TextUI/strict-incomplete.phpt | 23 + .../Tests/TextUI/strict-isolation.phpt | 24 + .../phpunit/phpunit/Tests/TextUI/strict.phpt | 23 + .../phpunit/phpunit/Tests/TextUI/tap.phpt | 20 + .../Tests/TextUI/test-suffix-multiple.phpt | 22 + .../Tests/TextUI/test-suffix-single.phpt | 22 + .../phpunit/Tests/TextUI/testdox-html.phpt | 23 + .../phpunit/Tests/TextUI/testdox-text.phpt | 27 + .../phpunit/phpunit/Tests/TextUI/testdox.phpt | 21 + .../phpunit/phpunit/Tests/Util/ClassTest.php | 84 + .../phpunit/Tests/Util/ConfigurationTest.php | 390 ++ .../phpunit/phpunit/Tests/Util/DiffTest.php | 268 ++ .../Tests/Util/TestDox/NamePrettifierTest.php | 108 + .../phpunit/phpunit/Tests/Util/TestTest.php | 236 + .../phpunit/phpunit/Tests/Util/TypeTest.php | 269 ++ .../phpunit/phpunit/Tests/Util/XMLTest.php | 323 ++ .../phpunit/Tests/_files/AbstractTest.php | 7 + .../phpunit/phpunit/Tests/_files/Author.php | 66 + .../phpunit/Tests/_files/BankAccount.php | 116 + .../phpunit/Tests/_files/BankAccountTest.php | 133 + .../Tests/_files/BankAccountTest.test.php | 133 + .../phpunit/phpunit/Tests/_files/Book.php | 59 + .../phpunit/Tests/_files/Calculator.php | 14 + .../ChangeCurrentWorkingDirectoryTest.php | 10 + .../_files/ClassWithNonPublicAttributes.php | 29 + .../Tests/_files/ClassWithToString.php | 61 + .../phpunit/Tests/_files/ConcreteTest.my.php | 9 + .../phpunit/Tests/_files/ConcreteTest.php | 9 + .../phpunit/Tests/_files/DataProviderTest.php | 21 + .../Tests/_files/DependencyFailureTest.php | 22 + .../Tests/_files/DependencySuccessTest.php | 21 + .../Tests/_files/DependencyTestSuite.php | 16 + .../phpunit/Tests/_files/DoubleTestCase.php | 25 + .../Tests/_files/EmptyTestCaseTest.php | 4 + .../phpunit/phpunit/Tests/_files/Error.php | 8 + .../ExceptionInAssertPostConditionsTest.php | 35 + .../ExceptionInAssertPreConditionsTest.php | 35 + .../Tests/_files/ExceptionInSetUpTest.php | 35 + .../Tests/_files/ExceptionInTearDownTest.php | 35 + .../phpunit/Tests/_files/ExceptionInTest.php | 35 + .../Tests/_files/ExceptionNamespaceTest.php | 38 + .../phpunit/Tests/_files/ExceptionStack.php | 23 + .../phpunit/Tests/_files/ExceptionTest.php | 97 + .../phpunit/phpunit/Tests/_files/Failure.php | 8 + .../phpunit/Tests/_files/FailureTest.php | 76 + .../phpunit/Tests/_files/FatalTest.php | 10 + .../phpunit/Tests/_files/IncompleteTest.php | 8 + .../Tests/_files/InheritedTestCase.php | 9 + .../Tests/_files/JsonData/arrayObject.js | 1 + .../Tests/_files/JsonData/simpleObject.js | 1 + .../Tests/_files/JsonData/simpleObject2.js | 1 + .../phpunit/Tests/_files/MockRunner.php | 7 + .../Tests/_files/MultiDependencyTest.php | 23 + .../Tests/_files/NoArgTestCaseTest.php | 7 + .../phpunit/Tests/_files/NoTestCaseClass.php | 4 + .../phpunit/Tests/_files/NoTestCases.php | 7 + .../phpunit/Tests/_files/NonStatic.php | 8 + .../Tests/_files/NotPublicTestCase.php | 11 + .../phpunit/Tests/_files/NotVoidTestCase.php | 4 + .../phpunit/Tests/_files/NothingTest.php | 7 + .../phpunit/Tests/_files/OneTestCase.php | 11 + .../phpunit/Tests/_files/OutputTestCase.php | 27 + .../phpunit/Tests/_files/OverrideTestCase.php | 9 + .../_files/RequirementsClassDocBlockTest.php | 23 + .../phpunit/Tests/_files/RequirementsTest.php | 112 + .../phpunit/Tests/_files/SampleClass.php | 14 + .../_files/SelectorAssertionsFixture.html | 44 + .../phpunit/Tests/_files/Singleton.php | 22 + .../phpunit/Tests/_files/StackTest.php | 24 + .../phpunit/phpunit/Tests/_files/Struct.php | 10 + .../phpunit/phpunit/Tests/_files/Success.php | 7 + .../Tests/_files/TemplateMethodsTest.php | 52 + .../phpunit/Tests/_files/TestIterator.php | 36 + .../Tests/_files/ThrowExceptionTestCase.php | 8 + .../Tests/_files/ThrowNoExceptionTestCase.php | 7 + .../phpunit/phpunit/Tests/_files/WasRun.php | 10 + .../phpunit/phpunit/Tests/_files/bar.xml | 1 + .../phpunit/Tests/_files/configuration.xml | 115 + .../Tests/_files/configuration_xinclude.xml | 68 + .../Tests/_files/expectedFileFormat.txt | 1 + .../phpunit/phpunit/Tests/_files/foo.xml | 1 + ...uctureAttributesAreSameButValuesAreNot.xml | 10 + .../Tests/_files/structureExpected.xml | 10 + .../Tests/_files/structureIgnoreTextNodes.xml | 13 + .../_files/structureIsSameButDataIsNot.xml | 10 + .../structureWrongNumberOfAttributes.xml | 10 + .../_files/structureWrongNumberOfNodes.xml | 9 + .../kohana/vendor/phpunit/phpunit/build.xml | 220 + .../ControlSignatureSniff.php | 23 + .../Whitespace/ConcatenationSpacingSniff.php | 22 + .../phpunit/phpunit/build/PHPCS/ruleset.xml | 35 + .../phpunit/phpunit/build/assertions.php | 65 + .../build/dependencies/DbUnit-1.2.1.tgz | Bin 0 -> 41892 bytes .../dependencies/File_Iterator-1.3.3.tgz | Bin 0 -> 5152 bytes .../dependencies/PHPUnit_MockObject-1.2.1.tgz | Bin 0 -> 20417 bytes .../dependencies/PHPUnit_Selenium-1.2.9.tgz | Bin 0 -> 36429 bytes .../dependencies/PHP_CodeCoverage-1.2.6.tgz | Bin 0 -> 155960 bytes .../build/dependencies/PHP_Invoker-1.1.2.tgz | Bin 0 -> 3705 bytes .../build/dependencies/PHP_Timer-1.0.4.tgz | Bin 0 -> 3694 bytes .../dependencies/PHP_TokenStream-1.1.5.tgz | Bin 0 -> 9859 bytes .../dependencies/Text_Template-1.1.3.tgz | Bin 0 -> 3594 bytes .../phpunit/build/dependencies/Yaml-2.1.2.tgz | Bin 0 -> 38429 bytes .../phpunit/build/phar-autoload.php.in | 24 + .../vendor/phpunit/phpunit/build/phpmd.xml | 27 + .../vendor/phpunit/phpunit/composer.json | 62 + .../phpunit/phpunit/composer/bin/phpunit | 65 + .../kohana/vendor/phpunit/phpunit/package.xml | 292 ++ .../vendor/phpunit/phpunit/phpdox.xml.dist | 14 + .../kohana/vendor/phpunit/phpunit/phpunit.bat | 43 + .../kohana/vendor/phpunit/phpunit/phpunit.php | 46 + .../vendor/phpunit/phpunit/phpunit.xml.dist | 42 + .../kohana/vendor/phpunit/phpunit/phpunit.xsd | 251 + .../yaml/Symfony/Component/Yaml/CHANGELOG.md | 8 + .../yaml/Symfony/Component/Yaml/Dumper.php | 71 + .../yaml/Symfony/Component/Yaml/Escaper.php | 88 + .../Yaml/Exception/DumpException.php | 23 + .../Yaml/Exception/ExceptionInterface.php | 23 + .../Yaml/Exception/ParseException.php | 143 + .../yaml/Symfony/Component/Yaml/Inline.php | 424 ++ .../yaml/Symfony/Component/Yaml/LICENSE | 19 + .../yaml/Symfony/Component/Yaml/Parser.php | 620 +++ .../yaml/Symfony/Component/Yaml/README.md | 17 + .../Component/Yaml/Tests/DumperTest.php | 166 + .../Yaml/Tests/Fixtures/YtsAnchorAlias.yml | 31 + .../Yaml/Tests/Fixtures/YtsBasicTests.yml | 178 + .../Yaml/Tests/Fixtures/YtsBlockMapping.yml | 51 + .../Tests/Fixtures/YtsDocumentSeparator.yml | 85 + .../Yaml/Tests/Fixtures/YtsErrorTests.yml | 25 + .../Tests/Fixtures/YtsFlowCollections.yml | 60 + .../Yaml/Tests/Fixtures/YtsFoldedScalars.yml | 176 + .../Tests/Fixtures/YtsNullsAndEmpties.yml | 45 + .../Fixtures/YtsSpecificationExamples.yml | 1695 +++++++ .../Yaml/Tests/Fixtures/YtsTypeTransfers.yml | 244 + .../Yaml/Tests/Fixtures/embededPhp.yml | 1 + .../Yaml/Tests/Fixtures/escapedCharacters.yml | 139 + .../Component/Yaml/Tests/Fixtures/index.yml | 18 + .../Yaml/Tests/Fixtures/sfComments.yml | 51 + .../Yaml/Tests/Fixtures/sfCompact.yml | 159 + .../Yaml/Tests/Fixtures/sfMergeKey.yml | 27 + .../Yaml/Tests/Fixtures/sfObjects.yml | 11 + .../Yaml/Tests/Fixtures/sfQuotes.yml | 33 + .../Component/Yaml/Tests/Fixtures/sfTests.yml | 135 + .../Tests/Fixtures/unindentedCollections.yml | 62 + .../Component/Yaml/Tests/InlineTest.php | 211 + .../Component/Yaml/Tests/ParserTest.php | 193 + .../Symfony/Component/Yaml/Tests/YamlTest.php | 41 + .../Component/Yaml/Tests/bootstrap.php | 18 + .../yaml/Symfony/Component/Yaml/Unescaper.php | 145 + .../yaml/Symfony/Component/Yaml/Yaml.php | 113 + .../yaml/Symfony/Component/Yaml/composer.json | 31 + .../Symfony/Component/Yaml/phpunit.xml.dist | 29 + index.php | 52 +- 1293 files changed, 96042 insertions(+), 9545 deletions(-) create mode 100644 includes/kohana/.travis.yml create mode 100644 includes/kohana/composer.json create mode 100644 includes/kohana/composer.lock create mode 100644 includes/kohana/composer.phar create mode 100644 includes/kohana/modules/auth/classes/Auth.php create mode 100644 includes/kohana/modules/auth/classes/Auth/File.php rename includes/kohana/modules/auth/classes/{kohana/auth.php => Kohana/Auth.php} (80%) rename includes/kohana/modules/auth/classes/{kohana/auth/file.php => Kohana/Auth/File.php} (75%) delete mode 100644 includes/kohana/modules/auth/classes/auth.php delete mode 100644 includes/kohana/modules/auth/classes/auth/file.php create mode 100644 includes/kohana/modules/auth/config/userguide.php create mode 100644 includes/kohana/modules/auth/guide/auth/driver/develop.md create mode 100644 includes/kohana/modules/auth/guide/auth/driver/file.md delete mode 100644 includes/kohana/modules/auth/guide/auth/edit.md delete mode 100644 includes/kohana/modules/auth/guide/auth/register.md delete mode 100644 includes/kohana/modules/auth/guide/auth/roles.md delete mode 100644 includes/kohana/modules/auth/guide/auth/user.md rename includes/kohana/modules/cache/classes/{cache.php => Cache.php} (100%) rename includes/kohana/modules/cache/classes/{cache/apc.php => Cache/Apc.php} (100%) rename includes/kohana/modules/cache/classes/{cache/eaccelerator.php => Cache/Arithmetic.php} (50%) rename includes/kohana/modules/{database/classes/database/pdo.php => cache/classes/Cache/Exception.php} (52%) rename includes/kohana/modules/cache/classes/{cache/file.php => Cache/File.php} (100%) create mode 100644 includes/kohana/modules/cache/classes/Cache/GarbageCollect.php rename includes/kohana/modules/cache/classes/{cache/memcache.php => Cache/Memcache.php} (100%) rename includes/kohana/modules/cache/classes/{cache/memcachetag.php => Cache/MemcacheTag.php} (100%) rename includes/kohana/modules/cache/classes/{cache/sqlite.php => Cache/Sqlite.php} (100%) rename includes/kohana/modules/{database/classes/database.php => cache/classes/Cache/Tagging.php} (52%) rename includes/kohana/modules/cache/classes/{cache/wincache.php => Cache/Wincache.php} (100%) rename includes/kohana/modules/{image/classes/image/gd.php => cache/classes/HTTP/Cache.php} (57%) rename includes/kohana/modules/cache/classes/{kohana/cache.php => Kohana/Cache.php} (76%) rename includes/kohana/modules/cache/classes/{kohana/cache/apc.php => Kohana/Cache/Apc.php} (68%) create mode 100644 includes/kohana/modules/cache/classes/Kohana/Cache/Arithmetic.php rename includes/kohana/modules/cache/classes/{kohana/cache/exception.php => Kohana/Cache/Exception.php} (87%) rename includes/kohana/modules/cache/classes/{kohana/cache/file.php => Kohana/Cache/File.php} (72%) rename includes/kohana/modules/cache/classes/{kohana/cache/garbagecollect.php => Kohana/Cache/GarbageCollect.php} (93%) rename includes/kohana/modules/cache/classes/{kohana/cache/memcache.php => Kohana/Cache/Memcache.php} (83%) rename includes/kohana/modules/cache/classes/{kohana/cache/memcachetag.php => Kohana/Cache/MemcacheTag.php} (62%) rename includes/kohana/modules/cache/classes/{kohana/cache/sqlite.php => Kohana/Cache/Sqlite.php} (71%) rename includes/kohana/modules/cache/classes/{kohana/cache/tagging.php => Kohana/Cache/Tagging.php} (70%) rename includes/kohana/modules/cache/classes/{kohana/cache/wincache.php => Kohana/Cache/Wincache.php} (85%) create mode 100644 includes/kohana/modules/cache/classes/Kohana/HTTP/Cache.php delete mode 100644 includes/kohana/modules/cache/classes/cache/xcache.php delete mode 100644 includes/kohana/modules/cache/classes/kohana/cache/eaccelerator.php delete mode 100644 includes/kohana/modules/cache/classes/kohana/cache/xcache.php create mode 100644 includes/kohana/modules/cache/tests/cache/CacheBasicMethodsTest.php create mode 100644 includes/kohana/modules/cache/tests/cache/CacheTest.php create mode 100644 includes/kohana/modules/cache/tests/cache/FileTest.php delete mode 100644 includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php create mode 100644 includes/kohana/modules/cache/tests/cache/SqliteTest.php create mode 100644 includes/kohana/modules/cache/tests/cache/WincacheTest.php create mode 100644 includes/kohana/modules/cache/tests/cache/arithmetic/ApcTest.php create mode 100644 includes/kohana/modules/cache/tests/cache/arithmetic/CacheArithmeticMethods.php create mode 100644 includes/kohana/modules/cache/tests/cache/arithmetic/MemcacheTest.php create mode 100644 includes/kohana/modules/cache/tests/cache/request/client/CacheTest.php rename includes/kohana/modules/codebench/classes/{bench/arrcallback.php => Bench/ArrCallback.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/autolinkemails.php => Bench/AutoLinkEmails.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/datespan.php => Bench/DateSpan.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/explodelimit.php => Bench/ExplodeLimit.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/gruberurl.php => Bench/GruberURL.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/ltrimdigits.php => Bench/LtrimDigits.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/mddobaseurl.php => Bench/MDDoBaseURL.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/mddoimageurl.php => Bench/MDDoImageURL.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/mddoincludeviews.php => Bench/MDDoIncludeViews.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/stripnullbytes.php => Bench/StripNullBytes.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/transliterate.php => Bench/Transliterate.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/urlsite.php => Bench/URLSite.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/userfuncarray.php => Bench/UserFuncArray.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/validcolor.php => Bench/ValidColor.php} (100%) rename includes/kohana/modules/codebench/classes/{bench/validurl.php => Bench/ValidURL.php} (100%) rename includes/kohana/modules/codebench/classes/{codebench.php => Codebench.php} (100%) rename includes/kohana/modules/codebench/classes/{controller/codebench.php => Controller/Codebench.php} (82%) rename includes/kohana/modules/codebench/classes/{kohana/codebench.php => Kohana/Codebench.php} (99%) create mode 100644 includes/kohana/modules/database/classes/Config/Database.php create mode 100644 includes/kohana/modules/database/classes/Config/Database/Reader.php create mode 100644 includes/kohana/modules/database/classes/Config/Database/Writer.php create mode 100644 includes/kohana/modules/database/classes/DB.php create mode 100644 includes/kohana/modules/database/classes/Database.php rename includes/kohana/modules/database/classes/{database/exception.php => Database/Exception.php} (50%) rename includes/kohana/modules/database/classes/{database/expression.php => Database/Expression.php} (51%) create mode 100644 includes/kohana/modules/database/classes/Database/MySQL.php rename includes/kohana/modules/database/classes/{database/mysql/result.php => Database/MySQL/Result.php} (53%) create mode 100644 includes/kohana/modules/database/classes/Database/PDO.php create mode 100644 includes/kohana/modules/database/classes/Database/Query.php rename includes/kohana/modules/database/classes/{database/query/builder.php => Database/Query/Builder.php} (56%) rename includes/kohana/modules/database/classes/{database/query/builder/delete.php => Database/Query/Builder/Delete.php} (58%) rename includes/kohana/modules/database/classes/{database/query/builder/insert.php => Database/Query/Builder/Insert.php} (58%) rename includes/kohana/modules/database/classes/{database/query/builder/join.php => Database/Query/Builder/Join.php} (57%) rename includes/kohana/modules/database/classes/{database/query/builder/select.php => Database/Query/Builder/Select.php} (58%) rename includes/kohana/modules/database/classes/{database/query/builder/update.php => Database/Query/Builder/Update.php} (58%) rename includes/kohana/modules/database/classes/{database/query/builder/where.php => Database/Query/Builder/Where.php} (60%) rename includes/kohana/modules/database/classes/{database/result.php => Database/Result.php} (51%) rename includes/kohana/modules/database/classes/{database/result/cached.php => Database/Result/Cached.php} (53%) create mode 100644 includes/kohana/modules/database/classes/Kohana/Config/Database.php create mode 100644 includes/kohana/modules/database/classes/Kohana/Config/Database/Reader.php create mode 100644 includes/kohana/modules/database/classes/Kohana/Config/Database/Writer.php rename includes/kohana/modules/database/classes/{kohana/db.php => Kohana/DB.php} (83%) rename includes/kohana/modules/database/classes/{kohana/database.php => Kohana/Database.php} (82%) rename includes/kohana/modules/database/classes/{kohana/database/exception.php => Kohana/Database/Exception.php} (80%) create mode 100644 includes/kohana/modules/database/classes/Kohana/Database/Expression.php rename includes/kohana/modules/database/classes/{kohana/database/mysql.php => Kohana/Database/MySQL.php} (95%) rename includes/kohana/modules/database/classes/{kohana/database/mysql/result.php => Kohana/Database/MySQL/Result.php} (96%) rename includes/kohana/modules/database/classes/{kohana/database/pdo.php => Kohana/Database/PDO.php} (70%) rename includes/kohana/modules/database/classes/{kohana/database/query.php => Kohana/Database/Query.php} (65%) rename includes/kohana/modules/database/classes/{kohana/database/query/builder.php => Kohana/Database/Query/Builder.php} (72%) rename includes/kohana/modules/database/classes/{kohana/database/query/builder/delete.php => Kohana/Database/Query/Builder/Delete.php} (79%) rename includes/kohana/modules/database/classes/{kohana/database/query/builder/insert.php => Kohana/Database/Query/Builder/Insert.php} (84%) rename includes/kohana/modules/database/classes/{kohana/database/query/builder/join.php => Kohana/Database/Query/Builder/Join.php} (80%) rename includes/kohana/modules/database/classes/{kohana/database/query/builder/select.php => Kohana/Database/Query/Builder/Select.php} (79%) rename includes/kohana/modules/database/classes/{kohana/database/query/builder/update.php => Kohana/Database/Query/Builder/Update.php} (78%) rename includes/kohana/modules/database/classes/{kohana/database/query/builder/where.php => Kohana/Database/Query/Builder/Where.php} (65%) rename includes/kohana/modules/database/classes/{kohana/database/result.php => Kohana/Database/Result.php} (91%) rename includes/kohana/modules/database/classes/{kohana/database/result/cached.php => Kohana/Database/Result/Cached.php} (94%) rename includes/kohana/modules/database/classes/{kohana/model/database.php => Kohana/Model/Database.php} (69%) rename includes/kohana/modules/database/classes/{kohana/session/database.php => Kohana/Session/Database.php} (95%) rename includes/kohana/modules/database/classes/{model/database.php => Model/Database.php} (51%) create mode 100644 includes/kohana/modules/database/classes/Session/Database.php delete mode 100644 includes/kohana/modules/database/classes/database/mysql.php delete mode 100644 includes/kohana/modules/database/classes/database/query.php delete mode 100644 includes/kohana/modules/database/classes/db.php delete mode 100644 includes/kohana/modules/database/classes/kohana/config/database.php delete mode 100644 includes/kohana/modules/database/classes/kohana/database/expression.php delete mode 100644 includes/kohana/modules/database/classes/session/database.php rename includes/kohana/modules/database/guide/database/query/{prepared.md => parameterized.md} (89%) create mode 100644 includes/kohana/modules/image/classes/Image.php create mode 100644 includes/kohana/modules/image/classes/Image/GD.php create mode 100644 includes/kohana/modules/image/classes/Image/Imagick.php rename includes/kohana/modules/image/classes/{kohana/image.php => Kohana/Image.php} (83%) rename includes/kohana/modules/image/classes/{kohana/image/gd.php => Kohana/Image/GD.php} (87%) create mode 100644 includes/kohana/modules/image/classes/Kohana/Image/Imagick.php create mode 100644 includes/kohana/modules/image/guide/image/examples/crop.md create mode 100644 includes/kohana/modules/image/guide/image/examples/dynamic.md create mode 100644 includes/kohana/modules/image/guide/image/examples/upload.md create mode 100644 includes/kohana/modules/image/media/guide/image/Thumbs.db create mode 100644 includes/kohana/modules/image/media/guide/image/crop_form.jpg create mode 100644 includes/kohana/modules/image/media/guide/image/crop_orig.jpg create mode 100644 includes/kohana/modules/image/media/guide/image/crop_result.jpg create mode 100644 includes/kohana/modules/image/media/guide/image/dynamic-400.jpg create mode 100644 includes/kohana/modules/image/media/guide/image/dynamic-600.jpg create mode 100644 includes/kohana/modules/image/media/guide/image/upload_form.jpg create mode 100644 includes/kohana/modules/image/media/guide/image/upload_result.jpg create mode 100644 includes/kohana/modules/image/tests/kohana/ImageTest.php create mode 100644 includes/kohana/modules/image/tests/test_data/test_image create mode 100644 includes/kohana/modules/minion/README.md create mode 100644 includes/kohana/modules/minion/classes/Kohana/Minion/CLI.php create mode 100644 includes/kohana/modules/minion/classes/Kohana/Minion/Exception.php create mode 100644 includes/kohana/modules/minion/classes/Kohana/Minion/Exception/InvalidTask.php create mode 100644 includes/kohana/modules/minion/classes/Kohana/Minion/Task.php rename includes/kohana/modules/{image/classes/image.php => minion/classes/Minion/CLI.php} (57%) rename includes/kohana/modules/{database/classes/config/database.php => minion/classes/Minion/Exception.php} (51%) create mode 100644 includes/kohana/modules/minion/classes/Minion/Exception/InvalidTask.php create mode 100644 includes/kohana/modules/minion/classes/Minion/Task.php create mode 100644 includes/kohana/modules/minion/classes/Task/Help.php create mode 100644 includes/kohana/modules/minion/config/userguide.php create mode 100644 includes/kohana/modules/minion/guide/minion/index.md create mode 100644 includes/kohana/modules/minion/guide/minion/menu.md create mode 100644 includes/kohana/modules/minion/guide/minion/setup.md create mode 100644 includes/kohana/modules/minion/guide/minion/tasks.md create mode 100644 includes/kohana/modules/minion/messages/validation.php create mode 100644 includes/kohana/modules/minion/minion create mode 100644 includes/kohana/modules/minion/miniond create mode 100644 includes/kohana/modules/minion/tests/minion/task.php create mode 100644 includes/kohana/modules/minion/views/minion/error/validation.php create mode 100644 includes/kohana/modules/minion/views/minion/help/error.php create mode 100644 includes/kohana/modules/minion/views/minion/help/list.php create mode 100644 includes/kohana/modules/minion/views/minion/help/task.php create mode 100644 includes/kohana/modules/orm/classes/Auth/ORM.php rename includes/kohana/modules/orm/classes/{kohana/auth/orm.php => Kohana/Auth/ORM.php} (74%) rename includes/kohana/modules/orm/classes/{kohana/orm.php => Kohana/ORM.php} (56%) rename includes/kohana/modules/orm/classes/{kohana/orm/validation/exception.php => Kohana/ORM/Validation/Exception.php} (66%) rename includes/kohana/modules/orm/classes/{model/auth/role.php => Model/Auth/Role.php} (72%) rename includes/kohana/modules/orm/classes/{model/auth/user.php => Model/Auth/User.php} (72%) rename includes/kohana/modules/orm/classes/{model/auth/user/token.php => Model/Auth/User/Token.php} (74%) rename includes/kohana/modules/orm/classes/{model/role.php => Model/Role.php} (63%) rename includes/kohana/modules/orm/classes/{model/user.php => Model/User.php} (63%) rename includes/kohana/modules/orm/classes/{model/user/token.php => Model/User/Token.php} (67%) create mode 100644 includes/kohana/modules/orm/classes/ORM.php rename includes/kohana/modules/orm/classes/{orm/validation/exception.php => ORM/Validation/Exception.php} (80%) delete mode 100644 includes/kohana/modules/orm/classes/auth/orm.php delete mode 100644 includes/kohana/modules/orm/classes/orm.php create mode 100644 includes/kohana/modules/orm/guide/orm/upgrading.md create mode 100644 includes/kohana/modules/unittest/bootstrap_all_modules.php rename includes/kohana/modules/unittest/classes/{kohana/unittest/database/testcase.php => Kohana/Unittest/Database/TestCase.php} (86%) rename includes/kohana/modules/unittest/classes/{kohana/unittest/helpers.php => Kohana/Unittest/Helpers.php} (94%) rename includes/kohana/modules/unittest/classes/{kohana/unittest/testcase.php => Kohana/Unittest/TestCase.php} (92%) create mode 100644 includes/kohana/modules/unittest/classes/Kohana/Unittest/TestSuite.php rename includes/kohana/modules/unittest/classes/{kohana/unittest/tests.php => Kohana/Unittest/Tests.php} (61%) create mode 100644 includes/kohana/modules/unittest/classes/Unittest/Database/TestCase.php rename includes/kohana/modules/unittest/classes/{unittest/helpers.php => Unittest/Helpers.php} (100%) create mode 100644 includes/kohana/modules/unittest/classes/Unittest/TestCase.php create mode 100644 includes/kohana/modules/unittest/classes/Unittest/TestSuite.php rename includes/kohana/modules/unittest/classes/{unittest/tests.php => Unittest/Tests.php} (100%) delete mode 100644 includes/kohana/modules/unittest/classes/controller/unittest.php delete mode 100644 includes/kohana/modules/unittest/classes/kohana/unittest/runner.php delete mode 100644 includes/kohana/modules/unittest/classes/unittest/runner.php delete mode 100644 includes/kohana/modules/unittest/classes/unittest/testcase.php delete mode 100644 includes/kohana/modules/unittest/init.php delete mode 100644 includes/kohana/modules/unittest/views/unittest/index.php delete mode 100644 includes/kohana/modules/unittest/views/unittest/layout.php delete mode 100644 includes/kohana/modules/unittest/views/unittest/results.php create mode 100644 includes/kohana/modules/userguide/classes/Controller/Userguide.php rename includes/kohana/modules/userguide/classes/{kodoc.php => Kodoc.php} (100%) rename includes/kohana/modules/userguide/classes/{kodoc/class.php => Kodoc/Class.php} (100%) rename includes/kohana/modules/userguide/classes/{kodoc/markdown.php => Kodoc/Markdown.php} (100%) rename includes/kohana/modules/userguide/classes/{kodoc/method.php => Kodoc/Method.php} (100%) rename includes/kohana/modules/userguide/classes/{kodoc/method/param.php => Kodoc/Method/Param.php} (100%) rename includes/kohana/modules/userguide/classes/{kodoc/missing.php => Kodoc/Missing.php} (100%) rename includes/kohana/modules/userguide/classes/{kodoc/property.php => Kodoc/Property.php} (100%) rename includes/kohana/modules/userguide/classes/{controller/userguide.php => Kohana/Controller/Userguide.php} (79%) rename includes/kohana/modules/userguide/classes/{kohana/kodoc.php => Kohana/Kodoc.php} (52%) rename includes/kohana/modules/userguide/classes/{kohana/kodoc/class.php => Kohana/Kodoc/Class.php} (69%) rename includes/kohana/modules/userguide/classes/{kohana/kodoc/markdown.php => Kohana/Kodoc/Markdown.php} (86%) rename includes/kohana/modules/userguide/classes/{kohana/kodoc/method.php => Kohana/Kodoc/Method.php} (98%) rename includes/kohana/modules/userguide/classes/{kohana/kodoc/method/param.php => Kohana/Kodoc/Method/Param.php} (93%) rename includes/kohana/modules/userguide/classes/{kohana/kodoc/missing.php => Kohana/Kodoc/Missing.php} (100%) rename includes/kohana/modules/userguide/classes/{kohana/kodoc/property.php => Kohana/Kodoc/Property.php} (81%) delete mode 100644 includes/kohana/modules/userguide/i18n/de.php delete mode 100644 includes/kohana/modules/userguide/i18n/es.php delete mode 100644 includes/kohana/modules/userguide/i18n/fr.php delete mode 100644 includes/kohana/modules/userguide/i18n/he.php delete mode 100644 includes/kohana/modules/userguide/i18n/nl.php delete mode 100644 includes/kohana/modules/userguide/i18n/ru.php delete mode 100644 includes/kohana/modules/userguide/i18n/zh.php create mode 100644 includes/kohana/modules/userguide/tests/KodocTest.php create mode 100644 includes/kohana/modules/userguide/tests/userguide/ControllerTest.php create mode 100644 includes/kohana/system/classes/Arr.php create mode 100644 includes/kohana/system/classes/Config.php create mode 100644 includes/kohana/system/classes/Config/File.php create mode 100644 includes/kohana/system/classes/Config/Group.php create mode 100644 includes/kohana/system/classes/Controller.php rename includes/kohana/system/classes/{controller/template.php => Controller/Template.php} (54%) create mode 100644 includes/kohana/system/classes/Cookie.php create mode 100644 includes/kohana/system/classes/Date.php create mode 100644 includes/kohana/system/classes/Debug.php create mode 100644 includes/kohana/system/classes/Encrypt.php create mode 100644 includes/kohana/system/classes/Feed.php create mode 100644 includes/kohana/system/classes/File.php create mode 100644 includes/kohana/system/classes/Form.php create mode 100644 includes/kohana/system/classes/Fragment.php create mode 100644 includes/kohana/system/classes/HTML.php create mode 100644 includes/kohana/system/classes/HTTP.php create mode 100644 includes/kohana/system/classes/HTTP/Exception.php create mode 100644 includes/kohana/system/classes/HTTP/Exception/300.php create mode 100644 includes/kohana/system/classes/HTTP/Exception/301.php create mode 100644 includes/kohana/system/classes/HTTP/Exception/302.php create mode 100644 includes/kohana/system/classes/HTTP/Exception/303.php create mode 100644 includes/kohana/system/classes/HTTP/Exception/304.php create mode 100644 includes/kohana/system/classes/HTTP/Exception/305.php create mode 100644 includes/kohana/system/classes/HTTP/Exception/307.php rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/400.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/401.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/402.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/403.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/404.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/405.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/406.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/407.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/408.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/409.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/410.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/411.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/412.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/413.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/414.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/415.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/416.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/417.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/500.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/501.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/502.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/503.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/504.php (50%) rename includes/kohana/system/classes/{http/exception => HTTP/Exception}/505.php (50%) create mode 100644 includes/kohana/system/classes/HTTP/Exception/Expected.php create mode 100644 includes/kohana/system/classes/HTTP/Exception/Redirect.php create mode 100644 includes/kohana/system/classes/HTTP/Header.php create mode 100644 includes/kohana/system/classes/HTTP/Message.php create mode 100644 includes/kohana/system/classes/HTTP/Request.php create mode 100644 includes/kohana/system/classes/HTTP/Response.php create mode 100644 includes/kohana/system/classes/I18n.php create mode 100644 includes/kohana/system/classes/Inflector.php create mode 100644 includes/kohana/system/classes/Kohana.php rename includes/kohana/system/classes/{kohana/arr.php => Kohana/Arr.php} (70%) create mode 100644 includes/kohana/system/classes/Kohana/Config.php create mode 100644 includes/kohana/system/classes/Kohana/Config/File.php rename includes/kohana/system/classes/{kohana/config/file.php => Kohana/Config/File/Reader.php} (50%) create mode 100644 includes/kohana/system/classes/Kohana/Config/Group.php create mode 100644 includes/kohana/system/classes/Kohana/Config/Reader.php create mode 100644 includes/kohana/system/classes/Kohana/Config/Source.php create mode 100644 includes/kohana/system/classes/Kohana/Config/Writer.php create mode 100644 includes/kohana/system/classes/Kohana/Controller.php rename includes/kohana/system/classes/{kohana/controller/template.php => Kohana/Controller/Template.php} (84%) rename includes/kohana/system/classes/{kohana/cookie.php => Kohana/Cookie.php} (88%) rename includes/kohana/system/classes/{kohana/core.php => Kohana/Core.php} (83%) rename includes/kohana/system/classes/{kohana/date.php => Kohana/Date.php} (84%) rename includes/kohana/system/classes/{kohana/debug.php => Kohana/Debug.php} (89%) rename includes/kohana/system/classes/{kohana/encrypt.php => Kohana/Encrypt.php} (92%) create mode 100644 includes/kohana/system/classes/Kohana/Exception.php rename includes/kohana/system/classes/{kohana/feed.php => Kohana/Feed.php} (82%) rename includes/kohana/system/classes/{kohana/file.php => Kohana/File.php} (86%) rename includes/kohana/system/classes/{kohana/form.php => Kohana/Form.php} (78%) rename includes/kohana/system/classes/{kohana/fragment.php => Kohana/Fragment.php} (87%) rename includes/kohana/system/classes/{kohana/html.php => Kohana/HTML.php} (67%) rename includes/kohana/system/classes/{kohana/http.php => Kohana/HTTP.php} (67%) create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception.php create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/300.php create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/301.php create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/302.php create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/303.php create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/304.php create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/305.php create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/307.php rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/400.php (69%) create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/401.php rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/402.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/403.php (69%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/404.php (69%) create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/405.php rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/406.php (69%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/407.php (72%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/408.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/409.php (69%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/410.php (68%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/411.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/412.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/413.php (71%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/414.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/415.php (71%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/416.php (72%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/417.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/500.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/501.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/502.php (69%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/503.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/504.php (70%) rename includes/kohana/system/classes/{kohana/http/exception => Kohana/HTTP/Exception}/505.php (71%) create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/Expected.php create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Exception/Redirect.php create mode 100644 includes/kohana/system/classes/Kohana/HTTP/Header.php rename includes/kohana/system/classes/{kohana/http/interaction.php => Kohana/HTTP/Message.php} (91%) rename includes/kohana/system/classes/{kohana/http/request.php => Kohana/HTTP/Request.php} (88%) rename includes/kohana/system/classes/{kohana/http/response.php => Kohana/HTTP/Response.php} (81%) rename includes/kohana/system/classes/{kohana/i18n.php => Kohana/I18n.php} (88%) rename includes/kohana/system/classes/{kohana/inflector.php => Kohana/Inflector.php} (88%) create mode 100644 includes/kohana/system/classes/Kohana/Kohana/Exception.php rename includes/kohana/system/classes/{kohana/log.php => Kohana/Log.php} (66%) rename includes/kohana/system/classes/{kohana/log/file.php => Kohana/Log/File.php} (84%) rename includes/kohana/system/classes/{kohana/log/stderr.php => Kohana/Log/StdErr.php} (59%) rename includes/kohana/system/classes/{kohana/log/stdout.php => Kohana/Log/StdOut.php} (59%) rename includes/kohana/system/classes/{kohana/log/syslog.php => Kohana/Log/Syslog.php} (59%) create mode 100644 includes/kohana/system/classes/Kohana/Log/Writer.php rename includes/kohana/system/classes/{kohana/model.php => Kohana/Model.php} (76%) rename includes/kohana/system/classes/{kohana/num.php => Kohana/Num.php} (93%) rename includes/kohana/system/classes/{kohana/profiler.php => Kohana/Profiler.php} (94%) rename includes/kohana/system/classes/{kohana/request.php => Kohana/Request.php} (64%) create mode 100644 includes/kohana/system/classes/Kohana/Request/Client.php create mode 100644 includes/kohana/system/classes/Kohana/Request/Client/Curl.php create mode 100644 includes/kohana/system/classes/Kohana/Request/Client/External.php create mode 100644 includes/kohana/system/classes/Kohana/Request/Client/HTTP.php create mode 100644 includes/kohana/system/classes/Kohana/Request/Client/Internal.php create mode 100644 includes/kohana/system/classes/Kohana/Request/Client/Recursion/Exception.php create mode 100644 includes/kohana/system/classes/Kohana/Request/Client/Stream.php rename includes/kohana/system/classes/{kohana/request/exception.php => Kohana/Request/Exception.php} (65%) rename includes/kohana/system/classes/{kohana/response.php => Kohana/Response.php} (68%) rename includes/kohana/system/classes/{kohana/route.php => Kohana/Route.php} (68%) rename includes/kohana/system/classes/{kohana/security.php => Kohana/Security.php} (87%) rename includes/kohana/system/classes/{kohana/session.php => Kohana/Session.php} (75%) rename includes/kohana/system/classes/{kohana/session/cookie.php => Kohana/Session/Cookie.php} (80%) create mode 100644 includes/kohana/system/classes/Kohana/Session/Exception.php rename includes/kohana/system/classes/{kohana/session/native.php => Kohana/Session/Native.php} (82%) rename includes/kohana/system/classes/{kohana/text.php => Kohana/Text.php} (76%) rename includes/kohana/system/classes/{kohana/url.php => Kohana/URL.php} (87%) rename includes/kohana/system/classes/{kohana/utf8.php => Kohana/UTF8.php} (76%) create mode 100644 includes/kohana/system/classes/Kohana/UTF8/Exception.php rename includes/kohana/system/classes/{kohana/upload.php => Kohana/Upload.php} (65%) rename includes/kohana/system/classes/{kohana/valid.php => Kohana/Valid.php} (77%) rename includes/kohana/system/classes/{kohana/validation.php => Kohana/Validation.php} (74%) create mode 100644 includes/kohana/system/classes/Kohana/Validation/Exception.php rename includes/kohana/system/classes/{kohana/view.php => Kohana/View.php} (79%) rename includes/kohana/system/classes/{kohana/view/exception.php => Kohana/View/Exception.php} (65%) create mode 100644 includes/kohana/system/classes/Log.php create mode 100644 includes/kohana/system/classes/Log/File.php create mode 100644 includes/kohana/system/classes/Log/StdErr.php create mode 100644 includes/kohana/system/classes/Log/StdOut.php create mode 100644 includes/kohana/system/classes/Log/Syslog.php create mode 100644 includes/kohana/system/classes/Log/Writer.php create mode 100644 includes/kohana/system/classes/Model.php create mode 100644 includes/kohana/system/classes/Num.php create mode 100644 includes/kohana/system/classes/Profiler.php create mode 100644 includes/kohana/system/classes/Request.php rename includes/kohana/system/classes/{request/client.php => Request/Client.php} (50%) create mode 100644 includes/kohana/system/classes/Request/Client/Curl.php create mode 100644 includes/kohana/system/classes/Request/Client/External.php create mode 100644 includes/kohana/system/classes/Request/Client/HTTP.php rename includes/kohana/system/classes/{request/client/internal.php => Request/Client/Internal.php} (54%) create mode 100644 includes/kohana/system/classes/Request/Client/Recursion/Exception.php create mode 100644 includes/kohana/system/classes/Request/Client/Stream.php create mode 100644 includes/kohana/system/classes/Request/Exception.php create mode 100644 includes/kohana/system/classes/Response.php create mode 100644 includes/kohana/system/classes/Route.php create mode 100644 includes/kohana/system/classes/Security.php create mode 100644 includes/kohana/system/classes/Session.php create mode 100644 includes/kohana/system/classes/Session/Cookie.php create mode 100644 includes/kohana/system/classes/Session/Exception.php create mode 100644 includes/kohana/system/classes/Session/Native.php create mode 100644 includes/kohana/system/classes/Text.php create mode 100644 includes/kohana/system/classes/URL.php create mode 100644 includes/kohana/system/classes/UTF8.php create mode 100644 includes/kohana/system/classes/UTF8/Exception.php create mode 100644 includes/kohana/system/classes/Upload.php create mode 100644 includes/kohana/system/classes/Valid.php create mode 100644 includes/kohana/system/classes/Validation.php rename includes/kohana/system/classes/{validation/exception.php => Validation/Exception.php} (52%) create mode 100644 includes/kohana/system/classes/View.php create mode 100644 includes/kohana/system/classes/View/Exception.php delete mode 100644 includes/kohana/system/classes/arr.php delete mode 100644 includes/kohana/system/classes/cli.php delete mode 100644 includes/kohana/system/classes/config.php delete mode 100644 includes/kohana/system/classes/config/file.php delete mode 100644 includes/kohana/system/classes/config/reader.php delete mode 100644 includes/kohana/system/classes/controller.php delete mode 100644 includes/kohana/system/classes/controller/rest.php delete mode 100644 includes/kohana/system/classes/cookie.php delete mode 100644 includes/kohana/system/classes/date.php delete mode 100644 includes/kohana/system/classes/debug.php delete mode 100644 includes/kohana/system/classes/encrypt.php delete mode 100644 includes/kohana/system/classes/feed.php delete mode 100644 includes/kohana/system/classes/file.php delete mode 100644 includes/kohana/system/classes/form.php delete mode 100644 includes/kohana/system/classes/fragment.php delete mode 100644 includes/kohana/system/classes/html.php delete mode 100644 includes/kohana/system/classes/http.php delete mode 100644 includes/kohana/system/classes/http/exception.php delete mode 100644 includes/kohana/system/classes/http/header.php delete mode 100644 includes/kohana/system/classes/http/header/value.php delete mode 100644 includes/kohana/system/classes/http/interaction.php delete mode 100644 includes/kohana/system/classes/http/request.php delete mode 100644 includes/kohana/system/classes/http/response.php delete mode 100644 includes/kohana/system/classes/i18n.php delete mode 100644 includes/kohana/system/classes/inflector.php delete mode 100644 includes/kohana/system/classes/kohana.php delete mode 100644 includes/kohana/system/classes/kohana/cli.php delete mode 100644 includes/kohana/system/classes/kohana/config.php delete mode 100644 includes/kohana/system/classes/kohana/config/reader.php delete mode 100644 includes/kohana/system/classes/kohana/controller.php delete mode 100644 includes/kohana/system/classes/kohana/controller/rest.php delete mode 100644 includes/kohana/system/classes/kohana/exception.php delete mode 100644 includes/kohana/system/classes/kohana/http/exception.php delete mode 100644 includes/kohana/system/classes/kohana/http/exception/401.php delete mode 100644 includes/kohana/system/classes/kohana/http/exception/405.php delete mode 100644 includes/kohana/system/classes/kohana/http/header.php delete mode 100644 includes/kohana/system/classes/kohana/http/header/value.php delete mode 100644 includes/kohana/system/classes/kohana/kohana/exception.php delete mode 100644 includes/kohana/system/classes/kohana/log/writer.php delete mode 100644 includes/kohana/system/classes/kohana/request/client.php delete mode 100644 includes/kohana/system/classes/kohana/request/client/external.php delete mode 100644 includes/kohana/system/classes/kohana/request/client/internal.php delete mode 100644 includes/kohana/system/classes/kohana/validation/exception.php delete mode 100644 includes/kohana/system/classes/log.php delete mode 100644 includes/kohana/system/classes/log/file.php delete mode 100644 includes/kohana/system/classes/log/stderr.php delete mode 100644 includes/kohana/system/classes/log/stdout.php delete mode 100644 includes/kohana/system/classes/log/syslog.php delete mode 100644 includes/kohana/system/classes/log/writer.php delete mode 100644 includes/kohana/system/classes/model.php delete mode 100644 includes/kohana/system/classes/num.php delete mode 100644 includes/kohana/system/classes/profiler.php delete mode 100644 includes/kohana/system/classes/request.php delete mode 100644 includes/kohana/system/classes/request/client/external.php delete mode 100644 includes/kohana/system/classes/response.php delete mode 100644 includes/kohana/system/classes/route.php delete mode 100644 includes/kohana/system/classes/security.php delete mode 100644 includes/kohana/system/classes/session.php delete mode 100644 includes/kohana/system/classes/session/cookie.php delete mode 100644 includes/kohana/system/classes/session/native.php delete mode 100644 includes/kohana/system/classes/text.php delete mode 100644 includes/kohana/system/classes/upload.php delete mode 100644 includes/kohana/system/classes/url.php delete mode 100644 includes/kohana/system/classes/utf8.php delete mode 100644 includes/kohana/system/classes/valid.php delete mode 100644 includes/kohana/system/classes/validation.php delete mode 100644 includes/kohana/system/classes/view.php create mode 100644 includes/kohana/system/guide/kohana/config.md create mode 100644 includes/kohana/system/guide/kohana/tutorials/library-kohana.md create mode 100644 includes/kohana/system/messages/tests/validation/error_type_check.php delete mode 100644 includes/kohana/system/tests/kohana/CLITest.php create mode 100644 includes/kohana/system/tests/kohana/Config/File/ReaderTest.php create mode 100644 includes/kohana/system/tests/kohana/Config/GroupTest.php create mode 100644 includes/kohana/system/tests/kohana/HTTPTest.php delete mode 100644 includes/kohana/system/tests/kohana/Http/Header/ValueTest.php create mode 100644 includes/kohana/system/tests/kohana/request/ClientTest.php create mode 100644 includes/kohana/system/tests/kohana/request/client/ExternalTest.php create mode 100644 includes/kohana/system/tests/kohana/request/client/InternalTest.php delete mode 100644 includes/kohana/system/tests/kohana/request/client/internal.php create mode 100644 includes/kohana/vendor/autoload.php create mode 100644 includes/kohana/vendor/bin/phpunit create mode 100644 includes/kohana/vendor/composer/ClassLoader.php create mode 100644 includes/kohana/vendor/composer/autoload_classmap.php create mode 100644 includes/kohana/vendor/composer/autoload_namespaces.php create mode 100644 includes/kohana/vendor/composer/autoload_real.php create mode 100644 includes/kohana/vendor/composer/include_paths.php create mode 100644 includes/kohana/vendor/composer/installed.json create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/CONTRIBUTING.md create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/ChangeLog.markdown create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/LICENSE create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Autoload.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Autoload.php.in create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Driver.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Driver/Xdebug.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Exception.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Filter.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/Clover.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/Factory.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Dashboard.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Directory.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/File.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/coverage_bar.html.dist create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/css/bootstrap-responsive.min.css create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/css/bootstrap.min.css create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/css/style.css create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/dashboard.html.dist create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/directory.html.dist create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/directory_item.html.dist create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/file.html.dist create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/file_item.html.dist create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/img/glyphicons-halflings-white.png create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/img/glyphicons-halflings.png create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/js/bootstrap.min.js create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/js/highcharts.js create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/js/jquery.min.js create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/method_item.html.dist create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/Node.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/Node/Directory.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/Node/File.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/Node/Iterator.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/PHP.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/Text.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Util.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Util/InvalidArgumentHelper.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Version.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/README.markdown create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/PHP/CodeCoverage/FilterTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/PHP/CodeCoverage/Report/CloverTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/PHP/CodeCoverage/Report/FactoryTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/PHP/CodeCoverage/UtilTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/PHP/CodeCoverageTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/TestCase.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/BankAccount-clover.xml create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/BankAccount.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/BankAccountTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageClassExtendedTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageClassTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageFunctionTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageMethodOneLineAnnotationTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageMethodTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageNoneTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageNotPrivateTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageNotProtectedTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageNotPublicTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageNothingTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoveragePrivateTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageProtectedTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoveragePublicTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoverageTwoDefaultClassAnnotations.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoveredClass.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/CoveredFunction.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoverageClassExtendedTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoverageClassTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoverageCoversClassPublicTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoverageCoversClassTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoverageMethodTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoverageNotPrivateTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoverageNotProtectedTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoverageNotPublicTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoveragePrivateTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoverageProtectedTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoveragePublicTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NamespaceCoveredClass.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/NotExistingCoveredElementTest.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/ignored-lines-clover.xml create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/source_with_ignore.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/source_with_namespace.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/source_with_oneline_annotations.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/source_without_ignore.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/Tests/_files/source_without_namespace.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/build.xml create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/build/PHPCS/Sniffs/ControlStructures/ControlSignatureSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/build/PHPCS/Sniffs/Whitespace/ConcatenationSpacingSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/build/PHPCS/ruleset.xml create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/build/phpmd.xml create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/composer.json create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/package.xml create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/phpunit.xml.dist create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/scripts/auto_append.php create mode 100644 includes/kohana/vendor/phpunit/php-code-coverage/scripts/auto_prepend.php create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/ChangeLog.markdown create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/File/Iterator.php create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/File/Iterator/Autoload.php create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/File/Iterator/Autoload.php.in create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/File/Iterator/Facade.php create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/File/Iterator/Factory.php create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/LICENSE create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/README.markdown create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/build.xml create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/build/PHPCS/Sniffs/ControlStructures/ControlSignatureSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/build/PHPCS/Sniffs/Whitespace/ConcatenationSpacingSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/build/PHPCS/ruleset.xml create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/build/phpmd.xml create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/composer.json create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/package-composer.json create mode 100644 includes/kohana/vendor/phpunit/php-file-iterator/package.xml create mode 100644 includes/kohana/vendor/phpunit/php-text-template/ChangeLog.markdown create mode 100644 includes/kohana/vendor/phpunit/php-text-template/LICENSE create mode 100644 includes/kohana/vendor/phpunit/php-text-template/README.markdown create mode 100644 includes/kohana/vendor/phpunit/php-text-template/Text/Template.php create mode 100644 includes/kohana/vendor/phpunit/php-text-template/Text/Template/Autoload.php create mode 100644 includes/kohana/vendor/phpunit/php-text-template/Text/Template/Autoload.php.in create mode 100644 includes/kohana/vendor/phpunit/php-text-template/build.xml create mode 100644 includes/kohana/vendor/phpunit/php-text-template/build/PHPCS/Sniffs/ControlStructures/ControlSignatureSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-text-template/build/PHPCS/Sniffs/Whitespace/ConcatenationSpacingSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-text-template/build/PHPCS/ruleset.xml create mode 100644 includes/kohana/vendor/phpunit/php-text-template/build/phpmd.xml create mode 100644 includes/kohana/vendor/phpunit/php-text-template/composer.json create mode 100644 includes/kohana/vendor/phpunit/php-text-template/package-composer.json create mode 100644 includes/kohana/vendor/phpunit/php-text-template/package.xml create mode 100644 includes/kohana/vendor/phpunit/php-timer/ChangeLog.markdown create mode 100644 includes/kohana/vendor/phpunit/php-timer/LICENSE create mode 100644 includes/kohana/vendor/phpunit/php-timer/PHP/Timer.php create mode 100644 includes/kohana/vendor/phpunit/php-timer/PHP/Timer/Autoload.php create mode 100644 includes/kohana/vendor/phpunit/php-timer/PHP/Timer/Autoload.php.in create mode 100644 includes/kohana/vendor/phpunit/php-timer/README.markdown create mode 100644 includes/kohana/vendor/phpunit/php-timer/Tests/TimerTest.php create mode 100644 includes/kohana/vendor/phpunit/php-timer/build.xml create mode 100644 includes/kohana/vendor/phpunit/php-timer/build/PHPCS/Sniffs/ControlStructures/ControlSignatureSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-timer/build/PHPCS/Sniffs/Whitespace/ConcatenationSpacingSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-timer/build/PHPCS/ruleset.xml create mode 100644 includes/kohana/vendor/phpunit/php-timer/build/phpmd.xml create mode 100644 includes/kohana/vendor/phpunit/php-timer/composer.json create mode 100644 includes/kohana/vendor/phpunit/php-timer/package-composer.json create mode 100644 includes/kohana/vendor/phpunit/php-timer/package.xml create mode 100644 includes/kohana/vendor/phpunit/php-timer/phpunit.xml.dist create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/ChangeLog.markdown create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/LICENSE create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/PHP/Token.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/PHP/Token/Stream.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/PHP/Token/Stream/Autoload.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/PHP/Token/Stream/Autoload.php.in create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/PHP/Token/Stream/CachingFactory.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/README.markdown create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/Token/ClassTest.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/Token/FunctionTest.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/Token/IncludeTest.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/Token/InterfaceTest.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/Token/NamespaceTest.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/TokenTest.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/classExtendsNamespacedClass.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/classInNamespace.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/classInScopedNamespace.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/issue19.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/multipleNamespacesWithOneClassUsingBraces.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/multipleNamespacesWithOneClassUsingNonBraceSyntax.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/source.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/source2.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/source3.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/Tests/_files/source4.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/build.xml create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/build/PHPCS/Sniffs/ControlStructures/ControlSignatureSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/build/PHPCS/Sniffs/Whitespace/ConcatenationSpacingSniff.php create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/build/PHPCS/ruleset.xml create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/build/phpmd.xml create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/composer.json create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/package-composer.json create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/package.xml create mode 100644 includes/kohana/vendor/phpunit/php-token-stream/phpunit.xml.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/CONTRIBUTING.md create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/ChangeLog.markdown create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/LICENSE create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Autoload.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Autoload.php.in create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Builder/Identity.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Builder/InvocationMocker.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Builder/Match.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Builder/MethodNameMatch.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Builder/Namespace.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Builder/ParametersMatch.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Builder/Stub.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Generator.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Generator/mocked_class.tpl.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Generator/mocked_clone.tpl.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Generator/mocked_object_method.tpl.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Generator/mocked_static_method.tpl.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Generator/trait_class.tpl.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Generator/unmocked_clone.tpl.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Generator/wsdl_class.tpl.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Generator/wsdl_method.tpl.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Invocation.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Invocation/Object.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Invocation/Static.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/InvocationMocker.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Invokable.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/AnyInvokedCount.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/AnyParameters.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/Invocation.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/InvokedAtIndex.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/InvokedAtLeastOnce.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/InvokedCount.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/InvokedRecorder.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/MethodName.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/Parameters.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Matcher/StatelessInvocation.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/MockBuilder.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/MockObject.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Stub.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Stub/ConsecutiveCalls.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Stub/Exception.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Stub/MatcherCollection.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Stub/Return.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Stub/ReturnArgument.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Stub/ReturnCallback.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Stub/ReturnSelf.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Stub/ReturnValueMap.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/PHPUnit/Framework/MockObject/Verifiable.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/GeneratorTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockBuilderTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/Invocation/ObjectTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/Invocation/StaticTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/class.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/class_call_parent_clone.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/class_call_parent_constructor.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/class_dont_call_parent_clone.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/class_dont_call_parent_constructor.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/class_implementing_interface_call_parent_constructor.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/class_implementing_interface_dont_call_parent_constructor.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/class_partial.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/interface.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/invocation_object_clone_object.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/invocation_static_clone_object.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/namespaced_class.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/namespaced_class_call_parent_clone.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/namespaced_class_call_parent_constructor.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/namespaced_class_dont_call_parent_clone.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/namespaced_class_dont_call_parent_constructor.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/namespaced_class_implementing_interface_call_parent_constructor.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/namespaced_class_implementing_interface_dont_call_parent_constructor.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/namespaced_class_partial.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/namespaced_interface.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/nonexistent_class.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/nonexistent_class_with_namespace.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/nonexistent_class_with_namespace_starting_with_separator.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/wsdl_class.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/wsdl_class_namespace.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObject/wsdl_class_partial.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/MockObjectTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/_files/AbstractMockTestClass.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/_files/AnInterface.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/_files/FunctionCallback.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/_files/GoogleSearch.wsdl create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/_files/MethodCallback.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/_files/Mockable.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/_files/PartialMockTestClass.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/_files/SomeClass.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/Tests/_files/StaticMockTestClass.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/build.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/build/PHPCS/Sniffs/ControlStructures/ControlSignatureSniff.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/build/PHPCS/Sniffs/Whitespace/ConcatenationSpacingSniff.php create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/build/PHPCS/ruleset.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/build/phpmd.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/composer.json create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/package-composer.json create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/package.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit-mock-objects/phpunit.xml.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit/CONTRIBUTING.md create mode 100644 includes/kohana/vendor/phpunit/phpunit/ChangeLog.md create mode 100644 includes/kohana/vendor/phpunit/phpunit/LICENSE create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Autoload.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Autoload.php.in create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Extensions/GroupTestSuite.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Extensions/PhptTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Extensions/PhptTestCase/Logger.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Extensions/PhptTestSuite.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Extensions/RepeatedTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Extensions/TestDecorator.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Extensions/TicketListener.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Assert.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Assert/Functions.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Assert/Functions.php.in create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/AssertionFailedError.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/Array.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/DOMDocument.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/Double.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/Exception.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/MockObject.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/Numeric.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/Object.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/Resource.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/Scalar.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/SplObjectStorage.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Comparator/Type.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/ComparatorFactory.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/ComparisonFailure.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/And.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/ArrayHasKey.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/Attribute.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/Callback.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/ClassHasAttribute.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/ClassHasStaticAttribute.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/Composite.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/Count.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/Exception.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/ExceptionCode.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/ExceptionMessage.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/FileExists.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/GreaterThan.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/IsAnything.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/IsEmpty.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/IsEqual.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/IsFalse.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/IsIdentical.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/IsInstanceOf.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/IsNull.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/IsTrue.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/IsType.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/JsonMatches.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/JsonMatches/ErrorMessageProvider.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/LessThan.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/Not.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/ObjectHasAttribute.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/Or.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/PCREMatch.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/SameSize.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/StringContains.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/StringEndsWith.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/StringMatches.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/StringStartsWith.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/TraversableContains.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/TraversableContainsOnly.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Constraint/Xor.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Error.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Error/Deprecated.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Error/Notice.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Error/Warning.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Exception.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/ExpectationFailedException.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/IncompleteTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/IncompleteTestError.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/OutputError.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Process/TestCaseMethod.tpl.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/SelfDescribing.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/SkippedTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/SkippedTestError.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/SkippedTestSuiteError.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/SyntheticError.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/TestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/TestFailure.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/TestListener.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/TestResult.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/TestSuite.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/TestSuite/DataProvider.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Framework/Warning.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Runner/BaseTestRunner.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Runner/StandardTestSuiteLoader.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Runner/TestSuiteLoader.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Runner/Version.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/TextUI/Command.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/TextUI/ResultPrinter.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/TextUI/TestRunner.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Class.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Configuration.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/DeprecatedFeature.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/DeprecatedFeature/Logger.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Diff.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/ErrorHandler.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Fileloader.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Filesystem.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Filter.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Getopt.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/GlobalState.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/InvalidArgumentHelper.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Log/JSON.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Log/JUnit.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Log/TAP.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/PHP.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/PHP/Default.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/PHP/Windows.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Printer.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/String.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/TestDox/NamePrettifier.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/TestDox/ResultPrinter.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/TestDox/ResultPrinter/HTML.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/TestDox/ResultPrinter/Text.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/TestSuiteIterator.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/Type.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/PHPUnit/Util/XML.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/README.md create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Extensions/RepeatedTestTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Framework/AssertTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Framework/ComparatorTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Framework/Constraint/JsonMatches/ErrorMessageProviderTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Framework/Constraint/JsonMatchesTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Framework/ConstraintTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Framework/SuiteTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Framework/TestCaseTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Framework/TestImplementorTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Framework/TestListenerTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/1021.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/1021/Issue1021Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/523.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/523/Issue523Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/578.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/578/Issue578Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/684.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/684/Issue684Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/783.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/783/ChildSuite.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/783/OneTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/783/ParentSuite.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/783/TwoTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/244.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/244/Issue244Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/322.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/322/Issue322Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/322/phpunit322.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/433.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/433/Issue433Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/445.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/445/Issue445Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/503.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/503/Issue503Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/581.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/581/Issue581Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/74.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/74/Issue74Test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Regression/GitHub/74/NewException.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Runner/BaseTestRunnerTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/abstract-test-class.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/concrete-test-class.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/dataprovider-log-xml-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/dataprovider-log-xml.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/dataprovider-testdox.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/debug.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/default-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/default.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/dependencies-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/dependencies.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/dependencies2-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/dependencies2.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/dependencies3-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/dependencies3.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/empty-testcase.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/exception-stack.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/exclude-group-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/exclude-group.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/failure-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/failure.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/fatal-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/fatal.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/filter-class-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/filter-class.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/filter-method-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/filter-method.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/filter-no-results.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/group-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/group.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/help.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/help2.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/list-groups.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/log-json.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/log-tap.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/log-xml.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/strict-incomplete.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/strict-isolation.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/strict.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/tap.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/test-suffix-multiple.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/test-suffix-single.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/testdox-html.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/testdox-text.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/TextUI/testdox.phpt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Util/ClassTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Util/ConfigurationTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Util/DiffTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Util/TestDox/NamePrettifierTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Util/TestTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Util/TypeTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/Util/XMLTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/AbstractTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/Author.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/BankAccount.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/BankAccountTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/BankAccountTest.test.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/Book.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/Calculator.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ChangeCurrentWorkingDirectoryTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ClassWithNonPublicAttributes.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ClassWithToString.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ConcreteTest.my.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ConcreteTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/DataProviderTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/DependencyFailureTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/DependencySuccessTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/DependencyTestSuite.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/DoubleTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/EmptyTestCaseTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/Error.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ExceptionInAssertPostConditionsTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ExceptionInAssertPreConditionsTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ExceptionInSetUpTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ExceptionInTearDownTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ExceptionInTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ExceptionNamespaceTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ExceptionStack.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ExceptionTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/Failure.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/FailureTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/FatalTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/IncompleteTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/InheritedTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/JsonData/arrayObject.js create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/JsonData/simpleObject.js create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/JsonData/simpleObject2.js create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/MockRunner.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/MultiDependencyTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/NoArgTestCaseTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/NoTestCaseClass.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/NoTestCases.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/NonStatic.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/NotPublicTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/NotVoidTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/NothingTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/OneTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/OutputTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/OverrideTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/RequirementsClassDocBlockTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/RequirementsTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/SampleClass.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/SelectorAssertionsFixture.html create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/Singleton.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/StackTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/Struct.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/Success.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/TemplateMethodsTest.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/TestIterator.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ThrowExceptionTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/ThrowNoExceptionTestCase.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/WasRun.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/bar.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/configuration.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/configuration_xinclude.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/expectedFileFormat.txt create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/foo.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/structureAttributesAreSameButValuesAreNot.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/structureExpected.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/structureIgnoreTextNodes.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/structureIsSameButDataIsNot.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/structureWrongNumberOfAttributes.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/Tests/_files/structureWrongNumberOfNodes.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/build.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/PHPCS/Sniffs/ControlStructures/ControlSignatureSniff.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/PHPCS/Sniffs/Whitespace/ConcatenationSpacingSniff.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/PHPCS/ruleset.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/assertions.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/DbUnit-1.2.1.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/File_Iterator-1.3.3.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/PHPUnit_MockObject-1.2.1.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/PHPUnit_Selenium-1.2.9.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/PHP_CodeCoverage-1.2.6.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/PHP_Invoker-1.1.2.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/PHP_Timer-1.0.4.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/PHP_TokenStream-1.1.5.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/Text_Template-1.1.3.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/dependencies/Yaml-2.1.2.tgz create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/phar-autoload.php.in create mode 100644 includes/kohana/vendor/phpunit/phpunit/build/phpmd.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/composer.json create mode 100644 includes/kohana/vendor/phpunit/phpunit/composer/bin/phpunit create mode 100644 includes/kohana/vendor/phpunit/phpunit/package.xml create mode 100644 includes/kohana/vendor/phpunit/phpunit/phpdox.xml.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit/phpunit.bat create mode 100644 includes/kohana/vendor/phpunit/phpunit/phpunit.php create mode 100644 includes/kohana/vendor/phpunit/phpunit/phpunit.xml.dist create mode 100644 includes/kohana/vendor/phpunit/phpunit/phpunit.xsd create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/CHANGELOG.md create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Dumper.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Escaper.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Exception/DumpException.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Exception/ExceptionInterface.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Exception/ParseException.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Inline.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/LICENSE create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Parser.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/README.md create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/DumperTest.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsAnchorAlias.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsBasicTests.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsBlockMapping.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsDocumentSeparator.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsErrorTests.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsFlowCollections.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsFoldedScalars.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsNullsAndEmpties.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsSpecificationExamples.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/YtsTypeTransfers.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/embededPhp.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/escapedCharacters.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/index.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/sfComments.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/sfCompact.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/sfObjects.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/sfQuotes.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/sfTests.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/Fixtures/unindentedCollections.yml create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/InlineTest.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/ParserTest.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/YamlTest.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Tests/bootstrap.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Unescaper.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/Yaml.php create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/composer.json create mode 100644 includes/kohana/vendor/symfony/yaml/Symfony/Component/Yaml/phpunit.xml.dist diff --git a/.htaccess b/.htaccess index 6e25c67a..53b8ccbb 100644 --- a/.htaccess +++ b/.htaccess @@ -2,7 +2,7 @@ RewriteEngine On # Installation directory -RewriteBase /osb/ +RewriteBase / # Protect hidden files from being viewed @@ -11,7 +11,7 @@ RewriteBase /osb/ # Protect application and system files from being viewed -RewriteRule ^(?:application|modules|includes/kohana)\b.* index.php/$0 [L] +RewriteRule ^(?:application|modules|system)\b.* index.php/$0 [L] # Allow any files or directories that exist to be displayed directly RewriteCond %{REQUEST_FILENAME} !-f diff --git a/application/bootstrap.php b/application/bootstrap.php index 4841ad50..d5949260 100644 --- a/application/bootstrap.php +++ b/application/bootstrap.php @@ -3,48 +3,56 @@ // -- Environment setup -------------------------------------------------------- // Load the core Kohana class -require SYSPATH.'classes/kohana/core'.EXT; +require SYSPATH.'classes/Kohana/Core'.EXT; -if (is_file(APPPATH.'classes/kohana'.EXT)) +if (is_file(APPPATH.'classes/Kohana'.EXT)) { // Application extends the core - require APPPATH.'classes/kohana'.EXT; + require APPPATH.'classes/Kohana'.EXT; } else { // Load empty core extension - require SYSPATH.'classes/kohana'.EXT; + require SYSPATH.'classes/Kohana'.EXT; } /** * Set the default time zone. * - * @see http://kohanaframework.org/guide/using.configuration - * @see http://php.net/timezones + * @link http://kohanaframework.org/guide/using.configuration + * @link http://www.php.net/manual/timezones */ -date_default_timezone_set('Australia/Melbourne'); +date_default_timezone_set('America/Chicago'); /** * Set the default locale. * - * @see http://kohanaframework.org/guide/using.configuration - * @see http://php.net/setlocale + * @link http://kohanaframework.org/guide/using.configuration + * @link http://www.php.net/manual/function.setlocale */ setlocale(LC_ALL, 'en_US.utf-8'); /** * Enable the Kohana auto-loader. * - * @see http://kohanaframework.org/guide/using.autoloading - * @see http://php.net/spl_autoload_register + * @link http://kohanaframework.org/guide/using.autoloading + * @link http://www.php.net/manual/function.spl-autoload-register */ spl_autoload_register(array('Kohana', 'auto_load')); +/** + * Optionally, you can enable a compatibility auto-loader for use with + * older modules that have not been updated for PSR-0. + * + * It is recommended to not enable this unless absolutely necessary. + */ +//spl_autoload_register(array('Kohana', 'auto_load_lowercase')); + /** * Enable the Kohana auto-loader for unserialization. * - * @see http://php.net/spl_autoload_call - * @see http://php.net/manual/var.configuration.php#unserialize-callback-func + * @link http://www.php.net/manual/function.spl-autoload-call + * @link http://www.php.net/manual/var.configuration#unserialize-callback-func */ ini_set('unserialize_callback_func', 'spl_autoload_call'); @@ -75,16 +83,16 @@ if (isset($_SERVER['KOHANA_ENV'])) * - string index_file name of your index file, usually "index.php" index.php * - string charset internal character set used for input and output utf-8 * - string cache_dir set the internal cache directory APPPATH/cache + * - integer cache_life lifetime, in seconds, of items cached 60 * - boolean errors enable or disable error handling TRUE * - boolean profile enable or disable internal profiling TRUE * - boolean caching enable or disable internal caching FALSE + * - boolean expose set the X-Powered-By header FALSE */ Kohana::init(array( 'base_url' => '/osb', 'index_file' => '', 'caching' => TRUE, -// 'cache_dir' => '/dev/shm/lnapp', -// 'cache_life' => 180, )); /** @@ -107,6 +115,7 @@ Kohana::modules(array( // 'codebench' => SMDPATH.'codebench', // Benchmarking tool 'database' => SMDPATH.'database', // Database access // 'image' => SMDPATH.'image', // Image manipulation + // 'minion' => MODPATH.'minion', // CLI Tasks 'orm' => SMDPATH.'orm', // Object Relationship Mapping // 'unittest' => SMDPATH.'unittest', // Unit testing 'userguide' => SMDPATH.'userguide', // User guide and API documentation diff --git a/includes/kohana/.travis.yml b/includes/kohana/.travis.yml new file mode 100644 index 00000000..ba7adb47 --- /dev/null +++ b/includes/kohana/.travis.yml @@ -0,0 +1,24 @@ +language: php + +php: + - 5.3 + +before_install: + - "git submodule update --init --recursive" + +before_script: + - "pear channel-discover pear.phing.info" + - "pear install phing/phing" + - "phpenv rehash" + - "composer install" + +script: "phing test" + +notifications: + irc: + channels: + - "irc.freenode.org#kohana" + template: + - "%{repository}/%{branch} (%{commit}) - %{author}: %{message}" + - "Build details: %{build_url}" + email: false diff --git a/includes/kohana/LICENSE.md b/includes/kohana/LICENSE.md index 87af9ad5..a36fb76f 100644 --- a/includes/kohana/LICENSE.md +++ b/includes/kohana/LICENSE.md @@ -2,7 +2,7 @@ This license is a legal agreement between you and the Kohana Team for the use of Kohana Framework (the "Software"). By obtaining the Software you agree to comply with the terms and conditions of this license. -Copyright (c) 2007-2010 Kohana Team +Copyright (c) 2007-2011 Kohana Team All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/includes/kohana/README.md b/includes/kohana/README.md index e19aba89..220a9345 100644 --- a/includes/kohana/README.md +++ b/includes/kohana/README.md @@ -1,3 +1,19 @@ -# Kohana PHP Framework, version 3.1 (release) +# Kohana PHP Framework -This is the current release version of [Kohana](http://kohanaframework.org/). +[Kohana](http://kohanaframework.org/) is an elegant, open source, and object oriented HMVC framework built using PHP5, by a team of volunteers. It aims to be swift, secure, and small. + +Released under a [BSD license](http://kohanaframework.org/license), Kohana can be used legally for any open source, commercial, or personal project. + +## Documentation +Kohana's documentation can be found at which also contains an API browser. + +The `userguide` module included in all Kohana releases also allows you to view the documentation locally. Once the `userguide` module is enabled in the bootstrap, it is accessible from your site via `/index.php/guide` (or just `/guide` if you are rewriting your URLs). + +## Reporting bugs +If you've stumbled across a bug, please help us out by [reporting the bug](http://dev.kohanaframework.org/projects/kohana3/) you have found. Simply log in or register and submit a new issue, leaving as much information about the bug as possible, e.g. + +* Steps to reproduce +* Expected result +* Actual result + +This will help us to fix the bug as quickly as possible, and if you'd like to fix it yourself feel free to [fork us on GitHub](https://github.com/kohana) and submit a pull request! diff --git a/includes/kohana/composer.json b/includes/kohana/composer.json new file mode 100644 index 00000000..a473df1d --- /dev/null +++ b/includes/kohana/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "phpunit/phpunit": "3.7.*" + } +} diff --git a/includes/kohana/composer.lock b/includes/kohana/composer.lock new file mode 100644 index 00000000..3fe1de8a --- /dev/null +++ b/includes/kohana/composer.lock @@ -0,0 +1,413 @@ +{ + "hash": "11774bed2716724738d66bb56a8a1508", + "packages": [ + { + "name": "phpunit/php-code-coverage", + "version": "1.2.6", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "1.2.6" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/php-code-coverage/zipball/1.2.6", + "reference": "1.2.6", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": ">=1.3.0@stable", + "phpunit/php-token-stream": ">=1.1.3@stable", + "phpunit/php-text-template": ">=1.1.1@stable" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.0.5" + }, + "time": "2012-10-16 22:34:13", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "testing", + "coverage", + "xunit" + ] + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.3.3", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "1.3.3" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/php-file-iterator/zipball/1.3.3", + "reference": "1.3.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2012-10-11 04:44:38", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "File/" + ] + }, + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "filesystem", + "iterator" + ] + }, + { + "name": "phpunit/php-text-template", + "version": "1.1.3", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/php-text-template.git", + "reference": "1.1.3" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/php-text-template/zipball/1.1.3", + "reference": "1.1.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2012-10-11 04:48:39", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "Text/" + ] + }, + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "template" + ] + }, + { + "name": "phpunit/php-timer", + "version": "1.0.4", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/php-timer.git", + "reference": "1.0.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/php-timer/zipball/1.0.4", + "reference": "1.0.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2012-10-11 04:45:58", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "timer" + ] + }, + { + "name": "phpunit/php-token-stream", + "version": "1.1.5", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1.1.5" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/php-token-stream/zipball/1.1.5", + "reference": "1.1.5", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "time": "2012-10-11 04:47:14", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "tokenizer" + ] + }, + { + "name": "phpunit/phpunit", + "version": "3.7.8", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/phpunit.git", + "reference": "3.7.8" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/phpunit/zipball/3.7.8", + "reference": "3.7.8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": ">=1.3.1", + "phpunit/php-text-template": ">=1.1.1", + "phpunit/php-code-coverage": ">=1.2.1", + "phpunit/php-timer": ">=1.0.2", + "phpunit/phpunit-mock-objects": ">=1.2.0", + "symfony/yaml": ">=2.1.0", + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*" + }, + "suggest": { + "phpunit/php-invoker": ">=1.1.0", + "ext-json": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*" + }, + "time": "2012-10-16 22:37:08", + "bin": [ + "composer/bin/phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "testing", + "phpunit", + "xunit" + ] + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "1.2.1", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "1.2.1" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects/zipball/1.2.1", + "reference": "1.2.1", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-text-template": ">=1.1.1@stable", + "ext-reflection": "*", + "ext-spl": "*" + }, + "suggest": { + "ext-soap": "*" + }, + "time": "2012-10-05 00:00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "mock", + "xunit" + ] + }, + { + "name": "symfony/yaml", + "version": "v2.1.2", + "target-dir": "Symfony/Component/Yaml", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml", + "reference": "v2.1.0-RC2" + }, + "dist": { + "type": "zip", + "url": "https://github.com/symfony/Yaml/zipball/v2.1.0-RC2", + "reference": "v2.1.0-RC2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2012-08-22 06:48:41", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Yaml": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "http://symfony.com" + } + ], + "packages-dev": null, + "aliases": [ + + ], + "minimum-stability": "stable", + "stability-flags": [ + + ] +} diff --git a/includes/kohana/composer.phar b/includes/kohana/composer.phar new file mode 100644 index 0000000000000000000000000000000000000000..7c838090f118dec3740491a8e73eb5695a91a314 GIT binary patch literal 628852 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%0A@7eaI;k>%f6rzV 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 NV}KpU;q9>SA~-(L{H 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

Sw)>q}eC&tdn6;Y02 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 CDkT2^ literal 0 HcmV?d00001 diff --git a/includes/kohana/install.php b/includes/kohana/install.php index cbb0a4c5..f6541778 100644 --- a/includes/kohana/install.php +++ b/includes/kohana/install.php @@ -1,4 +1,20 @@ - + @@ -41,15 +57,15 @@ - =')): ?> + =')): ?> - + - + diff --git a/includes/kohana/modules/auth/classes/Auth.php b/includes/kohana/modules/auth/classes/Auth.php new file mode 100644 index 00000000..8d31fad6 --- /dev/null +++ b/includes/kohana/modules/auth/classes/Auth.php @@ -0,0 +1,3 @@ +load('auth'); if ( ! $type = $config->get('driver')) { @@ -47,6 +47,7 @@ abstract class Kohana_Auth { /** * Loads Session and configuration options. * + * @param array $config Config Options * @return void */ public function __construct($config = array()) @@ -54,7 +55,7 @@ abstract class Kohana_Auth { // Save the config in the object $this->_config = $config; - $this->_session = Session::instance(); + $this->_session = Session::instance($this->_config['session_type']); } abstract protected function _login($username, $password, $remember); @@ -67,6 +68,7 @@ abstract class Kohana_Auth { * Gets the currently logged in user from the session. * Returns NULL if no user is currently logged in. * + * @param mixed $default Default value to return if the user is currently not logged in. * @return mixed */ public function get_user($default = NULL) @@ -77,9 +79,9 @@ abstract class Kohana_Auth { /** * Attempt to log in a user by using an ORM object and plain-text password. * - * @param string username to log in - * @param string password to check against - * @param boolean enable autologin + * @param string $username Username to log in + * @param string $password Password to check against + * @param boolean $remember Enable autologin * @return boolean */ public function login($username, $password, $remember = FALSE) @@ -87,20 +89,14 @@ abstract class Kohana_Auth { if (empty($password)) return FALSE; - if (is_string($password)) - { - // Create a hashed password - $password = $this->hash($password); - } - return $this->_login($username, $password, $remember); } /** * Log out a user by removing the related session variables. * - * @param boolean completely destroy the session - * @param boolean remove all tokens for user + * @param boolean $destroy Completely destroy the session + * @param boolean $logout_all Remove all tokens for user * @return boolean */ public function logout($destroy = FALSE, $logout_all = FALSE) @@ -127,7 +123,7 @@ abstract class Kohana_Auth { * Check if there is an active session. Optionally allows checking for a * specific role. * - * @param string role name + * @param string $role role name * @return mixed */ public function logged_in($role = NULL) @@ -140,7 +136,7 @@ abstract class Kohana_Auth { * method is deprecated, [Auth::hash] should be used instead. * * @deprecated - * @param string plaintext password + * @param string $password Plaintext password */ public function hash_password($password) { @@ -150,7 +146,7 @@ abstract class Kohana_Auth { /** * Perform a hmac hash, using the configured method. * - * @param string string to hash + * @param string $str string to hash * @return string */ public function hash($str) diff --git a/includes/kohana/modules/auth/classes/kohana/auth/file.php b/includes/kohana/modules/auth/classes/Kohana/Auth/File.php similarity index 75% rename from includes/kohana/modules/auth/classes/kohana/auth/file.php rename to includes/kohana/modules/auth/classes/Kohana/Auth/File.php index 31ca0c3b..8640fdec 100644 --- a/includes/kohana/modules/auth/classes/kohana/auth/file.php +++ b/includes/kohana/modules/auth/classes/Kohana/Auth/File.php @@ -1,11 +1,11 @@ -hash($password); + } + if (isset($this->_users[$username]) AND $this->_users[$username] === $password) { // Complete the login @@ -47,7 +53,7 @@ class Kohana_Auth_File extends Auth { /** * Forces a user to be logged in, without specifying a password. * - * @param mixed username + * @param mixed $username Username * @return boolean */ public function force_login($username) @@ -59,7 +65,7 @@ class Kohana_Auth_File extends Auth { /** * Get the stored password for a username. * - * @param mixed username + * @param mixed $username Username * @return string */ public function password($username) @@ -70,7 +76,7 @@ class Kohana_Auth_File extends Auth { /** * Compare password with original (plain text). Works for current (logged in) user * - * @param string $password + * @param string $password Password * @return boolean */ public function check_password($password) @@ -85,4 +91,4 @@ class Kohana_Auth_File extends Auth { return ($password === $this->password($username)); } -} // End Auth File \ No newline at end of file +} // End Auth File diff --git a/includes/kohana/modules/auth/classes/auth.php b/includes/kohana/modules/auth/classes/auth.php deleted file mode 100644 index a02b1e5f..00000000 --- a/includes/kohana/modules/auth/classes/auth.php +++ /dev/null @@ -1,3 +0,0 @@ - 'file', + 'driver' => 'File', 'hash_method' => 'sha256', 'hash_key' => NULL, 'lifetime' => 1209600, + 'session_type' => Session::$default, 'session_key' => 'auth_user', // Username/password combinations for the Auth File driver diff --git a/includes/kohana/modules/auth/config/userguide.php b/includes/kohana/modules/auth/config/userguide.php new file mode 100644 index 00000000..425e27cd --- /dev/null +++ b/includes/kohana/modules/auth/config/userguide.php @@ -0,0 +1,23 @@ + array( + + // This should be the path to this modules userguide pages, without the 'guide/'. Ex: '/guide/modulename/' would be 'modulename' + 'auth' => array( + + // Whether this modules userguide pages should be shown + 'enabled' => TRUE, + + // The name that should show up on the userguide index page + 'name' => 'Auth', + + // A short description of this module, shown on the index page + 'description' => 'User authentication and authorization.', + + // Copyright message, shown in the footer for this module + 'copyright' => '© 2008–2012 Kohana Team', + ) + ) +); \ No newline at end of file diff --git a/includes/kohana/modules/auth/guide/auth/config.md b/includes/kohana/modules/auth/guide/auth/config.md index e69de29b..e411fa06 100644 --- a/includes/kohana/modules/auth/guide/auth/config.md +++ b/includes/kohana/modules/auth/guide/auth/config.md @@ -0,0 +1,13 @@ +# Configuration + +The default configuration file is located in `MODPATH/auth/config/auth.php`. You should copy this file to `APPPATH/config/auth.php` and make changes there, in keeping with the [cascading filesystem](../kohana/files). + +[Config merging](../kohana/config#config-merging) allows these default configuration settings to apply if you don't overwrite them in your application configuration file. + +Name | Type | Default | Description +-----|------|---------|------------ +driver | `string` | file | The name of the auth driver to use. +hash_method | `string` | sha256 | The hashing function to use on the passwords. +hash_key | `string` | NULL | The key to use when hashing the password. +session_type | `string` | [Session::$default] | The type of session to use when storing the auth user. +session_key | `string` | auth_user | The name of the session variable used to save the user. diff --git a/includes/kohana/modules/auth/guide/auth/driver/develop.md b/includes/kohana/modules/auth/guide/auth/driver/develop.md new file mode 100644 index 00000000..a69a08c5 --- /dev/null +++ b/includes/kohana/modules/auth/guide/auth/driver/develop.md @@ -0,0 +1,79 @@ +# Developing Drivers + +## Real World Example + +Sometimes the best way to learn is to jump right in and read the code from another module. The [ORM](https://github.com/kohana/orm/blob/3.2/develop/classes/kohana/auth/orm.php) module comes with an auth driver you can learn from. + +[!!] We will be developing an `example` driver. In your own driver you will substitute `example` with your driver name. + +This example file would be saved at `APPPATH/classes/auth/example.php` (or `MODPATH` if you are creating a module). + +--- + +## Quick Example + +First we will show you a quick example and then break down what is going on. + +~~~ +class Auth_Example extends Auth +{ + protected function _login($username, $password, $remember) + { + // Do username/password check here + } + + public function password($username) + { + // Return the password for the username + } + + public function check_password($password) + { + // Check to see if the logged in user has the given password + } + + public function logged_in($role = NULL) + { + // Check to see if the user is logged in, and if $role is set, has all roles + } + + public function get_user($default = NULL) + { + // Get the logged in user, or return the $default if a user is not found + } +} +~~~ + +## Extending Auth + +All drivers must extend the [Auth] class. + + class Auth_Example extends Auth + +## Abstract Methods + +The `Auth` class has 3 abstract methods that must be defined in your new driver. + +~~~ +abstract protected function _login($username, $password, $remember); + +abstract public function password($username); + +abstract public function check_password($user); +~~~ + +## Extending Functionality + +Given that every auth system is going to check if users exist and if they have roles or not you will more than likely have to change some default functionality. + +Here are a few functions that you should pay attention to. + +~~~ +public function logged_in($role = NULL) + +public function get_user($default = NULL) +~~~ + +## Activating the Driver + +After you create your driver you will want to use it. It is a easy as setting the `driver` [configuration](config) option to the name of your driver (in our case `example`). diff --git a/includes/kohana/modules/auth/guide/auth/driver/file.md b/includes/kohana/modules/auth/guide/auth/driver/file.md new file mode 100644 index 00000000..7a6fa09e --- /dev/null +++ b/includes/kohana/modules/auth/guide/auth/driver/file.md @@ -0,0 +1,19 @@ +# File Driver + +The [Auth::File] driver is included with the auth module. + +Below are additional configuration options that can be set for this driver. + +Name | Type | Default | Description +-----|------|---------|------------- +users | `array` | array() | A user => password (_hashed_) array of all the users in your application + +## Forcing Login + +[Auth_File::force_login] allows you to force a user login without a password. + +~~~ +// Force the user with a username of admin to be logged into your application +Auth::instance()->force_login('admin'); +$user = Auth::instance()->get_user(); +~~~ diff --git a/includes/kohana/modules/auth/guide/auth/edit.md b/includes/kohana/modules/auth/guide/auth/edit.md deleted file mode 100644 index e69de29b..00000000 diff --git a/includes/kohana/modules/auth/guide/auth/index.md b/includes/kohana/modules/auth/guide/auth/index.md index e69de29b..18127e6d 100644 --- a/includes/kohana/modules/auth/guide/auth/index.md +++ b/includes/kohana/modules/auth/guide/auth/index.md @@ -0,0 +1,19 @@ +# Auth + +User authentication and authorization is provided by the auth module. + +The auth module is included with Kohana, but needs to be enabled before you can use it. To enable, open your `application/bootstrap.php` file and modify the call to [Kohana::modules] by including the auth module like so: + +~~~ +Kohana::modules(array( + ... + 'auth' => MODPATH.'auth', + ... +)); +~~~ + +Next, you will then need to [configure](config) the auth module. + +The auth module provides the [Auth::File] driver for you. There is also an auth driver included with the ORM module. + +As your application needs change you may need to find another driver or [develop](driver/develop) your own. diff --git a/includes/kohana/modules/auth/guide/auth/login.md b/includes/kohana/modules/auth/guide/auth/login.md index e69de29b..1207b5d8 100644 --- a/includes/kohana/modules/auth/guide/auth/login.md +++ b/includes/kohana/modules/auth/guide/auth/login.md @@ -0,0 +1,62 @@ +# Log in and out + +The auth module provides methods to help you log users in and out of your application. + +## Log in + +The [Auth::login] method handles the login. + +~~~ +// Handled from a form with inputs with names email / password +$post = $this->request->post(); +$success = Auth::instance()->login($post['email'], $post['password']); + +if ($success) +{ + // Login successful, send to app +} +else +{ + // Login failed, send back to form with error message +} +~~~ + +## Logged in User + +There are two ways to check if a user is logged in. If you just need to check if the user is logged in use [Auth::logged_in]. + +~~~ +if (Auth::instance()->logged_in()) +{ + // User is logged in, continue on +} +else +{ + // User isn't logged in, redirect to the login form. +} +~~~ + +You can also get the logged in user object by using [Auth::get_user]. If the user is null, then no user was found. + +~~~ +$user = Auth::instance()->get_user(); + +// Check for a user (NULL if not user is found) +if ($user !== null) +{ + // User is found, continue on +} +else +{ + // User was not found, redirect to the login form +} +~~~ + +## Log out + +The [Auth::logout] method will take care of logging out a user. + +~~~ +Auth::instance()->logout(); +// Redirect the user back to login page +~~~ diff --git a/includes/kohana/modules/auth/guide/auth/menu.md b/includes/kohana/modules/auth/guide/auth/menu.md index 1708caa9..23fc0eea 100644 --- a/includes/kohana/modules/auth/guide/auth/menu.md +++ b/includes/kohana/modules/auth/guide/auth/menu.md @@ -1,7 +1,6 @@ ## [Auth]() -- [Config](config) -- [User Model](user) -- [Register Users](register) +- [Configuration](config) - [Log in and out](login) -- [Edit User](edit) -- [Using Roles](roles) +- Drivers + - [File](driver/file) + - [Developing](driver/develop) diff --git a/includes/kohana/modules/auth/guide/auth/register.md b/includes/kohana/modules/auth/guide/auth/register.md deleted file mode 100644 index e69de29b..00000000 diff --git a/includes/kohana/modules/auth/guide/auth/roles.md b/includes/kohana/modules/auth/guide/auth/roles.md deleted file mode 100644 index e69de29b..00000000 diff --git a/includes/kohana/modules/auth/guide/auth/user.md b/includes/kohana/modules/auth/guide/auth/user.md deleted file mode 100644 index e69de29b..00000000 diff --git a/includes/kohana/modules/cache/README.md b/includes/kohana/modules/cache/README.md index cd04fbde..7acff9cf 100644 --- a/includes/kohana/modules/cache/README.md +++ b/includes/kohana/modules/cache/README.md @@ -9,13 +9,11 @@ Supported cache solutions Currently this module supports the following cache methods. 1. APC -2. eAccelerator -3. Memcache -4. Memcached-tags (Supports tags) -5. SQLite (Supports tags) -6. File -7. Xcache -8. Wincache +2. Memcache +3. Memcached-tags (Supports tags) +4. SQLite (Supports tags) +5. File +6. Wincache Planned support --------------- diff --git a/includes/kohana/modules/cache/classes/cache.php b/includes/kohana/modules/cache/classes/Cache.php similarity index 100% rename from includes/kohana/modules/cache/classes/cache.php rename to includes/kohana/modules/cache/classes/Cache.php diff --git a/includes/kohana/modules/cache/classes/cache/apc.php b/includes/kohana/modules/cache/classes/Cache/Apc.php similarity index 100% rename from includes/kohana/modules/cache/classes/cache/apc.php rename to includes/kohana/modules/cache/classes/Cache/Apc.php diff --git a/includes/kohana/modules/cache/classes/cache/eaccelerator.php b/includes/kohana/modules/cache/classes/Cache/Arithmetic.php similarity index 50% rename from includes/kohana/modules/cache/classes/cache/eaccelerator.php rename to includes/kohana/modules/cache/classes/Cache/Arithmetic.php index 0aa168d8..ab832c3a 100644 --- a/includes/kohana/modules/cache/classes/cache/eaccelerator.php +++ b/includes/kohana/modules/cache/classes/Cache/Arithmetic.php @@ -1,3 +1,3 @@ array( // Default group * 'driver' => 'memcache', // using Memcache driver @@ -48,30 +48,30 @@ * 'compression' => FALSE, // Use compression? * ), * ) - * + * * In cases where only one cache group is required, if the group is named `default` there is * no need to pass the group name when instantiating a cache instance. - * + * * #### General cache group configuration settings - * + * * Below are the settings available to all types of cache driver. - * + * * Name | Required | Description * -------------- | -------- | --------------------------------------------------------------- * driver | __YES__ | (_string_) The driver type to use - * + * * Details of the settings specific to each driver are available within the drivers documentation. - * + * * ### System requirements - * + * * * Kohana 3.0.x * * PHP 5.2.4 or greater - * + * * @package Kohana/Cache * @category Base * @version 2.0 * @author Kohana Team - * @copyright (c) 2009-2010 Kohana Team + * @copyright (c) 2009-2012 Kohana Team * @license http://kohanaphp.com/license */ abstract class Kohana_Cache { @@ -91,19 +91,19 @@ abstract class Kohana_Cache { /** * Creates a singleton of a Kohana Cache group. If no group is supplied * the __default__ cache group is used. - * + * * // Create an instance of the default group * $default_group = Cache::instance(); - * + * * // Create an instance of a group * $foo_group = Cache::instance('foo'); - * + * * // Access an instantiated group directly * $foo_group = Cache::$instances['default']; * - * @param string the name of the cache group to use [Optional] - * @return Kohana_Cache - * @throws Kohana_Cache_Exception + * @param string $group the name of the cache group to use [Optional] + * @return Cache + * @throws Cache_Exception */ public static function instance($group = NULL) { @@ -120,11 +120,14 @@ abstract class Kohana_Cache { return Cache::$instances[$group]; } - $config = Kohana::config('cache'); + $config = Kohana::$config->load('cache'); if ( ! $config->offsetExists($group)) { - throw new Kohana_Cache_Exception('Failed to load Kohana Cache group: :group', array(':group' => $group)); + throw new Cache_Exception( + 'Failed to load Kohana Cache group: :group', + array(':group' => $group) + ); } $config = $config->get($group); @@ -140,59 +143,100 @@ abstract class Kohana_Cache { /** * @var Config */ - protected $_config; + protected $_config = array(); /** * Ensures singleton pattern is observed, loads the default expiry - * - * @param array configuration + * + * @param array $config configuration */ protected function __construct(array $config) { - $this->_config = $config; + $this->config($config); + } + + /** + * Getter and setter for the configuration. If no argument provided, the + * current configuration is returned. Otherwise the configuration is set + * to this class. + * + * // Overwrite all configuration + * $cache->config(array('driver' => 'memcache', '...')); + * + * // Set a new configuration setting + * $cache->config('servers', array( + * 'foo' => 'bar', + * '...' + * )); + * + * // Get a configuration setting + * $servers = $cache->config('servers); + * + * @param mixed key to set to array, either array or config path + * @param mixed value to associate with key + * @return mixed + */ + public function config($key = NULL, $value = NULL) + { + if ($key === NULL) + return $this->_config; + + if (is_array($key)) + { + $this->_config = $key; + } + else + { + if ($value === NULL) + return Arr::get($this->_config, $key); + + $this->_config[$key] = $value; + } + + return $this; } /** * Overload the __clone() method to prevent cloning * * @return void - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ - public function __clone() + final public function __clone() { - throw new Kohana_Cache_Exception('Cloning of Kohana_Cache objects is forbidden'); + throw new Cache_Exception('Cloning of Kohana_Cache objects is forbidden'); } /** * Retrieve a cached value entry by id. - * + * * // Retrieve cache entry from default group * $data = Cache::instance()->get('foo'); - * + * * // Retrieve cache entry from default group and return 'bar' if miss * $data = Cache::instance()->get('foo', 'bar'); - * + * * // Retrieve cache entry from memcache group * $data = Cache::instance('memcache')->get('foo'); * - * @param string id of cache to entry - * @param string default value to return if cache miss + * @param string $id id of cache to entry + * @param string $default default value to return if cache miss * @return mixed - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ abstract public function get($id, $default = NULL); /** * Set a value to cache with id and lifetime - * + * * $data = 'bar'; - * + * * // Set 'bar' to 'foo' in default group, using default expiry * Cache::instance()->set('foo', $data); - * + * * // Set 'bar' to 'foo' in default group for 30 seconds * Cache::instance()->set('foo', $data, 30); - * + * * // Set 'bar' to 'foo' in memcache group for 10 minutes * if (Cache::instance('memcache')->set('foo', $data, 600)) * { @@ -200,37 +244,37 @@ abstract class Kohana_Cache { * return * } * - * @param string id of cache entry - * @param string data to set to cache - * @param integer lifetime in seconds + * @param string $id id of cache entry + * @param string $data data to set to cache + * @param integer $lifetime lifetime in seconds * @return boolean */ abstract public function set($id, $data, $lifetime = 3600); /** * Delete a cache entry based on id - * + * * // Delete 'foo' entry from the default group * Cache::instance()->delete('foo'); - * + * * // Delete 'foo' entry from the memcache group * Cache::instance('memcache')->delete('foo') * - * @param string id to remove from cache + * @param string $id id to remove from cache * @return boolean */ abstract public function delete($id); /** * Delete all cache entries. - * + * * Beware of using this method when * using shared memory cache systems, as it will wipe every * entry within the system for all clients. - * + * * // Delete all cache entries in the default group * Cache::instance()->delete_all(); - * + * * // Delete all cache entries in the memcache group * Cache::instance('memcache')->delete_all(); * @@ -243,8 +287,8 @@ abstract class Kohana_Cache { * * // Sanitize a cache id * $id = $this->_sanitize_id($id); - * - * @param string id of cache to sanitize + * + * @param string $id id of cache to sanitize * @return string */ protected function _sanitize_id($id) diff --git a/includes/kohana/modules/cache/classes/kohana/cache/apc.php b/includes/kohana/modules/cache/classes/Kohana/Cache/Apc.php similarity index 68% rename from includes/kohana/modules/cache/classes/kohana/cache/apc.php rename to includes/kohana/modules/cache/classes/Kohana/Cache/Apc.php index 78765fe7..acefcc83 100644 --- a/includes/kohana/modules/cache/classes/kohana/cache/apc.php +++ b/includes/kohana/modules/cache/classes/Kohana/Cache/Apc.php @@ -2,54 +2,54 @@ /** * [Kohana Cache](api/Kohana_Cache) APC driver. Provides an opcode based * driver for the Kohana Cache library. - * + * * ### Configuration example - * + * * Below is an example of an _apc_ server configuration. - * + * * return array( * 'apc' => array( // Driver group * 'driver' => 'apc', // using APC driver * ), * ) - * + * * In cases where only one cache group is required, if the group is named `default` there is * no need to pass the group name when instantiating a cache instance. - * + * * #### General cache group configuration settings - * + * * Below are the settings available to all types of cache driver. - * + * * Name | Required | Description * -------------- | -------- | --------------------------------------------------------------- * driver | __YES__ | (_string_) The driver type to use - * + * * ### System requirements - * + * * * Kohana 3.0.x * * PHP 5.2.4 or greater * * APC PHP extension - * + * * @package Kohana/Cache * @category Base * @author Kohana Team - * @copyright (c) 2009-2010 Kohana Team + * @copyright (c) 2009-2012 Kohana Team * @license http://kohanaphp.com/license */ -class Kohana_Cache_Apc extends Cache { +class Kohana_Cache_Apc extends Cache implements Cache_Arithmetic { /** * Check for existence of the APC extension This method cannot be invoked externally. The driver must * be instantiated using the `Cache::instance()` method. * - * @param array configuration - * @throws Kohana_Cache_Exception + * @param array $config configuration + * @throws Cache_Exception */ protected function __construct(array $config) { if ( ! extension_loaded('apc')) { - throw new Kohana_Cache_Exception('PHP APC extension is not available.'); + throw new Cache_Exception('PHP APC extension is not available.'); } parent::__construct($config); @@ -57,17 +57,17 @@ class Kohana_Cache_Apc extends Cache { /** * Retrieve a cached value entry by id. - * + * * // Retrieve cache entry from apc group * $data = Cache::instance('apc')->get('foo'); - * + * * // Retrieve cache entry from apc group and return 'bar' if miss * $data = Cache::instance('apc')->get('foo', 'bar'); * - * @param string id of cache to entry - * @param string default value to return if cache miss + * @param string $id id of cache to entry + * @param string $default default value to return if cache miss * @return mixed - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function get($id, $default = NULL) { @@ -78,18 +78,18 @@ class Kohana_Cache_Apc extends Cache { /** * Set a value to cache with id and lifetime - * + * * $data = 'bar'; - * + * * // Set 'bar' to 'foo' in apc group, using default expiry * Cache::instance('apc')->set('foo', $data); - * + * * // Set 'bar' to 'foo' in apc group for 30 seconds * Cache::instance('apc')->set('foo', $data, 30); * - * @param string id of cache entry - * @param string data to set to cache - * @param integer lifetime in seconds + * @param string $id id of cache entry + * @param string $data data to set to cache + * @param integer $lifetime lifetime in seconds * @return boolean */ public function set($id, $data, $lifetime = NULL) @@ -104,11 +104,11 @@ class Kohana_Cache_Apc extends Cache { /** * Delete a cache entry based on id - * + * * // Delete 'foo' entry from the apc group * Cache::instance('apc')->delete('foo'); * - * @param string id to remove from cache + * @param string $id id to remove from cache * @return boolean */ public function delete($id) @@ -118,11 +118,11 @@ class Kohana_Cache_Apc extends Cache { /** * Delete all cache entries. - * + * * Beware of using this method when * using shared memory cache systems, as it will wipe every * entry within the system for all clients. - * + * * // Delete all cache entries in the apc group * Cache::instance('apc')->delete_all(); * @@ -132,4 +132,35 @@ class Kohana_Cache_Apc extends Cache { { return apc_clear_cache('user'); } -} + + /** + * Increments a given value by the step value supplied. + * Useful for shared counters and other persistent integer based + * tracking. + * + * @param string id of cache entry to increment + * @param int step value to increment by + * @return integer + * @return boolean + */ + public function increment($id, $step = 1) + { + return apc_inc($id, $step); + } + + /** + * Decrements a given value by the step value supplied. + * Useful for shared counters and other persistent integer based + * tracking. + * + * @param string id of cache entry to decrement + * @param int step value to decrement by + * @return integer + * @return boolean + */ + public function decrement($id, $step = 1) + { + return apc_dec($id, $step); + } + +} // End Kohana_Cache_Apc diff --git a/includes/kohana/modules/cache/classes/Kohana/Cache/Arithmetic.php b/includes/kohana/modules/cache/classes/Kohana/Cache/Arithmetic.php new file mode 100644 index 00000000..1bdfb311 --- /dev/null +++ b/includes/kohana/modules/cache/classes/Kohana/Cache/Arithmetic.php @@ -0,0 +1,39 @@ + array( // File driver group * 'driver' => 'file', // using File driver * 'cache_dir' => APPPATH.'cache/.kohana_cache', // Cache location * ), * ) - * + * * In cases where only one cache group is required, if the group is named `default` there is * no need to pass the group name when instantiating a cache instance. - * + * * #### General cache group configuration settings - * + * * Below are the settings available to all types of cache driver. - * + * * Name | Required | Description * -------------- | -------- | --------------------------------------------------------------- * driver | __YES__ | (_string_) The driver type to use * cache_dir | __NO__ | (_string_) The cache directory to use for this cache instance - * + * * ### System requirements - * + * * * Kohana 3.0.x * * PHP 5.2.4 or greater - * + * * @package Kohana/Cache * @category Base * @author Kohana Team - * @copyright (c) 2009-2010 Kohana Team + * @copyright (c) 2009-2012 Kohana Team * @license http://kohanaphp.com/license */ -class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { +class Kohana_Cache_File extends Cache implements Cache_GarbageCollect { /** * Creates a hashed filename based on the string. This is used * to create shorter unique IDs for each cache filename. - * + * * // Create the cache filename * $filename = Cache_File::filename($this->_sanitize_id($id)); * - * @param string string to hash into filename + * @param string $string string to hash into filename * @return string */ protected static function filename($string) { - return sha1($string).'.json'; + return sha1($string).'.cache'; } /** @@ -64,8 +64,8 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { * Constructs the file cache driver. This method cannot be invoked externally. The file cache driver must * be instantiated using the `Cache::instance()` method. * - * @param array config - * @throws Kohana_Cache_Exception + * @param array $config config + * @throws Cache_Exception */ protected function __construct(array $config) { @@ -91,35 +91,35 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { // If the defined directory is a file, get outta here if ($this->_cache_dir->isFile()) { - throw new Kohana_Cache_Exception('Unable to create cache directory as a file already exists : :resource', array(':resource' => $this->_cache_dir->getRealPath())); + throw new Cache_Exception('Unable to create cache directory as a file already exists : :resource', array(':resource' => $this->_cache_dir->getRealPath())); } // Check the read status of the directory if ( ! $this->_cache_dir->isReadable()) { - throw new Kohana_Cache_Exception('Unable to read from the cache directory :resource', array(':resource' => $this->_cache_dir->getRealPath())); + throw new Cache_Exception('Unable to read from the cache directory :resource', array(':resource' => $this->_cache_dir->getRealPath())); } // Check the write status of the directory if ( ! $this->_cache_dir->isWritable()) { - throw new Kohana_Cache_Exception('Unable to write to the cache directory :resource', array(':resource' => $this->_cache_dir->getRealPath())); + throw new Cache_Exception('Unable to write to the cache directory :resource', array(':resource' => $this->_cache_dir->getRealPath())); } } /** * Retrieve a cached value entry by id. - * + * * // Retrieve cache entry from file group * $data = Cache::instance('file')->get('foo'); - * + * * // Retrieve cache entry from file group and return 'bar' if miss * $data = Cache::instance('file')->get('foo', 'bar'); * - * @param string id of cache to entry - * @param string default value to return if cache miss + * @param string $id id of cache to entry + * @param string $default default value to return if cache miss * @return mixed - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function get($id, $default = NULL) { @@ -140,34 +140,44 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { } else { - // Open the file and extract the json - $json = $file->openFile()->current(); + // Open the file and parse data + $created = $file->getMTime(); + $data = $file->openFile(); + $lifetime = $data->fgets(); - // Decode the json into PHP object - $data = json_decode($json); + // If we're at the EOF at this point, corrupted! + if ($data->eof()) + { + throw new Cache_Exception(__METHOD__.' corrupted cache file!'); + } + + $cache = ''; + + while ($data->eof() === FALSE) + { + $cache .= $data->fgets(); + } // Test the expiry - if ($data->expiry < time()) + if (($created + (int) $lifetime) < time()) { // Delete the file $this->_delete_file($file, NULL, TRUE); - - // Return default value return $default; } else { - return ($data->type === 'string') ? $data->payload : unserialize($data->payload); + return unserialize($cache); } } - + } catch (ErrorException $e) { // Handle ErrorException caused by failed unserialization if ($e->getCode() === E_NOTICE) { - throw new Kohana_Cache_Exception(__METHOD__.' failed to unserialize cached object with message : '.$e->getMessage()); + throw new Cache_Exception(__METHOD__.' failed to unserialize cached object with message : '.$e->getMessage()); } // Otherwise throw the exception @@ -177,18 +187,18 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { /** * Set a value to cache with id and lifetime - * + * * $data = 'bar'; - * + * * // Set 'bar' to 'foo' in file group, using default expiry * Cache::instance('file')->set('foo', $data); - * + * * // Set 'bar' to 'foo' in file group for 30 seconds * Cache::instance('file')->set('foo', $data, 30); * - * @param string id of cache entry - * @param string data to set to cache - * @param integer lifetime in seconds + * @param string $id id of cache entry + * @param string $data data to set to cache + * @param integer $lifetime lifetime in seconds * @return boolean */ public function set($id, $data, $lifetime = NULL) @@ -209,10 +219,10 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { // If the directory path is not a directory if ( ! $dir->isDir()) { - // Create the directory + // Create the directory if ( ! mkdir($directory, 0777, TRUE)) { - throw new Kohana_Cache_Exception(__METHOD__.' unable to create directory : :directory', array(':directory' => $directory)); + throw new Cache_Exception(__METHOD__.' unable to create directory : :directory', array(':directory' => $directory)); } // chmod to solve potential umask issues @@ -225,16 +235,9 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { try { - $type = gettype($data); - - // Serialize the data - $data = json_encode( (object) array( - 'payload' => ($type === 'string') ? $data : serialize($data), - 'expiry' => time() + $lifetime, - 'type' => $type - )); - - $size = strlen($data); + $data = $lifetime."\n".serialize($data); + $file->fwrite($data, strlen($data)); + return (bool) $file->fflush(); } catch (ErrorException $e) { @@ -242,31 +245,21 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { if ($e->getCode() === E_NOTICE) { // Throw a caching error - throw new Kohana_Cache_Exception(__METHOD__.' failed to serialize data for caching with message : '.$e->getMessage()); + throw new Cache_Exception(__METHOD__.' failed to serialize data for caching with message : '.$e->getMessage()); } // Else rethrow the error exception throw $e; } - - try - { - $file->fwrite($data, $size); - return (bool) $file->fflush(); - } - catch (Exception $e) - { - throw $e; - } } /** * Delete a cache entry based on id - * + * * // Delete 'foo' entry from the file group * Cache::instance('file')->delete('foo'); * - * @param string id to remove from cache + * @param string $id id to remove from cache * @return boolean */ public function delete($id) @@ -279,11 +272,11 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { /** * Delete all cache entries. - * + * * Beware of using this method when * using shared memory cache systems, as it will wipe every * entry within the system for all clients. - * + * * // Delete all cache entries in the file group * Cache::instance('file')->delete_all(); * @@ -308,16 +301,16 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { /** * Deletes files recursively and returns FALSE on any errors - * + * * // Delete a file or folder whilst retaining parent directory and ignore all errors * $this->_delete_file($folder, TRUE, TRUE); * - * @param SplFileInfo file - * @param boolean retain the parent directory - * @param boolean ignore_errors to prevent all exceptions interrupting exec - * @param boolean only expired files + * @param SplFileInfo $file file + * @param boolean $retain_parent_directory retain the parent directory + * @param boolean $ignore_errors ignore_errors to prevent all exceptions interrupting exec + * @param boolean $only_expired only expired files * @return boolean - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ protected function _delete_file(SplFileInfo $file, $retain_parent_directory = FALSE, $ignore_errors = FALSE, $only_expired = FALSE) { @@ -329,8 +322,13 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { { try { + // Handle ignore files + if (in_array($file->getFilename(), $this->config('ignore_on_delete'))) + { + $delete = FALSE; + } // If only expired is not set - if ($only_expired === FALSE) + elseif ($only_expired === FALSE) { // We want to delete the file $delete = TRUE; @@ -344,19 +342,18 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { $delete = $data->expiry < time(); } - // If the delete flag is set + // If the delete flag is set delete file if ($delete === TRUE) - { - // Try to delete - unlink($file->getRealPath()); - } + return unlink($file->getRealPath()); + else + return FALSE; } catch (ErrorException $e) { // Catch any delete file warnings if ($e->getCode() === E_WARNING) { - throw new Kohana_Cache_Exception(__METHOD__.' failed to delete file : :file', array(':file' => $file->getRealPath())); + throw new Cache_Exception(__METHOD__.' failed to delete file : :file', array(':file' => $file->getRealPath())); } } } @@ -373,7 +370,7 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { $name = $files->getFilename(); // If the name is not a dot - if ($name != '.' AND $name != '..' AND substr($file->getFilename(), 0, 1) == '.') + if ($name != '.' AND $name != '..') { // Create new file resource $fp = new SplFileInfo($files->getRealPath()); @@ -405,10 +402,16 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { // Catch any delete directory warnings if ($e->getCode() === E_WARNING) { - throw new Kohana_Cache_Exception(__METHOD__.' failed to delete directory : :directory', array(':directory' => $file->getRealPath())); + throw new Cache_Exception(__METHOD__.' failed to delete directory : :directory', array(':directory' => $file->getRealPath())); } + throw $e; } } + else + { + // We get here if a file has already been deleted + return FALSE; + } } // Catch all exceptions catch (Exception $e) @@ -426,11 +429,11 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { /** * Resolves the cache directory real path from the filename - * + * * // Get the realpath of the cache folder * $realpath = $this->_resolve_directory($filename); * - * @param string filename to resolve + * @param string $filename filename to resolve * @return string */ protected function _resolve_directory($filename) @@ -442,22 +445,22 @@ class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect { * Makes the cache directory if it doesn't exist. Simply a wrapper for * `mkdir` to ensure DRY principles * - * @see http://php.net/manual/en/function.mkdir.php - * @param string directory - * @param string mode - * @param string recursive - * @param string context + * @link http://php.net/manual/en/function.mkdir.php + * @param string $directory + * @param integer $mode + * @param boolean $recursive + * @param resource $context * @return SplFileInfo - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ protected function _make_directory($directory, $mode = 0777, $recursive = FALSE, $context = NULL) { if ( ! mkdir($directory, $mode, $recursive, $context)) { - throw new Kohana_Cache_Exception('Failed to create the defined cache directory : :directory', array(':directory' => $directory)); + throw new Cache_Exception('Failed to create the defined cache directory : :directory', array(':directory' => $directory)); } chmod($directory, $mode); - return new SplFileInfo($directory);; + return new SplFileInfo($directory); } } diff --git a/includes/kohana/modules/cache/classes/kohana/cache/garbagecollect.php b/includes/kohana/modules/cache/classes/Kohana/Cache/GarbageCollect.php similarity index 93% rename from includes/kohana/modules/cache/classes/kohana/cache/garbagecollect.php rename to includes/kohana/modules/cache/classes/Kohana/Cache/GarbageCollect.php index 62c3148e..c0bc5196 100644 --- a/includes/kohana/modules/cache/classes/kohana/cache/garbagecollect.php +++ b/includes/kohana/modules/cache/classes/Kohana/Cache/GarbageCollect.php @@ -8,7 +8,7 @@ * @category Base * @version 2.0 * @author Kohana Team - * @copyright (c) 2009-2010 Kohana Team + * @copyright (c) 2009-2012 Kohana Team * @license http://kohanaphp.com/license * @since 3.0.8 */ diff --git a/includes/kohana/modules/cache/classes/kohana/cache/memcache.php b/includes/kohana/modules/cache/classes/Kohana/Cache/Memcache.php similarity index 83% rename from includes/kohana/modules/cache/classes/kohana/cache/memcache.php rename to includes/kohana/modules/cache/classes/Kohana/Cache/Memcache.php index c377dc46..b6a1cdbb 100644 --- a/includes/kohana/modules/cache/classes/kohana/cache/memcache.php +++ b/includes/kohana/modules/cache/classes/Kohana/Cache/Memcache.php @@ -1,16 +1,16 @@ array( // Default group * 'driver' => 'memcache', // using Memcache driver @@ -37,24 +37,24 @@ * 'compression' => FALSE, // Use compression? * ), * ) - * + * * In cases where only one cache group is required, if the group is named `default` there is * no need to pass the group name when instantiating a cache instance. - * + * * #### General cache group configuration settings - * + * * Below are the settings available to all types of cache driver. - * + * * Name | Required | Description * -------------- | -------- | --------------------------------------------------------------- * driver | __YES__ | (_string_) The driver type to use * servers | __YES__ | (_array_) Associative array of server details, must include a __host__ key. (see _Memcache server configuration_ below) * compression | __NO__ | (_boolean_) Use data compression when caching - * + * * #### Memcache server configuration - * + * * The following settings should be used when defining each memcache server - * + * * Name | Required | Description * ---------------- | -------- | --------------------------------------------------------------- * host | __YES__ | (_string_) The host of the memcache server, i.e. __localhost__; or __127.0.0.1__; or __memcache.domain.tld__ @@ -65,22 +65,22 @@ * retry_interval | __NO__ | (_integer_) Controls how often a failed server will be retried, the default value is 15 seconds. Setting this parameter to -1 disables automatic retry. Default __15__ * status | __NO__ | (_boolean_) Controls if the server should be flagged as online. Default __TRUE__ * failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback)_) Allows the user to specify a callback function to run upon encountering an error. The callback is run before failover is attempted. The function takes two parameters, the hostname and port of the failed server. Default __NULL__ - * + * * ### System requirements - * + * * * Kohana 3.0.x * * PHP 5.2.4 or greater * * Memcache (plus Memcached-tags for native tagging support) * * Zlib - * + * * @package Kohana/Cache * @category Base * @version 2.0 * @author Kohana Team - * @copyright (c) 2009-2010 Kohana Team + * @copyright (c) 2009-2012 Kohana Team * @license http://kohanaphp.com/license */ -class Kohana_Cache_Memcache extends Cache { +class Kohana_Cache_Memcache extends Cache implements Cache_Arithmetic { // Memcache has a maximum cache lifetime of 30 days const CACHE_CEILING = 2592000; @@ -109,15 +109,15 @@ class Kohana_Cache_Memcache extends Cache { /** * Constructs the memcache Kohana_Cache object * - * @param array configuration - * @throws Kohana_Cache_Exception + * @param array $config configuration + * @throws Cache_Exception */ protected function __construct(array $config) { // Check for the memcache extention if ( ! extension_loaded('memcache')) { - throw new Kohana_Cache_Exception('Memcache PHP extention not loaded'); + throw new Cache_Exception('Memcache PHP extention not loaded'); } parent::__construct($config); @@ -131,7 +131,7 @@ class Kohana_Cache_Memcache extends Cache { if ( ! $servers) { // Throw an exception if no server found - throw new Kohana_Cache_Exception('No Memcache servers defined in configuration'); + throw new Cache_Exception('No Memcache servers defined in configuration'); } // Setup default server configuration @@ -155,7 +155,7 @@ class Kohana_Cache_Memcache extends Cache { if ( ! $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], $server['weight'], $server['timeout'], $server['retry_interval'], $server['status'], $server['failure_callback'])) { - throw new Kohana_Cache_Exception('Memcache could not connect to host \':host\' using port \':port\'', array(':host' => $server['host'], ':port' => $server['port'])); + throw new Cache_Exception('Memcache could not connect to host \':host\' using port \':port\'', array(':host' => $server['host'], ':port' => $server['port'])); } } @@ -165,17 +165,17 @@ class Kohana_Cache_Memcache extends Cache { /** * Retrieve a cached value entry by id. - * + * * // Retrieve cache entry from memcache group * $data = Cache::instance('memcache')->get('foo'); - * + * * // Retrieve cache entry from memcache group and return 'bar' if miss * $data = Cache::instance('memcache')->get('foo', 'bar'); * - * @param string id of cache to entry - * @param string default value to return if cache miss + * @param string $id id of cache to entry + * @param string $default default value to return if cache miss * @return mixed - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function get($id, $default = NULL) { @@ -194,9 +194,9 @@ class Kohana_Cache_Memcache extends Cache { /** * Set a value to cache with id and lifetime - * + * * $data = 'bar'; - * + * * // Set 'bar' to 'foo' in memcache group for 10 minutes * if (Cache::instance('memcache')->set('foo', $data, 600)) * { @@ -204,9 +204,9 @@ class Kohana_Cache_Memcache extends Cache { * return * } * - * @param string id of cache entry - * @param mixed data to set to cache - * @param integer lifetime in seconds, maximum value 2592000 + * @param string $id id of cache entry + * @param mixed $data data to set to cache + * @param integer $lifetime lifetime in seconds, maximum value 2592000 * @return boolean */ public function set($id, $data, $lifetime = 3600) @@ -235,15 +235,15 @@ class Kohana_Cache_Memcache extends Cache { /** * Delete a cache entry based on id - * + * * // Delete the 'foo' cache entry immediately * Cache::instance('memcache')->delete('foo'); - * + * * // Delete the 'bar' cache entry after 30 seconds * Cache::instance('memcache')->delete('bar', 30); * - * @param string id of entry to delete - * @param integer timeout of entry, if zero item is deleted immediately, otherwise the item will delete after the specified value in seconds + * @param string $id id of entry to delete + * @param integer $timeout timeout of entry, if zero item is deleted immediately, otherwise the item will delete after the specified value in seconds * @return boolean */ public function delete($id, $timeout = 0) @@ -254,11 +254,11 @@ class Kohana_Cache_Memcache extends Cache { /** * Delete all cache entries. - * + * * Beware of using this method when * using shared memory cache systems, as it will wipe every * entry within the system for all clients. - * + * * // Delete all cache entries in the default group * Cache::instance('memcache')->delete_all(); * @@ -280,15 +280,15 @@ class Kohana_Cache_Memcache extends Cache { * on a particular server fails. This method switches off that instance of the * server if the configuration setting `instant_death` is set to `TRUE`. * - * @param string hostname - * @param integer port + * @param string $hostname + * @param integer $port * @return void|boolean * @since 3.0.8 */ public function _failed_request($hostname, $port) { if ( ! $this->_config['instant_death']) - return; + return; // Setup non-existent host $host = FALSE; @@ -321,4 +321,34 @@ class Kohana_Cache_Memcache extends Cache { )); } } + + /** + * Increments a given value by the step value supplied. + * Useful for shared counters and other persistent integer based + * tracking. + * + * @param string id of cache entry to increment + * @param int step value to increment by + * @return integer + * @return boolean + */ + public function increment($id, $step = 1) + { + return $this->_memcache->increment($id, $step); + } + + /** + * Decrements a given value by the step value supplied. + * Useful for shared counters and other persistent integer based + * tracking. + * + * @param string id of cache entry to decrement + * @param int step value to decrement by + * @return integer + * @return boolean + */ + public function decrement($id, $step = 1) + { + return $this->_memcache->decrement($id, $step); + } } \ No newline at end of file diff --git a/includes/kohana/modules/cache/classes/kohana/cache/memcachetag.php b/includes/kohana/modules/cache/classes/Kohana/Cache/MemcacheTag.php similarity index 62% rename from includes/kohana/modules/cache/classes/kohana/cache/memcachetag.php rename to includes/kohana/modules/cache/classes/Kohana/Cache/MemcacheTag.php index 866ab9bf..644e5439 100644 --- a/includes/kohana/modules/cache/classes/kohana/cache/memcachetag.php +++ b/includes/kohana/modules/cache/classes/Kohana/Cache/MemcacheTag.php @@ -1,21 +1,21 @@ _memcache, 'tag_add')) { - throw new Kohana_Cache_Exception('Memcached-tags PHP plugin not present. Please see http://code.google.com/p/memcached-tags/ for more information'); + throw new Cache_Exception('Memcached-tags PHP plugin not present. Please see http://code.google.com/p/memcached-tags/ for more information'); } } /** * Set a value based on an id with tags - * - * @param string id - * @param mixed data - * @param integer lifetime [Optional] - * @param array tags [Optional] + * + * @param string $id id + * @param mixed $data data + * @param integer $lifetime lifetime [Optional] + * @param array $tags tags [Optional] * @return boolean */ public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL) { + $id = $this->_sanitize_id($id); + $result = $this->set($id, $data, $lifetime); if ($result and $tags) @@ -54,7 +56,7 @@ class Kohana_Cache_MemcacheTag extends Cache_Memcache implements Kohana_Cache_Ta /** * Delete cache entries based on a tag * - * @param string tag + * @param string $tag tag * @return boolean */ public function delete_tag($tag) @@ -65,12 +67,12 @@ class Kohana_Cache_MemcacheTag extends Cache_Memcache implements Kohana_Cache_Ta /** * Find cache entries based on a tag * - * @param string tag + * @param string $tag tag * @return void - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function find($tag) { - throw new Kohana_Cache_Exception('Memcached-tags does not support finding by tag'); - } + throw new Cache_Exception('Memcached-tags does not support finding by tag'); + } } diff --git a/includes/kohana/modules/cache/classes/kohana/cache/sqlite.php b/includes/kohana/modules/cache/classes/Kohana/Cache/Sqlite.php similarity index 71% rename from includes/kohana/modules/cache/classes/kohana/cache/sqlite.php rename to includes/kohana/modules/cache/classes/Kohana/Cache/Sqlite.php index 70045e53..932704a2 100644 --- a/includes/kohana/modules/cache/classes/kohana/cache/sqlite.php +++ b/includes/kohana/modules/cache/classes/Kohana/Cache/Sqlite.php @@ -1,16 +1,16 @@ $e->getMessage())); + throw new Cache_Exception('Failed to create new SQLite caches table with the following error : :error', array(':error' => $e->getMessage())); } } } @@ -68,10 +68,10 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ /** * Retrieve a value based on an id * - * @param string id - * @param string default [Optional] Default value to return if id not found + * @param string $id id + * @param string $default default [Optional] Default value to return if id not found * @return mixed - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function get($id, $default = NULL) { @@ -85,7 +85,7 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ } catch (PDOException $e) { - throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); } if ( ! $result = $statement->fetch(PDO::FETCH_OBJ)) @@ -105,7 +105,7 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ { // Disable notices for unserializing $ER = error_reporting(~E_NOTICE); - + // Return the valid cache data $data = unserialize($result->cache); @@ -120,9 +120,9 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ /** * Set a value based on an id. Optionally add tags. * - * @param string id - * @param mixed data - * @param integer lifetime [Optional] + * @param string $id id + * @param mixed $data data + * @param integer $lifetime lifetime [Optional] * @return boolean */ public function set($id, $data, $lifetime = NULL) @@ -133,10 +133,9 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ /** * Delete a cache entry based on id * - * @param string id - * @param integer timeout [Optional] + * @param string $id id * @return boolean - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function delete($id) { @@ -150,7 +149,7 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ } catch (PDOException $e) { - throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); } return (bool) $statement->rowCount(); @@ -181,13 +180,13 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ /** * Set a value based on an id. Optionally add tags. - * - * @param string id - * @param mixed data - * @param integer lifetime [Optional] - * @param array tags [Optional] + * + * @param string $id id + * @param mixed $data data + * @param integer $lifetime lifetime [Optional] + * @param array $tags tags [Optional] * @return boolean - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL) { @@ -200,7 +199,7 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ // Setup lifetime if ($lifetime === NULL) { - $lifetime = (0 === Arr::get('default_expire', NULL)) ? 0 : (Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE) + time()); + $lifetime = (0 === Arr::get($this->_config, 'default_expire', NULL)) ? 0 : (Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE) + time()); } else { @@ -208,7 +207,7 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ } // Prepare statement - // $this->exists() may throw Kohana_Cache_Exception, no need to catch/rethrow + // $this->exists() may throw Cache_Exception, no need to catch/rethrow $statement = $this->exists($id) ? $this->_db->prepare('UPDATE caches SET expiration = :expiration, cache = :cache, tags = :tags WHERE id = :id') : $this->_db->prepare('INSERT INTO caches (id, cache, expiration, tags) VALUES (:id, :cache, :expiration, :tags)'); // Try to insert @@ -218,7 +217,7 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ } catch (PDOException $e) { - throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); } return (bool) $statement->rowCount(); @@ -227,10 +226,9 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ /** * Delete cache entries based on a tag * - * @param string tag - * @param integer timeout [Optional] + * @param string $tag tag * @return boolean - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function delete_tag($tag) { @@ -253,9 +251,9 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ /** * Find cache entries based on a tag * - * @param string tag + * @param string $tag tag * @return array - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function find($tag) { @@ -272,7 +270,7 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ } catch (PDOException $e) { - throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); } $result = array(); @@ -308,16 +306,16 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ } catch (PDOException $e) { - throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); } } /** * Tests whether an id exists or not * - * @param string id + * @param string $id id * @return boolean - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ protected function exists($id) { @@ -328,7 +326,7 @@ class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_ } catch (PDOExeption $e) { - throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); } return (bool) $statement->fetchAll(); diff --git a/includes/kohana/modules/cache/classes/kohana/cache/tagging.php b/includes/kohana/modules/cache/classes/Kohana/Cache/Tagging.php similarity index 70% rename from includes/kohana/modules/cache/classes/kohana/cache/tagging.php rename to includes/kohana/modules/cache/classes/Kohana/Cache/Tagging.php index 001a2246..70d4d632 100644 --- a/includes/kohana/modules/cache/classes/kohana/cache/tagging.php +++ b/includes/kohana/modules/cache/classes/Kohana/Cache/Tagging.php @@ -1,25 +1,25 @@ array( // Driver group * 'driver' => 'wincache', // using wincache driver * ), * ) - * + * * In cases where only one cache group is required, if the group is named `default` there is * no need to pass the group name when instantiating a cache instance. - * + * * #### General cache group configuration settings - * + * * Below are the settings available to all types of cache driver. - * + * * Name | Required | Description * -------------- | -------- | --------------------------------------------------------------- * driver | __YES__ | (_string_) The driver type to use - * + * * ### System requirements - * + * * * Windows XP SP3 with IIS 5.1 and » FastCGI Extension * * Windows Server 2003 with IIS 6.0 and » FastCGI Extension * * Windows Vista SP1 with IIS 7.0 and FastCGI Module @@ -34,11 +34,11 @@ * * Windows Server 2008 R2 with IIS 7.5 and FastCGI Module * * PHP 5.2.X, Non-thread-safe build * * PHP 5.3 X86, Non-thread-safe VC9 build - * + * * @package Kohana/Cache * @category Base * @author Kohana Team - * @copyright (c) 2009-2010 Kohana Team + * @copyright (c) 2009-2012 Kohana Team * @license http://kohanaphp.com/license */ class Kohana_Cache_Wincache extends Cache { @@ -47,14 +47,14 @@ class Kohana_Cache_Wincache extends Cache { * Check for existence of the wincache extension This method cannot be invoked externally. The driver must * be instantiated using the `Cache::instance()` method. * - * @param array configuration - * @throws Kohana_Cache_Exception + * @param array $config configuration + * @throws Cache_Exception */ protected function __construct(array $config) { if ( ! extension_loaded('wincache')) { - throw new Kohana_Cache_Exception('PHP wincache extension is not available.'); + throw new Cache_Exception('PHP wincache extension is not available.'); } parent::__construct($config); @@ -62,17 +62,17 @@ class Kohana_Cache_Wincache extends Cache { /** * Retrieve a cached value entry by id. - * + * * // Retrieve cache entry from wincache group * $data = Cache::instance('wincache')->get('foo'); - * + * * // Retrieve cache entry from wincache group and return 'bar' if miss * $data = Cache::instance('wincache')->get('foo', 'bar'); * - * @param string id of cache to entry - * @param string default value to return if cache miss + * @param string $id id of cache to entry + * @param string $default default value to return if cache miss * @return mixed - * @throws Kohana_Cache_Exception + * @throws Cache_Exception */ public function get($id, $default = NULL) { @@ -83,18 +83,18 @@ class Kohana_Cache_Wincache extends Cache { /** * Set a value to cache with id and lifetime - * + * * $data = 'bar'; - * + * * // Set 'bar' to 'foo' in wincache group, using default expiry * Cache::instance('wincache')->set('foo', $data); - * + * * // Set 'bar' to 'foo' in wincache group for 30 seconds * Cache::instance('wincache')->set('foo', $data, 30); * - * @param string id of cache entry - * @param string data to set to cache - * @param integer lifetime in seconds + * @param string $id id of cache entry + * @param string $data data to set to cache + * @param integer $lifetime lifetime in seconds * @return boolean */ public function set($id, $data, $lifetime = NULL) @@ -109,11 +109,11 @@ class Kohana_Cache_Wincache extends Cache { /** * Delete a cache entry based on id - * + * * // Delete 'foo' entry from the wincache group * Cache::instance('wincache')->delete('foo'); * - * @param string id to remove from cache + * @param string $id id to remove from cache * @return boolean */ public function delete($id) @@ -123,11 +123,11 @@ class Kohana_Cache_Wincache extends Cache { /** * Delete all cache entries. - * + * * Beware of using this method when * using shared memory cache systems, as it will wipe every * entry within the system for all clients. - * + * * // Delete all cache entries in the wincache group * Cache::instance('wincache')->delete_all(); * diff --git a/includes/kohana/modules/cache/classes/Kohana/HTTP/Cache.php b/includes/kohana/modules/cache/classes/Kohana/HTTP/Cache.php new file mode 100644 index 00000000..2507f811 --- /dev/null +++ b/includes/kohana/modules/cache/classes/Kohana/HTTP/Cache.php @@ -0,0 +1,503 @@ + FALSE + * ) + * ); + * + * // Create HTTP_Cache with supplied cache engine + * $http_cache = HTTP_Cache::factory(Cache::instance('memcache'), + * array( + * 'allow_private_cache' => FALSE + * ) + * ); + * + * @uses [Cache] + * @param mixed $cache cache engine to use + * @param array $options options to set to this class + * @return HTTP_Cache + */ + public static function factory($cache, array $options = array()) + { + if ( ! $cache instanceof Cache) + { + $cache = Cache::instance($cache); + } + + $options['cache'] = $cache; + + return new HTTP_Cache($options); + } + + /** + * Basic cache key generator that hashes the entire request and returns + * it. This is fine for static content, or dynamic content where user + * specific information is encoded into the request. + * + * // Generate cache key + * $cache_key = HTTP_Cache::basic_cache_key_generator($request); + * + * @param Request $request + * @return string + */ + public static function basic_cache_key_generator(Request $request) + { + $uri = $request->uri(); + $query = $request->query(); + $headers = $request->headers()->getArrayCopy(); + $body = $request->body(); + + return sha1($uri.'?'.http_build_query($query, NULL, '&').'~'.implode('~', $headers).'~'.$body); + } + + /** + * @var Cache cache driver to use for HTTP caching + */ + protected $_cache; + + /** + * @var callback Cache key generator callback + */ + protected $_cache_key_callback; + + /** + * @var boolean Defines whether this client should cache `private` cache directives + * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + */ + protected $_allow_private_cache = FALSE; + + /** + * @var int The timestamp of the request + */ + protected $_request_time; + + /** + * @var int The timestamp of the response + */ + protected $_response_time; + + /** + * Constructor method for this class. Allows dependency injection of the + * required components such as `Cache` and the cache key generator. + * + * @param array $options + */ + public function __construct(array $options = array()) + { + foreach ($options as $key => $value) + { + if (method_exists($this, $key)) + { + $this->$key($value); + } + } + + if ($this->_cache_key_callback === NULL) + { + $this->cache_key_callback('HTTP_Cache::basic_cache_key_generator'); + } + } + + /** + * Executes the supplied [Request] with the supplied [Request_Client]. + * Before execution, the HTTP_Cache adapter checks the request type, + * destructive requests such as `POST`, `PUT` and `DELETE` will bypass + * cache completely and ensure the response is not cached. All other + * Request methods will allow caching, if the rules are met. + * + * @param Request_Client $client client to execute with Cache-Control + * @param Request $request request to execute with client + * @return [Response] + */ + public function execute(Request_Client $client, Request $request, Response $response) + { + if ( ! $this->_cache instanceof Cache) + return $client->execute_request($request, $response); + + // If this is a destructive request, by-pass cache completely + if (in_array($request->method(), array( + HTTP_Request::POST, + HTTP_Request::PUT, + HTTP_Request::DELETE))) + { + // Kill existing caches for this request + $this->invalidate_cache($request); + + $response = $client->execute_request($request, $response); + + $cache_control = HTTP_Header::create_cache_control(array( + 'no-cache', + 'must-revalidate' + )); + + // Ensure client respects destructive action + return $response->headers('cache-control', $cache_control); + } + + // Create the cache key + $cache_key = $this->create_cache_key($request, $this->_cache_key_callback); + + // Try and return cached version + if (($cached_response = $this->cache_response($cache_key, $request)) instanceof Response) + return $cached_response; + + // Start request time + $this->_request_time = time(); + + // Execute the request with the Request client + $response = $client->execute_request($request, $response); + + // Stop response time + $this->_response_time = (time() - $this->_request_time); + + // Cache the response + $this->cache_response($cache_key, $request, $response); + + $response->headers(HTTP_Cache::CACHE_STATUS_KEY, + HTTP_Cache::CACHE_STATUS_MISS); + + return $response; + } + + /** + * Invalidate a cached response for the [Request] supplied. + * This has the effect of deleting the response from the + * [Cache] entry. + * + * @param Request $request Response to remove from cache + * @return void + */ + public function invalidate_cache(Request $request) + { + if (($cache = $this->cache()) instanceof Cache) + { + $cache->delete($this->create_cache_key($request, $this->_cache_key_callback)); + } + + return; + } + + /** + * Getter and setter for the internal caching engine, + * used to cache responses if available and valid. + * + * @param Kohana_Cache $cache engine to use for caching + * @return Kohana_Cache + * @return Kohana_Request_Client + */ + public function cache(Cache $cache = NULL) + { + if ($cache === NULL) + return $this->_cache; + + $this->_cache = $cache; + return $this; + } + + /** + * Gets or sets the [Request_Client::allow_private_cache] setting. + * If set to `TRUE`, the client will also cache cache-control directives + * that have the `private` setting. + * + * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + * @param boolean $setting allow caching of privately marked responses + * @return boolean + * @return [Request_Client] + */ + public function allow_private_cache($setting = NULL) + { + if ($setting === NULL) + return $this->_allow_private_cache; + + $this->_allow_private_cache = (bool) $setting; + return $this; + } + + /** + * Sets or gets the cache key generator callback for this caching + * class. The cache key generator provides a unique hash based on the + * `Request` object passed to it. + * + * The default generator is [HTTP_Cache::basic_cache_key_generator()], which + * serializes the entire `HTTP_Request` into a unique sha1 hash. This will + * provide basic caching for static and simple dynamic pages. More complex + * algorithms can be defined and then passed into `HTTP_Cache` using this + * method. + * + * // Get the cache key callback + * $callback = $http_cache->cache_key_callback(); + * + * // Set the cache key callback + * $http_cache->cache_key_callback('Foo::cache_key'); + * + * // Alternatively, in PHP 5.3 use a closure + * $http_cache->cache_key_callback(function (Request $request) { + * return sha1($request->render()); + * }); + * + * @param callback $callback + * @return mixed + * @throws HTTP_Exception + */ + public function cache_key_callback($callback = NULL) + { + if ($callback === NULL) + return $this->_cache_key_callback; + + if ( ! is_callable($callback)) + throw new Kohana_Exception('cache_key_callback must be callable!'); + + $this->_cache_key_callback = $callback; + return $this; + } + + /** + * Creates a cache key for the request to use for caching + * [Kohana_Response] returned by [Request::execute]. + * + * This is the default cache key generating logic, but can be overridden + * by setting [HTTP_Cache::cache_key_callback()]. + * + * @param Request $request request to create key for + * @param callback $callback optional callback to use instead of built-in method + * @return string + */ + public function create_cache_key(Request $request, $callback = FALSE) + { + if (is_callable($callback)) + return call_user_func($callback, $request); + else + return HTTP_Cache::basic_cache_key_generator($request); + } + + /** + * Controls whether the response can be cached. Uses HTTP + * protocol to determine whether the response can be cached. + * + * @link RFC 2616 http://www.w3.org/Protocols/rfc2616/ + * @param Response $response The Response + * @return boolean + */ + public function set_cache(Response $response) + { + $headers = $response->headers()->getArrayCopy(); + + if ($cache_control = Arr::get($headers, 'cache-control')) + { + // Parse the cache control + $cache_control = HTTP_Header::parse_cache_control($cache_control); + + // If the no-cache or no-store directive is set, return + if (array_intersect($cache_control, array('no-cache', 'no-store'))) + return FALSE; + + // Check for private cache and get out of here if invalid + if ( ! $this->_allow_private_cache AND in_array('private', $cache_control)) + { + if ( ! isset($cache_control['s-maxage'])) + return FALSE; + + // If there is a s-maxage directive we can use that + $cache_control['max-age'] = $cache_control['s-maxage']; + } + + // Check that max-age has been set and if it is valid for caching + if (isset($cache_control['max-age']) AND $cache_control['max-age'] < 1) + return FALSE; + } + + if ($expires = Arr::get($headers, 'expires') AND ! isset($cache_control['max-age'])) + { + // Can't cache things that have expired already + if (strtotime($expires) <= time()) + return FALSE; + } + + return TRUE; + } + + /** + * Caches a [Response] using the supplied [Cache] + * and the key generated by [Request_Client::_create_cache_key]. + * + * If not response is supplied, the cache will be checked for an existing + * one that is available. + * + * @param string $key the cache key to use + * @param Request $request the HTTP Request + * @param Response $response the HTTP Response + * @return mixed + */ + public function cache_response($key, Request $request, Response $response = NULL) + { + if ( ! $this->_cache instanceof Cache) + return FALSE; + + // Check for Pragma: no-cache + if ($pragma = $request->headers('pragma')) + { + if ($pragma == 'no-cache') + return FALSE; + elseif (is_array($pragma) AND in_array('no-cache', $pragma)) + return FALSE; + } + + // If there is no response, lookup an existing cached response + if ($response === NULL) + { + $response = $this->_cache->get($key); + + if ( ! $response instanceof Response) + return FALSE; + + // Do cache hit arithmetic, using fast arithmetic if available + if ($this->_cache instanceof Cache_Arithmetic) + { + $hit_count = $this->_cache->increment(HTTP_Cache::CACHE_HIT_KEY.$key); + } + else + { + $hit_count = $this->_cache->get(HTTP_Cache::CACHE_HIT_KEY.$key); + $this->_cache->set(HTTP_Cache::CACHE_HIT_KEY.$key, ++$hit_count); + } + + // Update the header to have correct HIT status and count + $response->headers(HTTP_Cache::CACHE_STATUS_KEY, + HTTP_Cache::CACHE_STATUS_HIT) + ->headers(HTTP_Cache::CACHE_HIT_KEY, $hit_count); + + return $response; + } + else + { + if (($ttl = $this->cache_lifetime($response)) === FALSE) + return FALSE; + + $response->headers(HTTP_Cache::CACHE_STATUS_KEY, + HTTP_Cache::CACHE_STATUS_SAVED); + + // Set the hit count to zero + $this->_cache->set(HTTP_Cache::CACHE_HIT_KEY.$key, 0); + + return $this->_cache->set($key, $response, $ttl); + } + } + + /** + * Calculates the total Time To Live based on the specification + * RFC 2616 cache lifetime rules. + * + * @param Response $response Response to evaluate + * @return mixed TTL value or false if the response should not be cached + */ + public function cache_lifetime(Response $response) + { + // Get out of here if this cannot be cached + if ( ! $this->set_cache($response)) + return FALSE; + + // Calculate apparent age + if ($date = $response->headers('date')) + { + $apparent_age = max(0, $this->_response_time - strtotime($date)); + } + else + { + $apparent_age = max(0, $this->_response_time); + } + + // Calculate corrected received age + if ($age = $response->headers('age')) + { + $corrected_received_age = max($apparent_age, intval($age)); + } + else + { + $corrected_received_age = $apparent_age; + } + + // Corrected initial age + $corrected_initial_age = $corrected_received_age + $this->request_execution_time(); + + // Resident time + $resident_time = time() - $this->_response_time; + + // Current age + $current_age = $corrected_initial_age + $resident_time; + + // Prepare the cache freshness lifetime + $ttl = NULL; + + // Cache control overrides + if ($cache_control = $response->headers('cache-control')) + { + // Parse the cache control header + $cache_control = HTTP_Header::parse_cache_control($cache_control); + + if (isset($cache_control['max-age'])) + { + $ttl = $cache_control['max-age']; + } + + if (isset($cache_control['s-maxage']) AND isset($cache_control['private']) AND $this->_allow_private_cache) + { + $ttl = $cache_control['s-maxage']; + } + + if (isset($cache_control['max-stale']) AND ! isset($cache_control['must-revalidate'])) + { + $ttl = $current_age + $cache_control['max-stale']; + } + } + + // If we have a TTL at this point, return + if ($ttl !== NULL) + return $ttl; + + if ($expires = $response->headers('expires')) + return strtotime($expires) - $current_age; + + return FALSE; + } + + /** + * Returns the duration of the last request execution. + * Either returns the time of completed requests or + * `FALSE` if the request hasn't finished executing, or + * is yet to be run. + * + * @return mixed + */ + public function request_execution_time() + { + if ($this->_request_time === NULL OR $this->_response_time === NULL) + return FALSE; + + return $this->_response_time - $this->_request_time; + } + +} // End Kohana_HTTP_Cache \ No newline at end of file diff --git a/includes/kohana/modules/cache/classes/cache/xcache.php b/includes/kohana/modules/cache/classes/cache/xcache.php deleted file mode 100644 index 16036299..00000000 --- a/includes/kohana/modules/cache/classes/cache/xcache.php +++ /dev/null @@ -1,3 +0,0 @@ - array( // Driver group - * 'driver' => 'eaccelerator', // using Eaccelerator driver - * ), - * ) - * - * In cases where only one cache group is required, if the group is named `default` there is - * no need to pass the group name when instantiating a cache instance. - * - * #### General cache group configuration settings - * - * Below are the settings available to all types of cache driver. - * - * Name | Required | Description - * -------------- | -------- | --------------------------------------------------------------- - * driver | __YES__ | (_string_) The driver type to use - * - * ### System requirements - * - * * Kohana 3.0.x - * * PHP 5.2.4 or greater - * * Eaccelerator PHP extension - * - * @package Kohana/Cache - * @category Base - * @author Kohana Team - * @copyright (c) 2009-2010 Kohana Team - * @license http://kohanaphp.com/license - */ -class Kohana_Cache_Eaccelerator extends Cache { - - /** - * Check for existence of the eAccelerator extension This method cannot be invoked externally. The driver must - * be instantiated using the `Cache::instance()` method. - * - * @param array configuration - * @throws Kohana_Cache_Exception - */ - protected function __construct(array $config) - { - if ( ! extension_loaded('eaccelerator')) - { - throw new Kohana_Cache_Exception('PHP eAccelerator extension is not available.'); - } - - parent::__construct($config); - } - - /** - * Retrieve a cached value entry by id. - * - * // Retrieve cache entry from eaccelerator group - * $data = Cache::instance('eaccelerator')->get('foo'); - * - * // Retrieve cache entry from eaccelerator group and return 'bar' if miss - * $data = Cache::instance('eaccelerator')->get('foo', 'bar'); - * - * @param string id of cache to entry - * @param string default value to return if cache miss - * @return mixed - * @throws Kohana_Cache_Exception - */ - public function get($id, $default = NULL) - { - return (($data = eaccelerator_get($this->_sanitize_id($id))) === FALSE) ? $default : $data; - } - - /** - * Set a value to cache with id and lifetime - * - * $data = 'bar'; - * - * // Set 'bar' to 'foo' in eaccelerator group, using default expiry - * Cache::instance('eaccelerator')->set('foo', $data); - * - * // Set 'bar' to 'foo' in eaccelerator group for 30 seconds - * Cache::instance('eaccelerator')->set('foo', $data, 30); - * - * @param string id of cache entry - * @param string data to set to cache - * @param integer lifetime in seconds - * @return boolean - */ - public function set($id, $data, $lifetime = NULL) - { - if ($lifetime === NULL) - { - $lifetime = time() + Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE); - } - - return eaccelerator_put($this->_sanitize_id($id), $data, $lifetime); - } - - /** - * Delete a cache entry based on id - * - * // Delete 'foo' entry from the eaccelerator group - * Cache::instance('eaccelerator')->delete('foo'); - * - * @param string id to remove from cache - * @return boolean - */ - public function delete($id) - { - return eaccelerator_rm($this->_sanitize_id($id)); - } - - /** - * Delete all cache entries. - * - * Beware of using this method when - * using shared memory cache systems, as it will wipe every - * entry within the system for all clients. - * - * // Delete all cache entries in the eaccelerator group - * Cache::instance('eaccelerator')->delete_all(); - * - * @return boolean - */ - public function delete_all() - { - return eaccelerator_clean(); - } -} diff --git a/includes/kohana/modules/cache/classes/kohana/cache/xcache.php b/includes/kohana/modules/cache/classes/kohana/cache/xcache.php deleted file mode 100644 index 133f18b3..00000000 --- a/includes/kohana/modules/cache/classes/kohana/cache/xcache.php +++ /dev/null @@ -1,84 +0,0 @@ -_sanitize_id($id))) === NULL) ? $default : $data; - } - - /** - * Set a value based on an id. Optionally add tags. - * - * @param string id - * @param string data - * @param integer lifetime [Optional] - * @return boolean - */ - public function set($id, $data, $lifetime = NULL) - { - if (NULL === $lifetime) - { - $lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE); - } - - return xcache_set($this->_sanitize_id($id), $data, $lifetime); - } - - /** - * Delete a cache entry based on id - * - * @param string id - * @param integer timeout [Optional] - * @return boolean - */ - public function delete($id) - { - return xcache_unset($this->_sanitize_id($id)); - } - - /** - * Delete all cache entries - * To use this method xcache.admin.enable_auth has to be Off in xcache.ini - * - * @return void - */ - public function delete_all() - { - xcache_clear_cache(XC_TYPE_PHP, 0); - } -} diff --git a/includes/kohana/modules/cache/config/cache.php b/includes/kohana/modules/cache/config/cache.php index 9ec311c0..acc567e5 100644 --- a/includes/kohana/modules/cache/config/cache.php +++ b/includes/kohana/modules/cache/config/cache.php @@ -1,15 +1,12 @@ array - ( +/* 'memcache' => array( 'driver' => 'memcache', 'default_expire' => 3600, 'compression' => FALSE, // Use Zlib compression (can cause issues with integers) - 'servers' => array - ( - array - ( + 'servers' => array( + 'local' => array( 'host' => 'localhost', // Memcache Server 'port' => 11211, // Memcache port number 'persistent' => FALSE, // Persistent connection @@ -21,15 +18,12 @@ return array ), 'instant_death' => TRUE, // Take server offline immediately on first fail (no retry) ), - 'memcachetag' => array - ( + 'memcachetag' => array( 'driver' => 'memcachetag', 'default_expire' => 3600, 'compression' => FALSE, // Use Zlib compression (can cause issues with integers) - 'servers' => array - ( - array - ( + 'servers' => array( + 'local' => array( 'host' => 'localhost', // Memcache Server 'port' => 11211, // Memcache port number 'persistent' => FALSE, // Persistent connection @@ -41,36 +35,36 @@ return array ), 'instant_death' => TRUE, ), - 'apc' => array - ( + 'apc' => array( 'driver' => 'apc', 'default_expire' => 3600, ), - 'wincache' => array - ( + 'wincache' => array( 'driver' => 'wincache', 'default_expire' => 3600, ), - 'sqlite' => array - ( + 'sqlite' => array( 'driver' => 'sqlite', 'default_expire' => 3600, 'database' => APPPATH.'cache/kohana-cache.sql3', 'schema' => 'CREATE TABLE caches(id VARCHAR(127) PRIMARY KEY, tags VARCHAR(255), expiration INTEGER, cache TEXT)', ), - 'eaccelerator' => array - ( + 'eaccelerator' => array( 'driver' => 'eaccelerator', ), - 'xcache' => array - ( + 'xcache' => array( 'driver' => 'xcache', 'default_expire' => 3600, ), - 'file' => array - ( + 'file' => array( 'driver' => 'file', 'cache_dir' => APPPATH.'cache', 'default_expire' => 3600, + 'ignore_on_delete' => array( + '.gitignore', + '.git', + '.svn' + ) ) -); \ No newline at end of file +*/ +); diff --git a/includes/kohana/modules/cache/config/userguide.php b/includes/kohana/modules/cache/config/userguide.php index 524afc41..07566003 100644 --- a/includes/kohana/modules/cache/config/userguide.php +++ b/includes/kohana/modules/cache/config/userguide.php @@ -17,7 +17,7 @@ return array( 'description' => 'Common interface for caching engines.', // Copyright message, shown in the footer for this module - 'copyright' => '© 2008–2010 Kohana Team', + 'copyright' => '© 2008–2012 Kohana Team', ) ) ); \ No newline at end of file diff --git a/includes/kohana/modules/cache/guide/cache.usage.md b/includes/kohana/modules/cache/guide/cache.usage.md index 8a8239bc..15d7c52f 100644 --- a/includes/kohana/modules/cache/guide/cache.usage.md +++ b/includes/kohana/modules/cache/guide/cache.usage.md @@ -67,7 +67,7 @@ Certain cache drivers support setting values with tags. To set a value to cache $memcache = Cache::instance('memcachetag'); // Test for tagging interface - if ($memcache instanceof Kohana_Cache_Tagging) + if ($memcache instanceof Cache_Tagging) { // Set a value with some tags for 30 seconds $memcache->set('foo', $object, 30, array('snafu', 'stfu', 'fubar')); @@ -79,7 +79,7 @@ Certain cache drivers support setting values with tags. To set a value to cache $memcache->set('foo', $object, 30); } -It is possible to implement custom tagging solutions onto existing or new cache drivers by implementing the [Kohana_Cache_Tagging] interface. Kohana_Cache only applies the interface to drivers that support tagging natively as standard. +It is possible to implement custom tagging solutions onto existing or new cache drivers by implementing the [Cache_Tagging] interface. Kohana_Cache only applies the interface to drivers that support tagging natively as standard. ### Getting a value from cache @@ -115,7 +115,7 @@ It is possible to retrieve values from cache grouped by tag, using the [Cache::f // Find values based on tag return $cache->find('snafu'); } - catch (Kohana_Cache_Exception $e) + catch (Cache_Exception $e) { // Handle gracefully return FALSE; @@ -170,7 +170,7 @@ Some of the caching drivers support deleting by tag. This will remove all the ca $cache = Cache::instance(); // Check for tagging interface - if ($cache instanceof Kohana_Cache_Tagging) + if ($cache instanceof Cache_Tagging) { // Delete all entries by the tag 'snafu' $cache->delete_tag('snafu'); @@ -189,7 +189,7 @@ When not automated, garbage collection is the responsibility of the developer. I $gc = 10; // If the GC probability is a hit - if (rand(0,99) <= $gc and $cache_file instanceof Kohana_Cache_GarbageCollect) + if (rand(0,99) <= $gc and $cache_file instanceof Cache_GarbageCollect) { // Garbage Collect $cache_file->garbage_collect(); @@ -199,10 +199,10 @@ When not automated, garbage collection is the responsibility of the developer. I Kohana Cache comes with two interfaces that are implemented where the drivers support them: - - __[Kohana_Cache_Tagging] for tagging support on cache entries__ + - __[Cache_Tagging] for tagging support on cache entries__ - [Cache_MemcacheTag] - [Cache_Sqlite] - - __[Kohana_Cache_GarbageCollect] for garbage collection with drivers without native support__ + - __[Cache_GarbageCollect] for garbage collection with drivers without native support__ - [Cache_File] - [Cache_Sqlite] @@ -212,7 +212,7 @@ When using interface specific caching features, ensure that code checks for the $cache = Cache::instance(); // Test for Garbage Collection - if ($cache instanceof Kohana_Cache_GarbageCollect) + if ($cache instanceof Cache_GarbageCollect) { // Collect garbage $cache->garbage_collect(); diff --git a/includes/kohana/modules/cache/guide/cache/config.md b/includes/kohana/modules/cache/guide/cache/config.md index a6d428fe..450bea8b 100644 --- a/includes/kohana/modules/cache/guide/cache/config.md +++ b/includes/kohana/modules/cache/guide/cache/config.md @@ -58,7 +58,7 @@ failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language (can cause issues with integers) 'servers' => array ( - array + 'local' => array ( 'host' => 'localhost', // Memcache Server 'port' => 11211, // Memcache port number @@ -74,7 +74,7 @@ failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language (can cause issues with integers) 'servers' => array ( - array + 'local' => array ( 'host' => 'localhost', // Memcache Server 'port' => 11211, // Memcache port number @@ -102,21 +102,6 @@ failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language tags VARCHAR(255), expiration INTEGER, cache TEXT)', ), -## Eaccelerator settings - - 'eaccelerator' array - ( - 'driver' => 'eaccelerator', - ), - -## Xcache settings - - 'xcache' => array - ( - 'driver' => 'xcache', - 'default_expire' => 3600, - ), - ## File settings 'file' => array @@ -126,6 +111,15 @@ failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language 'default_expire' => 3600, ) +## Wincache settings + + 'wincache' => array + ( + 'driver' => 'wincache', + 'default_expire' => 3600, + ), + + ## Override existing configuration group The following example demonstrates how to override an existing configuration setting, using the config file in `/application/config/cache.php`. @@ -165,4 +159,4 @@ The following example demonstrates how to add a new configuration setting, using 'driver' => 'apc', // Use Memcached as the default driver 'default_expire' => 1000, // Overide default expiry ) - ); \ No newline at end of file + ); diff --git a/includes/kohana/modules/cache/guide/cache/index.md b/includes/kohana/modules/cache/guide/cache/index.md index b93b11d6..0df10c9b 100644 --- a/includes/kohana/modules/cache/guide/cache/index.md +++ b/includes/kohana/modules/cache/guide/cache/index.md @@ -1,18 +1,17 @@ # About Kohana Cache -[Kohana_Cache] provides a common interface to a variety of caching engines. [Kohana_Cache_Tagging] is +[Kohana_Cache] provides a common interface to a variety of caching engines. [Cache_Tagging] is supported where available natively to the cache system. Kohana Cache supports multiple instances of cache engines through a grouped singleton pattern. ## Supported cache engines * APC ([Cache_Apc]) - * eAccelerator ([Cache_Eaccelerator]) * File ([Cache_File]) * Memcached ([Cache_Memcache]) * Memcached-tags ([Cache_Memcachetag]) * SQLite ([Cache_Sqlite]) - * Xcache ([Cache_Xcache]) + * Wincache ## Introduction to caching @@ -45,13 +44,12 @@ Getting and setting values to cache is very simple when using the _Kohana Cache_ Driver | Storage | Speed | Tags | Distributed | Automatic Garbage Collection | Notes ---------------- | ------------ | --------- | -------- | ----------- | ---------------------------- | ----------------------- APC | __Memory__ | Excellent | No | No | Yes | Widely available PHP opcode caching solution, improves php execution performance -eAccelerator | __Memory__ | Excellent | No | No | Yes | Limited support and no longer developed. Included for legacy systems +Wincache | __Memory__ | Excellent | No | No | Yes | Windows variant of APC File | __Disk__ | Poor | No | No | No | Marginally faster than execution -Memcache (tag) | __Memory__ | Good | No (yes) | Yes | Yes | Generally fast distributed solution, but has a speed hit due to variable network latency +Memcache (tag) | __Memory__ | Good | No (yes) | Yes | Yes | Generally fast distributed solution, but has a speed hit due to variable network latency and serialization Sqlite | __Disk__ | Poor | Yes | No | No | Marginally faster than execution -Xcache | __Memory__ | Excellent | Yes | No | Yes | Very fast memory solution and alternative to APC -It is possible to have hybrid cache solutions that use a combination of the engines above in different contexts. This is supported with _Kohana Cache_ as well. +It is possible to have hybrid cache solutions that use a combination of the engines above in different contexts. This is supported with _Kohana Cache_ as well ## Minimum requirements diff --git a/includes/kohana/modules/cache/guide/cache/usage.md b/includes/kohana/modules/cache/guide/cache/usage.md index 8a8239bc..15d7c52f 100644 --- a/includes/kohana/modules/cache/guide/cache/usage.md +++ b/includes/kohana/modules/cache/guide/cache/usage.md @@ -67,7 +67,7 @@ Certain cache drivers support setting values with tags. To set a value to cache $memcache = Cache::instance('memcachetag'); // Test for tagging interface - if ($memcache instanceof Kohana_Cache_Tagging) + if ($memcache instanceof Cache_Tagging) { // Set a value with some tags for 30 seconds $memcache->set('foo', $object, 30, array('snafu', 'stfu', 'fubar')); @@ -79,7 +79,7 @@ Certain cache drivers support setting values with tags. To set a value to cache $memcache->set('foo', $object, 30); } -It is possible to implement custom tagging solutions onto existing or new cache drivers by implementing the [Kohana_Cache_Tagging] interface. Kohana_Cache only applies the interface to drivers that support tagging natively as standard. +It is possible to implement custom tagging solutions onto existing or new cache drivers by implementing the [Cache_Tagging] interface. Kohana_Cache only applies the interface to drivers that support tagging natively as standard. ### Getting a value from cache @@ -115,7 +115,7 @@ It is possible to retrieve values from cache grouped by tag, using the [Cache::f // Find values based on tag return $cache->find('snafu'); } - catch (Kohana_Cache_Exception $e) + catch (Cache_Exception $e) { // Handle gracefully return FALSE; @@ -170,7 +170,7 @@ Some of the caching drivers support deleting by tag. This will remove all the ca $cache = Cache::instance(); // Check for tagging interface - if ($cache instanceof Kohana_Cache_Tagging) + if ($cache instanceof Cache_Tagging) { // Delete all entries by the tag 'snafu' $cache->delete_tag('snafu'); @@ -189,7 +189,7 @@ When not automated, garbage collection is the responsibility of the developer. I $gc = 10; // If the GC probability is a hit - if (rand(0,99) <= $gc and $cache_file instanceof Kohana_Cache_GarbageCollect) + if (rand(0,99) <= $gc and $cache_file instanceof Cache_GarbageCollect) { // Garbage Collect $cache_file->garbage_collect(); @@ -199,10 +199,10 @@ When not automated, garbage collection is the responsibility of the developer. I Kohana Cache comes with two interfaces that are implemented where the drivers support them: - - __[Kohana_Cache_Tagging] for tagging support on cache entries__ + - __[Cache_Tagging] for tagging support on cache entries__ - [Cache_MemcacheTag] - [Cache_Sqlite] - - __[Kohana_Cache_GarbageCollect] for garbage collection with drivers without native support__ + - __[Cache_GarbageCollect] for garbage collection with drivers without native support__ - [Cache_File] - [Cache_Sqlite] @@ -212,7 +212,7 @@ When using interface specific caching features, ensure that code checks for the $cache = Cache::instance(); // Test for Garbage Collection - if ($cache instanceof Kohana_Cache_GarbageCollect) + if ($cache instanceof Cache_GarbageCollect) { // Collect garbage $cache->garbage_collect(); diff --git a/includes/kohana/modules/cache/tests/cache/CacheBasicMethodsTest.php b/includes/kohana/modules/cache/tests/cache/CacheBasicMethodsTest.php new file mode 100644 index 00000000..5fdae60e --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/CacheBasicMethodsTest.php @@ -0,0 +1,299 @@ +_cache_driver; + + $this->_cache_driver = $cache; + return $this; + } + + /** + * Data provider for test_set_get() + * + * @return array + */ + public function provider_set_get() + { + $object = new StdClass; + $object->foo = 'foo'; + $object->bar = 'bar'; + + $html_text = << + + + + + + +TESTTEXT; + + return array( + array( + array( + 'id' => 'string', // Key to set to cache + 'value' => 'foobar', // Value to set to key + 'ttl' => 0, // Time to live + 'wait' => FALSE, // Test wait time to let cache expire + 'type' => 'string', // Type test + 'default' => NULL // Default value get should return + ), + 'foobar' + ), + array( + array( + 'id' => 'integer', + 'value' => 101010, + 'ttl' => 0, + 'wait' => FALSE, + 'type' => 'integer', + 'default' => NULL + ), + 101010 + ), + array( + array( + 'id' => 'float', + 'value' => 10.00, + 'ttl' => 0, + 'wait' => FALSE, + 'type' => 'float', + 'default' => NULL + ), + 10.00 + ), + array( + array( + 'id' => 'array', + 'value' => array( + 'key' => 'foo', + 'value' => 'bar' + ), + 'ttl' => 0, + 'wait' => FALSE, + 'type' => 'array', + 'default' => NULL + ), + array( + 'key' => 'foo', + 'value' => 'bar' + ) + ), + array( + array( + 'id' => 'boolean', + 'value' => TRUE, + 'ttl' => 0, + 'wait' => FALSE, + 'type' => 'boolean', + 'default' => NULL + ), + TRUE + ), + array( + array( + 'id' => 'null', + 'value' => NULL, + 'ttl' => 0, + 'wait' => FALSE, + 'type' => 'null', + 'default' => NULL + ), + NULL + ), + array( + array( + 'id' => 'object', + 'value' => $object, + 'ttl' => 0, + 'wait' => FALSE, + 'type' => 'object', + 'default' => NULL + ), + $object + ), + array( + array( + 'id' => 'bar\\ with / troublesome key', + 'value' => 'foo bar snafu', + 'ttl' => 0, + 'wait' => FALSE, + 'type' => 'string', + 'default' => NULL + ), + 'foo bar snafu' + ), + array( + array( + 'id' => 'bar', + 'value' => 'foo', + 'ttl' => 3, + 'wait' => 5, + 'type' => 'null', + 'default' => NULL + ), + NULL + ), + array( + array( + 'id' => 'snafu', + 'value' => 'fubar', + 'ttl' => 3, + 'wait' => 5, + 'type' => 'string', + 'default' => 'something completely different!' + ), + 'something completely different!' + ), + array( + array( + 'id' => 'new line test with HTML', + 'value' => $html_text, + 'ttl' => 10, + 'wait' => FALSE, + 'type' => 'string', + 'default' => NULL, + ), + $html_text + ) + ); + } + + /** + * Tests the [Cache::set()] method, testing; + * + * - The value is cached + * - The lifetime is respected + * - The returned value type is as expected + * - The default not-found value is respected + * + * @dataProvider provider_set_get + * + * @param array data + * @param mixed expected + * @return void + */ + public function test_set_get(array $data, $expected) + { + $cache = $this->cache(); + extract($data); + + $this->assertTrue($cache->set($id, $value, $ttl)); + + if ($wait !== FALSE) + { + // Lets let the cache expire + sleep($wait); + } + + $result = $cache->get($id, $default); + $this->assertEquals($expected, $result); + $this->assertInternalType($type, $result); + + unset($id, $value, $ttl, $wait, $type, $default); + } + + /** + * Tests the [Cache::delete()] method, testing; + * + * - The a cached value is deleted from cache + * - The cache returns a TRUE value upon deletion + * - The cache returns a FALSE value if no value exists to delete + * + * @return void + */ + public function test_delete() + { + // Init + $cache = $this->cache(); + $cache->delete_all(); + + // Test deletion if real cached value + if ( ! $cache->set('test_delete_1', 'This should not be here!', 0)) + { + $this->fail('Unable to set cache value to delete!'); + } + + // Test delete returns TRUE and check the value is gone + $this->assertTrue($cache->delete('test_delete_1')); + $this->assertNull($cache->get('test_delete_1')); + + // Test non-existant cache value returns FALSE if no error + $this->assertFalse($cache->delete('test_delete_1')); + } + + /** + * Tests [Cache::delete_all()] works as specified + * + * @return void + * @uses Kohana_CacheBasicMethodsTest::provider_set_get() + */ + public function test_delete_all() + { + // Init + $cache = $this->cache(); + $data = $this->provider_set_get(); + + foreach ($data as $key => $values) + { + extract($values[0]); + if ( ! $cache->set($id, $value)) + { + $this->fail('Unable to set: '.$key.' => '.$value.' to cache'); + } + unset($id, $value, $ttl, $wait, $type, $default); + } + + // Test delete_all is successful + $this->assertTrue($cache->delete_all()); + + foreach ($data as $key => $values) + { + // Verify data has been purged + $this->assertSame('Cache Deleted!', $cache->get($values[0]['id'], + 'Cache Deleted!')); + } + } + +} // End Kohana_CacheBasicMethodsTest diff --git a/includes/kohana/modules/cache/tests/cache/CacheTest.php b/includes/kohana/modules/cache/tests/cache/CacheTest.php new file mode 100644 index 00000000..a5c7564a --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/CacheTest.php @@ -0,0 +1,242 @@ +load('cache.file')) + { + $base = array( + // Test default group + array( + NULL, + Cache::instance('file') + ), + // Test defined group + array( + 'file', + Cache::instance('file') + ), + ); + } + + + return array( + // Test bad group definition + $base+array( + Kohana_CacheTest::BAD_GROUP_DEFINITION, + 'Failed to load Kohana Cache group: 1010' + ), + ); + } + + /** + * Tests the [Cache::factory()] method behaves as expected + * + * @dataProvider provider_instance + * + * @return void + */ + public function test_instance($group, $expected) + { + if (in_array($group, array( + Kohana_CacheTest::BAD_GROUP_DEFINITION, + ) + )) + { + $this->setExpectedException('Cache_Exception'); + } + + try + { + $cache = Cache::instance($group); + } + catch (Cache_Exception $e) + { + $this->assertSame($expected, $e->getMessage()); + throw $e; + } + + $this->assertInstanceOf(get_class($expected), $cache); + $this->assertSame($expected->config(), $cache->config()); + } + + /** + * Tests that `clone($cache)` will be prevented to maintain singleton + * + * @return void + * @expectedException Cache_Exception + */ + public function test_cloning_fails() + { + if ( ! Kohana::$config->load('cache.file')) + { + $this->markTestSkipped('Unable to load File configuration'); + } + + try + { + $cache_clone = clone(Cache::instance('file')); + } + catch (Cache_Exception $e) + { + $this->assertSame('Cloning of Kohana_Cache objects is forbidden', + $e->getMessage()); + throw $e; + } + } + + /** + * Data provider for test_config + * + * @return array + */ + public function provider_config() + { + return array( + array( + array( + 'server' => 'otherhost', + 'port' => 5555, + 'persistent' => TRUE, + ), + NULL, + Kohana_CacheTest::EXPECT_SELF, + array( + 'server' => 'otherhost', + 'port' => 5555, + 'persistent' => TRUE, + ), + ), + array( + 'foo', + 'bar', + Kohana_CacheTest::EXPECT_SELF, + array( + 'foo' => 'bar' + ) + ), + array( + 'server', + NULL, + NULL, + array() + ), + array( + NULL, + NULL, + array(), + array() + ) + ); + } + + /** + * Tests the config method behaviour + * + * @dataProvider provider_config + * + * @param mixed key value to set or get + * @param mixed value to set to key + * @param mixed expected result from [Cache::config()] + * @param array expected config within cache + * @return void + */ + public function test_config($key, $value, $expected_result, array $expected_config) + { + $cache = $this->getMock('Cache_File', NULL, array(), '', FALSE); + + if ($expected_result === Kohana_CacheTest::EXPECT_SELF) + { + $expected_result = $cache; + } + + $this->assertSame($expected_result, $cache->config($key, $value)); + $this->assertSame($expected_config, $cache->config()); + } + + /** + * Data provider for test_sanitize_id + * + * @return array + */ + public function provider_sanitize_id() + { + return array( + array( + 'foo', + 'foo' + ), + array( + 'foo+-!@', + 'foo+-!@' + ), + array( + 'foo/bar', + 'foo_bar', + ), + array( + 'foo\\bar', + 'foo_bar' + ), + array( + 'foo bar', + 'foo_bar' + ), + array( + 'foo\\bar snafu/stfu', + 'foo_bar_snafu_stfu' + ) + ); + } + + /** + * Tests the [Cache::_sanitize_id()] method works as expected. + * This uses some nasty reflection techniques to access a protected + * method. + * + * @dataProvider provider_sanitize_id + * + * @param string id + * @param string expected + * @return void + */ + public function test_sanitize_id($id, $expected) + { + $cache = $this->getMock('Cache', array( + 'get', + 'set', + 'delete', + 'delete_all' + ), array(array()), + '', FALSE + ); + + $cache_reflection = new ReflectionClass($cache); + $sanitize_id = $cache_reflection->getMethod('_sanitize_id'); + $sanitize_id->setAccessible(TRUE); + + $this->assertSame($expected, $sanitize_id->invoke($cache, $id)); + } +} // End Kohana_CacheTest diff --git a/includes/kohana/modules/cache/tests/cache/FileTest.php b/includes/kohana/modules/cache/tests/cache/FileTest.php new file mode 100644 index 00000000..803258f2 --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/FileTest.php @@ -0,0 +1,98 @@ +load('cache.file')) + { + $this->markTestSkipped('Unable to load File configuration'); + } + + $this->cache(Cache::instance('file')); + } + + /** + * Tests that ignored files are not removed from file cache + * + * @return void + */ + public function test_ignore_delete_file() + { + $cache = $this->cache(); + $config = Kohana::$config->load('cache')->file; + $file = $config['cache_dir'].'/.gitignore'; + + // Lets pollute the cache folder + file_put_contents($file, 'foobar'); + + $this->assertTrue($cache->delete_all()); + $this->assertTrue(file_exists($file)); + $this->assertEquals('foobar', file_get_contents($file)); + + unlink($file); + } + + /** + * Provider for test_utf8 + * + * @return array + */ + public function provider_utf8() + { + return array( + array( + 'This is â ütf-8 Ӝ☃ string', + 'This is â ütf-8 Ӝ☃ string' + ), + array( + '㆓㆕㆙㆛', + '㆓㆕㆙㆛' + ), + array( + 'அஆஇஈஊ', + 'அஆஇஈஊ' + ) + ); + } + + /** + * Tests the file driver supports utf-8 strings + * + * @dataProvider provider_utf8 + * + * @return void + */ + public function test_utf8($input, $expected) + { + $cache = $this->cache(); + $cache->set('utf8', $input); + + $this->assertSame($expected, $cache->get('utf8')); + } + +} // End Kohana_SqliteTest diff --git a/includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php b/includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php deleted file mode 100644 index 229e7d32..00000000 --- a/includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php +++ /dev/null @@ -1,91 +0,0 @@ -delete_all(); - - self::$test_instance->set('testGet1', 'foo', 3600); - } - - public function tearDown() - { - self::$test_instance->delete_all(); - self::$test_instance = NULL; - } - - /** - * Tests the cache static instance method - */ - public function testInstance() - { - $file_instance = Cache::instance('file'); - $file_instance2 = Cache::instance('file'); - - // Try and load a Cache instance - $this->assertType('Kohana_Cache', Cache::instance()); - $this->assertType('Kohana_Cache_File', $file_instance); - - // Test instances are only initialised once - $this->assertTrue(spl_object_hash($file_instance) == spl_object_hash($file_instance2)); - - // Test the publically accessible Cache instance store - $this->assertTrue(spl_object_hash(Cache::$instances['file']) == spl_object_hash($file_instance)); - - // Get the constructor method - $constructorMethod = new ReflectionMethod($file_instance, '__construct'); - - // Test the constructor for hidden visibility - $this->assertTrue($constructorMethod->isProtected(), '__construct is does not have protected visibility'); - } - - public function testGet() - { - // Try and get a non property - $this->assertNull(self::$test_instance->get('testGet0')); - - // Try and get a non property with default return value - $this->assertEquals('bar', self::$test_instance->get('testGet0', 'bar')); - - // Try and get a real cached property - $this->assertEquals('foo', self::$test_instance->get('testGet1')); - } - - public function testSet() - { - $value = 'foobar'; - $value2 = 'snafu'; - - // Set a new property - $this->assertTrue(self::$test_instance->set('testSet1', $value)); - - // Test the property exists - $this->assertEquals(self::$test_instance->get('testSet1'), $value); - - // Test short set - $this->assertTrue(self::$test_instance->set('testSet2', $value2, 3)); - - // Test the property exists - $this->assertEquals(self::$test_instance->get('testSet2'), $value2); - - // Allow test2 to expire - sleep(4); - - // Test the property has expired - $this->assertNull(self::$test_instance->get('testSet2')); - } - - public function testDelete() - { - - } - - public function testDeleteAll() - { - - } -} \ No newline at end of file diff --git a/includes/kohana/modules/cache/tests/cache/SqliteTest.php b/includes/kohana/modules/cache/tests/cache/SqliteTest.php new file mode 100644 index 00000000..4a9c2ea6 --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/SqliteTest.php @@ -0,0 +1,44 @@ +markTestSkipped('SQLite PDO PHP Extension is not available'); + } + + if ( ! Kohana::$config->load('cache.sqlite')) + { + $this->markTestIncomplete('Unable to load sqlite configuration'); + } + + $this->cache(Cache::instance('sqlite')); + } + +} // End Kohana_SqliteTest diff --git a/includes/kohana/modules/cache/tests/cache/WincacheTest.php b/includes/kohana/modules/cache/tests/cache/WincacheTest.php new file mode 100644 index 00000000..70e6f791 --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/WincacheTest.php @@ -0,0 +1,39 @@ +markTestSkipped('Wincache PHP Extension is not available'); + } + + $this->cache(Cache::instance('wincache')); + } + +} // End Kohana_WincacheTest diff --git a/includes/kohana/modules/cache/tests/cache/arithmetic/ApcTest.php b/includes/kohana/modules/cache/tests/cache/arithmetic/ApcTest.php new file mode 100644 index 00000000..e1597cdc --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/arithmetic/ApcTest.php @@ -0,0 +1,75 @@ +markTestSkipped('APC PHP Extension is not available'); + } + + if (ini_get('apc.enable_cli') != '1') + { + $this->markTestSkipped('Unable to test APC in CLI mode. To fix '. + 'place "apc.enable_cli=1" in your php.ini file'); + } + + $this->cache(Cache::instance('apc')); + } + + /** + * Tests the [Cache::set()] method, testing; + * + * - The value is cached + * - The lifetime is respected + * - The returned value type is as expected + * - The default not-found value is respected + * + * This test doesn't test the TTL as there is a known bug/feature + * in APC that prevents the same request from killing cache on timeout. + * + * @link http://pecl.php.net/bugs/bug.php?id=16814 + * + * @dataProvider provider_set_get + * + * @param array data + * @param mixed expected + * @return void + */ + public function test_set_get(array $data, $expected) + { + if ($data['wait'] !== FALSE) + { + $this->markTestSkipped('Unable to perform TTL test in CLI, see: '. + 'http://pecl.php.net/bugs/bug.php?id=16814 for more info!'); + } + + parent::test_set_get($data, $expected); + } + +} // End Kohana_ApcTest diff --git a/includes/kohana/modules/cache/tests/cache/arithmetic/CacheArithmeticMethods.php b/includes/kohana/modules/cache/tests/cache/arithmetic/CacheArithmeticMethods.php new file mode 100644 index 00000000..1dcc7c7b --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/arithmetic/CacheArithmeticMethods.php @@ -0,0 +1,173 @@ +cache(); + + if ($cache instanceof Cache) + { + $cache->delete_all(); + } + } + + /** + * Provider for test_increment + * + * @return array + */ + public function provider_increment() + { + return array( + array( + 0, + array( + 'id' => 'increment_test_1', + 'step' => 1 + ), + 1 + ), + array( + 1, + array( + 'id' => 'increment_test_2', + 'step' => 1 + ), + 2 + ), + array( + 5, + array( + 'id' => 'increment_test_3', + 'step' => 5 + ), + 10 + ), + array( + NULL, + array( + 'id' => 'increment_test_4', + 'step' => 1 + ), + FALSE + ), + ); + } + + /** + * Test for [Cache_Arithmetic::increment()] + * + * @dataProvider provider_increment + * + * @param integer start state + * @param array increment arguments + * @return void + */ + public function test_increment( + $start_state = NULL, + array $inc_args, + $expected) + { + $cache = $this->cache(); + + if ($start_state !== NULL) + { + $cache->set($inc_args['id'], $start_state, 0); + } + + $this->assertSame( + $expected, + $cache->increment( + $inc_args['id'], + $inc_args['step'] + ) + ); + } + + /** + * Provider for test_decrement + * + * @return array + */ + public function provider_decrement() + { + return array( + array( + 10, + array( + 'id' => 'decrement_test_1', + 'step' => 1 + ), + 9 + ), + array( + 10, + array( + 'id' => 'decrement_test_2', + 'step' => 2 + ), + 8 + ), + array( + 50, + array( + 'id' => 'decrement_test_3', + 'step' => 5 + ), + 45 + ), + array( + NULL, + array( + 'id' => 'decrement_test_4', + 'step' => 1 + ), + FALSE + ), + ); } + + /** + * Test for [Cache_Arithmetic::decrement()] + * + * @dataProvider provider_decrement + * + * @param integer start state + * @param array decrement arguments + * @return void + */ + public function test_decrement( + $start_state = NULL, + array $dec_args, + $expected) + { + $cache = $this->cache(); + + if ($start_state !== NULL) + { + $cache->set($dec_args['id'], $start_state, 0); + } + + $this->assertSame( + $expected, + $cache->decrement( + $dec_args['id'], + $dec_args['step'] + ) + ); + } + +} // End Kohana_CacheArithmeticMethodsTest diff --git a/includes/kohana/modules/cache/tests/cache/arithmetic/MemcacheTest.php b/includes/kohana/modules/cache/tests/cache/arithmetic/MemcacheTest.php new file mode 100644 index 00000000..07cb9ef7 --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/arithmetic/MemcacheTest.php @@ -0,0 +1,103 @@ +markTestSkipped('Memcache PHP Extension is not available'); + } + if ( ! $config = Kohana::$config->load('cache.memcache')) + { + $this->markTestSkipped('Unable to load Memcache configuration'); + } + + $memcache = new Memcache; + if ( ! $memcache->connect($config['servers']['local']['host'], + $config['servers']['local']['port'])) + { + $this->markTestSkipped('Unable to connect to memcache server @ '. + $config['servers']['local']['host'].':'. + $config['servers']['local']['port']); + } + + if ($memcache->getVersion() === FALSE) + { + $this->markTestSkipped('Memcache server @ '. + $config['servers']['local']['host'].':'. + $config['servers']['local']['port']. + ' not responding!'); + } + + unset($memcache); + + $this->cache(Cache::instance('memcache')); + } + + /** + * Tests that multiple values set with Memcache do not cause unexpected + * results. For accurate results, this should be run with a memcache + * configuration that includes multiple servers. + * + * This is to test #4110 + * + * @link http://dev.kohanaframework.org/issues/4110 + * @return void + */ + public function test_multiple_set() + { + $cache = $this->cache(); + $id_set = 'set_id'; + $ttl = 300; + + $data = array( + 'foobar', + 0, + 1.0, + new stdClass, + array('foo', 'bar' => 1), + TRUE, + NULL, + FALSE + ); + + $previous_set = $cache->get($id_set, NULL); + + foreach ($data as $value) + { + // Use Equals over Sames as Objects will not be equal + $this->assertEquals($previous_set, $cache->get($id_set, NULL)); + $cache->set($id_set, $value, $ttl); + + $previous_set = $value; + } + } + + +} // End Kohana_CacheArithmeticMemcacheTest diff --git a/includes/kohana/modules/cache/tests/cache/request/client/CacheTest.php b/includes/kohana/modules/cache/tests/cache/request/client/CacheTest.php new file mode 100644 index 00000000..11f9a3ec --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/request/client/CacheTest.php @@ -0,0 +1,265 @@ +defaults(array( + 'controller' => 'welcome', + 'action' => 'index' + )); + + parent::setUp(); + } + + /** + * Tests the Client does not attempt to load cache if no Cache library + * is present + * + * @return void + */ + public function test_cache_not_called_with_no_cache() + { + $request = new Request('welcome/index'); + $response = new Response; + + $client_mock = $this->getMock('Request_Client_Internal'); + + $request->client($client_mock); + $client_mock->expects($this->exactly(0)) + ->method('execute_request'); + $client_mock->expects($this->once()) + ->method('execute') + ->will($this->returnValue($response)); + + $this->assertSame($response, $request->execute()); + } + + /** + * Tests that the client attempts to load a cached response from the + * cache library, but fails. + * + * @return void + */ + public function test_cache_miss() + { + $route = new Route('welcome/index'); + $route->defaults(array( + 'controller' => 'Kohana_Request_CacheTest_Dummy', + 'action' => 'index', + )); + + $request = new Request('welcome/index', NULL, array($route)); + $cache_mock = $this->_get_cache_mock(); + + $request->client()->cache(HTTP_Cache::factory($cache_mock)); + + $cache_mock->expects($this->once()) + ->method('get') + ->with($request->client()->cache()->create_cache_key($request)) + ->will($this->returnValue(FALSE)); + + $response = $request->client()->execute($request); + + $this->assertSame(HTTP_Cache::CACHE_STATUS_MISS, + $response->headers(HTTP_Cache::CACHE_STATUS_KEY)); + } + + /** + * Tests the client saves a response if the correct headers are set + * + * @return void + */ + public function test_cache_save() + { + $lifetime = 800; + $request = new Request('welcome/index'); + $cache_mock = $this->_get_cache_mock(); + $response = Response::factory(); + + $request->client()->cache(new HTTP_Cache(array( + 'cache' => $cache_mock + ) + )); + + $response->headers('cache-control', 'max-age='.$lifetime); + + $key = $request->client()->cache()->create_cache_key($request); + + $cache_mock->expects($this->at(0)) + ->method('set') + ->with($this->stringEndsWith($key), $this->identicalTo(0)); + + $cache_mock->expects($this->at(1)) + ->method('set') + ->with($this->identicalTo($key), $this->anything(), $this->identicalTo($lifetime)) + ->will($this->returnValue(TRUE)); + + $this->assertTrue( + $request->client()->cache() + ->cache_response($key, $request, $response) + ); + + $this->assertSame(HTTP_Cache::CACHE_STATUS_SAVED, + $response->headers(HTTP_Cache::CACHE_STATUS_KEY)); + } + + /** + * Tests the client handles a cache HIT event correctly + * + * @return void + */ + public function test_cache_hit() + { + $lifetime = 800; + $request = new Request('welcome/index'); + $cache_mock = $this->_get_cache_mock(); + + $request->client()->cache(new HTTP_Cache(array( + 'cache' => $cache_mock + ) + )); + + $response = Response::factory(); + + $response->headers(array( + 'cache-control' => 'max-age='.$lifetime, + HTTP_Cache::CACHE_STATUS_KEY => + HTTP_Cache::CACHE_STATUS_HIT + )); + + $key = $request->client()->cache()->create_cache_key($request); + + $cache_mock->expects($this->exactly(2)) + ->method('get') + ->with($this->stringContains($key)) + ->will($this->returnValue($response)); + + $request->client()->cache()->cache_response($key, $request); + + $this->assertSame(HTTP_Cache::CACHE_STATUS_HIT, + $response->headers(HTTP_Cache::CACHE_STATUS_KEY)); + } + + + /** + * Data provider for test_set_cache + * + * @return array + */ + public function provider_set_cache() + { + return array( + array( + new HTTP_Header(array('cache-control' => 'no-cache')), + array('no-cache' => NULL), + FALSE, + ), + array( + new HTTP_Header(array('cache-control' => 'no-store')), + array('no-store' => NULL), + FALSE, + ), + array( + new HTTP_Header(array('cache-control' => 'max-age=100')), + array('max-age' => '100'), + TRUE + ), + array( + new HTTP_Header(array('cache-control' => 'private')), + array('private' => NULL), + FALSE + ), + array( + new HTTP_Header(array('cache-control' => 'private, max-age=100')), + array('private' => NULL, 'max-age' => '100'), + FALSE + ), + array( + new HTTP_Header(array('cache-control' => 'private, s-maxage=100')), + array('private' => NULL, 's-maxage' => '100'), + TRUE + ), + array( + new HTTP_Header(array( + 'expires' => date('m/d/Y', strtotime('-1 day')), + )), + array(), + FALSE + ), + array( + new HTTP_Header(array( + 'expires' => date('m/d/Y', strtotime('+1 day')), + )), + array(), + TRUE + ), + array( + new HTTP_Header(array()), + array(), + TRUE + ), + ); + } + + /** + * Tests the set_cache() method + * + * @test + * @dataProvider provider_set_cache + * + * @return null + */ + public function test_set_cache($headers, $cache_control, $expected) + { + /** + * Set up a mock response object to test with + */ + $response = $this->getMock('Response'); + + $response->expects($this->any()) + ->method('headers') + ->will($this->returnValue($headers)); + + $request = new Request_Client_Internal; + $request->cache(new HTTP_Cache); + $this->assertEquals($request->cache()->set_cache($response), $expected); + } + + /** + * Returns a mock object for Cache + * + * @return Cache + */ + protected function _get_cache_mock() + { + return $this->getMock('Cache_File', array(), array(), '', FALSE); + } +} // End Kohana_Request_Client_CacheTest + +class Controller_Kohana_Request_CacheTest_Dummy extends Controller +{ + public function action_index() + { + + } +} \ No newline at end of file diff --git a/includes/kohana/modules/cache/tests/phpunit.xml b/includes/kohana/modules/cache/tests/phpunit.xml index 5e3b9c79..c6590be2 100644 --- a/includes/kohana/modules/cache/tests/phpunit.xml +++ b/includes/kohana/modules/cache/tests/phpunit.xml @@ -7,7 +7,10 @@ Any options you specify when calling phpunit will override the ones in here --> - + + ./cache + + cache/ diff --git a/includes/kohana/modules/codebench/classes/bench/arrcallback.php b/includes/kohana/modules/codebench/classes/Bench/ArrCallback.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/arrcallback.php rename to includes/kohana/modules/codebench/classes/Bench/ArrCallback.php diff --git a/includes/kohana/modules/codebench/classes/bench/autolinkemails.php b/includes/kohana/modules/codebench/classes/Bench/AutoLinkEmails.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/autolinkemails.php rename to includes/kohana/modules/codebench/classes/Bench/AutoLinkEmails.php diff --git a/includes/kohana/modules/codebench/classes/bench/datespan.php b/includes/kohana/modules/codebench/classes/Bench/DateSpan.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/datespan.php rename to includes/kohana/modules/codebench/classes/Bench/DateSpan.php diff --git a/includes/kohana/modules/codebench/classes/bench/explodelimit.php b/includes/kohana/modules/codebench/classes/Bench/ExplodeLimit.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/explodelimit.php rename to includes/kohana/modules/codebench/classes/Bench/ExplodeLimit.php diff --git a/includes/kohana/modules/codebench/classes/bench/gruberurl.php b/includes/kohana/modules/codebench/classes/Bench/GruberURL.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/gruberurl.php rename to includes/kohana/modules/codebench/classes/Bench/GruberURL.php diff --git a/includes/kohana/modules/codebench/classes/bench/ltrimdigits.php b/includes/kohana/modules/codebench/classes/Bench/LtrimDigits.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/ltrimdigits.php rename to includes/kohana/modules/codebench/classes/Bench/LtrimDigits.php diff --git a/includes/kohana/modules/codebench/classes/bench/mddobaseurl.php b/includes/kohana/modules/codebench/classes/Bench/MDDoBaseURL.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/mddobaseurl.php rename to includes/kohana/modules/codebench/classes/Bench/MDDoBaseURL.php diff --git a/includes/kohana/modules/codebench/classes/bench/mddoimageurl.php b/includes/kohana/modules/codebench/classes/Bench/MDDoImageURL.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/mddoimageurl.php rename to includes/kohana/modules/codebench/classes/Bench/MDDoImageURL.php diff --git a/includes/kohana/modules/codebench/classes/bench/mddoincludeviews.php b/includes/kohana/modules/codebench/classes/Bench/MDDoIncludeViews.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/mddoincludeviews.php rename to includes/kohana/modules/codebench/classes/Bench/MDDoIncludeViews.php diff --git a/includes/kohana/modules/codebench/classes/bench/stripnullbytes.php b/includes/kohana/modules/codebench/classes/Bench/StripNullBytes.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/stripnullbytes.php rename to includes/kohana/modules/codebench/classes/Bench/StripNullBytes.php diff --git a/includes/kohana/modules/codebench/classes/bench/transliterate.php b/includes/kohana/modules/codebench/classes/Bench/Transliterate.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/transliterate.php rename to includes/kohana/modules/codebench/classes/Bench/Transliterate.php diff --git a/includes/kohana/modules/codebench/classes/bench/urlsite.php b/includes/kohana/modules/codebench/classes/Bench/URLSite.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/urlsite.php rename to includes/kohana/modules/codebench/classes/Bench/URLSite.php diff --git a/includes/kohana/modules/codebench/classes/bench/userfuncarray.php b/includes/kohana/modules/codebench/classes/Bench/UserFuncArray.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/userfuncarray.php rename to includes/kohana/modules/codebench/classes/Bench/UserFuncArray.php diff --git a/includes/kohana/modules/codebench/classes/bench/validcolor.php b/includes/kohana/modules/codebench/classes/Bench/ValidColor.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/validcolor.php rename to includes/kohana/modules/codebench/classes/Bench/ValidColor.php diff --git a/includes/kohana/modules/codebench/classes/bench/validurl.php b/includes/kohana/modules/codebench/classes/Bench/ValidURL.php similarity index 100% rename from includes/kohana/modules/codebench/classes/bench/validurl.php rename to includes/kohana/modules/codebench/classes/Bench/ValidURL.php diff --git a/includes/kohana/modules/codebench/classes/codebench.php b/includes/kohana/modules/codebench/classes/Codebench.php similarity index 100% rename from includes/kohana/modules/codebench/classes/codebench.php rename to includes/kohana/modules/codebench/classes/Codebench.php diff --git a/includes/kohana/modules/codebench/classes/controller/codebench.php b/includes/kohana/modules/codebench/classes/Controller/Codebench.php similarity index 82% rename from includes/kohana/modules/codebench/classes/controller/codebench.php rename to includes/kohana/modules/codebench/classes/Controller/Codebench.php index cdd9a705..c940a3c3 100644 --- a/includes/kohana/modules/codebench/classes/controller/codebench.php +++ b/includes/kohana/modules/codebench/classes/Controller/Codebench.php @@ -13,12 +13,14 @@ class Controller_Codebench extends Kohana_Controller_Template { // The codebench view public $template = 'codebench'; - public function action_index($class) + public function action_index() { + $class = $this->request->param('class'); + // Convert submitted class name to URI segment if (isset($_POST['class'])) { - $this->request->redirect('codebench/'.trim($_POST['class'])); + throw HTTP_Exception::factory(302)->location('codebench/'.trim($_POST['class'])); } // Pass the class name on to the view diff --git a/includes/kohana/modules/codebench/classes/kohana/codebench.php b/includes/kohana/modules/codebench/classes/Kohana/Codebench.php similarity index 99% rename from includes/kohana/modules/codebench/classes/kohana/codebench.php rename to includes/kohana/modules/codebench/classes/Kohana/Codebench.php index 68601bd1..0acfa5ce 100644 --- a/includes/kohana/modules/codebench/classes/kohana/codebench.php +++ b/includes/kohana/modules/codebench/classes/Kohana/Codebench.php @@ -47,7 +47,7 @@ abstract class Kohana_Codebench { public function __construct() { // Set the maximum execution time - set_time_limit(Kohana::config('codebench')->max_execution_time); + set_time_limit(Kohana::$config->load('codebench')->max_execution_time); } /** diff --git a/includes/kohana/modules/codebench/config/userguide.php b/includes/kohana/modules/codebench/config/userguide.php index f9e6a14d..e6943aca 100644 --- a/includes/kohana/modules/codebench/config/userguide.php +++ b/includes/kohana/modules/codebench/config/userguide.php @@ -17,7 +17,7 @@ return array( 'description' => 'Code benchmarking tool.', // Copyright message, shown in the footer for this module - 'copyright' => '© 2008–2010 Kohana Team', + 'copyright' => '© 2008–2012 Kohana Team', ) ) ); \ No newline at end of file diff --git a/includes/kohana/modules/codebench/init.php b/includes/kohana/modules/codebench/init.php index 866cc341..f238cb05 100644 --- a/includes/kohana/modules/codebench/init.php +++ b/includes/kohana/modules/codebench/init.php @@ -3,6 +3,6 @@ // Catch-all route for Codebench classes to run Route::set('codebench', 'codebench(/)') ->defaults(array( - 'controller' => 'codebench', + 'controller' => 'Codebench', 'action' => 'index', 'class' => NULL)); diff --git a/includes/kohana/modules/codebench/views/codebench.php b/includes/kohana/modules/codebench/views/codebench.php index 7b6ef2a9..64823403 100644 --- a/includes/kohana/modules/codebench/views/codebench.php +++ b/includes/kohana/modules/codebench/views/codebench.php @@ -111,7 +111,7 @@ } }); - expand_all) { ?> + load('codebench')->expand_all) { ?> // Expand all benchmark details by default $toggle_all.click(); @@ -245,7 +245,7 @@ - Raw output:', Kohana::debug($codebench) ?> + Raw output:', Debug::vars($codebench) ?> diff --git a/includes/kohana/modules/database/classes/Config/Database.php b/includes/kohana/modules/database/classes/Config/Database.php new file mode 100644 index 00000000..7acb31bf --- /dev/null +++ b/includes/kohana/modules/database/classes/Config/Database.php @@ -0,0 +1,12 @@ +_db_instance = $config['instance']; + } + elseif ($this->_db_instance === NULL) + { + $this->_db_instance = Database::$default; + } + + if (isset($config['table_name'])) + { + $this->_table_name = $config['table_name']; + } + } + + /** + * Tries to load the specificed configuration group + * + * Returns FALSE if group does not exist or an array if it does + * + * @param string $group Configuration group + * @return boolean|array + */ + public function load($group) + { + /** + * Prevents the catch-22 scenario where the database config reader attempts to load the + * database connections details from the database. + * + * @link http://dev.kohanaframework.org/issues/4316 + */ + if ($group === 'database') + return FALSE; + + $query = DB::select('config_key', 'config_value') + ->from($this->_table_name) + ->where('group_name', '=', $group) + ->execute($this->_db_instance); + + return count($query) ? array_map('unserialize', $query->as_array('config_key', 'config_value')) : FALSE; + } +} diff --git a/includes/kohana/modules/database/classes/Kohana/Config/Database/Writer.php b/includes/kohana/modules/database/classes/Kohana/Config/Database/Writer.php new file mode 100644 index 00000000..f6b67380 --- /dev/null +++ b/includes/kohana/modules/database/classes/Kohana/Config/Database/Writer.php @@ -0,0 +1,110 @@ +_loaded_keys[$group] = array_combine(array_keys($config), array_keys($config)); + } + + return $config; + } + + /** + * Writes the passed config for $group + * + * Returns chainable instance on success or throws + * Kohana_Config_Exception on failure + * + * @param string $group The config group + * @param string $key The config key to write to + * @param array $config The configuration to write + * @return boolean + */ + public function write($group, $key, $config) + { + $config = serialize($config); + + // Check to see if we've loaded the config from the table already + if (isset($this->_loaded_keys[$group][$key])) + { + $this->_update($group, $key, $config); + } + else + { + // Attempt to run an insert query + // This may fail if the config key already exists in the table + // and we don't know about it + try + { + $this->_insert($group, $key, $config); + } + catch (Database_Exception $e) + { + // Attempt to run an update instead + $this->_update($group, $key, $config); + } + } + + return TRUE; + } + + /** + * Insert the config values into the table + * + * @param string $group The config group + * @param string $key The config key to write to + * @param array $config The serialized configuration to write + * @return boolean + */ + protected function _insert($group, $key, $config) + { + DB::insert($this->_table_name, array('group_name', 'config_key', 'config_value')) + ->values(array($group, $key, $config)) + ->execute($this->_db_instance); + + return $this; + } + + /** + * Update the config values in the table + * + * @param string $group The config group + * @param string $key The config key to write to + * @param array $config The serialized configuration to write + * @return boolean + */ + protected function _update($group, $key, $config) + { + DB::update($this->_table_name) + ->set(array('config_value' => $config)) + ->where('group_name', '=', $group) + ->where('config_key', '=', $key) + ->execute($this->_db_instance); + + return $this; + } +} diff --git a/includes/kohana/modules/database/classes/kohana/db.php b/includes/kohana/modules/database/classes/Kohana/DB.php similarity index 83% rename from includes/kohana/modules/database/classes/kohana/db.php rename to includes/kohana/modules/database/classes/Kohana/DB.php index b6e513f5..77388a99 100644 --- a/includes/kohana/modules/database/classes/kohana/db.php +++ b/includes/kohana/modules/database/classes/Kohana/DB.php @@ -1,4 +1,4 @@ -set(array('login_count' => DB::expr('login_count + 1')))->where('id', '=', $id); * $users = ORM::factory('user')->where(DB::expr("BINARY `hash`"), '=', $hash)->find(); * - * @param string expression + * @param string $string expression + * @param array parameters * @return Database_Expression */ - public static function expr($string) + public static function expr($string, $parameters = array()) { - return new Database_Expression($string); + return new Database_Expression($string, $parameters); } } // End DB diff --git a/includes/kohana/modules/database/classes/kohana/database.php b/includes/kohana/modules/database/classes/Kohana/Database.php similarity index 82% rename from includes/kohana/modules/database/classes/kohana/database.php rename to includes/kohana/modules/database/classes/Kohana/Database.php index d84420a4..267d938c 100644 --- a/includes/kohana/modules/database/classes/kohana/database.php +++ b/includes/kohana/modules/database/classes/Kohana/Database.php @@ -1,9 +1,9 @@ -$name; + $config = Kohana::$config->load('database')->$name; } if ( ! isset($config['type'])) @@ -75,7 +75,10 @@ abstract class Kohana_Database { $driver = 'Database_'.ucfirst($config['type']); // Create the database connection instance - new $driver($name, $config); + $driver = new $driver($name, $config); + + // Store the database instance + Database::$instances[$name] = $driver; } return Database::$instances[$name]; @@ -105,7 +108,7 @@ abstract class Kohana_Database { * * @return void */ - protected function __construct($name, array $config) + public function __construct($name, array $config) { // Set the instance name $this->_instance = $name; @@ -113,8 +116,10 @@ abstract class Kohana_Database { // Store the config locally $this->_config = $config; - // Store the database instance - Database::$instances[$name] = $this; + if (empty($this->_config['table_prefix'])) + { + $this->_config['table_prefix'] = ''; + } } /** @@ -128,7 +133,7 @@ abstract class Kohana_Database { * * @return void */ - final public function __destruct() + public function __destruct() { $this->disconnect(); } @@ -140,7 +145,7 @@ abstract class Kohana_Database { * * @return string */ - final public function __toString() + public function __toString() { return $this->_instance; } @@ -177,7 +182,7 @@ abstract class Kohana_Database { * $db->set_charset('utf8'); * * @throws Database_Exception - * @param string character set name + * @param string $charset character set name * @return void */ abstract public function set_charset($charset); @@ -191,10 +196,10 @@ abstract class Kohana_Database { * // Make a SELECT query and use "Model_User" for the results * $db->query(Database::SELECT, 'SELECT * FROM users LIMIT 1', 'Model_User'); * - * @param integer Database::SELECT, Database::INSERT, etc - * @param string SQL query - * @param mixed result object class string, TRUE for stdClass, FALSE for assoc array - * @param array object construct parameters for result class + * @param integer $type Database::SELECT, Database::INSERT, etc + * @param string $sql SQL query + * @param mixed $as_object result object class string, TRUE for stdClass, FALSE for assoc array + * @param array $params object construct parameters for result class * @return object Database_Result for SELECT queries * @return array list (insert id, row count) for INSERT queries * @return integer number of affected rows for all other queries @@ -219,7 +224,7 @@ abstract class Kohana_Database { * $db->rollback(); * } * - * @param string transaction mode + * @param string $mode transaction mode * @return boolean */ abstract public function begin($mode = NULL); @@ -250,7 +255,7 @@ abstract class Kohana_Database { * // Get the total number of records in the "users" table * $count = $db->count_records('users'); * - * @param mixed table name string or array(query, alias) + * @param mixed $table table name string or array(query, alias) * @return integer */ public function count_records($table) @@ -267,7 +272,7 @@ abstract class Kohana_Database { * * $db->datatype('char'); * - * @param string SQL data type + * @param string $type SQL data type * @return array */ public function datatype($type) @@ -342,7 +347,7 @@ abstract class Kohana_Database { * // Get all user-related tables * $tables = $db->list_tables('user%'); * - * @param string table to search for + * @param string $like table to search for * @return array */ abstract public function list_tables($like = NULL); @@ -360,9 +365,9 @@ abstract class Kohana_Database { * // Get the columns from a table that doesn't use the table prefix * $columns = $db->list_columns('users', NULL, FALSE); * - * @param string table to get columns from - * @param string column to search for - * @param boolean whether to add the table prefix automatically or not + * @param string $table table to get columns from + * @param string $like column to search for + * @param boolean $add_prefix whether to add the table prefix automatically or not * @return array */ abstract public function list_columns($table, $like = NULL, $add_prefix = TRUE); @@ -373,7 +378,7 @@ abstract class Kohana_Database { * // Returns: array('CHAR', '6') * list($type, $length) = $db->_parse_type('CHAR(6)'); * - * @param string + * @param string $type * @return array list containing the type and length, if any */ protected function _parse_type($type) @@ -385,7 +390,7 @@ abstract class Kohana_Database { } // Closing parenthesis - $close = strpos($type, ')', $open); + $close = strrpos($type, ')', $open); // Length without parentheses $length = substr($type, $open + 1, $close - 1 - $open); @@ -416,11 +421,11 @@ abstract class Kohana_Database { * $db->quote('fred'); // 'fred' * * Objects passed to this function will be converted to strings. - * [Database_Expression] objects will use the value of the expression. + * [Database_Expression] objects will be compiled. * [Database_Query] objects will be compiled and converted to a sub-query. * All other objects will be converted using the `__toString` method. * - * @param mixed any value to quote + * @param mixed $value any value to quote * @return string * @uses Database::escape */ @@ -447,8 +452,8 @@ abstract class Kohana_Database { } elseif ($value instanceof Database_Expression) { - // Use a raw expression - return $value->value(); + // Compile the expression + return $value->compile($this); } else { @@ -480,19 +485,27 @@ abstract class Kohana_Database { * * You can also use SQL methods within identifiers. * - * // The value of "column" will be quoted - * $column = $db->quote_column('COUNT("column")'); + * $column = $db->quote_column(DB::expr('COUNT(`column`)')); * - * @param mixed column name or array(column, alias) + * Objects passed to this function will be converted to strings. + * [Database_Expression] objects will be compiled. + * [Database_Query] objects will be compiled and converted to a sub-query. + * All other objects will be converted using the `__toString` method. + * + * @param mixed $column column name or array(column, alias) * @return string * @uses Database::quote_identifier * @uses Database::table_prefix */ public function quote_column($column) { + // Identifiers are escaped by repeating them + $escaped_identifier = $this->_identifier.$this->_identifier; + if (is_array($column)) { list($column, $alias) = $column; + $alias = str_replace($this->_identifier, $escaped_identifier, $alias); } if ($column instanceof Database_Query) @@ -502,23 +515,20 @@ abstract class Kohana_Database { } elseif ($column instanceof Database_Expression) { - // Use a raw expression - $column = $column->value(); + // Compile the expression + $column = $column->compile($this); } else { // Convert to a string $column = (string) $column; + $column = str_replace($this->_identifier, $escaped_identifier, $column); + if ($column === '*') { return $column; } - elseif (strpos($column, '"') !== FALSE) - { - // Quote the column in FUNC("column") identifiers - $column = preg_replace('/"(.+?)"/e', '$this->quote_column("$1")', $column); - } elseif (strpos($column, '.') !== FALSE) { $parts = explode('.', $column); @@ -562,16 +572,25 @@ abstract class Kohana_Database { * * $table = $db->quote_table($table); * - * @param mixed table name or array(table, alias) + * Objects passed to this function will be converted to strings. + * [Database_Expression] objects will be compiled. + * [Database_Query] objects will be compiled and converted to a sub-query. + * All other objects will be converted using the `__toString` method. + * + * @param mixed $table table name or array(table, alias) * @return string * @uses Database::quote_identifier * @uses Database::table_prefix */ public function quote_table($table) { + // Identifiers are escaped by repeating them + $escaped_identifier = $this->_identifier.$this->_identifier; + if (is_array($table)) { list($table, $alias) = $table; + $alias = str_replace($this->_identifier, $escaped_identifier, $alias); } if ($table instanceof Database_Query) @@ -581,14 +600,16 @@ abstract class Kohana_Database { } elseif ($table instanceof Database_Expression) { - // Use a raw expression - $table = $table->value(); + // Compile the expression + $table = $table->compile($this); } else { // Convert to a string $table = (string) $table; + $table = str_replace($this->_identifier, $escaped_identifier, $table); + if (strpos($table, '.') !== FALSE) { $parts = explode('.', $table); @@ -630,18 +651,22 @@ abstract class Kohana_Database { * Quote a database identifier * * Objects passed to this function will be converted to strings. - * [Database_Expression] objects will use the value of the expression. + * [Database_Expression] objects will be compiled. * [Database_Query] objects will be compiled and converted to a sub-query. * All other objects will be converted using the `__toString` method. * - * @param mixed any identifier + * @param mixed $value any identifier * @return string */ public function quote_identifier($value) { + // Identifiers are escaped by repeating them + $escaped_identifier = $this->_identifier.$this->_identifier; + if (is_array($value)) { list($value, $alias) = $value; + $alias = str_replace($this->_identifier, $escaped_identifier, $alias); } if ($value instanceof Database_Query) @@ -651,14 +676,16 @@ abstract class Kohana_Database { } elseif ($value instanceof Database_Expression) { - // Use a raw expression - $value = $value->value(); + // Compile the expression + $value = $value->compile($this); } else { // Convert to a string $value = (string) $value; + $value = str_replace($this->_identifier, $escaped_identifier, $value); + if (strpos($value, '.') !== FALSE) { $parts = explode('.', $value); @@ -691,7 +718,7 @@ abstract class Kohana_Database { * * $value = $db->escape('any string'); * - * @param string value to quote + * @param string $value value to quote * @return string */ abstract public function escape($value); diff --git a/includes/kohana/modules/database/classes/kohana/database/exception.php b/includes/kohana/modules/database/classes/Kohana/Database/Exception.php similarity index 80% rename from includes/kohana/modules/database/classes/kohana/database/exception.php rename to includes/kohana/modules/database/classes/Kohana/Database/Exception.php index ea2630ed..68f709e0 100644 --- a/includes/kohana/modules/database/classes/kohana/database/exception.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Exception.php @@ -1,4 +1,4 @@ -_value = $value; + $this->_parameters = $parameters; + } + + /** + * Bind a variable to a parameter. + * + * @param string $param parameter key to replace + * @param mixed $var variable to use + * @return $this + */ + public function bind($param, & $var) + { + $this->_parameters[$param] =& $var; + + return $this; + } + + /** + * Set the value of a parameter. + * + * @param string $param parameter key to replace + * @param mixed $value value to use + * @return $this + */ + public function param($param, $value) + { + $this->_parameters[$param] = $value; + + return $this; + } + + /** + * Add multiple parameter values. + * + * @param array $params list of parameter values + * @return $this + */ + public function parameters(array $params) + { + $this->_parameters = $params + $this->_parameters; + + return $this; + } + + /** + * Get the expression value as a string. + * + * $sql = $expression->value(); + * + * @return string + */ + public function value() + { + return (string) $this->_value; + } + + /** + * Return the value of the expression as a string. + * + * echo $expression; + * + * @return string + * @uses Database_Expression::value + */ + public function __toString() + { + return $this->value(); + } + + /** + * Compile the SQL expression and return it. Replaces any parameters with + * their given values. + * + * @param mixed Database instance or name of instance + * @return string + */ + public function compile($db = NULL) + { + if ( ! is_object($db)) + { + // Get the database instance + $db = Database::instance($db); + } + + $value = $this->value(); + + if ( ! empty($this->_parameters)) + { + // Quote all of the parameter values + $params = array_map(array($db, 'quote'), $this->_parameters); + + // Replace the values in the expression + $value = strtr($value, $params); + } + + return $value; + } + +} // End Database_Expression diff --git a/includes/kohana/modules/database/classes/kohana/database/mysql.php b/includes/kohana/modules/database/classes/Kohana/Database/MySQL.php similarity index 95% rename from includes/kohana/modules/database/classes/kohana/database/mysql.php rename to includes/kohana/modules/database/classes/Kohana/Database/MySQL.php index 4fdbf019..8700904e 100644 --- a/includes/kohana/modules/database/classes/kohana/database/mysql.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/MySQL.php @@ -1,4 +1,4 @@ -_connection = mysql_connect($hostname, $username, $password, TRUE); } } - catch (ErrorException $e) + catch (Exception $e) { // No connection exists $this->_connection = NULL; throw new Database_Exception(':error', - array(':error' => mysql_error()), - mysql_errno()); + array(':error' => $e->getMessage()), + $e->getCode()); } // \xFF is a better delimiter, but the PHP driver uses underscore @@ -79,12 +79,25 @@ class Kohana_Database_MySQL extends Database { // Set the character set $this->set_charset($this->_config['charset']); } + + if ( ! empty($this->_config['connection']['variables'])) + { + // Set session variables + $variables = array(); + + foreach ($this->_config['connection']['variables'] as $var => $val) + { + $variables[] = 'SESSION '.$var.' = '.$this->quote($val); + } + + mysql_query('SET '.implode(', ', $variables), $this->_connection); + } } /** * Select the database * - * @param string Database + * @param string $database Database * @return void */ protected function _select_db($database) @@ -157,7 +170,7 @@ class Kohana_Database_MySQL extends Database { // Make sure the database is connected $this->_connection or $this->connect(); - if ( ! empty($this->_config['profiling'])) + if (Kohana::$profiling) { // Benchmark this query for the current instance $benchmark = Profiler::start("Database ({$this->_instance})", $sql); @@ -263,7 +276,7 @@ class Kohana_Database_MySQL extends Database { * * @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html * - * @param string Isolation level + * @param string $mode Isolation level * @return boolean */ public function begin($mode = NULL) @@ -284,7 +297,6 @@ class Kohana_Database_MySQL extends Database { /** * Commit a SQL transaction * - * @param string Isolation level * @return boolean */ public function commit() @@ -298,7 +310,6 @@ class Kohana_Database_MySQL extends Database { /** * Rollback a SQL transaction * - * @param string Isolation level * @return boolean */ public function rollback() diff --git a/includes/kohana/modules/database/classes/kohana/database/mysql/result.php b/includes/kohana/modules/database/classes/Kohana/Database/MySQL/Result.php similarity index 96% rename from includes/kohana/modules/database/classes/kohana/database/mysql/result.php rename to includes/kohana/modules/database/classes/Kohana/Database/MySQL/Result.php index bbfa66ed..22a5d143 100644 --- a/includes/kohana/modules/database/classes/kohana/database/mysql/result.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/MySQL/Result.php @@ -1,4 +1,4 @@ -_config['connection']); // Force PDO to use exceptions for all errors - $attrs = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + $options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; if ( ! empty($persistent)) { // Make the connection persistent - $attrs[PDO::ATTR_PERSISTENT] = TRUE; + $options[PDO::ATTR_PERSISTENT] = TRUE; } try { // Create a new PDO connection - $this->_connection = new PDO($dsn, $username, $password, $attrs); + $this->_connection = new PDO($dsn, $username, $password, $options); } catch (PDOException $e) { @@ -60,12 +60,51 @@ class Kohana_Database_PDO extends Database { array(':error' => $e->getMessage()), $e->getCode()); } + } - if ( ! empty($this->_config['charset'])) - { - // Set the character set - $this->set_charset($this->_config['charset']); - } + /** + * Create or redefine a SQL aggregate function. + * + * [!!] Works only with SQLite + * + * @link http://php.net/manual/function.pdo-sqlitecreateaggregate + * + * @param string $name Name of the SQL function to be created or redefined + * @param callback $step Called for each row of a result set + * @param callback $final Called after all rows of a result set have been processed + * @param integer $arguments Number of arguments that the SQL function takes + * + * @return boolean + */ + public function create_aggregate($name, $step, $final, $arguments = -1) + { + $this->_connection or $this->connect(); + + return $this->_connection->sqliteCreateAggregate( + $name, $step, $final, $arguments + ); + } + + /** + * Create or redefine a SQL function. + * + * [!!] Works only with SQLite + * + * @link http://php.net/manual/function.pdo-sqlitecreatefunction + * + * @param string $name Name of the SQL function to be created or redefined + * @param callback $callback Callback which implements the SQL function + * @param integer $arguments Number of arguments that the SQL function takes + * + * @return boolean + */ + public function create_function($name, $callback, $arguments = -1) + { + $this->_connection or $this->connect(); + + return $this->_connection->sqliteCreateFunction( + $name, $callback, $arguments + ); } public function disconnect() @@ -79,9 +118,9 @@ class Kohana_Database_PDO extends Database { public function set_charset($charset) { // Make sure the database is connected - $this->_connection or $this->connect(); + $this->_connection OR $this->connect(); - // Execute a raw SET NAMES query + // This SQL-92 syntax is not supported by all drivers $this->_connection->exec('SET NAMES '.$this->quote($charset)); } @@ -90,7 +129,7 @@ class Kohana_Database_PDO extends Database { // Make sure the database is connected $this->_connection or $this->connect(); - if ( ! empty($this->_config['profiling'])) + if (Kohana::$profiling) { // Benchmark this query for the current instance $benchmark = Profiler::start("Database ({$this->_instance})", $sql); diff --git a/includes/kohana/modules/database/classes/kohana/database/query.php b/includes/kohana/modules/database/classes/Kohana/Database/Query.php similarity index 65% rename from includes/kohana/modules/database/classes/kohana/database/query.php rename to includes/kohana/modules/database/classes/Kohana/Database/Query.php index f08f3c1b..480e41b8 100644 --- a/includes/kohana/modules/database/classes/kohana/database/query.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Query.php @@ -1,6 +1,6 @@ -_force_execute = $force; $this->_lifetime = $lifetime; return $this; @@ -106,7 +111,8 @@ class Kohana_Database_Query { /** * Returns results as objects * - * @param string classname or TRUE for stdClass + * @param string $class classname or TRUE for stdClass + * @param array $params * @return $this */ public function as_object($class = TRUE, array $params = NULL) @@ -125,8 +131,8 @@ class Kohana_Database_Query { /** * Set the value of a parameter in the query. * - * @param string parameter key to replace - * @param mixed value to use + * @param string $param parameter key to replace + * @param mixed $value value to use * @return $this */ public function param($param, $value) @@ -140,8 +146,8 @@ class Kohana_Database_Query { /** * Bind a variable to a parameter in the query. * - * @param string parameter key to replace - * @param mixed variable to use + * @param string $param parameter key to replace + * @param mixed $var variable to use * @return $this */ public function bind($param, & $var) @@ -155,7 +161,7 @@ class Kohana_Database_Query { /** * Add multiple parameters to the query. * - * @param array list of parameters + * @param array $params list of parameters * @return $this */ public function parameters(array $params) @@ -170,11 +176,17 @@ class Kohana_Database_Query { * Compile the SQL query and return it. Replaces any parameters with their * given values. * - * @param object Database instance + * @param mixed $db Database instance or name of instance * @return string */ - public function compile(Database $db) + public function compile($db = NULL) { + if ( ! is_object($db)) + { + // Get the database instance + $db = Database::instance($db); + } + // Import the SQL locally $sql = $this->_sql; @@ -193,12 +205,14 @@ class Kohana_Database_Query { /** * Execute the current query on the given database. * - * @param mixed Database instance or name of instance + * @param mixed $db Database instance or name of instance + * @param string result object classname, TRUE for stdClass or FALSE for array + * @param array result object constructor arguments * @return object Database_Result for SELECT queries * @return mixed the insert id for INSERT queries * @return integer number of affected rows for all other queries */ - public function execute($db = NULL) + public function execute($db = NULL, $as_object = NULL, $object_params = NULL) { if ( ! is_object($db)) { @@ -206,6 +220,16 @@ class Kohana_Database_Query { $db = Database::instance($db); } + if ($as_object === NULL) + { + $as_object = $this->_as_object; + } + + if ($object_params === NULL) + { + $object_params = $this->_object_params; + } + // Compile the SQL query $sql = $this->compile($db); @@ -214,17 +238,19 @@ class Kohana_Database_Query { // Set the cache key based on the database instance name and SQL $cache_key = 'Database::query("'.$db.'", "'.$sql.'")'; - if (! is_null($result = Kohana::cache($cache_key, NULL, $this->_lifetime))) + // Read the cache first to delete a possible hit with lifetime <= 0 + if (($result = Kohana::cache($cache_key, NULL, $this->_lifetime)) !== NULL + AND ! $this->_force_execute) { // Return a cached result - return new Database_Result_Cached($result, $sql, $this->_as_object, $this->_object_params); + return new Database_Result_Cached($result, $sql, $as_object, $object_params); } } // Execute the query - $result = $db->query($this->_type, $sql, $this->_as_object, $this->_object_params); + $result = $db->query($this->_type, $sql, $as_object, $object_params); - if (isset($cache_key)) + if (isset($cache_key) AND $this->_lifetime > 0) { // Cache the result array Kohana::cache($cache_key, $result->as_array(), $this->_lifetime); diff --git a/includes/kohana/modules/database/classes/kohana/database/query/builder.php b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder.php similarity index 72% rename from includes/kohana/modules/database/classes/kohana/database/query/builder.php rename to includes/kohana/modules/database/classes/Kohana/Database/Query/Builder.php index 8d36e790..3e1bdb50 100644 --- a/includes/kohana/modules/database/classes/kohana/database/query/builder.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder.php @@ -1,4 +1,4 @@ -quote($value); - } elseif ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE) { // Quote the value, it is not a parameter @@ -122,8 +118,16 @@ abstract class Kohana_Database_Query_Builder extends Database_Query { if ($column) { - // Apply proper quoting to the column - $column = $db->quote_column($column); + if (is_array($column)) + { + // Use the column name + $column = $db->quote_identifier(reset($column)); + } + else + { + // Apply proper quoting to the column + $column = $db->quote_column($column); + } } // Append the statement to the query @@ -140,8 +144,8 @@ abstract class Kohana_Database_Query_Builder extends Database_Query { /** * Compiles an array of set values into an SQL partial. Used for UPDATE. * - * @param object Database instance - * @param array updated values + * @param object $db Database instance + * @param array $values updated values * @return string */ protected function _compile_set(Database $db, array $values) @@ -167,11 +171,41 @@ abstract class Kohana_Database_Query_Builder extends Database_Query { return implode(', ', $set); } + /** + * Compiles an array of GROUP BY columns into an SQL partial. + * + * @param object $db Database instance + * @param array $columns + * @return string + */ + protected function _compile_group_by(Database $db, array $columns) + { + $group = array(); + + foreach ($columns as $column) + { + if (is_array($column)) + { + // Use the column alias + $column = $db->quote_identifier(end($column)); + } + else + { + // Apply proper quoting to the column + $column = $db->quote_column($column); + } + + $group[] = $column; + } + + return 'GROUP BY '.implode(', ', $group); + } + /** * Compiles an array of ORDER BY statements into an SQL partial. * - * @param object Database instance - * @param array sorting columns + * @param object $db Database instance + * @param array $columns sorting columns * @return string */ protected function _compile_order_by(Database $db, array $columns) @@ -181,19 +215,24 @@ abstract class Kohana_Database_Query_Builder extends Database_Query { { list ($column, $direction) = $group; - if ($direction) + if (is_array($column)) { - // Make the direction uppercase - $direction = strtoupper($direction); + // Use the column alias + $column = $db->quote_identifier(end($column)); } - - if ($column) + else { - // Quote the column, if it has a value + // Apply proper quoting to the column $column = $db->quote_column($column); } - $sort[] = trim($column.' '.$direction); + if ($direction) + { + // Make the direction uppercase + $direction = ' '.strtoupper($direction); + } + + $sort[] = $column.$direction; } return 'ORDER BY '.implode(', ', $sort); diff --git a/includes/kohana/modules/database/classes/kohana/database/query/builder/delete.php b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Delete.php similarity index 79% rename from includes/kohana/modules/database/classes/kohana/database/query/builder/delete.php rename to includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Delete.php index b7dec297..0bc47613 100644 --- a/includes/kohana/modules/database/classes/kohana/database/query/builder/delete.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Delete.php @@ -1,4 +1,4 @@ -quote_table($this->_table); diff --git a/includes/kohana/modules/database/classes/kohana/database/query/builder/insert.php b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Insert.php similarity index 84% rename from includes/kohana/modules/database/classes/kohana/database/query/builder/insert.php rename to includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Insert.php index 525fbfe6..aa3c8073 100644 --- a/includes/kohana/modules/database/classes/kohana/database/query/builder/insert.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Insert.php @@ -1,4 +1,4 @@ -quote_table($this->_table); diff --git a/includes/kohana/modules/database/classes/kohana/database/query/builder/join.php b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Join.php similarity index 80% rename from includes/kohana/modules/database/classes/kohana/database/query/builder/join.php rename to includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Join.php index 03448c7e..10402f66 100644 --- a/includes/kohana/modules/database/classes/kohana/database/query/builder/join.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Join.php @@ -1,4 +1,4 @@ -_type) { $sql = strtoupper($this->_type).' JOIN'; diff --git a/includes/kohana/modules/database/classes/kohana/database/query/builder/select.php b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Select.php similarity index 79% rename from includes/kohana/modules/database/classes/kohana/database/query/builder/select.php rename to includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Select.php index 4c456241..3492a711 100644 --- a/includes/kohana/modules/database/classes/kohana/database/query/builder/select.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Select.php @@ -1,4 +1,4 @@ -_union []= array('select' => $select, 'all' => $all); return $this; - } + } /** * Start returning results after "OFFSET ..." * - * @param integer starting result number + * @param integer $number starting result number or NULL to reset * @return $this */ public function offset($number) { - $this->_offset = (int) $number; + $this->_offset = $number; return $this; } @@ -321,11 +317,17 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where /** * Compile the SQL query and return it. * - * @param object Database instance + * @param mixed $db Database instance or name of instance * @return string */ - public function compile(Database $db) + public function compile($db = NULL) { + if ( ! is_object($db)) + { + // Get the database instance + $db = Database::instance($db); + } + // Callback to quote columns $quote_column = array($db, 'quote_column'); @@ -372,8 +374,8 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where if ( ! empty($this->_group_by)) { - // Add sorting - $query .= ' GROUP BY '.implode(', ', array_map($quote_column, $this->_group_by)); + // Add grouping + $query .= ' '.$this->_compile_group_by($db, $this->_group_by); } if ( ! empty($this->_having)) @@ -399,7 +401,7 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where // Add offsets $query .= ' OFFSET '.$this->_offset; } - + if ( ! empty($this->_union)) { foreach ($this->_union as $u) { @@ -442,4 +444,3 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where } } // End Database_Query_Select - diff --git a/includes/kohana/modules/database/classes/kohana/database/query/builder/update.php b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Update.php similarity index 78% rename from includes/kohana/modules/database/classes/kohana/database/query/builder/update.php rename to includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Update.php index 4658e6a3..e6e3da50 100644 --- a/includes/kohana/modules/database/classes/kohana/database/query/builder/update.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Update.php @@ -1,4 +1,4 @@ - value) list + * @param array $pairs associative (column => value) list * @return $this */ public function set(array $pairs) @@ -66,8 +66,8 @@ class Kohana_Database_Query_Builder_Update extends Database_Query_Builder_Where /** * Set the value of a single column. * - * @param mixed table name or array($table, $alias) or object - * @param mixed column value + * @param mixed $column table name or array($table, $alias) or object + * @param mixed $value column value * @return $this */ public function value($column, $value) @@ -80,11 +80,17 @@ class Kohana_Database_Query_Builder_Update extends Database_Query_Builder_Where /** * Compile the SQL query and return it. * - * @param object Database instance + * @param mixed $db Database instance or name of instance * @return string */ - public function compile(Database $db) + public function compile($db = NULL) { + if ( ! is_object($db)) + { + // Get the database instance + $db = Database::instance($db); + } + // Start an update query $query = 'UPDATE '.$db->quote_table($this->_table); diff --git a/includes/kohana/modules/database/classes/kohana/database/query/builder/where.php b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Where.php similarity index 65% rename from includes/kohana/modules/database/classes/kohana/database/query/builder/where.php rename to includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Where.php index aeece75d..58f6b5dd 100644 --- a/includes/kohana/modules/database/classes/kohana/database/query/builder/where.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Query/Builder/Where.php @@ -1,4 +1,4 @@ -_where); + + if ($group AND reset($group) === '(') + { + array_pop($this->_where); + + return $this; + } + + return $this->where_close(); + } + + /** + * Closes an open "WHERE (...)" grouping. * * @return $this */ @@ -119,7 +139,7 @@ abstract class Kohana_Database_Query_Builder_Where extends Database_Query_Builde } /** - * Closes an open "OR WHERE (...)" grouping. + * Closes an open "WHERE (...)" grouping. * * @return $this */ @@ -133,8 +153,8 @@ abstract class Kohana_Database_Query_Builder_Where extends Database_Query_Builde /** * Applies sorting with "ORDER BY ..." * - * @param mixed column name or array($column, $alias) or object - * @param string direction of sorting + * @param mixed $column column name or array($column, $alias) or object + * @param string $direction direction of sorting * @return $this */ public function order_by($column, $direction = NULL) @@ -147,12 +167,12 @@ abstract class Kohana_Database_Query_Builder_Where extends Database_Query_Builde /** * Return up to "LIMIT ..." results * - * @param integer maximum results to return + * @param integer $number maximum results to return or NULL to reset * @return $this */ public function limit($number) { - $this->_limit = (int) $number; + $this->_limit = $number; return $this; } diff --git a/includes/kohana/modules/database/classes/kohana/database/result.php b/includes/kohana/modules/database/classes/Kohana/Database/Result.php similarity index 91% rename from includes/kohana/modules/database/classes/kohana/database/result.php rename to includes/kohana/modules/database/classes/Kohana/Database/Result.php index 6ddd4581..3c3284f9 100644 --- a/includes/kohana/modules/database/classes/kohana/database/result.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Result.php @@ -1,4 +1,4 @@ - "name" * $rows = $result->as_array('id', 'name'); * - * @param string column for associative keys - * @param string column for values + * @param string $key column for associative keys + * @param string $value column for values * @return array */ public function as_array($key = NULL, $value = NULL) @@ -175,8 +177,8 @@ abstract class Kohana_Database_Result implements Countable, Iterator, SeekableIt * // Get the "id" value * $id = $result->get('id'); * - * @param string column to get - * @param mixed default value if the column does not exist + * @param string $name column to get + * @param mixed $default default value if the column does not exist * @return mixed */ public function get($name, $default = NULL) @@ -217,6 +219,7 @@ abstract class Kohana_Database_Result implements Countable, Iterator, SeekableIt * // Row 10 exists * } * + * @param int $offset * @return boolean */ public function offsetExists($offset) @@ -229,6 +232,7 @@ abstract class Kohana_Database_Result implements Countable, Iterator, SeekableIt * * $row = $result[10]; * + * @param int $offset * @return mixed */ public function offsetGet($offset) @@ -244,6 +248,8 @@ abstract class Kohana_Database_Result implements Countable, Iterator, SeekableIt * * [!!] You cannot modify a database result. * + * @param int $offset + * @param mixed $value * @return void * @throws Kohana_Exception */ @@ -257,6 +263,7 @@ abstract class Kohana_Database_Result implements Countable, Iterator, SeekableIt * * [!!] You cannot modify a database result. * + * @param int $offset * @return void * @throws Kohana_Exception */ diff --git a/includes/kohana/modules/database/classes/kohana/database/result/cached.php b/includes/kohana/modules/database/classes/Kohana/Database/Result/Cached.php similarity index 94% rename from includes/kohana/modules/database/classes/kohana/database/result/cached.php rename to includes/kohana/modules/database/classes/Kohana/Database/Result/Cached.php index ee8659a6..8af08543 100644 --- a/includes/kohana/modules/database/classes/kohana/database/result/cached.php +++ b/includes/kohana/modules/database/classes/Kohana/Database/Result/Cached.php @@ -1,4 +1,4 @@ -_db = $db; } + elseif ( ! $this->_db) + { + // Use the default name + $this->_db = Database::$default; + } if (is_string($this->_db)) { diff --git a/includes/kohana/modules/database/classes/kohana/session/database.php b/includes/kohana/modules/database/classes/Kohana/Session/Database.php similarity index 95% rename from includes/kohana/modules/database/classes/kohana/session/database.php rename to includes/kohana/modules/database/classes/Kohana/Session/Database.php index 1a381cd9..34abbbd2 100644 --- a/includes/kohana/modules/database/classes/kohana/session/database.php +++ b/includes/kohana/modules/database/classes/Kohana/Session/Database.php @@ -1,4 +1,4 @@ -_regenerate(); + + return TRUE; + } + protected function _destroy() { if ($this->_update_id === NULL) diff --git a/includes/kohana/modules/database/classes/model/database.php b/includes/kohana/modules/database/classes/Model/Database.php similarity index 51% rename from includes/kohana/modules/database/classes/model/database.php rename to includes/kohana/modules/database/classes/Model/Database.php index e26ea12b..3b6e609d 100644 --- a/includes/kohana/modules/database/classes/model/database.php +++ b/includes/kohana/modules/database/classes/Model/Database.php @@ -1,3 +1,3 @@ -_database_instance = $config['instance']; - } - - if (isset($config['table'])) - { - $this->_database_table = $config['table']; - } - - parent::__construct(); - } - - /** - * Query the configuration table for all values for this group and - * unserialize each of the values. - * - * @param string group name - * @param array configuration array - * @return $this clone of the current object - */ - public function load($group, array $config = NULL) - { - if ($config === NULL AND $group !== 'database') - { - // Load all of the configuration values for this group - $query = DB::select('config_key', 'config_value') - ->from($this->_database_table) - ->where('group_name', '=', $group) - ->execute($this->_database_instance); - - if (count($query) > 0) - { - // Unserialize the configuration values - $config = array_map('unserialize', $query->as_array('config_key', 'config_value')); - } - } - - return parent::load($group, $config); - } - - /** - * Overload setting offsets to insert or update the database values as - * changes occur. - * - * @param string array key - * @param mixed new value - * @return mixed - */ - public function offsetSet($key, $value) - { - if ( ! $this->offsetExists($key)) - { - // Insert a new value - DB::insert($this->_database_table, array('group_name', 'config_key', 'config_value')) - ->values(array($this->_configuration_group, $key, serialize($value))) - ->execute($this->_database_instance); - } - elseif ($this->offsetGet($key) !== $value) - { - // Update the value - DB::update($this->_database_table) - ->value('config_value', serialize($value)) - ->where('group_name', '=', $this->_configuration_group) - ->where('config_key', '=', $key) - ->execute($this->_database_instance); - } - - return parent::offsetSet($key, $value); - } - -} // End Kohana_Config_Database diff --git a/includes/kohana/modules/database/classes/kohana/database/expression.php b/includes/kohana/modules/database/classes/kohana/database/expression.php deleted file mode 100644 index 90b81009..00000000 --- a/includes/kohana/modules/database/classes/kohana/database/expression.php +++ /dev/null @@ -1,62 +0,0 @@ -_value = $value; - } - - /** - * Get the expression value as a string. - * - * $sql = $expression->value(); - * - * @return string - */ - public function value() - { - return (string) $this->_value; - } - - /** - * Return the value of the expression as a string. - * - * echo $expression; - * - * @return string - * @uses Database_Expression::value - */ - public function __toString() - { - return $this->value(); - } - -} // End Database_Expression diff --git a/includes/kohana/modules/database/classes/session/database.php b/includes/kohana/modules/database/classes/session/database.php deleted file mode 100644 index 1e75223e..00000000 --- a/includes/kohana/modules/database/classes/session/database.php +++ /dev/null @@ -1,3 +0,0 @@ - array ( - 'type' => 'mysql', + 'type' => 'MySQL', 'connection' => array( /** * The following options are available for MySQL: @@ -14,6 +14,7 @@ return array * string username database username * string password database password * boolean persistent use persistent connections? + * array variables system variables as "key => value" pairs * * Ports and sockets may be appended to the hostname. */ @@ -26,10 +27,9 @@ return array 'table_prefix' => '', 'charset' => 'utf8', 'caching' => FALSE, - 'profiling' => TRUE, ), 'alternate' => array( - 'type' => 'pdo', + 'type' => 'PDO', 'connection' => array( /** * The following options are available for PDO: @@ -52,6 +52,5 @@ return array 'table_prefix' => '', 'charset' => 'utf8', 'caching' => FALSE, - 'profiling' => TRUE, ), -); \ No newline at end of file +); diff --git a/includes/kohana/modules/database/config/session.php b/includes/kohana/modules/database/config/session.php index a4229c71..58263ae6 100644 --- a/includes/kohana/modules/database/config/session.php +++ b/includes/kohana/modules/database/config/session.php @@ -1,4 +1,4 @@ - array( diff --git a/includes/kohana/modules/database/config/userguide.php b/includes/kohana/modules/database/config/userguide.php index 36b79a0b..88ff2a32 100644 --- a/includes/kohana/modules/database/config/userguide.php +++ b/includes/kohana/modules/database/config/userguide.php @@ -1,4 +1,4 @@ - 'Database agnostic querying and result management.', // Copyright message, shown in the footer for this module - 'copyright' => '© 2008–2010 Kohana Team', + 'copyright' => '© 2008–2012 Kohana Team', ) ) ); \ No newline at end of file diff --git a/includes/kohana/modules/database/guide/database/config.md b/includes/kohana/modules/database/guide/database/config.md index 6879c0ac..240a6bb4 100644 --- a/includes/kohana/modules/database/guide/database/config.md +++ b/includes/kohana/modules/database/guide/database/config.md @@ -9,7 +9,6 @@ The database configuration file contains an array of configuration groups. The s 'connection' => array CONNECTION_ARRAY, 'table_prefix' => string TABLE_PREFIX, 'charset' => string CHARACTER_SET, - 'profiling' => boolean QUERY_PROFILING, ), Understanding each of these settings is important. @@ -24,10 +23,8 @@ CONNECTION_ARRAY : Specific driver options for connecting to your database. (Driver options are explained [below](#connection-settings).) TABLE_PREFIX -: Prefix that will be added to all table names by the [query builder](#query_building). Prepared statements will **not** use the table prefix. +: Prefix that will be added to all table names by the [query builder](#query_building). -QUERY_PROFILING -: Enables [profiling](../kohana/profiling) of database queries. This is useful for seeing how many queries each page is using, and which are taking the longest. You must enable the profiler the view these stats. ## Example @@ -47,7 +44,6 @@ The example file below shows 2 MySQL connections, one local and one remote. ), 'table_prefix' => '', 'charset' => 'utf8', - 'profiling' => TRUE, ), 'remote' => array( 'type' => 'mysql', @@ -60,7 +56,6 @@ The example file below shows 2 MySQL connections, one local and one remote. ), 'table_prefix' => '', 'charset' => 'utf8', - 'profiling' => TRUE, ), ); @@ -111,8 +106,11 @@ A [PDO database](http://php.net/manual/en/book.pdo.php) can accept these options Type | Option | Description | Default value ----------|------------|----------------------------| ------------------------- `string` | dsn | PDO data source identifier | `localhost` +`array` | options | Driver-specific options | none `string` | username | Database username | `NULL` `string` | password | Database password | `NULL` `boolean` | persistent | Persistent connections | `FALSE` +The connection character set should be configured using the DSN string or `options` array. + [!!] If you are using PDO and are not sure what to use for the `dsn` option, review [PDO::__construct](http://php.net/pdo.construct). \ No newline at end of file diff --git a/includes/kohana/modules/database/guide/database/examples.md b/includes/kohana/modules/database/guide/database/examples.md index 48498235..6a9d1b59 100644 --- a/includes/kohana/modules/database/guide/database/examples.md +++ b/includes/kohana/modules/database/guide/database/examples.md @@ -2,9 +2,9 @@ Here are some "real world" examples of using the database library to construct your queries and use the results. -## Examples of Prepared Statements +## Examples of Parameterized Statements -TODO: 4-6 examples of prepared statements of varying complexity, including a good bind() example. +TODO: 4-6 examples of parameterized statements of varying complexity, including a good bind() example. ## Pagination and search/filter @@ -25,10 +25,10 @@ In this example, we loop through an array of whitelisted input fields and for ea //copy the query & execute it $pagination_query = clone $query; - $count = $pagination_query->select('COUNT("*") AS mycount')->execute()->get('mycount'); + $count = $pagination_query->select(DB::expr('COUNT(*)) AS mycount')->execute()->get('mycount'); //pass the total item count to Pagination - $config = Kohana::config('pagination'); + $config = Kohana::$config->load('pagination'); $pagination = Pagination::factory(array( 'total_items' => $count, 'current_page' => array('source' => 'route', 'key' => 'page'), diff --git a/includes/kohana/modules/database/guide/database/menu.md b/includes/kohana/modules/database/guide/database/menu.md index e3f2c7a6..e1c06a81 100644 --- a/includes/kohana/modules/database/guide/database/menu.md +++ b/includes/kohana/modules/database/guide/database/menu.md @@ -1,7 +1,7 @@ ## [Database]() - [Configuration](config) - [Querying](query) - - [Prepared Statements](query/prepared) + - [Parameterized Statements](query/parameterized) - [Query Builder](query/builder) - [Results](results) - [Examples](examples) \ No newline at end of file diff --git a/includes/kohana/modules/database/guide/database/query.md b/includes/kohana/modules/database/guide/database/query.md index 2e9bd41c..0a15bf55 100644 --- a/includes/kohana/modules/database/guide/database/query.md +++ b/includes/kohana/modules/database/guide/database/query.md @@ -1,5 +1,5 @@ # Making Queries -There are two different ways to make queries. The simplest way to make a query is to use [Database_Query], via [DB::query], to manually create queries. These queries are called [prepared statements](query/prepared) and allow you to set query parameters which are automatically escaped. The second way to make a query is by building the query using method calls. This is done using the [query builder](query/builder). +There are two different ways to make queries. The simplest way to make a query is to use [Database_Query], via [DB::query], to manually create queries. These queries are called [parameterized statements](query/parameterized) and allow you to set query parameters which are automatically escaped. The second way to make a query is by building the query using method calls. This is done using the [query builder](query/builder). [!!] All queries are run using the `execute` method, which accepts a [Database] object or instance name. See [Database_Query::execute] for more information. \ No newline at end of file diff --git a/includes/kohana/modules/database/guide/database/query/builder.md b/includes/kohana/modules/database/guide/database/query/builder.md index ec11b1a6..d2fd893c 100644 --- a/includes/kohana/modules/database/guide/database/query/builder.md +++ b/includes/kohana/modules/database/guide/database/query/builder.md @@ -2,8 +2,6 @@ Creating queries dynamically using objects and methods allows queries to be written very quickly in an agnostic way. Query building also adds identifier (table and column name) quoting, as well as value quoting. -[!!] At this time, it is not possible to combine query building with prepared statements. - ## Select Each type of database query is represented by a different class, each with their own methods. For instance, to create a SELECT query, we use [DB::select] which is a shortcut to return a new [Database_Query_Builder_Select] object: @@ -38,7 +36,7 @@ By default, [DB::select] will select all columns (`SELECT * ...`), but you can a Now take a minute to look at what this method chain is doing. First, we create a new selection object using the [DB::select] method. Next, we set table(s) using the `from()` method. Last, we search for a specific records using the `where()` method. We can display the SQL that will be executed by casting the query to a string: - echo Kohana::debug((string) $query); + echo Debug::vars((string) $query); // Should display: // SELECT `username`, `password` FROM `users` WHERE `username` = 'john' @@ -150,11 +148,11 @@ This query would generate the following SQL: ### Database Functions -Eventually you will probably run into a situation where you need to call `COUNT` or some other database function within your query. The query builder supports these functions in two ways. The first is by using quotes within aliases: +Eventually you will probably run into a situation where you need to call `COUNT` or some other database function within your query. The query builder supports these functions using the `Database_Expression` class: - $query = DB::select(array('COUNT("username")', 'total_users'))->from('users'); + $query = DB::select(array(DB::expr('COUNT(`username`)'), 'total_users'))->from('users'); -This looks almost exactly the same as a standard `AS` alias, but note how the column name is wrapped in double quotes. Any time a double-quoted value appears inside of a column name, **only** the part inside the double quotes will be escaped. This query would generate the following SQL: +This looks almost exactly the same as a standard `AS` alias, but note how the column name is put in a call to `DB::expr()`. Any time `DB::expr()` is used, the column name will **not** be escaped. This query would generate the following SQL: SELECT COUNT(`username`) AS `total_users` FROM `users` @@ -166,14 +164,14 @@ This looks almost exactly the same as a standard `AS` alias, but note how the co ->where('posts.created', '>=', $yesterday); $total = clone $query; - $total->select(array('COUNT( DISTINCT "username")', 'unique_users')); + $total->select(array(DB::expr('COUNT( DISTINCT `username`)'), 'unique_users')); $query->select('posts.username')->distinct(); ### Aggregate Functions Aggregate functions like `COUNT()`, `SUM()`, `AVG()`, etc. will most likely be used with the `group_by()` and possibly the `having()` methods in order to group and filter the results on a set of columns. - $query = DB::select('username', array('COUNT("id")', 'total_posts') + $query = DB::select('username', array(DB::expr('COUNT(`id`)'), 'total_posts') ->from('posts')->group_by('username')->having('total_posts', '>=', 10); This will generate the following query: @@ -184,7 +182,7 @@ This will generate the following query: Query Builder objects can be passed as parameters to many of the methods to create subqueries. Let's take the previous example query and pass it to a new query. - $sub = DB::select('username', array('COUNT("id")', 'total_posts') + $sub = DB::select('username', array(DB::expr('COUNT(`id`)'), 'total_posts') ->from('posts')->group_by('username')->having('total_posts', '>=', 10); $query = DB::select('profiles.*', 'posts.total_posts')->from('profiles') @@ -198,7 +196,7 @@ This will generate the following query: Insert queries can also use a select query for the input values - $sub = DB::select('username', array('COUNT("id")', 'total_posts') + $sub = DB::select('username', array(DB::expr('COUNT(`id`)'), 'total_posts') ->from('posts')->group_by('username')->having('total_posts', '>=', 10); $query = DB::insert('post_totals', array('username', 'posts'))->select($sub); diff --git a/includes/kohana/modules/database/guide/database/query/prepared.md b/includes/kohana/modules/database/guide/database/query/parameterized.md similarity index 89% rename from includes/kohana/modules/database/guide/database/query/prepared.md rename to includes/kohana/modules/database/guide/database/query/parameterized.md index 53611b67..5a4e5370 100644 --- a/includes/kohana/modules/database/guide/database/query/prepared.md +++ b/includes/kohana/modules/database/guide/database/query/parameterized.md @@ -1,6 +1,6 @@ -# Prepared Statements +# Parameterized Statements -Using prepared statements allows you to write SQL queries manually while still escaping the query values automatically to prevent [SQL injection](http://wikipedia.org/wiki/SQL_Injection). Creating a query is simple: +Using parameterized statements allows you to write SQL queries manually while still escaping the query values automatically to prevent [SQL injection](http://wikipedia.org/wiki/SQL_Injection). Creating a query is simple: $query = DB::query(Database::SELECT, 'SELECT * FROM users WHERE username = :user'); @@ -50,9 +50,9 @@ The only difference between `param()` and `bind()` is that `bind()` passes the v ## Display the raw query -If you want to display the SQL that will be executed, simply cast the object to a string: +If you want to display the SQL that will be executed, you can simply echo the query: - echo Kohana::debug((string) $query); + echo $query; // Should display: // SELECT * FROM users WHERE username = 'john' diff --git a/includes/kohana/modules/database/guide/database/results.md b/includes/kohana/modules/database/guide/database/results.md index 653aacc4..e13dfa12 100644 --- a/includes/kohana/modules/database/guide/database/results.md +++ b/includes/kohana/modules/database/guide/database/results.md @@ -2,7 +2,7 @@ ## Execute -Once you have a query object built, either through a prepared statement or through the builder, you must then `execute()` the query and retrieve the results. Depending on the query type used, the results returned will vary. +Once you have a query object built, either through a parameterized statement or through the builder, you must then `execute()` the query and retrieve the results. Depending on the query type used, the results returned will vary. ## Select @@ -70,7 +70,7 @@ To return a non-associative array, leave `$key` as NULL and just pass a `$value` Sometime you only want a single value from a query. The `get()` method returns the value of the named column from the current row. The second parameter, `$default`, is used to supply a default value when the result is NULL. - $total_users = DB::select(array('COUNT("username")', 'total_users'))->from('users')->execute()->get('total_users', 0); + $total_users = DB::select(array(DB::expr('COUNT(`username`)'), 'total_users'))->from('users')->execute()->get('total_users', 0); ### Select - `cached()` diff --git a/includes/kohana/modules/image/classes/Image.php b/includes/kohana/modules/image/classes/Image.php new file mode 100644 index 00000000..559d3440 --- /dev/null +++ b/includes/kohana/modules/image/classes/Image.php @@ -0,0 +1,3 @@ +resize(200, 500, Image::NONE); * - * @param integer new width - * @param integer new height - * @param integer master dimension + * @param integer $width new width + * @param integer $height new height + * @param integer $master master dimension * @return $this * @uses Image::_do_resize */ @@ -247,6 +248,19 @@ abstract class Kohana_Image { // Recalculate the width based on the height proportions $width = $this->width * $height / $this->height; break; + case Image::PRECISE: + // Resize to precise size + $ratio = $this->width / $this->height; + + if ($width / $height > $ratio) + { + $height = $this->height * $width / $this->width; + } + else + { + $width = $this->width * $height / $this->height; + } + break; } // Convert the width and height to integers, minimum value is 1px @@ -268,10 +282,10 @@ abstract class Kohana_Image { * // Crop the image to 200x200 pixels, from the center * $image->crop(200, 200); * - * @param integer new width - * @param integer new height - * @param mixed offset from the left - * @param mixed offset from the top + * @param integer $width new width + * @param integer $height new height + * @param mixed $offset_x offset from the left + * @param mixed $offset_y offset from the top * @return $this * @uses Image::_do_crop */ @@ -351,7 +365,7 @@ abstract class Kohana_Image { * // Rotate 90% counter-clockwise * $image->rotate(-90); * - * @param integer degrees to rotate: -360-360 + * @param integer $degrees degrees to rotate: -360-360 * @return $this * @uses Image::_do_rotate */ @@ -367,7 +381,7 @@ abstract class Kohana_Image { // Keep subtracting full circles until the degrees have normalized $degrees -= 360; } - while($degrees > 180); + while ($degrees > 180); } if ($degrees < -180) @@ -377,7 +391,7 @@ abstract class Kohana_Image { // Keep adding full circles until the degrees have normalized $degrees += 360; } - while($degrees < -180); + while ($degrees < -180); } $this->_do_rotate($degrees); @@ -394,7 +408,7 @@ abstract class Kohana_Image { * // Flip the image from left to right * $image->flip(Image::VERTICAL); * - * @param integer direction: Image::HORIZONTAL, Image::VERTICAL + * @param integer $direction direction: Image::HORIZONTAL, Image::VERTICAL * @return $this * @uses Image::_do_flip */ @@ -417,7 +431,7 @@ abstract class Kohana_Image { * // Sharpen the image by 20% * $image->sharpen(20); * - * @param integer amount to sharpen: 1-100 + * @param integer $amount amount to sharpen: 1-100 * @return $this * @uses Image::_do_sharpen */ @@ -448,9 +462,9 @@ abstract class Kohana_Image { * [!!] By default, the reflection will be go from transparent at the top * to opaque at the bottom. * - * @param integer reflection height - * @param integer reflection opacity: 0-100 - * @param boolean TRUE to fade in, FALSE to fade out + * @param integer $height reflection height + * @param integer $opacity reflection opacity: 0-100 + * @param boolean $fade_in TRUE to fade in, FALSE to fade out * @return $this * @uses Image::_do_reflection */ @@ -481,10 +495,10 @@ abstract class Kohana_Image { * $mark = Image::factory('upload/watermark.png'); * $image->watermark($mark, TRUE, TRUE); * - * @param object watermark Image instance - * @param integer offset from the left - * @param integer offset from the top - * @param integer opacity of watermark: 1-100 + * @param Image $watermark watermark Image instance + * @param integer $offset_x offset from the left + * @param integer $offset_y offset from the top + * @param integer $opacity opacity of watermark: 1-100 * @return $this * @uses Image::_do_watermark */ @@ -540,8 +554,8 @@ abstract class Kohana_Image { * // Make the image background black with 50% opacity * $image->background('#000', 50); * - * @param string hexadecimal color value - * @param integer background opacity: 0-100 + * @param string $color hexadecimal color value + * @param integer $opacity background opacity: 0-100 * @return $this * @uses Image::_do_background */ @@ -585,8 +599,8 @@ abstract class Kohana_Image { * [!!] If the file does not exist, and the directory is not writable, an * exception will be thrown. * - * @param string new image path - * @param integer quality of image: 1-100 + * @param string $file new image path + * @param integer $quality quality of image: 1-100 * @return boolean * @uses Image::_save * @throws Kohana_Exception @@ -634,8 +648,8 @@ abstract class Kohana_Image { * // Render the image as a PNG * $data = $image->render('png'); * - * @param string image type to return: png, jpg, gif, etc - * @param integer quality of image: 1-100 + * @param string $type image type to return: png, jpg, gif, etc + * @param integer $quality quality of image: 1-100 * @return string * @uses Image::_do_render */ @@ -653,8 +667,8 @@ abstract class Kohana_Image { /** * Execute a resize. * - * @param integer new width - * @param integer new height + * @param integer $width new width + * @param integer $height new height * @return void */ abstract protected function _do_resize($width, $height); @@ -662,10 +676,10 @@ abstract class Kohana_Image { /** * Execute a crop. * - * @param integer new width - * @param integer new height - * @param integer offset from the left - * @param integer offset from the top + * @param integer $width new width + * @param integer $height new height + * @param integer $offset_x offset from the left + * @param integer $offset_y offset from the top * @return void */ abstract protected function _do_crop($width, $height, $offset_x, $offset_y); @@ -673,7 +687,7 @@ abstract class Kohana_Image { /** * Execute a rotation. * - * @param integer degrees to rotate + * @param integer $degrees degrees to rotate * @return void */ abstract protected function _do_rotate($degrees); @@ -681,7 +695,7 @@ abstract class Kohana_Image { /** * Execute a flip. * - * @param integer direction to flip + * @param integer $direction direction to flip * @return void */ abstract protected function _do_flip($direction); @@ -689,7 +703,7 @@ abstract class Kohana_Image { /** * Execute a sharpen. * - * @param integer amount to sharpen + * @param integer $amount amount to sharpen * @return void */ abstract protected function _do_sharpen($amount); @@ -697,9 +711,9 @@ abstract class Kohana_Image { /** * Execute a reflection. * - * @param integer reflection height - * @param integer reflection opacity - * @param boolean TRUE to fade out, FALSE to fade in + * @param integer $height reflection height + * @param integer $opacity reflection opacity + * @param boolean $fade_in TRUE to fade out, FALSE to fade in * @return void */ abstract protected function _do_reflection($height, $opacity, $fade_in); @@ -707,10 +721,10 @@ abstract class Kohana_Image { /** * Execute a watermarking. * - * @param object watermarking Image - * @param integer offset from the left - * @param integer offset from the top - * @param integer opacity of watermark + * @param Image $image watermarking Image + * @param integer $offset_x offset from the left + * @param integer $offset_y offset from the top + * @param integer $opacity opacity of watermark * @return void */ abstract protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity); @@ -718,10 +732,10 @@ abstract class Kohana_Image { /** * Execute a background. * - * @param integer red - * @param integer green - * @param integer blue - * @param integer opacity + * @param integer $r red + * @param integer $g green + * @param integer $b blue + * @param integer $opacity opacity * @return void */ abstract protected function _do_background($r, $g, $b, $opacity); @@ -729,8 +743,8 @@ abstract class Kohana_Image { /** * Execute a save. * - * @param string new image filename - * @param integer quality + * @param string $file new image filename + * @param integer $quality quality * @return boolean */ abstract protected function _do_save($file, $quality); @@ -738,8 +752,8 @@ abstract class Kohana_Image { /** * Execute a render. * - * @param string image type: png, jpg, gif, etc - * @param integer quality + * @param string $type image type: png, jpg, gif, etc + * @param integer $quality quality * @return string */ abstract protected function _do_render($type, $quality); diff --git a/includes/kohana/modules/image/classes/kohana/image/gd.php b/includes/kohana/modules/image/classes/Kohana/Image/GD.php similarity index 87% rename from includes/kohana/modules/image/classes/kohana/image/gd.php rename to includes/kohana/modules/image/classes/Kohana/Image/GD.php index 38d0e240..6f383e3d 100644 --- a/includes/kohana/modules/image/classes/kohana/image/gd.php +++ b/includes/kohana/modules/image/classes/Kohana/Image/GD.php @@ -1,4 +1,4 @@ -height = imagesy($flipped); } + /** + * Execute a sharpen. + * + * @param integer $amount amount to sharpen + * @return void + */ protected function _do_sharpen($amount) { if ( ! Image_GD::$_bundled) @@ -322,6 +357,14 @@ class Kohana_Image_GD extends Image { } } + /** + * Execute a reflection. + * + * @param integer $height reflection height + * @param integer $opacity reflection opacity + * @param boolean $fade_in TRUE to fade out, FALSE to fade in + * @return void + */ protected function _do_reflection($height, $opacity, $fade_in) { if ( ! Image_GD::$_bundled) @@ -394,6 +437,15 @@ class Kohana_Image_GD extends Image { $this->height = imagesy($reflection); } + /** + * Execute a watermarking. + * + * @param Image $image watermarking Image + * @param integer $offset_x offset from the left + * @param integer $offset_y offset from the top + * @param integer $opacity opacity of watermark + * @return void + */ protected function _do_watermark(Image $watermark, $offset_x, $offset_y, $opacity) { if ( ! Image_GD::$_bundled) @@ -439,6 +491,15 @@ class Kohana_Image_GD extends Image { } } + /** + * Execute a background. + * + * @param integer $r red + * @param integer $g green + * @param integer $b blue + * @param integer $opacity opacity + * @return void + */ protected function _do_background($r, $g, $b, $opacity) { // Loads image if not yet loaded @@ -468,6 +529,13 @@ class Kohana_Image_GD extends Image { } } + /** + * Execute a save. + * + * @param string $file new image filename + * @param integer $quality quality + * @return boolean + */ protected function _do_save($file, $quality) { // Loads image if not yet loaded @@ -492,6 +560,13 @@ class Kohana_Image_GD extends Image { return TRUE; } + /** + * Execute a render. + * + * @param string $type image type: png, jpg, gif, etc + * @param integer $quality quality + * @return string + */ protected function _do_render($type, $quality) { // Loads image if not yet loaded @@ -520,13 +595,19 @@ class Kohana_Image_GD extends Image { * Get the GD saving function and image type for this extension. * Also normalizes the quality setting * - * @param string image type: png, jpg, etc - * @param integer image quality + * @param string $extension image type: png, jpg, etc + * @param integer $quality image quality * @return array save function, IMAGETYPE_* constant * @throws Kohana_Exception */ protected function _save_function($extension, & $quality) { + if ( ! $extension) + { + // Use the current image type + $extension = image_type_to_extension($this->type, FALSE); + } + switch (strtolower($extension)) { case 'jpg': @@ -563,8 +644,8 @@ class Kohana_Image_GD extends Image { /** * Create an empty image with the given width and height. * - * @param integer image width - * @param integer image height + * @param integer $width image width + * @param integer $height image height * @return resource */ protected function _create($width, $height) diff --git a/includes/kohana/modules/image/classes/Kohana/Image/Imagick.php b/includes/kohana/modules/image/classes/Kohana/Image/Imagick.php new file mode 100644 index 00000000..21adbbf5 --- /dev/null +++ b/includes/kohana/modules/image/classes/Kohana/Image/Imagick.php @@ -0,0 +1,334 @@ +im = new Imagick; + $this->im->readImage($file); + + if ( ! $this->im->getImageAlphaChannel()) + { + // Force the image to have an alpha channel + $this->im->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); + } + } + + /** + * Destroys the loaded image to free up resources. + * + * @return void + */ + public function __destruct() + { + $this->im->clear(); + $this->im->destroy(); + } + + protected function _do_resize($width, $height) + { + if ($this->im->scaleImage($width, $height)) + { + // Reset the width and height + $this->width = $this->im->getImageWidth(); + $this->height = $this->im->getImageHeight(); + + return TRUE; + } + + return FALSE; + } + + protected function _do_crop($width, $height, $offset_x, $offset_y) + { + if ($this->im->cropImage($width, $height, $offset_x, $offset_y)) + { + // Reset the width and height + $this->width = $this->im->getImageWidth(); + $this->height = $this->im->getImageHeight(); + + // Trim off hidden areas + $this->im->setImagePage($this->width, $this->height, 0, 0); + + return TRUE; + } + + return FALSE; + } + + protected function _do_rotate($degrees) + { + if ($this->im->rotateImage(new ImagickPixel('transparent'), $degrees)) + { + // Reset the width and height + $this->width = $this->im->getImageWidth(); + $this->height = $this->im->getImageHeight(); + + // Trim off hidden areas + $this->im->setImagePage($this->width, $this->height, 0, 0); + + return TRUE; + } + + return FALSE; + } + + protected function _do_flip($direction) + { + if ($direction === Image::HORIZONTAL) + { + return $this->im->flopImage(); + } + else + { + return $this->im->flipImage(); + } + } + + protected function _do_sharpen($amount) + { + // IM not support $amount under 5 (0.15) + $amount = ($amount < 5) ? 5 : $amount; + + // Amount should be in the range of 0.0 to 3.0 + $amount = ($amount * 3.0) / 100; + + return $this->im->sharpenImage(0, $amount); + } + + protected function _do_reflection($height, $opacity, $fade_in) + { + // Clone the current image and flip it for reflection + $reflection = $this->im->clone(); + $reflection->flipImage(); + + // Crop the reflection to the selected height + $reflection->cropImage($this->width, $height, 0, 0); + $reflection->setImagePage($this->width, $height, 0, 0); + + // Select the fade direction + $direction = array('transparent', 'black'); + + if ($fade_in) + { + // Change the direction of the fade + $direction = array_reverse($direction); + } + + // Create a gradient for fading + $fade = new Imagick; + $fade->newPseudoImage($reflection->getImageWidth(), $reflection->getImageHeight(), vsprintf('gradient:%s-%s', $direction)); + + // Apply the fade alpha channel to the reflection + $reflection->compositeImage($fade, Imagick::COMPOSITE_DSTOUT, 0, 0); + + // NOTE: Using setImageOpacity will destroy alpha channels! + $reflection->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA); + + // Create a new container to hold the image and reflection + $image = new Imagick; + $image->newImage($this->width, $this->height + $height, new ImagickPixel); + + // Force the image to have an alpha channel + $image->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); + + // Force the background color to be transparent + // $image->setImageBackgroundColor(new ImagickPixel('transparent')); + + // Match the colorspace between the two images before compositing + $image->setColorspace($this->im->getColorspace()); + + // Place the image and reflection into the container + if ($image->compositeImage($this->im, Imagick::COMPOSITE_SRC, 0, 0) + AND $image->compositeImage($reflection, Imagick::COMPOSITE_OVER, 0, $this->height)) + { + // Replace the current image with the reflected image + $this->im = $image; + + // Reset the width and height + $this->width = $this->im->getImageWidth(); + $this->height = $this->im->getImageHeight(); + + return TRUE; + } + + return FALSE; + } + + protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity) + { + // Convert the Image intance into an Imagick instance + $watermark = new Imagick; + $watermark->readImageBlob($image->render(), $image->file); + + if ($watermark->getImageAlphaChannel() !== Imagick::ALPHACHANNEL_ACTIVATE) + { + // Force the image to have an alpha channel + $watermark->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE); + } + + if ($opacity < 100) + { + // NOTE: Using setImageOpacity will destroy current alpha channels! + $watermark->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA); + } + + // Match the colorspace between the two images before compositing + // $watermark->setColorspace($this->im->getColorspace()); + + // Apply the watermark to the image + return $this->im->compositeImage($watermark, Imagick::COMPOSITE_DISSOLVE, $offset_x, $offset_y); + } + + protected function _do_background($r, $g, $b, $opacity) + { + // Create a RGB color for the background + $color = sprintf('rgb(%d, %d, %d)', $r, $g, $b); + + // Create a new image for the background + $background = new Imagick; + $background->newImage($this->width, $this->height, new ImagickPixel($color)); + + if ( ! $background->getImageAlphaChannel()) + { + // Force the image to have an alpha channel + $background->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); + } + + // Clear the background image + $background->setImageBackgroundColor(new ImagickPixel('transparent')); + + // NOTE: Using setImageOpacity will destroy current alpha channels! + $background->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA); + + // Match the colorspace between the two images before compositing + $background->setColorspace($this->im->getColorspace()); + + if ($background->compositeImage($this->im, Imagick::COMPOSITE_DISSOLVE, 0, 0)) + { + // Replace the current image with the new image + $this->im = $background; + + return TRUE; + } + + return FALSE; + } + + protected function _do_save($file, $quality) + { + // Get the image format and type + list($format, $type) = $this->_get_imagetype(pathinfo($file, PATHINFO_EXTENSION)); + + // Set the output image type + $this->im->setFormat($format); + + // Set the output quality + $this->im->setImageCompressionQuality($quality); + + if ($this->im->writeImage($file)) + { + // Reset the image type and mime type + $this->type = $type; + $this->mime = image_type_to_mime_type($type); + + return TRUE; + } + + return FALSE; + } + + protected function _do_render($type, $quality) + { + // Get the image format and type + list($format, $type) = $this->_get_imagetype($type); + + // Set the output image type + $this->im->setFormat($format); + + // Set the output quality + $this->im->setImageCompressionQuality($quality); + + // Reset the image type and mime type + $this->type = $type; + $this->mime = image_type_to_mime_type($type); + + return (string) $this->im; + } + + /** + * Get the image type and format for an extension. + * + * @param string $extension image extension: png, jpg, etc + * @return string IMAGETYPE_* constant + * @throws Kohana_Exception + */ + protected function _get_imagetype($extension) + { + // Normalize the extension to a format + $format = strtolower($extension); + + switch ($format) + { + case 'jpg': + case 'jpeg': + $type = IMAGETYPE_JPEG; + break; + case 'gif': + $type = IMAGETYPE_GIF; + break; + case 'png': + $type = IMAGETYPE_PNG; + break; + default: + throw new Kohana_Exception('Installed ImageMagick does not support :type images', + array(':type' => $extension)); + break; + } + + return array($format, $type); + } +} // End Kohana_Image_Imagick \ No newline at end of file diff --git a/includes/kohana/modules/image/config/userguide.php b/includes/kohana/modules/image/config/userguide.php index 2ca21c44..c04ae71d 100644 --- a/includes/kohana/modules/image/config/userguide.php +++ b/includes/kohana/modules/image/config/userguide.php @@ -1,4 +1,4 @@ - 'Image manipulation.', // Copyright message, shown in the footer for this module - 'copyright' => '© 2008–2010 Kohana Team', + 'copyright' => '© 2008–2012 Kohana Team', ) ) ); \ No newline at end of file diff --git a/includes/kohana/modules/image/guide/image/examples.md b/includes/kohana/modules/image/guide/image/examples.md index e69de29b..8f59c822 100644 --- a/includes/kohana/modules/image/guide/image/examples.md +++ b/includes/kohana/modules/image/guide/image/examples.md @@ -0,0 +1,7 @@ +# Examples + +The following are mini applications that uses the [Image] module. They are very straight forward and did not include additional code such as validations and the like. They are designed to be simple and should work out of the box. + +* [Uploading image](examples/upload) +* [Cropping profile images](examples/crop) +* [Serving images with dynamic dimension](examples/dynamic) \ No newline at end of file diff --git a/includes/kohana/modules/image/guide/image/examples/crop.md b/includes/kohana/modules/image/guide/image/examples/crop.md new file mode 100644 index 00000000..fe79657b --- /dev/null +++ b/includes/kohana/modules/image/guide/image/examples/crop.md @@ -0,0 +1,141 @@ +# Crop Profile Image + +This example is very similar to our previous example and even uses the same upload logics. The only difference is that the uploaded image is cropped to square from the center whose dimension is half the original height of the image. + +## Controller + +We name our new controller as `Controller_Crop` and accessible through `/crop` URL. Assuming that your project is located at [http://localhost/kohana](http://localhost/kohana), then our crop controller is at [http://localhost/kohana/crop](http://localhost/kohana/crop). + +~~~ +response->body($view); + } + + public function action_do() + { + $view = View::factory('crop/do'); + $error_message = NULL; + $filename = NULL; + + if ($this->request->method() == Request::POST) + { + if (isset($_FILES['avatar'])) + { + $filename = $this->_save_image($_FILES['avatar']); + } + } + + if ( ! $filename) + { + $error_message = 'There was a problem while uploading the image. + Make sure it is uploaded and must be JPG/PNG/GIF file.'; + } + + $view->uploaded_file = $filename; + $view->error_message = $error_message; + $this->response->body($view); + } + + protected function _save_image($image) + { + if ( + ! Upload::valid($image) OR + ! Upload::not_empty($image) OR + ! Upload::type($image, array('jpg', 'jpeg', 'png', 'gif'))) + { + return FALSE; + } + + $directory = DOCROOT.'uploads/'; + + if ($file = Upload::save($image, NULL, $directory)) + { + $filename = strtolower(Text::random('alnum', 20)).'.jpg'; + + $img = Image::factory($file); + + // Crop the image square half the height and crop from center + $new_height = (int) $img->height / 2; + + $img->crop($new_height, $new_height) + ->save($directory.$filename); + + // Delete the temporary file + unlink($file); + + return $filename; + } + + return FALSE; + } + +} +~~~ + +The `index` action displays the upload form whereas the `do` action will process the uploaded image and provides feedback to the user. + +In `do` action, it checks if the request method was `POST`, then delegates the process to `_save_image()` method which in turn performs various checks and finally crops and saves the image to the `uploads` directory. + +## Views + +For the upload form (the `index` action), the view is located at `views/crop/index.php`. + +~~~ + + + Upload Profile Image + + +

Upload your profile image

+
+

Choose file:

+

+

+ + + +~~~ + +View for `crop/do` action goes to `views/crop/do.php`. + +~~~ + + + Upload Profile Image Result + + + +

Upload success

+

+ Here is your uploaded and cropped avatar: + " alt="Uploaded avatar" /> +

+ +

Something went wrong with the upload

+

+ + + +~~~ + +## Screenshots + +Below are screenshots for this example. + +![Original image](crop_orig.jpg) + +_Original image to upload_ + +![Upload image form](crop_form.jpg) + +_Upload image form_ + +![Upload result page](crop_result.jpg) + +_Upload result form_ \ No newline at end of file diff --git a/includes/kohana/modules/image/guide/image/examples/dynamic.md b/includes/kohana/modules/image/guide/image/examples/dynamic.md new file mode 100644 index 00000000..2d70260b --- /dev/null +++ b/includes/kohana/modules/image/guide/image/examples/dynamic.md @@ -0,0 +1,108 @@ +# Dynamic Image Controller + +In this example, we have images under `/uploads` under the webroot directory. We allow the user to render any image with dynamic dimension and is resized on the fly. It also caches the response for 1 hour to show basic caching mechanism. + +## Route + +First, we need a [Route]. This [Route] is based on this URL pattern: + +`/imagefly/filename/width/height` - where filename is the name of the image without the extension. This assumes that all images are in `jpg` and all filenames uses numbers, letters, dash and underscores only. + +This is our [Route] definition: + +~~~ +/** + * Set route for image fly + */ +Route::set('imagefly', 'imagefly///', array('image' => '[-09a-zA-Z_]+', 'width' => '[0-9]+', 'height' => '[0-9]+')) + ->defaults(array( + 'controller' => 'imagefly', + 'action' => 'index' + )); +~~~ + +We ensure that the filename is only composed of letters, numbers and underscores, width and height must be numeric. + +## Controller + +Our controller simply accepts the request and capture the following parameters as defined by the [Route]: + +* `filename` - without the filename extension (and without dot) +* `width` +* `height` + +Then it finds the image file and when found, render it on the browser. Additional features added are browser caching. + +~~~ +request->param('image'); + $width = (int) $this->request->param('width'); + $height = (int) $this->request->param('height'); + + $rendered = FALSE; + if ($file AND $width AND $height) + { + $filename = DOCROOT.'uploads/'.$file.'.jpg'; + + if (is_file($filename)) + { + $this->_render_image($filename, $width, $height); + $rendered = TRUE; + } + } + + if ( ! $rendered) + { + $this->response->status(404); + } + } + + protected function _render_image($filename, $width, $height) + { + // Calculate ETag from original file padded with the dimension specs + $etag_sum = md5(base64_encode(file_get_contents($filename)).$width.','.$height); + + // Render as image and cache for 1 hour + $this->response->headers('Content-Type', 'image/jpeg') + ->headers('Cache-Control', 'max-age='.Date::HOUR.', public, must-revalidate') + ->headers('Expires', gmdate('D, d M Y H:i:s', time() + Date::HOUR).' GMT') + ->headers('Last-Modified', date('r', filemtime($filename))) + ->headers('ETag', $etag_sum); + + if ( + $this->request->headers('if-none-match') AND + (string) $this->request->headers('if-none-match') === $etag_sum) + { + $this->response->status(304) + ->headers('Content-Length', '0'); + } + else + { + $result = Image::factory($filename) + ->resize($width, $height) + ->render('jpg'); + + $this->response->body($result); + } + } +} +~~~ + +When the parameters are invalid or the filename does not exists, it simply returns 404 not found error. + +The rendering of image uses some caching mechanism. One by setting the max age and expire headers and second by using etags. + +## Screenshots + +Visiting [http://localhost/kohana/imagefly/kitteh/400/400](http://localhost/kohana/imagefly/kitteh/400/400) yields: + +![Kitten 400x400](dynamic-400.jpg) + +Visiting [http://localhost/kohana/imagefly/kitteh/600/500](http://localhost/kohana/imagefly/kitteh/600/500) yields: + +![Kitten 400x400](dynamic-600.jpg) \ No newline at end of file diff --git a/includes/kohana/modules/image/guide/image/examples/upload.md b/includes/kohana/modules/image/guide/image/examples/upload.md new file mode 100644 index 00000000..e08b780c --- /dev/null +++ b/includes/kohana/modules/image/guide/image/examples/upload.md @@ -0,0 +1,139 @@ +# Upload Image + +The following example shows how to handle uploading of an image, resize it and save it to a file. Be sure you have enabled the [Image] module as discussed in getting started guide. + +Assuming you are creating a web application that allows your members to upload their profile picture (avatar), the steps below explains it how. + +## Controller + +First we need to create a controller that handles the requests for uploading an image. We will name it `Controller_Avatar` and accessible through `/avatar` URL. Assuming that your project is located at [http://localhost/kohana](http://localhost/kohana), then our avatar controller is at [http://localhost/kohana/avatar](http://localhost/kohana/avatar). + +For simplicity, the upload form will be on `index` action and `upload` action will process the uploaded file. This is what our controller now looks like. Please note that we are not using [Controller_Template], just [Controller]. + +~~~ +response->body($view); + } + + public function action_upload() + { + $view = View::factory('avatar/upload'); + $error_message = NULL; + $filename = NULL; + + if ($this->request->method() == Request::POST) + { + if (isset($_FILES['avatar'])) + { + $filename = $this->_save_image($_FILES['avatar']); + } + } + + if ( ! $filename) + { + $error_message = 'There was a problem while uploading the image. + Make sure it is uploaded and must be JPG/PNG/GIF file.'; + } + + $view->uploaded_file = $filename; + $view->error_message = $error_message; + $this->response->body($view); + } + + protected function _save_image($image) + { + if ( + ! Upload::valid($image) OR + ! Upload::not_empty($image) OR + ! Upload::type($image, array('jpg', 'jpeg', 'png', 'gif'))) + { + return FALSE; + } + + $directory = DOCROOT.'uploads/'; + + if ($file = Upload::save($image, NULL, $directory)) + { + $filename = strtolower(Text::random('alnum', 20)).'.jpg'; + + Image::factory($file) + ->resize(200, 200, Image::AUTO) + ->save($directory.$filename); + + // Delete the temporary file + unlink($file); + + return $filename; + } + + return FALSE; + } + +} +~~~ + +We have `index` and `upload` actions. `index` action will display the upload form and `upload` action will process the uploaded image and provides feedback to the user. + +In `upload` action, it checks if the request method was `POST`, then delegates the process to `_save_image()` method which in turn performs various checks and finally resize and save the image to the `uploads` directory. + +## Views + +For the upload form (the `index` action), the view is located at `views/avatar/index.php`. + +~~~ + + + Upload Avatar + + +

Upload your avatar

+
+

Choose file:

+

+

+ + + +~~~ + +Take note of the action attribute. It points to our `avatar/upload` action whose view code goes to `views/avatar/upload.php`. + +~~~ + + + Upload Avatar Result + + + +

Upload success

+

+ Here is your uploaded avatar: + " alt="Uploaded avatar" /> +

+ +

Something went wrong with the upload

+

+ + + +~~~ + +When the upload is successfull, a success message is displayed with the uploaded image displayed. Otherwise, when it fails, it displays an error message. + +## Screenshots + +Below are screenshots for this example. + +![Upload image form](upload_form.jpg) + +_Upload image form_ + +![Upload result page](upload_result.jpg) + +_Upload result form_ \ No newline at end of file diff --git a/includes/kohana/modules/image/guide/image/index.md b/includes/kohana/modules/image/guide/image/index.md index e69de29b..0225c26a 100644 --- a/includes/kohana/modules/image/guide/image/index.md +++ b/includes/kohana/modules/image/guide/image/index.md @@ -0,0 +1,21 @@ +# Image + +Kohana 3.x provides a simple yet powerful image manipulation module. The [Image] module provides features that allows your application to resize images, crop, rotate, flip and many more. + +## Drivers + +[Image] module ships with [Image_GD] driver which requires `GD` extension enabled in your PHP installation. This is the default driver. Additional drivers can be created by extending the [Image] class. + +## Getting Started + +Before using the image module, we must enable it first on `APPPATH/bootstrap.php`: + +~~~ +Kohana::modules(array( + ... + 'image' => MODPATH.'image', // Image manipulation + ... +)); +~~~ + +Next: [Using the image module](using). \ No newline at end of file diff --git a/includes/kohana/modules/image/guide/image/menu.md b/includes/kohana/modules/image/guide/image/menu.md index b8e8cb3a..8c877435 100644 --- a/includes/kohana/modules/image/guide/image/menu.md +++ b/includes/kohana/modules/image/guide/image/menu.md @@ -1,3 +1,6 @@ ## [Image]() - [Using](using) -- [Examples](examples) \ No newline at end of file +- [Examples](examples) + - [Upload Image](examples/upload) + - [Crop Profile Image](examples/crop) + - [Dynamic Image Controller](examples/dynamic) \ No newline at end of file diff --git a/includes/kohana/modules/image/guide/image/using.md b/includes/kohana/modules/image/guide/image/using.md index e69de29b..fd1859b3 100644 --- a/includes/kohana/modules/image/guide/image/using.md +++ b/includes/kohana/modules/image/guide/image/using.md @@ -0,0 +1,112 @@ +# Basic Usage + +Shown here are the basic usage of this module. For full documentation about the image module usage, visit the [Image] api browser. + +## Creating Instance + +[Image::factory()] creates an instance of the image object and prepares it for manipulation. It accepts the `filename` as an arguement and an optional `driver` parameter. When `driver` is not specified, the default driver `GD` is used. + +~~~ +// Uses the image from upload directory +$img = Image::factory(DOCROOT.'uploads/sample-image.jpg'); +~~~ + +Once an instance is created, you can now manipulate the image by using the following instance methods. + +## Resize + +Resize the image to the given size. Either the width or the height can be omitted and the image will be resized proportionally. + +Using the image object above, we can resize our image to say 150x150 pixels with automatic scaling using the code below: + +~~~ +$img->resize(150, 150, Image::AUTO); +~~~ + +The parameters are `width`, `height` and `master` dimension respectively. With `AUTO` master dimension, the image is resized by either width or height depending on which is closer to the specified dimension. + +Other examples: + +~~~ +// Resize to 200 pixels on the shortest side +$img->resize(200, 200); + +// Resize to 200x200 pixels, keeping aspect ratio +$img->resize(200, 200, Image::INVERSE); + +// Resize to 500 pixel width, keeping aspect ratio +$img->resize(500, NULL); + +// Resize to 500 pixel height, keeping aspect ratio +$img->resize(NULL, 500); + +// Resize to 200x500 pixels, ignoring aspect ratio +$img->resize(200, 500, Image::NONE); +~~~ + +## Render + +You can render the image object directly to the browser using the [Image::render()] method. + +~~~ +$img = Image::factory(DOCROOT.'uploads/colorado-farm-1920x1200.jpg'); + +header('Content-Type: image/jpeg'); + +echo $img->resize(300, 300) + ->render(); +~~~ + +What it did is resize a 1920x1200 wallpaper image into 300x300 proportionally and render it to the browser. If you are trying to render the image in a controller action, you can do instead: + +~~~ +$img = Image::factory(DOCROOT.'uploads/colorado-farm-1920x1200.jpg'); + +$this->response->headers('Content-Type', 'image/jpg'); + +$this->response->body( + $img->resize(300, 300) + ->render() +); +~~~ + +[Image::render()] method also allows you to specify the type and quality of the rendered image. + +~~~ +// Render the image at 50% quality +$img->render(NULL, 50); + +// Render the image as a PNG +$img->render('png'); +~~~ + +## Save To File + +[Image::save()] let's you save the image object to a file. It has two parameters: `filename` and `quality`. If `filename` is omitted, the original file used will be overwritten instead. The `quality` parameter is an integer from 1-100 which indicates the quality of image to save which defaults to 100. + +On our example above, instead of rendering the file to the browser, you may want to save it somewhere instead. To do so, you may: + +~~~ +$img = Image::factory(DOCROOT.'uploads/colorado-farm-1920x1200.jpg'); + +$filename = DOCROOT.'uploads/img-'.uniqid().'.jpg'; + +$img->resize(300, 300) + ->save($filename, 80); +~~~ + +What we do is resize the image and save it to file reducing quality to 80% and save it to the upload directory using a unique filename. + +## Other Methods + +There are more methods available for the [Image] module which provides powerfull features that are best describe in the API documentation. Here are some of them: + +* [Image::background()] - Set the background color of an image. +* [Image::crop()] - Crop an image to the given size. +* [Image::flip()] - Flip the image along the horizontal or vertical axis. +* [Image::reflection()] - Add a reflection to an image. +* [Image::rotate()] - Rotate the image by a given amount. +* [Image::sharpen()] - Sharpen the image by a given amount. +* [Image::watermark()] - Add a watermark to an image with a specified opacity. + +Next: [Examples](examples) \ No newline at end of file diff --git a/includes/kohana/modules/image/media/guide/image/Thumbs.db b/includes/kohana/modules/image/media/guide/image/Thumbs.db new file mode 100644 index 0000000000000000000000000000000000000000..a0f028f80c3d119e1f50f6f3ab445a267420816c GIT binary patch literal 19968 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
jmD(tbYq_^*NB)Il%NP+ ze;`#>fBzNn{bHcVcltM=80*$8pPTz3VQd4L-ir}hI!c&4D1suVJKRffzNR=Ef1g8U z(c3tOx2-lBVfNpJ*Bv-3Sy=L|d>Q@jF;kSoW8~RjIFoi{-{>dY&qzM&D%f zV|RBvYmkAs7OT#}m5}v{6HK%;1qs@gi?C7=fq=E_Oo3YXg#HDOs{di&tElYvxnBka zb226de$wqc+l%%|j%f9vjI}frF5r8|^yczc6+Ija>z+u(mXt$VqOmLEVs6yMzjy2r zOlW2#BjG7MM(fK68C(#crv8Gb9K5F9Ze!QcY3 zL=dSdv>?=_d5u*0uzZv8un9?@I8c!TO%`U=c$_RkFb@Fs_kv867uI8;OBY6O?9tgK zb_a{TEg;j^cT?5)jgJqVzW9mfAks=FW@Dj?j5Z(0lFae*RATo9Nl#J*5L3LmV~Mg8 zd1XziAkpD1>tai#Q9VnZlL^T&B6|VhD$owCp}qQ%4pmhRL@GlU=7)p9MWY+6*eFb( z7XSd;T$z^LpeVj0*^l|@R0PztQp$wWO+^UQbdW|_*_3b{o#1j;;GE;hnR@le|DpR3 zk+42;(*H{)q&+g2G-L)ix;Bf?O3=kzc6s{gVLxzYs|b&6ftIk~vPz~f7>DCghYj01 zS_pW)K9X}~8}M*_Iq}w7P7dZW9WaMu66?|3+5!f%s6MX!z?Q>|8I&xIz1XcOd(wX_ zyB-(Zbo!dH76P^@oH!>bo5|NO9ha4}G0QGMDxg~yGEYb=K&>t41bKo6FQ#P6Y}WSe ztz@NR0h@fapcEw=ZUPr66b?o^ZvY@wg~xywAcYyAzJgS literal 0 HcmV?d00001 diff --git a/includes/kohana/modules/image/media/guide/image/crop_orig.jpg b/includes/kohana/modules/image/media/guide/image/crop_orig.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1389acc3f6954c66a7abeb0ddd587960d12b4926 GIT binary patch literal 31360 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 literal 0 HcmV?d00001 diff --git a/includes/kohana/modules/image/media/guide/image/dynamic-400.jpg b/includes/kohana/modules/image/media/guide/image/dynamic-400.jpg new file mode 100644 index 0000000000000000000000000000000000000000..13b691f07b876985a4227afd62a821629adbe68f GIT binary patch literal 27609 zcmc$_WmF~0vLL*1cXw;t-5nZt_r@D&++7-n#@$^u?(XhRU>Fr0JleQ(~InYCut z_v4GKT^SJ-6%|=KGAk-7^JD2_3xF*BRq87M1QY-;`7D5sGXRR1v$2&s00aOI008(t zSr-5bVG~;;Q-DeLCkF%!fB_B$35x&#;I{z)R4f31$Q=M+_4{KL5D0(-2M31$hlGHD zgn|70!9hbpLc_ts!otDA!Xv}~4ao3_NXSTt@Mvi0=xAv8czAgDME?OGP*6|^@Cew* z$k@0TC>Xf^ap3oJ;D zcCfn+h!a&o|AGntfI&h0=j8#&ApQViyO=8!VJ5#E!&o$se=LFg%l?0<{?v@DWJqj( zt^K zi!VKIId{6!t2yPqG~fV*49nn$UKvkV=u@6?C}@SjllrkJ<>5eEI}nQ+U}5wdJ#tyO zI&o|iW`LdLW($q4N>(tvDN{^)Y`^+8ePX69liED~I9vRe#lhVa8$2Azl*&f);Sb6R zTgOFTeJe9tIdWMUep5|LwKGFQ*oA`f3+o5QOx(=cvz5CGy^^FtTISpw_!!xt}?@iA{{SJw!WU!Z>Jlz;|qC9bLz5|`H-nCq%9Lz?pljU%`JOZDyjNOJL~DT z{4R6@Vfy?0?tLm@F3x>No5s@sklV|MzO-nU*(}-L>0b|NrO)|x+-pY9fp=z|Wa*+s zZ(U7YNq477=eNHL*hxG#^BmQultMa8To_En7UmkSu zu1u^2!^E+&mXVixIqX-+FQ}z5M%!{n+b3oTVvmm4a`PWoY&^&AnlI3mO5L+A3nb~9 z17H650r-#xb{;E`*r)GxKLBiXz>JLZALEm^qLyBD9SMc1Q}m1{r?DrOJ`FXO z{)OAJq2UAn^>_MA2W#mZhXxsLW&VA*8tWWfOB7-sC;AmY&z4j8Yliyt(G^tx+X*{!n~S*Mjm|U3#ax`s`2iGX4hvRvdzjs>{z2 z_u7Cq-0Kzzyldh6Sm8$aeanWHYu+G9i!ZH0u;pt*`k?4SD?fkirkG{yXsy-MCHL!; zm1x!Qt6slZ^ew*(sJ{*Er~ri`jd#mvcZ_8ibkKEHR)?ImUw zlX=P~JzYl&l5X)dlOaVRI zo*w`Z(7NWk{N4{#_po*>v;S(v??*j!i@H(SoL4o`E3qqaI-(-_3hk1dY36X~#@~My4yHxqiuf1X;k+ z{NO#_d}l$jN{6F!KQMBw3Vi6wVSGd9hT+`K)7X5%=I#x^>51+S#~HBW2}i=>j^VB~9;o%A zZo3yDMI;gQb((c@8gOTxjCJx;o+r&_dDZ;Z*5yd9qIs&j`vAZOUcyq0T|On*_o}T3MGRKVIkWv&K*p@>D8+@A%mL~go?m12^=Q#OAdG@`hU7Cps z%WWU`5d4ZsLYV9m>K2T(K9z}bo2pXp3qJr6;nSKUheEzVDV^R~>22W{{dVTD+RE`Y z{@r4N!&nC$|DTvm@gzgqMNT~4u|(Da@~=8W9%io$wXZx84XPMa+o%vs$?(rrL_BVX zg444%`KNO}lrvqT(*UroP2OMZYq_&s&d01yrrpP!UQ{0VmzqhQ)HWua*~&qEledN8 zSB-deK0|ay1?Gp+GR39|7lkjstT@PNwZLL~!DS{BzY@h`Z+MwqhoAdo>%S6w_B7K& zX>!DiBg>i}1tHEVFx`9*)IQKU8BI7U~Z%_W`Z6sc+Fud1%^RK0K<1Au4nm>MuviUF07 zS0z;=wZOPO96Os&KGJGlUQXvIPmn#|9^Q`QWG~BP#p7qLnPX2Pe@WC(7L&*D0~g!2 zAg$)$i~%4q4r#EE%a z{a0}>8u@oAJV>Yb-<9OFAO9-)|8d&CPyeT`D<1L68p%)n8kX`PZr5wz%kZxE*6B+) z;!6so@lD$2q}jAg+oYUR=~W zl9%j0tc0TJAUubt`teA%MoG!cjg!9qJ3)(oymG<^DdA~$V(94tMbgf=Kzx=wKeqos zW$fUFF{48wLId-1$A?~zi^2g6@-Z>OEfKw<`K?Bw{-BFu^H<~Luf{B^qRkO??~pI6 zQ}_`Uhj!ffp>FE@=yiJ5UOGwXai>8IoAE>S>1AEcwAKSfJ)`JrG&cN`^q2+~xhc9y z8kp^x33@ue}I@1B%$J(Mrk=~4A`c2694a2DNdq|bTS z%Ka~B@U#eSS^D%+(f{UH`n01vwjkMX)@!W)4Sq(J^X;Yq6w8x))0FRU)(0RSiZ`DB zlEU*sOyBf?(6ra{^pJ9|1w-irFzdMEYqVdlkbY3-nI*f~#aqRh1@#@~CqF&8+Pg$r z@w2=W5#tRcQv8AVGod=x-m`c#$CdZ_?WP|8{q2SU|N8CvI{(7l=kAktL)qE+srs+o zLH|-Zn$ZvOi(Jp+GYPktM&e7P??!j@>+qDFlytrW^Ws&SJ0~)y)!(oUZBFJ?e}@4% z?M-24PdaD|WY~5YvnV|)On(Ed{3cXX7p8kjQqzi$_@uPDZ()}G(qw;xcI;4^-Ku4m zC>q$eH0W9E2MU1_fIc20G+XB(r1q_ME(ug@s=We*rRHdR z!xfoXowss|KFWB1-zf0v_TZ_n!H}Rh#pxb-S~xd#uUpNq{&qBs0o|@?ndX^R!<=H7 zsIqb56i%(KhpW>IWNjqA>qwI>!I>j41=UhSMts#Sij-qA!BU2;86rc_bEc;9{}#0+ zY8SG{^;|PrYXDSVpB+qWLHQ9rqrq|^pb_8gl_ik0MS(`xK`ABlO^cB>Ob`<#g> zw;OOm3DAn;jfQ2W@^##j_Gi~V#F;NEv!KKAIG8LgDZnz7F=Pc{S}m2WtJg#%8%wkn zds=6-TX8fq(uqsj^9sQkTV(8H#J5?jEy|2s2GxYI&LjB)bR;BVpA|2o{go-{8DaPZ zPhORRG+QCK?zn#ZVRuEH3;Yo=iGwfa06^VtLcDu6cQ} z+W}^jDf3zdgVsgd#Z0}3_*h@oG05e|sUuh{W>0*Wl^Nyv_>C>=x>@CZSn7pFgXn-p zZuyTU3%@!~G$qqrt5`MG43i8&LpXUi;{fhypnsyRR4W4)>;UZ%hIX=DC zl+nUB1P1)hl3oq7cxf^jw=w0ST((h$WaKlx70uWr{4ntX`fjt0AxtfftQzH)5*6sw zP*d+0ZC377SpNaZ^{k%8i6d!w<^X=$JOOk#wh5tuI!Z1^vpiqd;3TKyrB?q%ZgdQJ zF1km>B`a9|#Gl+S9{^6#@X8v}6*_fTBpj4^;)M@Dk_QS$?rur6RMJTre4%lrDWCcT z1`33u4Y}T_tW&kTCITLboMySIWmU9MwN7IKen|DUHIMM3?Ke_QHo<~Z2#rT`%HD7p z24?+Kdkbx&q4`qBK(oevy+xAbHsYDVKMx-IWo%h;-x?7$kjI5r5U)>%o1$^^NY$1U zWq~DOpP=0{JA6-VrD$pP8-^~tJoa)}9j8TeN*Q*TV&8FiGQt*H+Sz1Yq|jfd&q2Be z@laGfX!?XQZsB|-x6r2Xw@eW?_{nZrgIX?!rT6IRVp1|l8L@jk8nR2;wxV!>co~X1 z-IO?rNA-APKBG6wYW3)V_Nv-YnRZDMVi36JXj8nH1VY!47`{XP5WIaO z(nJ;oJkrIm>x=WxqgEO!Pc?2GWjRwys5AMAkyj3drs38xCHr_pObB_i=aOVBxo1We zzG=N;HESdIu!(?r+Wg3?8hna_8zt7Fxft?Ycbx9(2CB@}pdru2z zm?8S0YM-j&jpguG;@l0T_?nBJ+S1IcoRR?wA!gXxdt%CA=QsW#VN4__My2G=^-4IO4j&~cSAO+lVFXU6c zQ=GGIh_!6jMDBh%%}?OioxpqAOq1od7B(zFGRKqBWv7L;^NpHHqB9mx^oy1bcBAW8 z@6lwrMqV4wR~e;Y%V&6+S`&6rX{d{c4DPT{9JA(%lwxwP zn{_!kBZu#Wq#c~{JlX~7y_ypxiv{rIyJ@ELCka(=^^Npt+ihsB4K9W>bu`7byQPNy zaT?qjsmu!Mm&|PA&fURkv;1Z6RhH9slTU{_iGGedWdH> zjM;)}c-E*hv8g1gK&i~Jva&8P@$it*zBz!{LY~<*{I=!8J=?kIHbCgYlF{uVP$j|} zgzQAZFuFcT@i+4a#VN8b4Q}+Oq>QfYR8DIlLb@RxR(AlOOw|tkYR1HF*!-|3WV(1H z=b1Epin*LpEzS>5KUcrNH1oq-&r=rqPIWH(8fF;FOMxQc>KILSHO(F*E%K6i+UcAb z=c|Bibl%CC<>t=w$k^`9Y5$Kscq**|i`FhV#?VF+8<_DEEGbjES?>k#1`C_>V!a4OTA4840&>c!V0oEF$|#zD#s7MgzSwwGz)oKaLJ zr*>?*GO-np@y970?$P<2n*0VCSw(|{Surjh5)w$Rtz?AEd0!{lRyMDz^rR`S&O?gI zQP?dV^HRWXx@OybVxS{J5c;_@t+0Dax_PomWbpym?PQ_6SBTOStg`EWM~V>i-=5lP zj(?J;^F7U=q5~^b0ij(9-329URn6Cuju;8a!`Ce0D{7@OxO}i%<#wqsYlU22LnK^RSBB zWZ5sO5#qF<^~VO1b!Nzp4_xmlb1@om%k3L?ehli?m2>3e`aFHZx|Rph&59AvW0|LX|)pD>4qn zY9w?2LSdEazspyV^Gb$8a&&+lkz%yW90UHxU^FY`n)nyICNX#D5<|5JGuWVJ8Mqa`|D7 zYP{aZ$7SVMWvMw1;OA(M%rpK}XJciM<)j3f!K_LA==K1E9d{zT#9;+B!VGZ7+25Jl zo+E52!ljspW0PY=lO5V*cTbZ{qAMT7xi&JgNO?0vv%&ZPsO=d27cj1*NG-BRGo$!~ zPbB3p_y7oh0RHY)V1IS14*=Ly=)3OSl)u+d@)FXO?1T!d>iA!hE%HM8%C{uvcOAq7GGAT&)d?46WlIb-OX=Kg8c-+ZVg0uz*u zy{5leARwQjPY+QtO#26Qi27Fle^-ZfOEY8}9K*|mmMGbK$5ZOPvlzVkUt9uT$)7fV zG1~ALKRa7DZbkVmK6Z5>7+0TIVutG9$s@BIudM@_X4!r=bA&L2xc!+9&ZMNQ0?^Z_ zJUTPO!ELS^xtoXeG2CNYc--?ZSqyKb`V9~xm3!z#W1PL@v+Qu?as^1hAcH(J4hcrt z_h~4Ks4&OC8G;T1RNeAQoTONm`sBdow!u9u#xSc3U(G zI6vw#XkuP0ti8D8z)h{Y{wt)bW&iv6DZ z-bcANSr9;{QwFM}?(P32BC9Sg@typvJ~x zsfhU^UG6Vz`%AIoMa^o&JA1PMs(z>!g$dbhftWi~;+b<(94ze43>F{RuNG}5M*t@7eHGG)dwGbhWCN?G9LhnYzzd^r;W8EyLcKxPntC5=kx46i{nf^0^1hs z6==ntA}3#5?(y?vmkC4M;a(w-wT2xh(@UI4=3aZw;t7kvZ-b&(1=9yquF>nF+GLne zsK1^}`{MdwjQn44rW~~+A}P!<2?;V1d@*^zL*IoK9&1ElqT8aQD4*$ZPDB-_6g0!6e~v-P$^n4T7HPz`v1g7!i`K#yroL7`=IGzN<$J62(5<&V|zdsZCPp4(K9- zC(_YI>8rx$65EAji}n1GH=p6;OooF1`Pjfe!pCkebCRyoV09!awabsQJ>Soo2HkY8 z82S0|Kb)Uq@g%3CUv*eue$&&m+K%2>d?Y9!4`$@P>WIzAak_%+p>P#EXTfB7SiO)~ z_)|+X3j4W}g?-wvzD42_#3ytBneb%q9FTno3%D`qxPr@|w=8w%WCJDfkYXnxy6udq037wzNo_V_+5fky zG!o8tZoz}Cx}ze{S1sYmS$OMQDH%BG#Zz9~_y1OvNszmeJG8~s(~2}`SML3*sF$gY zs)S(&MPbD3A9O}ZA}NsD1~i5**q+t-j4@wnMF)&~p@nJeRa%vFX2~4ym4(EWhJ$7_ zYFB)o30$!JRHYvD{d3dW-<~l~4iV4NC%|-fbNVFb?!MqH0Dpy2#S`8!>ScqL28HKp zr)UP#Y?QvZJh83yzA}r0a@1N!raO(rb&@qOQ2~A8fMHD*S9D^>n5)>Wm67C5ioIsa6?hOB|ukdOM0PHK(=$Ez^ND1(yErg;L7m> z$2}~6_JYOtUX;k_Vl1`N+|nP?dA(3JBk<%i+glR7b2(fA;U!Vauqe_!P=#) z*EHTQYlH8f*1?NdL}yKsnWh(pdq<<`r373qi?3wzn4E+?rtRYkvJEItX9ty7ejS@m zp1X&@8N554Rgs>bl`Vw35FeAYRn-V^G@B&n0TyP1#Ngp)20APa&L0&mec@=lc39cP@VK8YpjR>}${jGhq-9CnovCt1{fIA5|k1dgt+gj_;>$#7A1ktYXt3&uu9p7fcp zz3|{|_`p|Pw4(V}hw5A^RVkKx$`;VD=F;xqFfj+U>T*sDC6P$qxyE$b>vHirJ53V~ z_A)Gj6lbJD&sav#JXjRYa!KGy-p^l0g9}0fE)*Pe;Q8TW;r$E^Y~@Q>z*l6dAT%$< z$THN>=DZI;em^EvS`ladfT7;#W}MZxjwKC*VayUaL*Y)#FIpbhExCl8mlhG>zBF6r6bl2vPo9HGe3r1w~EKf`YFyHflgbQb9oGy7$}MAEHCMr`NK>!2*y@!dYB0N4um4aIcj$-6=9_)JY%d?8}3Ojf$>FB>78& zN|%3a8`X*ooHl&RlA6H;@^}UK&t-B^e^rgCr3~jFO-hF zP{Uldq?%&_QfB!W9rSwIgfn2g(>t*TK_BPN|69@CBmjf`&Y zAgHQ_&{(xA=l!Zy=4=QB*?Gd`7S*^~phbCP`9@w1E{mOKas$v?gP@NAFBZxwM<89A z(o_H^>fWeCTcqEcE5!U5W+yOT1nV*4>!575xyvq%*YAt;EhU{RDvZ&6Mb3;W;kzHe^jTCa`ZRh zeoKIsdg2%@{>bh=^ro@4ct~_@;HZ(|J7E5)k#6p<-_=oxNm?0OVy#A*bk{rJUvCYc zGu;G9S{(Mrk{NXWuBi223AMJ`wcbbG2L+dgUa>y_jznk1egvr^JyS?2uHr&M2DYTjKCDE-dC%^nH;Lp; zi))QEhXgxGs}Rge8EquTCb+er~zFyt4Z4LgnM@<(9!PjNbu{DO ze;zGfqktQ&KP&L1w)+J~@;PtR2mUI#^zA72u^Bn0+HC>TH2YRPi2t;w zIA$(`S@4q)2)LUcL`~ujNEAnd=z%YC6Iihq#G@J~N4{a<*x?tu6i*YJ#LMb|nXYmuDc@l#(vJyRJf7eir9zJDX#P8NVoptgu zMveWi$o79fGy)0-y$w$8gRm+2bXLnSo>bgIM36jWdcExBV(@=LXvQJFeGi{ck&+ELt+ed&mXdhch+Y895Tm|(PdXg zGIuAE-_+wc_Ciz|S9SM?%-vg1vMTJ!|4pI4c*j@Cf#_$w-EN74LAj-3Zqpxm(vHj5 zXol#JlnBkO5@<)u#%MT!KwgDh!m%S^E^kWzyAZ=W%Ah;SusgCPZ|L>nS(2$4r04jz zk@qhj02Xx&!@o8(GS8O48V62A{jDuwigoM(uo;~A1BAx*?gul7Q#+eZ`TTCOpXO2b z$Z4*L1lvkaVO;hMOkmgWhornl2a1N{HEQmkX|DGU5WSy2b_rLLAZyYMWMfq6CQ$gn zn1c22JVL%lx+e{%qtzhyHiIYHD<7238gLz0g8J3wl=Rh`BOYRXKFDmi~6|+E@95FBQTM=vUaok2FmVo_! z$$c9B&{o750m&wz z@N^ig9=7qpD3XR>t_TcX+x**XL@b(3^ZPI^$+I@8kv5u5OcU#L&~oE#mw1?4x`jR? z8QCOUd`xjDJ^#tKpX7z!8chr5#JzOL;pH6hTzka|ofH@r)sW(jZnwto%mN3_o#Z)p4Fhk42};jKC0P;iL%$y(VZ zMI(oxWR}Zf2_=Z2?;fJ9dC6X*O$~^lF7JkRYTrcJ;4b!?G(y*n=ju^^wIH1-Hr0jCbrqf`4JfpA?9Z zvcR=`5lS(4jvdj57qy6eX9*_M5;BaLo0FcslB5@{ zsh$Sg%AxX}F*S;$aLQe8Jr+zUg?mG<`#fs*2dST+=CW7kbPaLp$WPvH{6*Y>nBycT z)INrEB79S&KhpY9{QfkCv>yZFLvwC9@wJyK2;n)?h?k=CniO@vNw&Bpc<;icKyB8K zE)U{g348!ZASS{2#8f*z3Su*A5hm?Iv9J8qE2hxjV*gNY$g_qbV-Szg7^uz!(YMCR5{-C#t4 z3hl?mlA?*V&&S@e)M+1Ljjm<)rAn;4pznZ8EuDO&ocyU~q-9H2O+)^5q|79xH)p^S zvu_Rq6}5DJ(=tmq)Iei~dw}?Oj@)U9XeRkn6CTMy<$K$&ue7lt$_1O> zzkPT2mW%C_wB|9=W#i2ZJ@c#h16^pBEbP>r!D8g9j1L22L}i1OSwCJJ7-1X7K*aXr z$5HhT6YsB88{*471(U}>@xE<%8&G63Eku&v$PG84fy=eT0lDGWtsuGI8c>sO{2a6y zN0nC!koCV}d}j{TW;%Q~{8x|bCa@9!g5RTI}GGkg*AKLIa}Me+t^ zcaM4?EdvuD<@6LP-9#sq*=1@l%5m;5KCjNEN+v_xp_!W^0Hu^iGlA>h$Q>Z!V7eLY zyfVtcM`nlZQG&ZmI98-FR9k)`LxAs?i0r{jn%|)(G(}&iG0aG817^*};b-i^=Pk(0 ze4oE)HEjlg3Ahw(H?BQC;YK~u2IS(`Tw zJuv<@GESF-uGjlJl#Vku6&fu<%0*)z6wtkBdKL~&Sa^3UTH3*&{@X@xTdoR^^ET?T z$zi|<9=O;4qvPRcC_^EDy(I-#pPb2X^%C%6ytmLkq}tP>)B3o>`h#{pvoy$4I3;|W zrfC!_sfva|Pq#(ANLhEW%`H6O+B0v+ZW3GA2h_w$F0|+Y5~d4=QCv=9#YYoPmc+9N zHo%h60*fjUH@loAi7S^%?03!y9&?nznwjD7dk%X~(!e*atp|VpN@S+GsrD*Qebg9P zG!Tga?^55>BzIh5WUyP@nBZ_{u<_WM-6UJL z<5w4~g$ipha_wm`4bm`YIcW5aVO-^JfX4?g?DK--;FCSyn*-rtC7Ap64Ze7PJ7=rC z+^5bx0d3(5Wxk?Kq3fyqZcqcqk>A@`M?Gk?I|&Ir@RaS<__UW&%o+nWcZRGVc7R#X9^!~?wZPRH3m%8C(V-|fP3sKk-Rw|`$w8}q zukQi_Ws(ERx5vb1GQYN<-;#23(vdUv8yuPO@ltmOyKTzcBu~0&unxA-EhQxPH>SfC3YO;RR1Ul5f#D3-FsW_~DhY0kCg8?; zhm1DCZ?QtqBcLl@+n+5KjB}gs-c%QarjXVSfo-t;tP$&On(rOd>4W z7h#iK50&-67ujYWuS^$ZQodogI9$LZSN#pnduU1N_Ory`3tA(&rD01{xHxNQ5%#d> z(8)q1cmq%^8L57}$m=Wk{xU0LrZfl#!qFH}nDB;D#1!*9V`}M??RUAy=2BjYd-seA zrtJ}W@MC7%N#5X*epRQb8Ti51n#{`-6d3JIY2yILKWOpT)P&_OGb;$Do#aeFT1^T9 zApX%Pv%`c4Umu;%clhB0`-(xWjHk<&vwo>9f}I2&l<|q*E9RW*4*RK3J6b|pd3c)p z0}d#qH)fYh|8 zE-A{uXGmAb$&y<#lxOY3)qw2`Bbu*6F!s|xBL=yLaa}W?AmGsk2`Wcvt5F+-*5t2r zDe2TaegI-%yDhV49f6(=FX1p_9)CoRto8+!6i;T%Pqf~b-itz|uvW&(@MQ|3y%!?f zEJfbnTZ!Jv;#D{|Y~A#Wu1|qP*g+amdyv|1jO_{RKl5Ffuz9NyOWhUb52$bc$enwa zSM|?&8{hL~o%!VWUjI?xT;?0UVa@vdYHOaF_0@g(Vv3jV?1uSsBEkDVA1FVUA_|gx z0L~ImprNjoy9}!D4o{ytL$<4;qgmgg$f6Ve%=?xM@*8aJ?qlB_5V*aA=vTzGqTX`c zb>&nw`#*bIn)?#XtBnMG0J;cvr+##5zK$DY-}M;ZL{N@A*zmvcU%fe^Aq@?^WiNcA5`T(4JqOD~0KTtOCm!2yad8#b^Mb!LFQ12ML1MV;*@S39o{*s#iORCvzd*@T6 zxQ0LM%p*(>HY1tazy-yKadrb;wFn3o4T6HphAuXwTqYUEC)^)jC#f149z$w8x!O`Q zE;MFeQ{oBzmJ@euJDfbQ3#E_7ZzE(f$v_w}04BM_(A`4PUlxea2M&Wy zTp>;&^Q%KsRxy!DZlsUZK&X>MDpgiGKueXL#{ZK(vf%Vx+#>pJZAYCjgydp9aJO zKe`;VPas&6QT&^52`uw8zKpE2dkWSYZX)fhv(yM{@JN~>3H*MHJ@=6*LdtmKz|cG* z#q0(5CILTP4fA+S3UigD7e50w$!wwYv3~f8hf|56k|J_g5?Xf@Q~gSg2Ra$@?))`< zAOdnHkLxxZa{;1&tXj2ukCYF=+wx_HncfZGr2|MO;9VKe$)!7Z?wL@_@^rDs@W>ol z>6jAYn(SC=<7&GC{+QnsaDA~%&T79GN_?xj>aflI0Qg~pDG3I2WeO&Qyq})>G)=#3 z6yMHQy}2^Z59QC_@hiMb;{{0{7Zel}7g1AFpEO7((x{dy7fL5C7W%#_{4+`7UtNAKH1?QCB5g0jY+GpCsU_)uQHW)r2Z z9zJDwwfZ`yKkXPQ{;vN3)cwhBHiAOEJS3WT`~a-2=xZCyFw!$J(j#UT#Yao-j(^0!n?2nzi zpbA7JpBT~@TxAl;ISYpQ4h|&GcwCerD^ZOAQ82G~Pe| z2XfzIqz?e7v+oOhXR*cyK<@7>zcHv;jsG{*Gg7o_OPw_oD)JLj*rJ1(odi^-sQF6W+$Y%#{gCzv8D zZ3AE8;$6^w4aR$N1m}{%4zqIF{Iur&lJZ)o8`nRbhag;L>K!b+M6A&^@m%p4BAsdp)Oo?%;*RxRoGhp04_Z1h zrEg<=8?dyNi-p*~zr^jPn=Sj}dWodJauTq)m+nL*NPUUjOYfn1r=@RCXL`a?I{;}8 zvXJ^xtw{mtGHO^FB^njNjxSH`Yzvek!P9Qb;f?|cHlHH3hYzQ9c!_3_f`caVjCAWB zZeiTns(OSF#6ppalgf2_gc-mTQDHi`tdd;)Tz?bh$QLKtl&k&qRnLgX@71GfMC;lG zRoEmBas|eB@+qk+Igtu(Q^~h}fYSX-3=EpAu>K#_yxKZ*Mpj&~Yz(iKFQKDk4jF;9 zB|vI7rhB(bv){|lQePf2_VGt4_fIRF&rV_=Jt+%oNu?;*uci+SjHXtYd59tzXSK** zU}vT1)EX~CX>4)u7JAQn*>4rhUOnTa^aLM#*D(`rlxso7-2=0qKLZ=DIE7xK=}hH_ zUSxX$=kH|X$sd3_4wE=|EQMrU6gF3^w47FX>;TCxBVqr=$Nrnk{WtQT*<7t?)~p9c#~GUTjJAP-<}aPdwwVFlJkDC1v_T_&Na97fn4NzE+4uVm%hR zgB{q$cS~&PA>3suTBY_(4Eih~I71m|?2R-k%d&jyFwC`D1`r}4S}kcc>@$}3P`q{5f{-RtWIbq0NtdNZ-eWMdSwY7jt*R191D|_)f%Ey zAm==%+c<*F4zXzitdRA+t!tbvELJvh<7LeTm%%;8s`&q?;w*sLh`PRyy9Eudff6i% z0tE`ip+SnfCWQjUfIIraXzkIIu9n{^iKPV@QT(KN10^b{0i*l$?Oex?v%{3O+3?Jo$Fnl- zO9mE=9m`AA?RaMq{n_-&?GU_o>-Q0i@bdK>C)!1w9SIH;!0Igv$()aB)moO`qvFGW zYL9{?8dsLqmKC;ELD66$T(NdNY#>``NmU7nk%Lt<;es>Xy25Il9zJ3YrJRyu;#k=Fs;*>9d<2`4dy-Zp;ejmnu!10-j zLHlUG8bq=#`hGD>wo{~Ws+jj1rv1jdJdfSBZSL-()q&+$|Gi26>w=ukJ^m_!rIyQ# zSvtc&ndwd5WB)E;fwR{>c>1mscEc@JUST2YqBs40{Nc^QU;4##yl{K9f}2Ry*()|6 zLbYT_!e=d;%!_v^d(ZJgoXMgIxm$upIgqGtXvc+-!kPNODuOppE2&MC#TK>_4U849CQG=3Hd#H^43L*W|MkcSgqao?a}4+%Eaft4GWYd<_~k&cjykfRn=QQU1b-&Qkmc~OtzO*pY^ zZMhe^hRuv=OBdIF!1gh*(ieqzpZB4K-K}o$Wb;|OmX772iO@U3L3FtL%a)D$!DtI7 zwA6CEHtrUUcY6N2jNMzo14lMRat1HjuRm^H{4{I14Ma62eO9#`iv*R9hu)jq!Cua2 zrg)@&$mcrXc`5n7M$P|gFiF-VrA6;Sl=S2X=I7l~TtG2M05Qb9{r`^s|6Cu9Gt?=2 z`hDrOU~aX3dR)uZeZS(X3`AGKB6I24u$Et!AM$#8}5|-lZ ziNwYkQ^D(B_)aLx+OCm6c8d`ixe38rAFdX6LR<`f`T14-S1jzS$Tri?JpU}!nt807 z@;}qGL7_6Q=K(_MVz^8@yjy*|S2;JH@+s}b=F1r_jJXA4^1{T?(nT<7tD_P zB_+a6Z-@RrF}h{E9bm{CUduQ5Lcg~17KB23E2IX0BMFRuTd?g1Fd^brsDqTfWFnt_ zU+jCKG9L-Fj_P$yNrJleuTd0P!) zrpw7pt|~-}%45+XB(|1WZi`3xBG;WHW?k&>mPlCJ&q(7tQ48E94H(o}V z{=>LV<>4r>V*CN6pJ-X5LbNwz6@h zi^#S3yQ)Y6wX5gf?(&$y?ix=C$Ck#c0LnEATa;hu`eZW13m>gRnf=kK4Icqp1nz?T zgo7gkhK5W!0vAkgT}V5PN?9k`=u3peEv~MGK>5YVnlR%eCKJ-lbIwiy|gl$p;I)5x=zmXlHK#+e-5-A9D{xPN&*1u)? zB@g#AxeIcT@l&Wq_X6M?R8&KovZE)ivq9`REV z{pjQG6+cM1ky%}4-L7k7)b0}P`kV0sRZ3~Xvmwt1*|}o=onGM`vwOR^{xTZe+>eqa zwz82LN>RV@FNe%S1;`!c5=D3r4Bv~704qYl$me^en$W#yP4;kW>T;N_wmGi(?sEM( z)d(Yd(#p`LsD*~W_5gU$kUi~zz+3=7^V@g7omSEu1c{8&b~IfD98%_lmc10HqztP&=) z#KX~%9fQ~KNJJog!)^4YXv3BS!8%A)aDwA6H6vWpK31h27;MSdt`X-`&i?eqrb4uq zuB-odkF69RGv!1&Bq$op z2soJ-@V~V~p^!zCu-3F~G~If4SuW3)%GB=#ghr(BPg&Z8$pQ()-&z?|4WoaGkykPB zp4$+(3wgSHXSkVg~e%dM56fBIbQ@MlY#RX=?9@2*1| z!&SV~Yei$*#~3hrm2TNA{oJj#ppD6{7LH7b1xE``oZt>qw=Pq4)cR)As*K$?7+Ry~4R`sY4%{upG8u%5 zc<~Y$BQNM8>t#f+nwT^d2GE3sG^S^qR0#T>?MyU#RzG-PnPOnVenqPjUi`SKM{tvq zurW!uXG+onpJXUA8Fp&Qq%;U9dA22ll6Z4E)A9Hwz=`p6xhJv&2C8F;?XwLi7}+;N zO?*O+tj{IN2=CPl!c=6YHEIF4u6@v5=ix*xNR>6HNbth&l9|ZnxLxC+p4*t@C~F~q zdRfdrE5Eqg{P>hqtAl;eFt&R*n|%4T$;(B4VhQ1QY9TEoNfnp1;Fs-=;e9xV*sw9x@S0*Ry+?HA@{qeNE3H_Al@HR-EaRDBAT(rC;O z26nmkxVl^HA+;?vuqUCDpa&@SNM}<(xks?gctgY(pVqYSV#lpU=>Aj1cY$RWpBYBS zYHF3*IBTAW4W_zcct!iFDy)JkMtpFNR24&U44d2|5Qdj zfcvF{(lei5M_4m0R-aXHCis#cel)|v--fPIsWzi>Pe^Sv;=-Drt6BfU(8cw9`*(nd z&FKU$_URd{3VNNLxCKZOiOT&oIOb&#g}WuN@r1Do?O?*_4{##=xPsSxU7iW&ESuZz2?-Z0GTJ(YsqxfjWSz(Tn1xh zM>W6WT~*ow;+kzs7lU*nmeM2I_2*G3bCxa6rDS&CraJ}vh7t5o=l$25zfA~8NU6Jx z9bm1!KJQh*Sp=#&*fqVQx9hq47O084t=`wqX~(O_0dtqC>F8#tk{olKA!Fn&zgVGR zg++|z`9F*=p{fEzX4^65Ggne6yQgh#_CJWoi8yI~me$yJenuj^cZS5^Ut6(7M~^kPV& zmmvepztN3euED5Nj69rOcJo^9tL&VJM)z*$Na+u<(~R5%gj(SQ?@QOrF>Q)J892hx z&*^S&^Fd;c@paA%pNP*oj>jiuhppB4e%0>yZ_HZ!CAaGeBZvM{-%=oQd;@vzNqT8deHZZMC?O?VN!TIW0U}>x~SYJ2?*XJ!EVIS?s zXl~7#3uYMF1#gdbseFOqiI3stG8{09qif!xi|_kAXNUJ-r5le~4sFDN;IH(ZoXof? zEwv8Eis2>|3KA3y$*VM-_^dS6XLqoNgXZk7udbO|!Bl;Y(I=M`MSz!hOgp%U9 zuAb6e6==3MQJay#bPe9|gy=v<3#`j<&J^vo{kqY9bcp=5{cGjtjE=49uWLAtbh$!4 zMTf(5b`e-5%1KL;P4bZ~ge0ikci-quR56jyvJQ?g91|JsZnnhsE&1s1W}nkvWpRF4 z*n*pPr>eEO{oW81uGADPjw29!t%2%f)1|}V%^-qvb8BZhd{5wrsngSAAI9b_uCb+u z(i(aSbB6pDezGw&E$_$<$6dAuJ@0b=z#q6$&B?oY{a$BtF;97Dm$MzfZo<38S0lp=87s>SzF}b})A|#e?2d9qc`y|m%L=CZG98$7 z#`OtdVlz>gIwQa69<3{!tWJv%wF}U4b=w?On^{A!~$Y zhT(|eM6=|$SzgN41&i6zp)p2D4O%_May05jnYQ*BxUXVE1o^khK2k$cI^7yAG0+yT8=~KxymDjxX29l z_~{NF|6N68)4p-EAt>DGWk=G53|->CmRU>9RzZ#sar2Vdkwbz>z6TJsXI4i0kD|fI zMB$Xr!eg9K?mwimta_gO0i2Xnly$bqB;{Hn*V+2D12&`9nIj>&?@?j-@-B#ggdK3z zsPvW!7W-Fl_}26QkS0=jD&lCXoFRwtl(N`F$*x)+Q6d-_pi=(MVhX|$X8W4re9@2_CpOF`S}U6(QfRzE4fGEupXaNr9UBg{)}xiS0qeD#=s^xT z&pn&Ps!faPDJGp7wvndtRcNx7RzCs4Q#b7!2R^;vZ!{ffpdDxwXR*%=wm0NF;k_)m z!HFcYEmPQNH_FjB@{UVjkmPZ(m!~%MQHgPA*g2v$aC)(22_jAEh$YLb?Ct8#csE8n z%hvi+Ys%CqGEXgbAaM>+qBVcCb`H^yfjynYLM}gjt247rA6|6Z7o=|OEPrezluG#~ zd{j}7e|ds)m!MaJ^o*|6u7bnmS7j5q{(L>4y`m*9FeMuUsLBv*nDM11*2)_bd|ErF z|6`?XfjgZ(sy`gxEtL4rm!=Y0`*Rw@G{5GrNM&VMSlew_XqqDdxY5|lD%#n;2C6kx zezuwEH5g_dJ@mpb$p1PD&o++LZyQRmVp}k#s9gO;2A-_vtCnS+A92Fsu#p$b`yn#u zBcMrdH50c}lPY?&+K7R2E5@-_&d}fGy&8M1w;5hsy2AlOaj)~VrVDPDdyJEuDpS#+ zbb*6q{8+sLPcuzxg*APf<9O8Na}`zz$Giel=S}~RHyzmQU)(7^d`|g$wsq171fN6t zEo4;`^*f;6d$`N@Wv?;5D|p6ad-+D2AHpMZ1gKag)1M3Z3VJ~(zAhVO!drMKqf7^+ z3W>?dvG zJRLIYvydHgR7oT(>_FGwf3rDZibxel=$0T9LzCEdwa%JYA!>vg#|T}!==;YiR#-uz zV909w^Gzge$-;ri?a=z>7*-7%D{ikU1Ej{cty(`w{!r({H$#w7Pep?fRmTkh%raAl}D%$*q!FhJHL>Wc)J8G1PXvfPd;{9Ud4m^Q1ZMZN`s{u_xva%Ra zVtj(OE|Q9kWaU*FHrPW8CFgf1I7s~}ruW|tzr)eN&T{EKv=JL>T!S;dUQ#)fI2xe4 z%H}xDy&s`6`1K+uxqmFG+;F?LMv{C8m*{f9DmAb-Ms625?VC4_mc%~B=}zHDs!Cwiu1YkrY2{{w1R6>tZ$(_ zd67>B|K{A2#iqQ<*PpGC*0K`@x->~MsbomNI>>d5LKFVO_vM?$lCRwb2u7=bG1u zAey7Bjx%WAAFh9uxNv6g5Jwbf*<87y~$qLEzCSS z-8|h)=w-Z>Ed>TFgZ^~MgxA_E`m#(q4%$v+Z$;K&n)G)evKD}(#Mig|1A=6eF{(I= zk`7oT2U%%X8Fb{+V9YrKM3<>ywBNEf9Y?8{si2cfGp_Bsk{l7BP9x=)r8c+4kJzq} z9#f6*Y)QkL%qQHO7|&GZRm%mRIh4)X;7+q7dF*DGm&Q-``p`wlh0BYp!)t81SiULx zK${4YZrC_X4XR_ABchIo4s7D}lon@~+3e#@+%S`W)sc5`btO`q871s3ylKWng_{ON zD<>BHSkD?ea(m5OOz9*KtezvxA_isbCIDi49ni&fia=C2ZvL>k7t5yq$7RROKjBMB zCcZvtVvCWVY}xBw5FJfsalz!kHmrKJAaPRkoOvW0?Oj+_SuU$0GvLe{qeMnUxG4In z(oo+!{gc}FW}_!kdacCBPl#nl-X3875-_WK_iB#4d@_)#yg80oG0{rKhB0FUxrmU0y)Ea=@aub&xqCM6X9HOj5QdcKMOr z9goFsG9r!hI^YYFk_-L&m|)CfJRVT1n3Rk)W-StWPT68*kb?BK6o0`RsJEfiY2O}J zQ~miBmmo2Iu6yc_N=wOi60*clHm_G_YzPeRZCW{7<9aVkK`A+x5ep=Zbpv0I4V9Wz z=P;LZVdMP6?lETPHglZ`Ujpqv49Gn3+TNQ{3BGK7C+>k!Qm?$}^=G8&M*RrvxagHD z3XYO*W%>+;Zss{nd=WmNa>Y<6M}3osW*GWIx5*LsYN49SEqo-eM?W$##s}Nm@o!_m z+G-%@k{sd4$0J6tSNBP|atjUr$ykrCk%WQB8r1>Ykznm)hH*>Xk=gxrQ(&rnfeYVF zj=E0a5Fvfs%BvU8zzLNVM+SxaSw@J%^$&gOc5-|MnQb)w->BO1_|lFr#NX}_Pt9V} zoFm2G`o%9xY2{hjm0khF{)+eZ;Na)|LR}8FT(hm<1|%dDJI|~;ky&|pmL>O}FqAf~ z)e@Occ7>=we_Ujr92Zvp4Y|#Bk)I;j!i@6uNlUISI z|1h*3l!EMUW5pkjV$hNE{QQTp9Q1#3S^r^)C>~iN<4o<-LuOflSRr$)4KUsV$ET<> z;7LlO{}pPnJMQM4OSY;CmQWe1kfcwXQg{90)}1At!E1CAUz|+r-!-paR_2C6^M)4x zFtP?*1_zXoqr;>AqU!?et3q_E_e}d^?UyfHmSL=HcPX2$GYcdS3zKElOdHo7_d{*x z7afJH%zt$wj+lsk^ERAY%+&PO&%da!dH$F5A+l`at`|Dhop!GP^Y_0~$lZKF+3f#) zxI+nkN_Snc?c?zGagDZ$a?YQ>_x*>_p4sZnnm9f(?`F1USlIGdWZqsmSP*+UPJ9UU zqfz0KwVBk2&iMgYzSz?gk&&zS_o}`bD(xos)MbwN)@b0|fERH&%e2R~LEn|R)r%uXvP*e2ThxetlIXeN4l@oM+yNjFH2uJp^xF%0Gz8ZL}>fgD#OT1+MNE-Ov{(aYXHe1F&K?}>sKS1*DBUW!d-SkEq(R9gp zWDw=d_FzjT{KIf)K>76kyt+fVip?I@-Z%6KXyCHGm@=HuJq}p93zYeUAyrtb$a^~4 zTLbKz_pcO_+TMS2JZR2h|NBU!I*w{@`=GZqJO78~^In!)bfXHY#eqW;rA z+6Dg|$K2a_%`*9DvvyvSeP6pv=ep~4dmu3puJrY_^T-rf%=o3|#nDNHR-z5>H(a|5 z-u|!uZIEk@dbs@gM<5j^8}Q8eXg!zRzVHtkZ7R z76a*ry3_8C?$ZZglZC4f0pNPM8r(C*lFpkgzzaM;oXI6^QLL!M29Rsn3az7`rJ_Eg z2Q$)ch?4C$>s1Y#LlnenYo@&nqj&UZsc_7-K{u+eveU<0Iv0V>G)m|%z5vbo;-Tr4 z8GxOzmsUBCjDs+p081GNV$}};Llf$meZo2VbF&EJ*BSHbcwc|J7}Aa?sM{~4%#d?K zkm^-Ws)e_Zg>tOwf0z*~B@4bJKtlA$f?G&EYCLLUSSgfk9W0gg!&lTG@&!=@K#&7f zb_)ltQkG1K#JH+6=#ve>3KR-6whMj_i{A{R=F8!dK;V}oMBb6CtrHFz#}+D7C=w_J;vwN2b-_ZUf?}t(RLU<2tt)IdiG4Dn zw;=&$=7PM+43pK8&iW#_W{x0yO*ucz2oh9rA;6B!aX>hGU;^nFT?S)>l+6+;9RC)8}tiW%iP=m2;pCp@+lKUJPt@kL1kK%mG;gf7N0 zn5yWAc^;-kH(HwhWZ)cOyWJBT4p+*gE1uFLOHu}BQernduwgiJn~|C|!lfKb+cLG9 zjtOYy{BCVe=%YLgt68j9@CNw7oJVP?NZhs^!~ujrko#qU0w08#cE)mc?^k99WuYMREnaEMS_9tL#uWE(JHGiO61%g=3x5voyXh z*TItz)!C6nHbws^#H+coZ% zKBaCA(5tS8)#@pg%M{vY(*E*tGBD3Vkw`g zTT~UNl*9Pp`5xGjg4qeU(55_2qb01QRo}H&-O_u=-g>yY*WBf{V5r#yi&^giv27_Az(v& z!oYC&$pZ-O$y9f~4e-Y#$+TeG6HD1wVm@aiTeM9X zC=tR^X(U@c^)OV{^CRKd8VnO8mmQe8b%0Q2=ZQnLGi%0J?R5&Zp!^@G@k@&L{TyT> zZsng`lFN~`-doM6{E-v?DOq3ZL1+#(gthtc6kV_l##>KGk{G+~_Ul!eJDc^iU+R@_ zjAp3q)4x#~y&wU^?3)qllzBiR^^Al!`Q|R_N??~Of>5~dVhyb>aX5wWRzD8QC!NwM zRHT`aTG)P1rckXPtG*4$=COf5kK5yJB}s|CB=>tag;P^g<3GDzaDmASz~trQ<-KS7 z4;{AhziLa^{|z*>@#ku%sSzb}`s30Pf=YkIS4e*kyLn43Oz8Nq<_4Mh=flQ*>kAVu z$L$BQznVq;4641$1rjYp+%VoNFN#}|_4vi2-bCdGel@cAdb;rD6>X-1quRhRD=S}m z&RL-{0N>}AQ1)$4TG#Jhr~aGY*MdQ*g}x6He-~(T{>%^4Z9GziT(!@m=IB$H9|@k} zWj;ZJ>fAfM?nG8rwHW=~zcmF*<3W~ce@o6Be%{_%Uh;`i}HwYB`t=GQwKNJ`y{e3a`ExxUTds+)WV4e%w1(bFxFgTolN@uzF zyrwcDsPN-~_4~Ki^?v@jA8vo%wH=(cF1={8YnNg5*|}kT%0smFBK5ZY27k4%b7NS6>$?F2(r8*qU-pF zVYx6v+#8AgxA2v3*uj;{UEs7+FU`%vKaAf)WkFliet?+>0lIHwNqxYI3tTIZjn(7p zVq+tzK!vW)He!VwL*6$=l9`Z!2{T4*29?xC|6$-VlTjAWYx5N)a3ly?dE%GJQyl1& zc~UUbYH-^J@Tl)6%1u9Fg29pooHVpHSOgxFF&Z`{ zf59UQyEGpOuQbOvun4_n3}XSoF7(Jg;};F%SixV%L*x#KW2Pu#FL5fMHn&uzn3N>t zo2+W-1Vw54?E(7J`(o)?jbsR_ipw?+PW%OPpvNP;*$b`vw> z?2{{X^(uMvs^Nc=rYqimmdRJ_0C3wKT#&2}Pij*}>j(@g8l{@kT0BDL)y`2S>G!y_ z&n|kp8g6N*k(cyHL?(V&n*v?kR@~IS^-P(D{7e}-{cuPy=a4THhYgP4r7I~L&BQN? z#{r;$jr-j=`|9${Gz2@r@s;LSx0&StKZ;eeArcXYydLsrKA0k2RrU};4Og1k-Ll7~ zEE=*bHg@Yaq$R!3jA=s+E9trw<;c*DkItQWcQUa2?jpEGY=~+ z+_9zAl=g=qam7(ahY&w1f`Pfx*@R$DD#|Kh1w0aWYzSO1Y_5$0O>4$9Oc)J~hz6L3 z3Kxab2RH+e0mQV@id81&5MV#IB(0g^SbGjQsv;Tyr9{wf3t~cs>6^`Y4&7xEJ0$Px~P1Wv{e*;%nr(FCY|fGkZKC1-s&qxlH^&7M=T0xd1{+ zY^_H68UdUSZ|6klAIbD+U~(*whD4IWgC#{8tXCHgzw8MER^b@c0rdb-v2?lDCUJ+x z2Lx;(l*Clo3Y0}{NIV-A973IL5@~+bg5-GP*m5o2Olobbi>8=Ef`)w$&|H}Z32;Vu zdtcJfWMBAJuTnc^UoDg@HnyaJ5(12wD^vci1q;n`287pT3X+V-L|FECu(n3N9ap4x0#YY~rqobqp77KFf&5y1341@R|#?|n@rT+oZhm}SE literal 0 HcmV?d00001 diff --git a/includes/kohana/modules/image/media/guide/image/dynamic-600.jpg b/includes/kohana/modules/image/media/guide/image/dynamic-600.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f521b49b14679d954038f36b2c2048176ccd2060 GIT binary patch literal 45517 zcma&NbzB@x7bc2_;4XncaCaL#!QE|e7#xC=z+k~0GPt|Dy99UlK=8rcJ^3AhcWy0hQPwZ;3L2zp`gRSaHhk+h=O5YZ0liQYDWLAzy!h|As`?i zA|N3mBE3g?OQ^_5NXV#YC@82XC}>z{{{}2H3`{Ic3^ZI^JUm=nQW6ppQu6;5VBfua zhmM9$goQ=)0UsOx!~d%I|C{;S1A~nWs{&^R4~q>0hYbsl4g0qbW)B7i4jvX3_Kp94 z13UsO93l+TyEpLP@IUdl0m8u}AR=MGz`?;Hz#zgQ!NDT_tN4avV^QEBeBz+Q#rurN zuJVPG3f~w>%<%`8H~}>`_S=<#MMOYGfPaSo|4(bMFxYSuSR7w);6JIFaI$}Pf=pB5 za{1-tBGib@sGN7JnZ{B%i{o)`Uijm@b&81izbU}LV)?-sSD%0R^b;F0jSKt61r8P- z?q9P1X_x=3q6DL0i`{(nVn(pa~O%>z6zhThe--NN@vERCXw8H7eVAR=U!sDgc zrX2`2rw_~h;v6&q*pB|R9R}DArQQx@)Y(${w50^_S9(ML&!}2mwrRF#3|k11+$k?+=&9s;+}LBJC+o0<%Eo19M9bNH0-!;(%;fE|Go zLO79B(_z7tr?!(=yIfyP_VNGhV>?jNj=B(#;}W@oWGlzTr@ACmf&78;oj6`T&XD@3 zEj}^x7e^~eBDGJS%1x|3GBIn4B>-w<(rC
d|V`}%hLO)+TPx5GZ|1VmQ?k`Ghd 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 W2xMeim*Bith0DYnkKn)kYya6hKV+c* literal 0 HcmV?d00001 diff --git a/includes/kohana/modules/image/tests/kohana/ImageTest.php b/includes/kohana/modules/image/tests/kohana/ImageTest.php new file mode 100644 index 00000000..967b7643 --- /dev/null +++ b/includes/kohana/modules/image/tests/kohana/ImageTest.php @@ -0,0 +1,36 @@ +markTestSkipped('The GD extension is not available.'); + } + } + + /** + * Tests the Image::save() method for files that don't have extensions + * + * @return void + */ + public function test_save_without_extension() + { + $image = Image::factory(MODPATH.'image/tests/test_data/test_image'); + $this->assertTrue($image->save(Kohana::$cache_dir.'/test_image')); + + unlink(Kohana::$cache_dir.'/test_image'); + } + +} // End Kohana_ImageTest diff --git a/includes/kohana/modules/image/tests/test_data/test_image b/includes/kohana/modules/image/tests/test_data/test_image new file mode 100644 index 0000000000000000000000000000000000000000..683a3c3e95a97f0f36a4b029d59840a51da473ce GIT binary patch literal 3455 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 AA^-pY literal 0 HcmV?d00001 diff --git a/includes/kohana/modules/minion/README.md b/includes/kohana/modules/minion/README.md new file mode 100644 index 00000000..6d317cb0 --- /dev/null +++ b/includes/kohana/modules/minion/README.md @@ -0,0 +1,62 @@ +# Minion + +Minion is a framework for running tasks via the CLI. + +The system is inspired by ruckusing, which had a nice system for defining tasks but lacked the desired flexibility for kohana integration. + +## Getting Started + +First off, download and enable the module in your bootstrap + +Then copy the bash script `minion` alongside your index.php (most likely the webroot). +If you'd rather the executable be in a different location to index.php then simply modify the bash script to point to index.php. + +You can then run minion like so: + + ./minion {task} + +To view a list of minion tasks, run minion without any parameters, or with the `--help` option + + ./minion + ./minion --help + +To view help for a specific minion task run + + ./minion {task} --help + +For security reasons Minion will only run from the cli. Attempting to access it over http will cause +a `Kohana_Exception` to be thrown. + +If you're unable to use the binary file for whatever reason then simply replace `./minion {task}` in the above +examples with + + php index.php --uri=minion --task={task} + +## Writing your own tasks + +All minion tasks must be located in `classes/task/`. They can be in any module, thus allowing you to +ship custom minion tasks with your own module / product. + +Each task must extend the abstract class `Minion_Task` and implement `Minion_Task::_execute()`. + +See `Minion_Task` for more details. + +## Documentation + +Code should be commented well enough not to need documentation, and minion can extract a class' doccomment to use +as documentation on the cli. + +## Testing + +This module is unittested using the [unittest module](http://github.com/kohana/unittest). +You can use the `minion` group to only run minion tests. + +i.e. + + phpunit --group minion + +Feel free to contribute tests(!), they can be found in the `tests/minion` directory. :) + +## License + +This is licensed under the [same license as Kohana](http://kohanaframework.org/license). diff --git a/includes/kohana/modules/minion/classes/Kohana/Minion/CLI.php b/includes/kohana/modules/minion/classes/Kohana/Minion/CLI.php new file mode 100644 index 00000000..13c1de66 --- /dev/null +++ b/includes/kohana/modules/minion/classes/Kohana/Minion/CLI.php @@ -0,0 +1,315 @@ + '0;30', + 'dark_gray' => '1;30', + 'blue' => '0;34', + 'light_blue' => '1;34', + 'green' => '0;32', + 'light_green' => '1;32', + 'cyan' => '0;36', + 'light_cyan' => '1;36', + 'red' => '0;31', + 'light_red' => '1;31', + 'purple' => '0;35', + 'light_purple' => '1;35', + 'brown' => '0;33', + 'yellow' => '1;33', + 'light_gray' => '0;37', + 'white' => '1;37', + ); + protected static $background_colors = array( + 'black' => '40', + 'red' => '41', + 'green' => '42', + 'yellow' => '43', + 'blue' => '44', + 'magenta' => '45', + 'cyan' => '46', + 'light_gray' => '47', + ); + + /** + * Returns one or more command-line options. Options are specified using + * standard CLI syntax: + * + * php index.php --username=john.smith --password=secret --var="some value with spaces" + * + * // Get the values of "username" and "password" + * $auth = Minion_CLI::options('username', 'password'); + * + * @param string $options,... option name + * @return array + */ + public static function options($options = NULL) + { + // Get all of the requested options + $options = func_get_args(); + + // Found option values + $values = array(); + + // Skip the first option, it is always the file executed + for ($i = 1; $i < $_SERVER['argc']; $i++) + { + if ( ! isset($_SERVER['argv'][$i])) + { + // No more args left + break; + } + + // Get the option + $opt = $_SERVER['argv'][$i]; + + if (substr($opt, 0, 2) !== '--') + { + // This is a positional argument + $values[] = $opt; + continue; + } + + // Remove the "--" prefix + $opt = substr($opt, 2); + + if (strpos($opt, '=')) + { + // Separate the name and value + list ($opt, $value) = explode('=', $opt, 2); + } + else + { + $value = NULL; + } + + $values[$opt] = $value; + } + + if ($options) + { + foreach ($values as $opt => $value) + { + if ( ! in_array($opt, $options)) + { + // Set the given value + unset($values[$opt]); + } + } + } + + return count($options) == 1 ? array_pop($values) : $values; + } + + /** + * Reads input from the user. This can have either 1 or 2 arguments. + * + * Usage: + * + * // Waits for any key press + * Minion_CLI::read(); + * + * // Takes any input + * $color = Minion_CLI::read('What is your favorite color?'); + * + * // Will only accept the options in the array + * $ready = Minion_CLI::read('Are you ready?', array('y','n')); + * + * @param string $text text to show user before waiting for input + * @param array $options array of options the user is shown + * @return string the user input + */ + public static function read($text = '', array $options = NULL) + { + // If a question has been asked with the read + $options_output = ''; + if ( ! empty($options)) + { + $options_output = ' [ '.implode(', ', $options).' ]'; + } + + fwrite(STDOUT, $text.$options_output.': '); + + // Read the input from keyboard. + $input = trim(fgets(STDIN)); + + // If options are provided and the choice is not in the array, tell them to try again + if ( ! empty($options) && ! in_array($input, $options)) + { + Minion_CLI::write('This is not a valid option. Please try again.'); + + $input = Minion_CLI::read($text, $options); + } + + // Read the input + return $input; + } + + /** + * Experimental feature. + * + * Reads hidden input from the user + * + * Usage: + * + * $password = Minion_CLI::password('Enter your password'); + * + * @author Mathew Davies. + * @return string + */ + public static function password($text = '') + { + $text .= ': '; + + if (Kohana::$is_windows) + { + $vbscript = sys_get_temp_dir().'Minion_CLI_Password.vbs'; + + // Create temporary file + file_put_contents($vbscript, 'wscript.echo(InputBox("'.addslashes($text).'"))'); + + $password = shell_exec('cscript //nologo '.escapeshellarg($command)); + + // Remove temporary file. + unlink($vbscript); + } + else + { + $password = shell_exec('/usr/bin/env bash -c \'read -s -p "'.escapeshellcmd($text).'" var && echo $var\''); + } + + Minion_CLI::write(); + + return trim($password); + } + + /** + * Outputs a string to the cli. If you send an array it will implode them + * with a line break. + * + * @param string|array $text the text to output, or array of lines + */ + public static function write($text = '') + { + if (is_array($text)) + { + foreach ($text as $line) + { + Minion_CLI::write($line); + } + } + else + { + fwrite(STDOUT, $text.PHP_EOL); + } + } + + /** + * Outputs a replacable line to the cli. You can continue replacing the + * line until `TRUE` is passed as the second parameter in order to indicate + * you are done modifying the line. + * + * // Sample progress indicator + * Minion_CLI::write_replace('0%'); + * Minion_CLI::write_replace('25%'); + * Minion_CLI::write_replace('50%'); + * Minion_CLI::write_replace('75%'); + * // Done writing this line + * Minion_CLI::write_replace('100%', TRUE); + * + * @param string $text the text to output + * @param boolean $end_line whether the line is done being replaced + */ + public static function write_replace($text = '', $end_line = FALSE) + { + // Append a newline if $end_line is TRUE + $text = $end_line ? $text.PHP_EOL : $text; + fwrite(STDOUT, "\r\033[K".$text); + } + + /** + * Waits a certain number of seconds, optionally showing a wait message and + * waiting for a key press. + * + * @author Fuel Development Team + * @license MIT License + * @copyright 2010 - 2011 Fuel Development Team + * @link http://fuelphp.com + * @param int $seconds number of seconds + * @param bool $countdown show a countdown or not + */ + public static function wait($seconds = 0, $countdown = false) + { + if ($countdown === true) + { + $time = $seconds; + + while ($time > 0) + { + fwrite(STDOUT, $time.'... '); + sleep(1); + $time--; + } + + Minion_CLI::write(); + } + else + { + if ($seconds > 0) + { + sleep($seconds); + } + else + { + Minion_CLI::write(Minion_CLI::$wait_msg); + Minion_CLI::read(); + } + } + } + + /** + * Returns the given text with the correct color codes for a foreground and + * optionally a background color. + * + * @author Fuel Development Team + * @license MIT License + * @copyright 2010 - 2011 Fuel Development Team + * @link http://fuelphp.com + * @param string $text the text to color + * @param atring $foreground the foreground color + * @param string $background the background color + * @return string the color coded string + */ + public static function color($text, $foreground, $background = null) + { + + if (Kohana::$is_windows) + { + return $text; + } + + if (!array_key_exists($foreground, Minion_CLI::$foreground_colors)) + { + throw new Kohana_Exception('Invalid CLI foreground color: '.$foreground); + } + + if ($background !== null and !array_key_exists($background, Minion_CLI::$background_colors)) + { + throw new Kohana_Exception('Invalid CLI background color: '.$background); + } + + $string = "\033[".Minion_CLI::$foreground_colors[$foreground]."m"; + + if ($background !== null) + { + $string .= "\033[".Minion_CLI::$background_colors[$background]."m"; + } + + $string .= $text."\033[0m"; + + return $string; + } + +} diff --git a/includes/kohana/modules/minion/classes/Kohana/Minion/Exception.php b/includes/kohana/modules/minion/classes/Kohana/Minion/Exception.php new file mode 100644 index 00000000..442e82d7 --- /dev/null +++ b/includes/kohana/modules/minion/classes/Kohana/Minion/Exception.php @@ -0,0 +1,64 @@ +format_for_cli(); + } + else + { + echo Kohana_Exception::text($e); + } + + $exit_code = $e->getCode(); + + // Never exit "0" after an exception. + if ($exit_code == 0) + { + $exit_code = 1; + } + + exit($exit_code); + } + catch (Exception $e) + { + // Clean the output buffer if one exists + ob_get_level() and ob_clean(); + + // Display the exception text + echo Kohana_Exception::text($e), "\n"; + + // Exit with an error status + exit(1); + } + } + + public function format_for_cli() + { + return Kohana_Exception::text($e); + } +} diff --git a/includes/kohana/modules/minion/classes/Kohana/Minion/Exception/InvalidTask.php b/includes/kohana/modules/minion/classes/Kohana/Minion/Exception/InvalidTask.php new file mode 100644 index 00000000..db8ead4c --- /dev/null +++ b/includes/kohana/modules/minion/classes/Kohana/Minion/Exception/InvalidTask.php @@ -0,0 +1,18 @@ +getMessage().PHP_EOL; + } + +} diff --git a/includes/kohana/modules/minion/classes/Kohana/Minion/Task.php b/includes/kohana/modules/minion/classes/Kohana/Minion/Task.php new file mode 100644 index 00000000..be4fcc0e --- /dev/null +++ b/includes/kohana/modules/minion/classes/Kohana/Minion/Task.php @@ -0,0 +1,364 @@ + $class) + ); + } + + $class = new $class; + + if ( ! $class instanceof Minion_Task) + { + throw new Minion_Exception_InvalidTask( + "Task ':task' is not a valid minion task", + array(':task' => $class) + ); + } + + $class->set_options($options); + + // Show the help page for this task if requested + if (array_key_exists('help', $options)) + { + $class->_method = '_help'; + } + + return $class; + } + + /** + * The list of options this task accepts and their default values. + * + * protected $_options = array( + * 'limit' => 4, + * 'table' => NULL, + * ); + * + * @var array + */ + protected $_options = array(); + + /** + * Populated with the accepted options for this task. + * This array is automatically populated based on $_options. + * + * @var array + */ + protected $_accepted_options = array(); + + protected $_method = '_execute'; + + protected function __construct() + { + // Populate $_accepted_options based on keys from $_options + $this->_accepted_options = array_keys($this->_options); + } + + /** + * The file that get's passes to Validation::errors() when validation fails + * @var string|NULL + */ + protected $_errors_file = 'validation'; + + /** + * Gets the task name for the task + * + * @return string + */ + public function __toString() + { + static $task_name = NULL; + + if ($task_name === NULL) + { + $task_name = Minion_Task::convert_class_to_task($this); + } + + return $task_name; + } + + /** + * Sets options for this task + * + * $param array the array of options to set + * @return this + */ + public function set_options(array $options) + { + foreach ($options as $key => $value) + { + $this->_options[$key] = $value; + } + + return $this; + } + + /** + * Get the options that were passed into this task with their defaults + * + * @return array + */ + public function get_options() + { + return (array) $this->_options; + } + + /** + * Get a set of options that this task can accept + * + * @return array + */ + public function get_accepted_options() + { + return (array) $this->_accepted_options; + } + + /** + * Adds any validation rules/labels for validating _options + * + * public function build_validation(Validation $validation) + * { + * return parent::build_validation($validation) + * ->rule('paramname', 'not_empty'); // Require this param + * } + * + * @param Validation the validation object to add rules to + * + * @return Validation + */ + public function build_validation(Validation $validation) + { + // Add a rule to each key making sure it's in the task + foreach ($validation->as_array() as $key => $value) + { + $validation->rule($key, array($this, 'valid_option'), array(':validation', ':field')); + } + + return $validation; + } + + /** + * Returns $_errors_file + * + * @return string + */ + public function get_errors_file() + { + return $this->_errors_file; + } + + /** + * Execute the task with the specified set of options + * + * @return null + */ + public function execute() + { + $options = $this->get_options(); + + // Validate $options + $validation = Validation::factory($options); + $validation = $this->build_validation($validation); + + if ( $this->_method != '_help' AND ! $validation->check()) + { + echo View::factory('minion/error/validation') + ->set('task', Minion_Task::convert_class_to_task($this)) + ->set('errors', $validation->errors($this->get_errors_file())); + } + else + { + // Finally, run the task + $method = $this->_method; + echo $this->{$method}($options); + } + } + + abstract protected function _execute(array $params); + + /** + * Outputs help for this task + * + * @return null + */ + protected function _help(array $params) + { + $tasks = $this->_compile_task_list(Kohana::list_files('classes/task')); + + $inspector = new ReflectionClass($this); + + list($description, $tags) = $this->_parse_doccomment($inspector->getDocComment()); + + $view = View::factory('minion/help/task') + ->set('description', $description) + ->set('tags', (array) $tags) + ->set('task', Minion_Task::convert_class_to_task($this)); + + echo $view; + } + + + public function valid_option(Validation $validation, $option) + { + if ( ! in_array($option, $this->_accepted_options)) + { + $validation->error($option, 'minion_option'); + } + } + + /** + * Parses a doccomment, extracting both the comment and any tags associated + * + * Based on the code in Kodoc::parse() + * + * @param string The comment to parse + * @return array First element is the comment, second is an array of tags + */ + protected function _parse_doccomment($comment) + { + // Normalize all new lines to \n + $comment = str_replace(array("\r\n", "\n"), "\n", $comment); + + // Remove the phpdoc open/close tags and split + $comment = array_slice(explode("\n", $comment), 1, -1); + + // Tag content + $tags = array(); + + foreach ($comment as $i => $line) + { + // Remove all leading whitespace + $line = preg_replace('/^\s*\* ?/m', '', $line); + + // Search this line for a tag + if (preg_match('/^@(\S+)(?:\s*(.+))?$/', $line, $matches)) + { + // This is a tag line + unset($comment[$i]); + + $name = $matches[1]; + $text = isset($matches[2]) ? $matches[2] : ''; + + $tags[$name] = $text; + } + else + { + $comment[$i] = (string) $line; + } + } + + $comment = trim(implode("\n", $comment)); + + return array($comment, $tags); + } + + /** + * Compiles a list of available tasks from a directory structure + * + * @param array Directory structure of tasks + * @param string prefix + * @return array Compiled tasks + */ + protected function _compile_task_list(array $files, $prefix = '') + { + $output = array(); + + foreach ($files as $file => $path) + { + $file = substr($file, strrpos($file, DIRECTORY_SEPARATOR) + 1); + + if (is_array($path) AND count($path)) + { + $task = $this->_compile_task_list($path, $prefix.$file.Minion_Task::$task_separator); + + if ($task) + { + $output = array_merge($output, $task); + } + } + else + { + $output[] = strtolower($prefix.substr($file, 0, -strlen(EXT))); + } + } + + return $output; + } +} diff --git a/includes/kohana/modules/image/classes/image.php b/includes/kohana/modules/minion/classes/Minion/CLI.php similarity index 57% rename from includes/kohana/modules/image/classes/image.php rename to includes/kohana/modules/minion/classes/Minion/CLI.php index 5b047749..d9987669 100644 --- a/includes/kohana/modules/image/classes/image.php +++ b/includes/kohana/modules/minion/classes/Minion/CLI.php @@ -1,3 +1,3 @@ _compile_task_list(Kohana::list_files('classes/Task')); + + $view = new View('minion/help/list'); + + $view->tasks = $tasks; + + echo $view; + } +} diff --git a/includes/kohana/modules/minion/config/userguide.php b/includes/kohana/modules/minion/config/userguide.php new file mode 100644 index 00000000..1bb7093d --- /dev/null +++ b/includes/kohana/modules/minion/config/userguide.php @@ -0,0 +1,13 @@ + array( + 'minion' => array( + 'enabled' => TRUE, + 'name' => 'Minion', + 'description' => 'Minion is a simple command line task runner', + 'copyright' => '© 2009-2011 Kohana Team', + ) + ) +); \ No newline at end of file diff --git a/includes/kohana/modules/minion/guide/minion/index.md b/includes/kohana/modules/minion/guide/minion/index.md new file mode 100644 index 00000000..5c3be5bf --- /dev/null +++ b/includes/kohana/modules/minion/guide/minion/index.md @@ -0,0 +1,3 @@ +# Minion + +Minion is a simple command line task runner. \ No newline at end of file diff --git a/includes/kohana/modules/minion/guide/minion/menu.md b/includes/kohana/modules/minion/guide/minion/menu.md new file mode 100644 index 00000000..9c22809e --- /dev/null +++ b/includes/kohana/modules/minion/guide/minion/menu.md @@ -0,0 +1,3 @@ +## [Minion]() + - [Setup](setup) + - [Writing a Task](tasks) \ No newline at end of file diff --git a/includes/kohana/modules/minion/guide/minion/setup.md b/includes/kohana/modules/minion/guide/minion/setup.md new file mode 100644 index 00000000..11e6256d --- /dev/null +++ b/includes/kohana/modules/minion/guide/minion/setup.md @@ -0,0 +1,32 @@ +# Minion Setup + +To use minion, you'll need to make a small change to your index.php file: + + -/** + - * Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO']. + - * If no source is specified, the URI will be automatically detected. + - */ + -echo Request::factory() + - ->execute() + - ->send_headers(TRUE) + - ->body(); + +if (PHP_SAPI == 'cli') // Try and load minion + +{ + + class_exists('Minion_Task') OR die('minion required!'); + + set_exception_handler(array('Kohana_Minion_Exception_Handler', 'handler')); + + + + Minion_Task::factory(Minion_CLI::options())->execute(); + +} + +else + +{ + + /** + + * Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO']. + + * If no source is specified, the URI will be automatically detected. + + */ + + echo Request::factory() + + ->execute() + + ->send_headers(TRUE) + + ->body(); + +} + +This will short-circuit your index file to intercept any cli calls, and route them to the minion module. \ No newline at end of file diff --git a/includes/kohana/modules/minion/guide/minion/tasks.md b/includes/kohana/modules/minion/guide/minion/tasks.md new file mode 100644 index 00000000..4bda3ef2 --- /dev/null +++ b/includes/kohana/modules/minion/guide/minion/tasks.md @@ -0,0 +1,71 @@ +# Writing Tasks + +Writing a task in minion is very easy. Simply create a new class called `Task_` and put it inside `classes/task/.php`. + + NULL, + ); + + /** + * This is a demo task + * + * @return null + */ + protected function _execute(array $params) + { + var_dump($params); + echo 'foobar'; + } + } + +You'll notice a few things here: + + - You need a main `_execute()` method. It should take one array parameter. + - This parameter contains any command line options passed to the task. + - For example, if you call the task above with `./minion --task=demo --foo=foobar` then `$params` will contain: `array('foo' => 'foobar', 'bar' => NULL)` + - It needs to have a `protected $_defaults` array. This is a list of parameters you want to accept for this task. Any parameters passed to the task not in this list will be rejected. + +## Namespacing Tasks + +You can "namespace" tasks by placing them all in a subdirectory: `classes/task/database/generate.php`. This task will be named `database:generate` and can be called with this task name. + +# Parameter Validations + +To add validations to your command line options, simply overload the `build_validation()` method in your task: + + public function build_validation(Validation $validation) + { + return parent::build_validation($validation) + ->rule('foo', 'not_empty') // Require this param + ->rule('bar', 'numeric'); // This param should be numeric + } + +These validations will run for every task call unless `--help` is passed to the task. + +# Task Help + +Tasks can have built-in help. Minion will read class docblocks that you specify: + + ':field is not a valid option for this task!', +); \ No newline at end of file diff --git a/includes/kohana/modules/minion/minion b/includes/kohana/modules/minion/minion new file mode 100644 index 00000000..dc4f755f --- /dev/null +++ b/includes/kohana/modules/minion/minion @@ -0,0 +1,4 @@ +#!/usr/bin/env php + /dev/null 2>&1 + then + start_daemon + fi +done \ No newline at end of file diff --git a/includes/kohana/modules/minion/tests/minion/task.php b/includes/kohana/modules/minion/tests/minion/task.php new file mode 100644 index 00000000..e7918a01 --- /dev/null +++ b/includes/kohana/modules/minion/tests/minion/task.php @@ -0,0 +1,70 @@ +assertSame($expected, Minion_Task::convert_task_to_class_name($task_name)); + } + + /** + * Provides test data for test_convert_class_to_task() + * + * @return array + */ + public function provider_convert_class_to_task() + { + return array( + array('db:migrate', 'Task_Db_Migrate'), + ); + } + + /** + * Tests that the task name can be found from a class name / object + * + * @test + * @covers Minion_Task::convert_class_to_task + * @dataProvider provider_convert_class_to_task + * @param string Expected task name + * @param mixed Input class + */ + public function test_convert_class_to_task($expected, $class) + { + $this->assertSame($expected, Minion_Task::convert_class_to_task($class)); + } +} diff --git a/includes/kohana/modules/minion/views/minion/error/validation.php b/includes/kohana/modules/minion/views/minion/error/validation.php new file mode 100644 index 00000000..a65758a7 --- /dev/null +++ b/includes/kohana/modules/minion/views/minion/error/validation.php @@ -0,0 +1,10 @@ +Parameter Errors: + $error): ?> + - + + +Run + + php index.php --task= --help + +for more help \ No newline at end of file diff --git a/includes/kohana/modules/minion/views/minion/help/error.php b/includes/kohana/modules/minion/views/minion/help/error.php new file mode 100644 index 00000000..504115de --- /dev/null +++ b/includes/kohana/modules/minion/views/minion/help/error.php @@ -0,0 +1,7 @@ + + +Run + + index.php --uri=minion + +for more help diff --git a/includes/kohana/modules/minion/views/minion/help/list.php b/includes/kohana/modules/minion/views/minion/help/list.php new file mode 100644 index 00000000..eeb06ef4 --- /dev/null +++ b/includes/kohana/modules/minion/views/minion/help/list.php @@ -0,0 +1,17 @@ +Minion is a cli tool for performing tasks + +Usage + + {task} --task=[options] + +Where {task} is one of the following: + + + * + + + +For more information on what a task does and usage details execute + + --task={task} --help + diff --git a/includes/kohana/modules/minion/views/minion/help/task.php b/includes/kohana/modules/minion/views/minion/help/task.php new file mode 100644 index 00000000..8f2fcd76 --- /dev/null +++ b/includes/kohana/modules/minion/views/minion/help/task.php @@ -0,0 +1,17 @@ + +Usage +======= +php minion.php --task= [--option1=value1] [--option2=value2] + +Details +======= + $tag_content): ?> +: + + + +Description +=========== + + + diff --git a/includes/kohana/modules/orm/auth-schema-mysql.sql b/includes/kohana/modules/orm/auth-schema-mysql.sql index ec4a4a80..61a803ac 100644 --- a/includes/kohana/modules/orm/auth-schema-mysql.sql +++ b/includes/kohana/modules/orm/auth-schema-mysql.sql @@ -18,7 +18,7 @@ CREATE TABLE IF NOT EXISTS `roles_users` ( CREATE TABLE IF NOT EXISTS `users` ( `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `email` varchar(127) NOT NULL, + `email` varchar(254) NOT NULL, `username` varchar(32) NOT NULL DEFAULT '', `password` varchar(64) NOT NULL, `logins` int(10) UNSIGNED NOT NULL DEFAULT '0', @@ -33,12 +33,12 @@ CREATE TABLE IF NOT EXISTS `user_tokens` ( `user_id` int(11) UNSIGNED NOT NULL, `user_agent` varchar(40) NOT NULL, `token` varchar(40) NOT NULL, - `type` varchar(100) NOT NULL, `created` int(10) UNSIGNED NOT NULL, `expires` int(10) UNSIGNED NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_token` (`token`), - KEY `fk_user_id` (`user_id`) + KEY `fk_user_id` (`user_id`), + KEY `expires` (`expires`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `roles_users` diff --git a/includes/kohana/modules/orm/auth-schema-postgresql.sql b/includes/kohana/modules/orm/auth-schema-postgresql.sql index 72f6424c..ce82ed12 100644 --- a/includes/kohana/modules/orm/auth-schema-postgresql.sql +++ b/includes/kohana/modules/orm/auth-schema-postgresql.sql @@ -16,7 +16,7 @@ CREATE TABLE roles_users CREATE TABLE users ( id serial, - email varchar(318) NOT NULL, + email varchar(254) NOT NULL, username varchar(32) NOT NULL, "password" varchar(64) NOT NULL, logins integer NOT NULL DEFAULT 0, diff --git a/includes/kohana/modules/orm/classes/Auth/ORM.php b/includes/kohana/modules/orm/classes/Auth/ORM.php new file mode 100644 index 00000000..590bef19 --- /dev/null +++ b/includes/kohana/modules/orm/classes/Auth/ORM.php @@ -0,0 +1,3 @@ +where('name', 'IN', $role) ->find_all() ->as_array(NULL, 'id'); @@ -46,7 +46,7 @@ class Kohana_Auth_ORM extends Auth { if ( ! is_object($role)) { // Load the role - $roles = ORM::factory('role', array('name' => $role)); + $roles = ORM::factory('Role', array('name' => $role)); if ( ! $roles->loaded()) return FALSE; @@ -60,9 +60,9 @@ class Kohana_Auth_ORM extends Auth { /** * Logs a user in. * - * @param string username - * @param string password - * @param boolean enable autologin + * @param string $username + * @param string $password + * @param boolean $remember enable autologin * @return boolean */ protected function _login($user, $password, $remember) @@ -72,24 +72,30 @@ class Kohana_Auth_ORM extends Auth { $username = $user; // Load the user - $user = ORM::factory('user'); + $user = ORM::factory('User'); $user->where($user->unique_key($username), '=', $username)->find(); } + if (is_string($password)) + { + // Create a hashed password + $password = $this->hash($password); + } + // If the passwords match, perform a login - if ($user->has('roles', ORM::factory('role', array('name' => 'login'))) AND $user->password === $password) + if ($user->has('roles', ORM::factory('Role', array('name' => 'login'))) AND $user->password === $password) { if ($remember === TRUE) { // Token data $data = array( - 'user_id' => $user->id, + 'user_id' => $user->pk(), 'expires' => time() + $this->_config['lifetime'], 'user_agent' => sha1(Request::$user_agent), ); // Create a new autologin token - $token = ORM::factory('user_token') + $token = ORM::factory('User_Token') ->values($data) ->create(); @@ -110,8 +116,8 @@ class Kohana_Auth_ORM extends Auth { /** * Forces a user to be logged in, without specifying a password. * - * @param mixed username string, or user ORM object - * @param boolean mark the session as forced + * @param mixed $user username string, or user ORM object + * @param boolean $mark_session_as_forced mark the session as forced * @return boolean */ public function force_login($user, $mark_session_as_forced = FALSE) @@ -121,7 +127,7 @@ class Kohana_Auth_ORM extends Auth { $username = $user; // Load the user - $user = ORM::factory('user'); + $user = ORM::factory('User'); $user->where($user->unique_key($username), '=', $username)->find(); } @@ -145,7 +151,7 @@ class Kohana_Auth_ORM extends Auth { if ($token = Cookie::get('authautologin')) { // Load the token and user - $token = ORM::factory('user_token', array('token' => $token)); + $token = ORM::factory('User_Token', array('token' => $token)); if ($token->loaded() AND $token->user->loaded()) { @@ -174,18 +180,20 @@ class Kohana_Auth_ORM extends Auth { /** * Gets the currently logged in user from the session (with auto_login check). - * Returns FALSE if no user is currently logged in. + * Returns $default if no user is currently logged in. * + * @param mixed $default to return in case user isn't logged in * @return mixed */ public function get_user($default = NULL) { $user = parent::get_user($default); - if ( ! $user) + if ($user === $default) { // check for "remembered" login - $user = $this->auto_login(); + if (($user = $this->auto_login()) === FALSE) + return $default; } return $user; @@ -194,8 +202,8 @@ class Kohana_Auth_ORM extends Auth { /** * Log a user out and remove any autologin cookies. * - * @param boolean completely destroy the session - * @param boolean remove all tokens for user + * @param boolean $destroy completely destroy the session + * @param boolean $logout_all remove all tokens for user * @return boolean */ public function logout($destroy = FALSE, $logout_all = FALSE) @@ -209,11 +217,17 @@ class Kohana_Auth_ORM extends Auth { Cookie::delete('authautologin'); // Clear the autologin token from the database - $token = ORM::factory('user_token', array('token' => $token)); + $token = ORM::factory('User_Token', array('token' => $token)); if ($token->loaded() AND $logout_all) { - ORM::factory('user_token')->where('user_id', '=', $token->user_id)->delete_all(); + // Delete all user tokens. This isn't the most elegant solution but does the job + $tokens = ORM::factory('User_Token')->where('user_id','=',$token->user_id)->find_all(); + + foreach ($tokens as $_token) + { + $_token->delete(); + } } elseif ($token->loaded()) { @@ -227,7 +241,7 @@ class Kohana_Auth_ORM extends Auth { /** * Get the stored password for a username. * - * @param mixed username string, or user ORM object + * @param mixed $user username string, or user ORM object * @return string */ public function password($user) @@ -237,7 +251,7 @@ class Kohana_Auth_ORM extends Auth { $username = $user; // Load the user - $user = ORM::factory('user'); + $user = ORM::factory('User'); $user->where($user->unique_key($username), '=', $username)->find(); } @@ -248,7 +262,7 @@ class Kohana_Auth_ORM extends Auth { * Complete the login for a user by incrementing the logins and setting * session data: user_id, username, roles. * - * @param object user ORM object + * @param object $user user ORM object * @return void */ protected function complete_login($user) @@ -274,4 +288,4 @@ class Kohana_Auth_ORM extends Auth { return ($this->hash($password) === $user->password); } -} // End Auth ORM \ No newline at end of file +} // End Auth ORM diff --git a/includes/kohana/modules/orm/classes/kohana/orm.php b/includes/kohana/modules/orm/classes/Kohana/ORM.php similarity index 56% rename from includes/kohana/modules/orm/classes/kohana/orm.php rename to includes/kohana/modules/orm/classes/Kohana/ORM.php index d9c164f8..716e51d2 100644 --- a/includes/kohana/modules/orm/classes/kohana/orm.php +++ b/includes/kohana/modules/orm/classes/Kohana/ORM.php @@ -1,4 +1,4 @@ -.* - protected $_disable_wild_select = FALSE; - - // Suppress ORMs inclusion of . to column joins - protected $_disable_join_table_name = FALSE; - - // Suppress ORMs use of limit - protected $_disable_limit = FALSE; - /** * Model configuration, reload on wakeup? * @var bool @@ -282,7 +221,7 @@ class Kohana_ORM extends Model implements serializable { /** * Database query builder - * @var Database_Query_Builder_Where + * @var Database_Query_Builder_Select */ protected $_db_builder; @@ -298,11 +237,17 @@ class Kohana_ORM extends Model implements serializable { */ protected $_cast_data = array(); + /** + * The message filename used for validation errors. + * Defaults to ORM::$_object_name + * @var string + */ + protected $_errors_filename = NULL; + /** * Constructs a new model and loads a record if given * * @param mixed $id Parameter for find or object to load - * @return void */ public function __construct($id = NULL) { @@ -323,7 +268,7 @@ class Kohana_ORM extends Model implements serializable { else { // Passing the primary key - $this->where(($this->_disable_join_table_name ? '' : $this->_table_name.'.').$this->_primary_key, '=', $id)->find(); + $this->where($this->_object_name.'.'.$this->_primary_key, '=', $id)->find(); } } elseif ( ! empty($this->_cast_data)) @@ -345,53 +290,98 @@ class Kohana_ORM extends Model implements serializable { { // Set the object name and plural name $this->_object_name = strtolower(substr(get_class($this), 6)); - $this->_object_plural = Inflector::plural($this->_object_name); - - if ( ! is_object($this->_db)) + + // Check if this model has already been initialized + if ( ! $init = Arr::get(ORM::$_init_cache, $this->_object_name, FALSE)) { - // Get database instance - $this->_db = Database::instance($this->_db_group); - } - - if (empty($this->_table_name)) - { - // Table name is the same as the object name - $this->_table_name = $this->_object_name; - - if ($this->_table_names_plural === TRUE) + $init = array( + '_belongs_to' => array(), + '_has_one' => array(), + '_has_many' => array(), + ); + + // Set the object plural name if none predefined + if ( ! isset($this->_object_plural)) { - // Make the table name plural - $this->_table_name = Inflector::plural($this->_table_name); + $init['_object_plural'] = Inflector::plural($this->_object_name); } - } - foreach ($this->_belongs_to as $alias => $details) + if ( ! $this->_errors_filename) + { + $init['_errors_filename'] = $this->_object_name; + } + + if ( ! is_object($this->_db)) + { + // Get database instance + $init['_db'] = Database::instance($this->_db_group); + } + + if (empty($this->_table_name)) + { + // Table name is the same as the object name + $init['_table_name'] = $this->_object_name; + + if ($this->_table_names_plural === TRUE) + { + // Make the table name plural + $init['_table_name'] = Arr::get($init, '_object_plural', $this->_object_plural); + } + } + + $defaults = array(); + + foreach ($this->_belongs_to as $alias => $details) + { + if ( ! isset($details['model'])) + { + $defaults['model'] = str_replace(' ', '_', ucwords(str_replace('_', ' ', $alias))); + } + + $defaults['foreign_key'] = $alias.$this->_foreign_key_suffix; + + $init['_belongs_to'][$alias] = array_merge($defaults, $details); + } + + foreach ($this->_has_one as $alias => $details) + { + if ( ! isset($details['model'])) + { + $defaults['model'] = str_replace(' ', '_', ucwords(str_replace('_', ' ', $alias))); + } + + $defaults['foreign_key'] = $this->_object_name.$this->_foreign_key_suffix; + + $init['_has_one'][$alias] = array_merge($defaults, $details); + } + + foreach ($this->_has_many as $alias => $details) + { + if ( ! isset($details['model'])) + { + $defaults['model'] = str_replace(' ', '_', ucwords(str_replace('_', ' ', Inflector::singular($alias)))); + } + + $defaults['foreign_key'] = $this->_object_name.$this->_foreign_key_suffix; + $defaults['through'] = NULL; + + if ( ! isset($details['far_key'])) + { + $defaults['far_key'] = Inflector::singular($alias).$this->_foreign_key_suffix; + } + + $init['_has_many'][$alias] = array_merge($defaults, $details); + } + + ORM::$_init_cache[$this->_object_name] = $init; + } + + // Assign initialized properties to the current object + foreach ($init as $property => $value) { - $defaults['model'] = $alias; - $defaults['foreign_key'] = $alias.$this->_foreign_key_suffix; - - $this->_belongs_to[$alias] = array_merge($defaults, $details); + $this->{$property} = $value; } - - foreach ($this->_has_one as $alias => $details) - { - $defaults['model'] = $alias; - $defaults['foreign_key'] = $this->_object_name.$this->_foreign_key_suffix; - $defaults['far_key'] = Inflector::singular($alias).$this->_foreign_key_suffix; - - $this->_has_one[$alias] = array_merge($defaults, $details); - } - - foreach ($this->_has_many as $alias => $details) - { - $defaults['model'] = $this->_model_names_plural ? Inflector::singular($alias) : $alias; - $defaults['foreign_key'] = $this->_object_name.$this->_foreign_key_suffix; - $defaults['through'] = NULL; - $defaults['far_key'] = Inflector::singular($alias).$this->_foreign_key_suffix; - - $this->_has_many[$alias] = array_merge($defaults, $details); - } - + // Load column information $this->reload_columns(); @@ -408,7 +398,9 @@ class Kohana_ORM extends Model implements serializable { { // Build the validation object with its rules $this->_validation = Validation::factory($this->_object) - ->bind(':model', $this); + ->bind(':model', $this) + ->bind(':original_values', $this->_original_values) + ->bind(':changed', $this->_changed); foreach ($this->rules() as $field => $rules) { @@ -446,7 +438,7 @@ class Kohana_ORM extends Model implements serializable { else { // Grab column information from database - $this->_table_columns = $this->list_columns(TRUE); + $this->_table_columns = $this->list_columns(); // Load column cache ORM::$_column_cache[$this->_object_name] = $this->_table_columns; @@ -468,13 +460,16 @@ class Kohana_ORM extends Model implements serializable { $values = array_combine(array_keys($this->_table_columns), array_fill(0, count($this->_table_columns), NULL)); // Replace the object and reset the object status - $this->_object = $this->_changed = $this->_related = array(); + $this->_object = $this->_changed = $this->_related = $this->_original_values = array(); // Replace the current object with an empty one $this->_load_values($values); // Reset primary key $this->_primary_key_value = NULL; + + // Reset the loaded state + $this->_loaded = FALSE; $this->reset(); @@ -492,12 +487,12 @@ class Kohana_ORM extends Model implements serializable { $primary_key = $this->pk(); // Replace the object and reset the object status - $this->_object = $this->_changed = $this->_related = array(); + $this->_object = $this->_changed = $this->_related = $this->_original_values = array(); // Only reload the object if we have one to reload if ($this->_loaded) return $this->clear() - ->where($this->_table_name.'.'.$this->_primary_key, '=', $primary_key) + ->where($this->_object_name.'.'.$this->_primary_key, '=', $primary_key) ->find(); else return $this->clear(); @@ -543,12 +538,12 @@ class Kohana_ORM extends Model implements serializable { * Allows serialization of only the object data and state, to prevent * "stale" objects being unserialized, which also requires less memory. * - * @return array + * @return string */ public function serialize() { // Store only information about the object - foreach (array('_primary_key_value', '_object', '_changed', '_loaded', '_saved', '_sorting') as $var) + foreach (array('_primary_key_value', '_object', '_changed', '_loaded', '_saved', '_sorting', '_original_values') as $var) { $data[$var] = $this->{$var}; } @@ -556,6 +551,20 @@ class Kohana_ORM extends Model implements serializable { return serialize($data); } + /** + * Check whether the model data has been modified. + * If $field is specified, checks whether that field was modified. + * + * @param string $field field to check for changes + * @return bool Whether or not the field has changed + */ + public function changed($field = NULL) + { + return ($field === NULL) + ? $this->_changed + : Arr::get($this->_changed, $field); + } + /** * Prepares the database connection and reloads the object. * @@ -579,56 +588,33 @@ class Kohana_ORM extends Model implements serializable { } } - /** - * Handles pass-through to database methods. Calls to query methods - * (query, get, insert, update) are not allowed. Query builder methods - * are chainable. - * - * @param string $method Method name - * @param array $args Method arguments - * @return mixed - */ - public function __call($method, array $args) - { - if (in_array($method, ORM::$_properties)) - { - if ($method === 'validation') - { - if ( ! isset($this->_validation)) - { - // Initialize the validation object - $this->_validation(); - } - } - - // Return the property - return $this->{'_'.$method}; - } - elseif (in_array($method, ORM::$_db_methods)) - { - // Add pending database call which is executed after query type is determined - $this->_db_pending[] = array('name' => $method, 'args' => $args); - - return $this; - } - else - { - throw new Kohana_Exception('Invalid method :method called in :class', - array(':method' => $method, ':class' => get_class($this))); - } - } - /** * Handles retrieval of all model values, relationships, and metadata. + * [!!] This should not be overridden. * * @param string $column Column name * @return mixed */ public function __get($column) + { + return $this->get($column); + } + + /** + * Handles getting of column + * Override this method to add custom get behavior + * + * @param string $column Column name + * @throws Kohana_Exception + * @return mixed + */ + public function get($column) { if (array_key_exists($column, $this->_object)) { - return $this->_object[$column]; + return (in_array($column, $this->_serialize_columns)) + ? $this->_unserialize_value($this->_object[$column]) + : $this->_object[$column]; } elseif (isset($this->_related[$column])) { @@ -640,10 +626,16 @@ class Kohana_ORM extends Model implements serializable { $model = $this->_related($column); // Use this model's column and foreign model's primary key - $col = ($this->_disable_join_table_name ? '' : $model->_table_name.'.').$model->_primary_key; + $col = $model->_object_name.'.'.$model->_primary_key; $val = $this->_object[$this->_belongs_to[$column]['foreign_key']]; - $model->where($col, '=', $val)->find(); + // Make sure we don't run WHERE "AUTO_INCREMENT column" = NULL queries. This would + // return the last inserted record instead of an empty result. + // See: http://mysql.localhost.net.ar/doc/refman/5.1/en/server-session-variables.html#sysvar_sql_auto_is_null + if ($val !== NULL) + { + $model->where($col, '=', $val)->find(); + } return $this->_related[$column] = $model; } @@ -651,23 +643,9 @@ class Kohana_ORM extends Model implements serializable { { $model = $this->_related($column); - if (! is_array($this->_has_one[$column]['foreign_key'])) - { - // Use this model's primary key value and foreign model's column - $col = ($this->_disable_join_table_name ? '' : $model->_table_name.'.').$this->_has_one[$column]['foreign_key']; - $val = $this->_object[$this->_has_one[$column]['far_key']]; - } - else - { - foreach ($this->_has_one[$column]['foreign_key'] as $fk) - { - // Simple has_many relationship, search where target model's foreign key is this model's primary key - $col = ($this->_disable_join_table_name ? '' : $model->_table_name.'.').$fk; - $val = $this->_object[$fk]; - - $model = $model->where($col, '=', $val); - } - } + // Use this model's primary key value and foreign model's column + $col = $model->_object_name.'.'.$this->_has_one[$column]['foreign_key']; + $val = $this->pk(); $model->where($col, '=', $val)->find(); @@ -677,53 +655,29 @@ class Kohana_ORM extends Model implements serializable { { $model = ORM::factory($this->_has_many[$column]['model']); - if (! is_array($this->_has_many[$column]['foreign_key'])) + if (isset($this->_has_many[$column]['through'])) { - if (isset($this->_has_many[$column]['through'])) - { - // Grab has_many "through" relationship table - $through = $this->_has_many[$column]['through']; + // Grab has_many "through" relationship table + $through = $this->_has_many[$column]['through']; - // Join on through model's target foreign key (far_key) and target model's primary key - $join_col1 = ($this->_disable_join_table_name ? '' : $through.'.').$this->_has_many[$column]['far_key']; - $join_col2 = ($this->_disable_join_table_name ? '' : $model->_table_name.'.').$model->_primary_key; + // Join on through model's target foreign key (far_key) and target model's primary key + $join_col1 = $through.'.'.$this->_has_many[$column]['far_key']; + $join_col2 = $model->_object_name.'.'.$model->_primary_key; - $model->join($through)->on($join_col1, '=', $join_col2) - ->on(($this->_disable_join_table_name ? '' : $through.'.').'site_id', '=', ($this->_disable_join_table_name ? '' : $model->_table_name.'.').'site_id'); + $model->join($through)->on($join_col1, '=', $join_col2); - // Through table's source foreign key (foreign_key) should be this model's primary key - $col = ($this->_disable_join_table_name ? '' : $through.'.').$this->_has_many[$column]['foreign_key']; - $val = $this->pk(); - } - else - { - // Simple has_many relationship, search where target model's foreign key is this model's primary key - $col = ($this->_disable_join_table_name ? '' : $model->_table_name.'.').$this->_has_many[$column]['foreign_key']; - $val = $this->_object[$this->_has_many[$column]['far_key']]; - } - - return $model->where($col, '=', $val); + // Through table's source foreign key (foreign_key) should be this model's primary key + $col = $through.'.'.$this->_has_many[$column]['foreign_key']; + $val = $this->pk(); } else { - foreach ($this->_has_many[$column]['foreign_key'] as $mk => $fk) - { - if (isset($this->_has_many[$column]['through'])) - { - throw new Kohana_Exception('This code hasnt been written yet!'); - } - else - { - // Simple has_many relationship, search where target model's foreign key is this model's primary key - $col = ($this->_disable_join_table_name ? '' : $model->_table_name.'.').$fk; - $val = $this->_object[$mk]; - } - - $model = $model->where($col, '=', $val); - } - - return $model; + // Simple has_many relationship, search where target model's foreign key is this model's primary key + $col = $model->_object_name.'.'.$this->_has_many[$column]['foreign_key']; + $val = $this->pk(); } + + return $model->where($col, '=', $val); } else { @@ -733,35 +687,42 @@ class Kohana_ORM extends Model implements serializable { } /** - * Base set method - this should not be overridden. + * Base set method. + * [!!] This should not be overridden. * * @param string $column Column name * @param mixed $value Column value * @return void */ public function __set($column, $value) + { + $this->set($column, $value); + } + + /** + * Handles setting of columns + * Override this method to add custom set behavior + * + * @param string $column Column name + * @param mixed $value Column value + * @throws Kohana_Exception + * @return ORM + */ + public function set($column, $value) { if ( ! isset($this->_object_name)) { // Object not yet constructed, so we're loading data from a database call cast $this->_cast_data[$column] = $value; + + return $this; } - else + + if (in_array($column, $this->_serialize_columns)) { - // Set the model's column to given value - $this->set($column, $value); + $value = $this->_serialize_value($value); } - } - /** - * Handles setting of column - * - * @param string $column Column name - * @param mixed $value Column value - * @return void - */ - public function set($column, $value) - { if (array_key_exists($column, $this->_object)) { // Filter the data @@ -785,7 +746,9 @@ class Kohana_ORM extends Model implements serializable { $this->_related[$column] = $value; // Update the foreign key of this model - $this->_object[$this->_belongs_to[$column]['foreign_key']] = $value->pk(); + $this->_object[$this->_belongs_to[$column]['foreign_key']] = ($value instanceof ORM) + ? $value->pk() + : NULL; $this->_changed[$column] = $this->_belongs_to[$column]['foreign_key']; } @@ -872,7 +835,7 @@ class Kohana_ORM extends Model implements serializable { * can be nested using 'object1:object2' syntax * * @param string $target_path Target model to bind to - * @return void + * @return ORM */ public function with($target_path) { @@ -908,7 +871,7 @@ class Kohana_ORM extends Model implements serializable { if (empty($parent_path)) { // Use this table name itself for the parent path - $parent_path = $this->_table_name; + $parent_path = $this->_object_name; } else { @@ -966,9 +929,10 @@ class Kohana_ORM extends Model implements serializable { $this->_db_builder = DB::select(); break; case Database::UPDATE: - $this->_db_builder = DB::update($this->_table_name); + $this->_db_builder = DB::update(array($this->_table_name, $this->_object_name)); break; case Database::DELETE: + // Cannot use an alias for DELETE queries $this->_db_builder = DB::delete($this->_table_name); } @@ -990,6 +954,7 @@ class Kohana_ORM extends Model implements serializable { * Finds and loads a single database row into the object. * * @chainable + * @throws Kohana_Exception * @return ORM */ public function find() @@ -1014,6 +979,7 @@ class Kohana_ORM extends Model implements serializable { /** * Finds multiple database rows and returns an iterator of the rows found. * + * @throws Kohana_Exception * @return Database_Result */ public function find_all() @@ -1035,6 +1001,24 @@ class Kohana_ORM extends Model implements serializable { return $this->_load_result(TRUE); } + /** + * Returns an array of columns to include in the select query. This method + * can be overridden to change the default select behavior. + * + * @return array Columns to select + */ + protected function _build_select() + { + $columns = array(); + + foreach ($this->_table_columns as $column => $_) + { + $columns[] = array($this->_object_name.'.'.$column, $column); + } + + return $columns; + } + /** * Loads a database result, either as a new record for this model, or as * an iterator for multiple rows. @@ -1045,17 +1029,16 @@ class Kohana_ORM extends Model implements serializable { */ protected function _load_result($multiple = FALSE) { - $this->_db_builder->from($this->_table_name); + $this->_db_builder->from(array($this->_table_name, $this->_object_name)); - if ($multiple === FALSE AND ! $this->_disable_limit) + if ($multiple === FALSE) { // Only fetch 1 record $this->_db_builder->limit(1); } // Select all columns by default - if (! $this->_disable_wild_select) - $this->_db_builder->select($this->_table_name.'.*'); + $this->_db_builder->select_array($this->_build_select()); if ( ! isset($this->_db_applied['order_by']) AND ! empty($this->_sorting)) { @@ -1064,7 +1047,7 @@ class Kohana_ORM extends Model implements serializable { if (strpos($column, '.') === FALSE) { // Sorting column for use in JOINs - $column = ($this->_disable_join_table_name ? '' : $this->_table_name.'.').$column; + $column = $this->_object_name.'.'.$column; } $this->_db_builder->order_by($column, $direction); @@ -1115,16 +1098,16 @@ class Kohana_ORM extends Model implements serializable { { if ($values[$this->_primary_key] !== NULL) { - // Flag as loaded, saved, and valid - $this->_loaded = $this->_saved = $this->_valid = TRUE; + // Flag as loaded and valid + $this->_loaded = $this->_valid = TRUE; // Store primary key $this->_primary_key_value = $values[$this->_primary_key]; } else { - // Not loaded, saved, or valid - $this->_loaded = $this->_saved = $this->_valid = FALSE; + // Not loaded or valid + $this->_loaded = $this->_valid = FALSE; } } @@ -1156,6 +1139,12 @@ class Kohana_ORM extends Model implements serializable { } } + if ($this->_loaded) + { + // Store the object in its original state + $this->_original_values = $this->_object; + } + return $this; } @@ -1176,10 +1165,9 @@ class Kohana_ORM extends Model implements serializable { * @param string $value The value to filter * @return string */ - protected function run_filter($field, $value, $filters=NULL) + protected function run_filter($field, $value) { - if (is_null($filters)) - $filters = $this->filters(); + $filters = $this->filters(); // Get the filters for this column $wildcards = empty($filters[TRUE]) ? array() : $filters[TRUE]; @@ -1266,6 +1254,7 @@ class Kohana_ORM extends Model implements serializable { * Validates the current model's data * * @param Validation $extra_validation Validation object + * @throws ORM_Validation_Exception * @return ORM */ public function check(Validation $extra_validation = NULL) @@ -1280,7 +1269,7 @@ class Kohana_ORM extends Model implements serializable { if (($this->_valid = $array->check()) === FALSE OR $extra_errors) { - $exception = new ORM_Validation_Exception($this->_object_name, $array); + $exception = new ORM_Validation_Exception($this->errors_filename(), $array); if ($extra_errors) { @@ -1296,6 +1285,7 @@ class Kohana_ORM extends Model implements serializable { /** * Insert a new object to the database * @param Validation $validation Validation object + * @throws Kohana_Exception * @return ORM */ public function create(Validation $validation = NULL) @@ -1304,7 +1294,7 @@ class Kohana_ORM extends Model implements serializable { throw new Kohana_Exception('Cannot create :model model because it is already loaded.', array(':model' => $this->_object_name)); // Require model validation before saving - if ( ! $this->_valid) + if ( ! $this->_valid OR $validation) { $this->check($validation); } @@ -1335,12 +1325,17 @@ class Kohana_ORM extends Model implements serializable { // Load the insert id as the primary key if it was left out $this->_object[$this->_primary_key] = $this->_primary_key_value = $result[0]; } + else + { + $this->_primary_key_value = $this->_object[$this->_primary_key]; + } // Object is now loaded and saved $this->_loaded = $this->_saved = TRUE; // All changes have been saved $this->_changed = array(); + $this->_original_values = $this->_object; return $this; } @@ -1350,6 +1345,7 @@ class Kohana_ORM extends Model implements serializable { * * @chainable * @param Validation $validation Validation object + * @throws Kohana_Exception * @return ORM */ public function update(Validation $validation = NULL) @@ -1357,18 +1353,18 @@ class Kohana_ORM extends Model implements serializable { if ( ! $this->_loaded) throw new Kohana_Exception('Cannot update :model model because it is not loaded.', array(':model' => $this->_object_name)); + // Run validation if the model isn't valid or we have additional validation rules. + if ( ! $this->_valid OR $validation) + { + $this->check($validation); + } + if (empty($this->_changed)) { // Nothing to update return $this; } - // Require model validation before saving - if ( ! $this->_valid) - { - $this->check($validation); - } - $data = array(); foreach ($this->_changed as $column) { @@ -1405,6 +1401,7 @@ class Kohana_ORM extends Model implements serializable { // All changes have been saved $this->_changed = array(); + $this->_original_values = $this->_object; return $this; } @@ -1422,9 +1419,10 @@ class Kohana_ORM extends Model implements serializable { } /** - * Deletes a single record or multiple records, ignoring relationships. + * Deletes a single record while ignoring relationships. * * @chainable + * @throws Kohana_Exception * @return ORM */ public function delete() @@ -1445,7 +1443,9 @@ class Kohana_ORM extends Model implements serializable { /** * Tests if this object has a relationship to a different model, - * or an array of different models. + * or an array of different models. When providing far keys, the number + * of relations must equal the number of keys. + * * * // Check if $model has the login role * $model->has('roles', ORM::factory('role', array('name' => 'login'))); @@ -1453,13 +1453,77 @@ class Kohana_ORM extends Model implements serializable { * $model->has('roles', 5); * // Check for all of the following roles * $model->has('roles', array(1, 2, 3, 4)); - + * // Check if $model has any roles + * $model->has('roles') + * * @param string $alias Alias of the has_many "through" relationship * @param mixed $far_keys Related model, primary key, or an array of primary keys - * @return Database_Result + * @return boolean */ - public function has($alias, $far_keys) + public function has($alias, $far_keys = NULL) { + $count = $this->count_relations($alias, $far_keys); + if ($far_keys === NULL) + { + return (bool) $count; + } + else + { + return $count === count($far_keys); + } + + } + + /** + * Tests if this object has a relationship to a different model, + * or an array of different models. When providing far keys, this function + * only checks that at least one of the relationships is satisfied. + * + * // Check if $model has the login role + * $model->has('roles', ORM::factory('role', array('name' => 'login'))); + * // Check for the login role if you know the roles.id is 5 + * $model->has('roles', 5); + * // Check for any of the following roles + * $model->has('roles', array(1, 2, 3, 4)); + * // Check if $model has any roles + * $model->has('roles') + * + * @param string $alias Alias of the has_many "through" relationship + * @param mixed $far_keys Related model, primary key, or an array of primary keys + * @return boolean + */ + public function has_any($alias, $far_keys = NULL) + { + return (bool) $this->count_relations($alias, $far_keys); + } + + /** + * Returns the number of relationships + * + * // Counts the number of times the login role is attached to $model + * $model->has('roles', ORM::factory('role', array('name' => 'login'))); + * // Counts the number of times role 5 is attached to $model + * $model->has('roles', 5); + * // Counts the number of times any of roles 1, 2, 3, or 4 are attached to + * // $model + * $model->has('roles', array(1, 2, 3, 4)); + * // Counts the number roles attached to $model + * $model->has('roles') + * + * @param string $alias Alias of the has_many "through" relationship + * @param mixed $far_keys Related model, primary key, or an array of primary keys + * @return integer + */ + public function count_relations($alias, $far_keys = NULL) + { + if ($far_keys === NULL) + { + return (int) DB::select(array(DB::expr('COUNT(*)'), 'records_found')) + ->from($this->_has_many[$alias]['through']) + ->where($this->_has_many[$alias]['foreign_key'], '=', $this->pk()) + ->execute($this->_db)->get('records_found'); + } + $far_keys = ($far_keys instanceof ORM) ? $far_keys->pk() : $far_keys; // We need an array to simplify the logic @@ -1467,16 +1531,16 @@ class Kohana_ORM extends Model implements serializable { // Nothing to check if the model isn't loaded or we don't have any far_keys if ( ! $far_keys OR ! $this->_loaded) - return FALSE; + return 0; - $count = (int) DB::select(array('COUNT("*")', 'records_found')) + $count = (int) DB::select(array(DB::expr('COUNT(*)'), 'records_found')) ->from($this->_has_many[$alias]['through']) ->where($this->_has_many[$alias]['foreign_key'], '=', $this->pk()) ->where($this->_has_many[$alias]['far_key'], 'IN', $far_keys) ->execute($this->_db)->get('records_found'); // Rows found need to match the rows searched - return $count === count($far_keys); + return (int) $count; } /** @@ -1576,8 +1640,8 @@ class Kohana_ORM extends Model implements serializable { $this->_build(Database::SELECT); - $records = $this->_db_builder->from($this->_table_name) - ->select(array('COUNT("*")', 'records_found')) + $records = $this->_db_builder->from(array($this->_table_name, $this->_object_name)) + ->select(array(DB::expr('COUNT(*)'), 'records_found')) ->execute($this->_db) ->get('records_found'); @@ -1601,23 +1665,6 @@ class Kohana_ORM extends Model implements serializable { return $this->_db->list_columns($this->_table_name); } - /** - * Proxy method to Database field_data. - * - * @chainable - * @param string $sql SQL query to clear - * @return ORM - */ - public function clear_cache($sql = NULL) - { - // Proxy to database - $this->_db->clear_cache($sql); - - ORM::$_column_cache = array(); - - return $this; - } - /** * Returns an ORM model for the given one-one related alias * @@ -1686,4 +1733,622 @@ class Kohana_ORM extends Model implements serializable { return $this; } + + protected function _serialize_value($value) + { + return json_encode($value); + } + + protected function _unserialize_value($value) + { + return json_decode($value, TRUE); + } + + public function object_name() + { + return $this->_object_name; + } + + public function object_plural() + { + return $this->_object_plural; + } + + public function loaded() + { + return $this->_loaded; + } + + public function saved() + { + return $this->_saved; + } + + public function primary_key() + { + return $this->_primary_key; + } + + public function table_name() + { + return $this->_table_name; + } + + public function table_columns() + { + return $this->_table_columns; + } + + public function has_one() + { + return $this->_has_one; + } + + public function belongs_to() + { + return $this->_belongs_to; + } + + public function has_many() + { + return $this->_has_many; + } + + public function load_with() + { + return $this->_load_with; + } + + public function original_values() + { + return $this->_original_values; + } + + public function created_column() + { + return $this->_created_column; + } + + public function updated_column() + { + return $this->_updated_column; + } + + public function validation() + { + if ( ! isset($this->_validation)) + { + // Initialize the validation object + $this->_validation(); + } + + return $this->_validation; + } + + public function object() + { + return $this->_object; + } + + public function errors_filename() + { + return $this->_errors_filename; + } + + /** + * Alias of and_where() + * + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value + * @return $this + */ + public function where($column, $op, $value) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'where', + 'args' => array($column, $op, $value), + ); + + return $this; + } + + /** + * Creates a new "AND WHERE" condition for the query. + * + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value + * @return $this + */ + public function and_where($column, $op, $value) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'and_where', + 'args' => array($column, $op, $value), + ); + + return $this; + } + + /** + * Creates a new "OR WHERE" condition for the query. + * + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value + * @return $this + */ + public function or_where($column, $op, $value) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'or_where', + 'args' => array($column, $op, $value), + ); + + return $this; + } + + /** + * Alias of and_where_open() + * + * @return $this + */ + public function where_open() + { + return $this->and_where_open(); + } + + /** + * Opens a new "AND WHERE (...)" grouping. + * + * @return $this + */ + public function and_where_open() + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'and_where_open', + 'args' => array(), + ); + + return $this; + } + + /** + * Opens a new "OR WHERE (...)" grouping. + * + * @return $this + */ + public function or_where_open() + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'or_where_open', + 'args' => array(), + ); + + return $this; + } + + /** + * Closes an open "AND WHERE (...)" grouping. + * + * @return $this + */ + public function where_close() + { + return $this->and_where_close(); + } + + /** + * Closes an open "AND WHERE (...)" grouping. + * + * @return $this + */ + public function and_where_close() + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'and_where_close', + 'args' => array(), + ); + + return $this; + } + + /** + * Closes an open "OR WHERE (...)" grouping. + * + * @return $this + */ + public function or_where_close() + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'or_where_close', + 'args' => array(), + ); + + return $this; + } + + /** + * Applies sorting with "ORDER BY ..." + * + * @param mixed $column column name or array($column, $alias) or object + * @param string $direction direction of sorting + * @return $this + */ + public function order_by($column, $direction = NULL) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'order_by', + 'args' => array($column, $direction), + ); + + return $this; + } + + /** + * Return up to "LIMIT ..." results + * + * @param integer $number maximum results to return + * @return $this + */ + public function limit($number) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'limit', + 'args' => array($number), + ); + + return $this; + } + + /** + * Enables or disables selecting only unique columns using "SELECT DISTINCT" + * + * @param boolean $value enable or disable distinct columns + * @return $this + */ + public function distinct($value) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'distinct', + 'args' => array($value), + ); + + return $this; + } + + /** + * Choose the columns to select from. + * + * @param mixed $columns column name or array($column, $alias) or object + * @param ... + * @return $this + */ + public function select($columns = NULL) + { + $columns = func_get_args(); + + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'select', + 'args' => $columns, + ); + + return $this; + } + + /** + * Choose the tables to select "FROM ..." + * + * @param mixed $tables table name or array($table, $alias) or object + * @param ... + * @return $this + */ + public function from($tables) + { + $tables = func_get_args(); + + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'from', + 'args' => $tables, + ); + + return $this; + } + + /** + * Adds addition tables to "JOIN ...". + * + * @param mixed $table column name or array($column, $alias) or object + * @param string $type join type (LEFT, RIGHT, INNER, etc) + * @return $this + */ + public function join($table, $type = NULL) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'join', + 'args' => array($table, $type), + ); + + return $this; + } + + /** + * Adds "ON ..." conditions for the last created JOIN statement. + * + * @param mixed $c1 column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $c2 column name or array($column, $alias) or object + * @return $this + */ + public function on($c1, $op, $c2) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'on', + 'args' => array($c1, $op, $c2), + ); + + return $this; + } + + /** + * Creates a "GROUP BY ..." filter. + * + * @param mixed $columns column name or array($column, $alias) or object + * @param ... + * @return $this + */ + public function group_by($columns) + { + $columns = func_get_args(); + + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'group_by', + 'args' => $columns, + ); + + return $this; + } + + /** + * Alias of and_having() + * + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value + * @return $this + */ + public function having($column, $op, $value = NULL) + { + return $this->and_having($column, $op, $value); + } + + /** + * Creates a new "AND HAVING" condition for the query. + * + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value + * @return $this + */ + public function and_having($column, $op, $value = NULL) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'and_having', + 'args' => array($column, $op, $value), + ); + + return $this; + } + + /** + * Creates a new "OR HAVING" condition for the query. + * + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value + * @return $this + */ + public function or_having($column, $op, $value = NULL) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'or_having', + 'args' => array($column, $op, $value), + ); + + return $this; + } + + /** + * Alias of and_having_open() + * + * @return $this + */ + public function having_open() + { + return $this->and_having_open(); + } + + /** + * Opens a new "AND HAVING (...)" grouping. + * + * @return $this + */ + public function and_having_open() + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'and_having_open', + 'args' => array(), + ); + + return $this; + } + + /** + * Opens a new "OR HAVING (...)" grouping. + * + * @return $this + */ + public function or_having_open() + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'or_having_open', + 'args' => array(), + ); + + return $this; + } + + /** + * Closes an open "AND HAVING (...)" grouping. + * + * @return $this + */ + public function having_close() + { + return $this->and_having_close(); + } + + /** + * Closes an open "AND HAVING (...)" grouping. + * + * @return $this + */ + public function and_having_close() + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'and_having_close', + 'args' => array(), + ); + + return $this; + } + + /** + * Closes an open "OR HAVING (...)" grouping. + * + * @return $this + */ + public function or_having_close() + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'or_having_close', + 'args' => array(), + ); + + return $this; + } + + /** + * Start returning results after "OFFSET ..." + * + * @param integer $number starting result number + * @return $this + */ + public function offset($number) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'offset', + 'args' => array($number), + ); + + return $this; + } + + /** + * Enables the query to be cached for a specified amount of time. + * + * @param integer $lifetime number of seconds to cache + * @return $this + * @uses Kohana::$cache_life + */ + public function cached($lifetime = NULL) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'cached', + 'args' => array($lifetime), + ); + + return $this; + } + + /** + * Set the value of a parameter in the query. + * + * @param string $param parameter key to replace + * @param mixed $value value to use + * @return $this + */ + public function param($param, $value) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'param', + 'args' => array($param, $value), + ); + + return $this; + } + + /** + * Adds "USING ..." conditions for the last created JOIN statement. + * + * @param string $columns column name + * @return $this + */ + public function using($columns) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array( + 'name' => 'using', + 'args' => array($columns), + ); + + return $this; + } + + /** + * Checks whether a column value is unique. + * Excludes itself if loaded. + * + * @param string $field the field to check for uniqueness + * @param mixed $value the value to check for uniqueness + * @return bool whteher the value is unique + */ + public function unique($field, $value) + { + $model = ORM::factory($this->object_name()) + ->where($field, '=', $value) + ->find(); + + if ($this->loaded()) + { + return ( ! ($model->loaded() AND $model->pk() != $this->pk())); + } + + return ( ! $model->loaded()); + } } // End ORM diff --git a/includes/kohana/modules/orm/classes/kohana/orm/validation/exception.php b/includes/kohana/modules/orm/classes/Kohana/ORM/Validation/Exception.php similarity index 66% rename from includes/kohana/modules/orm/classes/kohana/orm/validation/exception.php rename to includes/kohana/modules/orm/classes/Kohana/ORM/Validation/Exception.php index f4c303b4..d88fcd1f 100644 --- a/includes/kohana/modules/orm/classes/kohana/orm/validation/exception.php +++ b/includes/kohana/modules/orm/classes/Kohana/ORM/Validation/Exception.php @@ -1,10 +1,10 @@ -_object_name = $object_name; + $this->_alias = $alias; $this->_objects['_object'] = $object; + $this->_objects['_has_many'] = FALSE; - parent::__construct($message, $values, $code); + parent::__construct($message, $values, $code, $previous); } /** @@ -62,6 +63,9 @@ class Kohana_ORM_Validation_Exception extends Kohana_Exception { */ public function add_object($alias, Validation $object, $has_many = FALSE) { + // We will need this when generating errors + $this->_objects[$alias]['_has_many'] = ($has_many !== FALSE); + if ($has_many === TRUE) { // This is most likely a has_many relationship @@ -84,13 +88,17 @@ class Kohana_ORM_Validation_Exception extends Kohana_Exception { * Merges an ORM_Validation_Exception object into the current exception * Useful when you want to combine errors into one array * - * @param string $alias The relationship alias from the model * @param ORM_Validation_Exception $object The exception to merge * @param mixed $has_many The array key to use if this exception can be merged multiple times * @return ORM_Validation_Exception */ - public function merge($alias, ORM_Validation_Exception $object, $has_many = FALSE) + public function merge(ORM_Validation_Exception $object, $has_many = FALSE) { + $alias = $object->alias(); + + // We will need this when generating errors + $this->_objects[$alias]['_has_many'] = ($has_many !== FALSE); + if ($has_many === TRUE) { // This is most likely a has_many relationship @@ -122,48 +130,46 @@ class Kohana_ORM_Validation_Exception extends Kohana_Exception { */ public function errors($directory = NULL, $translate = TRUE) { - if ($directory !== NULL) - { - // Everything starts at $directory/$object_name - $directory .= '/'.$this->_object_name; - } - - return $this->generate_errors($this->_objects, $directory, $translate); + return $this->generate_errors($this->_alias, $this->_objects, $directory, $translate); } /** * Recursive method to fetch all the errors in this exception * + * @param string $alias Alias to use for messages file * @param array $array Array of Validation objects to get errors from * @param string $directory Directory to load error messages from * @param mixed $translate Translate the message * @return array */ - protected function generate_errors(array $array, $directory, $translate) + protected function generate_errors($alias, array $array, $directory, $translate) { $errors = array(); - foreach ($array as $alias => $object) + foreach ($array as $key => $object) { - if ($directory === NULL) - { - // Return the raw errors - $file = NULL; - } - else - { - $file = trim($directory.'/'.$alias, '/'); - } - if (is_array($object)) { - // Recursively fill the errors array - $errors[$alias] = $this->generate_errors($object, $file, $translate); + $errors[$key] = ($key === '_external') + // Search for errors in $alias/_external.php + ? $this->generate_errors($alias.'/'.$key, $object, $directory, $translate) + // Regular models get their own file not nested within $alias + : $this->generate_errors($key, $object, $directory, $translate); } - else + elseif ($object instanceof Validation) { + if ($directory === NULL) + { + // Return the raw errors + $file = NULL; + } + else + { + $file = trim($directory.'/'.$alias, '/'); + } + // Merge in this array of errors - $errors += $object->errors($directory, $translate); + $errors += $object->errors($file, $translate); } } @@ -179,4 +185,14 @@ class Kohana_ORM_Validation_Exception extends Kohana_Exception { { return $this->_objects; } + + /** + * Returns the protected _alias property from this exception + * + * @return string + */ + public function alias() + { + return $this->_alias; + } } // End Kohana_ORM_Validation_Exception diff --git a/includes/kohana/modules/orm/classes/model/auth/role.php b/includes/kohana/modules/orm/classes/Model/Auth/Role.php similarity index 72% rename from includes/kohana/modules/orm/classes/model/auth/role.php rename to includes/kohana/modules/orm/classes/Model/Auth/Role.php index 0ad4c662..5a12764f 100644 --- a/includes/kohana/modules/orm/classes/model/auth/role.php +++ b/includes/kohana/modules/orm/classes/Model/Auth/Role.php @@ -1,4 +1,4 @@ - array('through' => 'roles_users')); + protected $_has_many = array( + 'users' => array('model' => 'User','through' => 'roles_users'), + ); public function rules() { @@ -26,4 +28,4 @@ class Model_Auth_Role extends ORM { ); } -} // End Auth Role Model \ No newline at end of file +} // End Auth Role Model diff --git a/includes/kohana/modules/orm/classes/model/auth/user.php b/includes/kohana/modules/orm/classes/Model/Auth/User.php similarity index 72% rename from includes/kohana/modules/orm/classes/model/auth/user.php rename to includes/kohana/modules/orm/classes/Model/Auth/User.php index aeb1563b..d86fae41 100644 --- a/includes/kohana/modules/orm/classes/model/auth/user.php +++ b/includes/kohana/modules/orm/classes/Model/Auth/User.php @@ -1,10 +1,10 @@ - array('model' => 'user_token'), - 'roles' => array('model' => 'role', 'through' => 'roles_users'), + 'user_tokens' => array('model' => 'User_Token'), + 'roles' => array('model' => 'Role', 'through' => 'roles_users'), ); /** @@ -32,20 +32,16 @@ class Model_Auth_User extends ORM { return array( 'username' => array( array('not_empty'), - array('min_length', array(':value', 4)), array('max_length', array(':value', 32)), - array('regex', array(':value', '/^[-\pL\pN_.]++$/uD')), - array(array($this, 'username_available'), array(':validation', ':field')), + array(array($this, 'unique'), array('username', ':value')), ), 'password' => array( array('not_empty'), ), 'email' => array( array('not_empty'), - array('min_length', array(':value', 4)), - array('max_length', array(':value', 127)), array('email'), - array(array($this, 'email_available'), array(':validation', ':field')), + array(array($this, 'unique'), array('email', ':value')), ), ); } @@ -99,38 +95,6 @@ class Model_Auth_User extends ORM { } } - /** - * Does the reverse of unique_key_exists() by triggering error if username exists. - * Validation callback. - * - * @param Validation Validation object - * @param string Field name - * @return void - */ - public function username_available(Validation $validation, $field) - { - if ($this->unique_key_exists($validation[$field], 'username')) - { - $validation->error($field, 'username_available', array($validation[$field])); - } - } - - /** - * Does the reverse of unique_key_exists() by triggering error if email exists. - * Validation callback. - * - * @param Validation Validation object - * @param string Field name - * @return void - */ - public function email_available(Validation $validation, $field) - { - if ($this->unique_key_exists($validation[$field], 'email')) - { - $validation->error($field, 'email_available', array($validation[$field])); - } - } - /** * Tests if a unique key value exists in the database. * @@ -146,7 +110,7 @@ class Model_Auth_User extends ORM { $field = $this->unique_key($value); } - return (bool) DB::select(array('COUNT("*")', 'total_count')) + return (bool) DB::select(array(DB::expr('COUNT(*)'), 'total_count')) ->from($this->_table_name) ->where($field, '=', $value) ->where($this->_primary_key, '!=', $this->pk()) @@ -183,7 +147,7 @@ class Model_Auth_User extends ORM { * * Example usage: * ~~~ - * $user = ORM::factory('user')->create_user($_POST, array( + * $user = ORM::factory('User')->create_user($_POST, array( * 'username', * 'password', * 'email', @@ -210,7 +174,7 @@ class Model_Auth_User extends ORM { * * Example usage: * ~~~ - * $user = ORM::factory('user') + * $user = ORM::factory('User') * ->where('username', '=', 'kiall') * ->find() * ->update_user($_POST, array( @@ -237,4 +201,4 @@ class Model_Auth_User extends ORM { return $this->values($values, $expected)->update($extra_validation); } -} // End Auth User Model \ No newline at end of file +} // End Auth User Model diff --git a/includes/kohana/modules/orm/classes/model/auth/user/token.php b/includes/kohana/modules/orm/classes/Model/Auth/User/Token.php similarity index 74% rename from includes/kohana/modules/orm/classes/model/auth/user/token.php rename to includes/kohana/modules/orm/classes/Model/Auth/User/Token.php index ede1ee8f..3c69ead7 100644 --- a/includes/kohana/modules/orm/classes/model/auth/user/token.php +++ b/includes/kohana/modules/orm/classes/Model/Auth/User/Token.php @@ -1,16 +1,23 @@ - array()); + protected $_belongs_to = array( + 'user' => array('model' => 'User'), + ); + + protected $_created_column = array( + 'column' => 'created', + 'format' => TRUE, + ); /** * Handles garbage collection and deleting of expired objects. @@ -62,9 +69,9 @@ class Model_Auth_User_Token extends ORM { { $token = sha1(uniqid(Text::random('alnum', 32), TRUE)); } - while(ORM::factory('user_token', array('token' => $token))->loaded()); + while (ORM::factory('User_Token', array('token' => $token))->loaded()); return $token; } -} // End Auth User Token Model \ No newline at end of file +} // End Auth User Token Model diff --git a/includes/kohana/modules/orm/classes/model/role.php b/includes/kohana/modules/orm/classes/Model/Role.php similarity index 63% rename from includes/kohana/modules/orm/classes/model/role.php rename to includes/kohana/modules/orm/classes/Model/Role.php index 983fa96e..b1ead06d 100644 --- a/includes/kohana/modules/orm/classes/model/role.php +++ b/includes/kohana/modules/orm/classes/Model/Role.php @@ -1,4 +1,4 @@ - 'Official ORM module, a modeling library for object relational mapping.', // Copyright message, shown in the footer for this module - 'copyright' => '© 2008–2011 Kohana Team', + 'copyright' => '© 2008–2012 Kohana Team', ) ) ); diff --git a/includes/kohana/modules/orm/guide/orm/examples/simple.md b/includes/kohana/modules/orm/guide/orm/examples/simple.md index 96a23d15..b8abe928 100644 --- a/includes/kohana/modules/orm/guide/orm/examples/simple.md +++ b/includes/kohana/modules/orm/guide/orm/examples/simple.md @@ -67,7 +67,7 @@ This is a simple example of a single ORM model, that has no relationships, but u */ // Create an instance of a model - $members = ORM::factory('member'); + $members = ORM::factory('Member'); // Get all members with the first name "Peter" find_all() // means we get all records matching the query. @@ -81,7 +81,7 @@ This is a simple example of a single ORM model, that has no relationships, but u */ // Create an instance of a model - $member = ORM::factory('member'); + $member = ORM::factory('Member'); // Get a member with the user name "bongo" find() means // we only want the first record matching the query. @@ -92,7 +92,7 @@ This is a simple example of a single ORM model, that has no relationships, but u */ // Create an instance of a model - $member = ORM::factory('member'); + $member = ORM::factory('Member'); // Do an INSERT query $member->username = 'bongo'; @@ -106,7 +106,7 @@ This is a simple example of a single ORM model, that has no relationships, but u // Create an instance of a model where the // table field "id" is "1" - $member = ORM::factory('member', 1); + $member = ORM::factory('Member', 1); // Do an UPDATE query $member->username = 'bongo'; diff --git a/includes/kohana/modules/orm/guide/orm/examples/validation.md b/includes/kohana/modules/orm/guide/orm/examples/validation.md index bdbbc0cf..0510a453 100644 --- a/includes/kohana/modules/orm/guide/orm/examples/validation.md +++ b/includes/kohana/modules/orm/guide/orm/examples/validation.md @@ -44,7 +44,7 @@ This example will create user accounts and demonstrate how to handle model and c public function username_available($username) { // There are simpler ways to do this, but I will use ORM for the sake of the example - return ORM::factory('member', array('username' => $username))->loaded(); + return ORM::factory('Member', array('username' => $username))->loaded(); } public function hash_password($password) @@ -85,7 +85,7 @@ Please forgive my slightly ugly form. I am trying not to use any modules or unre if ($_POST) { - $member = ORM::factory('member') + $member = ORM::factory('Member') // The ORM::values() method is a shortcut to assign many values at once ->values($_POST, array('username', 'password')); diff --git a/includes/kohana/modules/orm/guide/orm/filters.md b/includes/kohana/modules/orm/guide/orm/filters.md index 5f20e60f..218f0a47 100644 --- a/includes/kohana/modules/orm/guide/orm/filters.md +++ b/includes/kohana/modules/orm/guide/orm/filters.md @@ -1,21 +1,40 @@ # Filters -Filters in ORM work much like they used to when they were part of the Validate class in 3.0.x however they have been modified to match the flexible syntax of [Validation] rules in 3.1.x. Filters run as soon as the field is set in your model and should be used to format the data before it is inserted into the Database. +Filters in ORM work much like they used to when they were part of the Validate class in 3.0.x. However, they have been modified to match the flexible syntax of [Validation] rules in 3.1.x. -Define your filters the same way you define rules, as an array returned by the `ORM::filters()` method like the following: +Filters run as soon as the field is set in your model and should be used to format the data before it is inserted into the Database. Filters are defined the same way you define [rules](validation), as an array returned by the `ORM::filters()` method, like the following: public function filters() { return array( + // Field Filters + // $field_name => array(mixed $callback[, array $params = array(':value')]), 'username' => array( + // PHP Function Callback, default implicit param of ':value' array('trim'), ), 'password' => array( - array(array($this, 'hash_password')), + // Callback method with object context and params + array(array($this, 'hash_password'), array(':value', Model_User::salt())), ), 'created_on' => array( + // Callback static method with params array('Format::date', array(':value', 'Y-m-d H:i:s')), ), + 'other_field' => array( + // Callback static method with implicit param of ':value' + array('MyClass::static_method'), + // Callback method with object context with implicit param of ':value' + array(array($this, 'change_other_field')), + // PHP function callback with explicit params + array('str_replace', array('luango', 'thomas', ':value'), + // Function as the callback (PHP 5.3+) + array(function($value) { + // Do something to $value and return it. + return some_function($value); + }), + ), + ); } diff --git a/includes/kohana/modules/orm/guide/orm/menu.md b/includes/kohana/modules/orm/guide/orm/menu.md index 03a43c19..ac2438c9 100644 --- a/includes/kohana/modules/orm/guide/orm/menu.md +++ b/includes/kohana/modules/orm/guide/orm/menu.md @@ -7,3 +7,4 @@ - [Examples](examples) - [Simple](examples/simple) - [Validation](examples/validation) +- [Upgrading](upgrading) diff --git a/includes/kohana/modules/orm/guide/orm/relationships.md b/includes/kohana/modules/orm/guide/orm/relationships.md index 074c1291..fac78452 100644 --- a/includes/kohana/modules/orm/guide/orm/relationships.md +++ b/includes/kohana/modules/orm/guide/orm/relationships.md @@ -37,7 +37,7 @@ If you wanted access a post's author by using code like `$post->author` then you protected $_belongs_to = array( 'author' => array( - 'model' => 'user', + 'model' => 'User', ), ); @@ -68,7 +68,7 @@ Let's assume now you want to access the posts using the name `stories` instead, protected $_has_many = array( 'stories' => array( - 'model' => 'post', + 'model' => 'Post', 'foreign_key' => 'author_id', ), ); @@ -79,7 +79,7 @@ A `has_one` relationship is almost identical to a `has_many` relationship. In a protected $_has_one = array( 'story' => array( - 'model' => 'post', + 'model' => 'Post', 'foreign_key' => 'author_id', ), ); @@ -92,7 +92,7 @@ To define the `has_many` "through" relationship, the same syntax for standard ha protected $_has_many = array( 'categories' => array( - 'model' => 'category', + 'model' => 'Category', 'through' => 'categories_posts', ), ); @@ -101,7 +101,7 @@ In the Category model: protected $_has_many = array( 'posts' => array( - 'model' => 'post', + 'model' => 'Post', 'through' => 'categories_posts', ), ); diff --git a/includes/kohana/modules/orm/guide/orm/upgrading.md b/includes/kohana/modules/orm/guide/orm/upgrading.md new file mode 100644 index 00000000..c51f0af1 --- /dev/null +++ b/includes/kohana/modules/orm/guide/orm/upgrading.md @@ -0,0 +1,16 @@ +# Upgrading + +## Table aliases + +ORM [will now alias the main table](http://dev.kohanaframework.org/issues/4066) in a query to the model's singular object name. +i.e. Prior to 3.2 ORM set the from table like so: + + $this->_db_builder->from($this->_table_name); + +As of 3.2 it is now aliased like so: + + $this->_db_builder->from(array($this->_table_name, $this->_object_name)); + +If you have a model `Model_Order` then when building a query use the alias like so: + + $model->where('order.id', '=', $id); diff --git a/includes/kohana/modules/orm/guide/orm/using.md b/includes/kohana/modules/orm/guide/orm/using.md index 8d54d655..9ef03cb1 100644 --- a/includes/kohana/modules/orm/guide/orm/using.md +++ b/includes/kohana/modules/orm/guide/orm/using.md @@ -4,7 +4,7 @@ To create a new `Model_User` instance, you can do one of two things: - $user = ORM::factory('user'); + $user = ORM::factory('User'); // Or $user = new Model_User(); @@ -12,7 +12,7 @@ To create a new `Model_User` instance, you can do one of two things: To insert a new record into the database, create a new instance of the model: - $user = ORM::factory('user'); + $user = ORM::factory('User'); Then, assign values for each of the properties; @@ -33,11 +33,11 @@ Insert the new record into the database by running [ORM::save]: To find an object you can call the [ORM::find] method or pass the id into the ORM constructor: // Find user with ID 20 - $user = ORM::factory('user') + $user = ORM::factory('User') ->where('id', '=', 20) ->find(); // Or - $user = ORM::factory('user', 20); + $user = ORM::factory('User', 20); ## Check that ORM loaded a record @@ -70,6 +70,25 @@ And if you want to save the changes you just made back to the database, just run To delete an object, you can call the [ORM::delete] method on a loaded ORM model. - $user = ORM::factory('user', 20); + $user = ORM::factory('User', 20); $user->delete(); + +## Mass assignment + + +To set multiple values at once, use [ORM::values] + + try + { + $user = ORM::factory('user') + ->values($this->request->post(), array('username','password')) + ->create(); + } + catch (ORM_Validation_Exception $e) + { + // Handle validation errors ... + } + +[!!] Although the second argument is optional, it is *highly recommended* to specify the list of columns you expect to change. Not doing so will leave your code _vulnerable_ in case the attacker adds fields you didn't expect. + diff --git a/includes/kohana/modules/orm/guide/orm/validation.md b/includes/kohana/modules/orm/guide/orm/validation.md index d83414fb..56a46ca3 100644 --- a/includes/kohana/modules/orm/guide/orm/validation.md +++ b/includes/kohana/modules/orm/guide/orm/validation.md @@ -41,7 +41,7 @@ All models automatically validate their own data when `ORM::save()`, `ORM::updat { try { - $user = ORM::factory('user'); + $user = ORM::factory('User'); $user->username = 'invalid username'; $user->save(); } @@ -61,7 +61,7 @@ In the below example, the error messages will be defined in `application/message { try { - $user = ORM::factory('user'); + $user = ORM::factory('User'); $user->username = 'invalid username'; $user->save(); } @@ -79,7 +79,7 @@ Certain forms contain information that should not be validated by the model, but { try { - $user = ORM::factory('user'); + $user = ORM::factory('User'); $user->username = $_POST['username']; $user->password = $_POST['password']; @@ -108,4 +108,4 @@ Because the validation object was passed as a parameter to the model, any errors This ensures that errors from multiple validation objects and models will never overwrite each other. -[!!] The power of the [ORM_Validation_Exception] can be leveraged in many different ways to merge errors from related models. Take a look at the list of [Examples](examples) for some great use cases. +[!!] The power of the [ORM_Validation_Exception] can be leveraged in many different ways to merge errors from related models. Take a look at the list of [Examples](examples) for some great use cases. \ No newline at end of file diff --git a/includes/kohana/modules/unittest/README.markdown b/includes/kohana/modules/unittest/README.markdown index 45e45c91..af4251a6 100644 --- a/includes/kohana/modules/unittest/README.markdown +++ b/includes/kohana/modules/unittest/README.markdown @@ -9,28 +9,16 @@ I've chosen to do this because it's part of the PHPUnit coding conventions and i * [PHPUnit](http://www.phpunit.de/) >= 3.4 -### Optional extras +## Usage -* The [Archive module](http://github.com/BRMatt/kohana-archive) is required if you want to download code coverage reports from the web ui, however you can also view them without downloading. + $ phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php -## Installation +Alternatively you can use a `phpunit.xml` to have a more fine grained control +over which tests are included and which files are whitelisted. -**Step 0**: Download this module! +Make sure you only whitelist the highest files in the cascading filesystem, else +you could end up with a lot of "class cannot be redefined" errors. -To get it from git execute the following command in the root of your project: - - $ git submodule add git://github.com/kohana/unittest.git modules/unittest - -And watch the gitorious magic... - -Of course, you can always download the code from the [github project](http://github.com/kohana/unittest) as an archive. - -## Running the tests - - $ phpunit --bootstrap=modules/unittest/bootstrap.php {tests} - -Where `{tests}` can either be a path to a folder of tests, or a path to the the `tests.php` (`modules/unittest/tests.php`) - -Please see the guide pages for more info. An example of how we run the tests for the kohana project can be found in the [phing build script](https://github.com/kohana/kohana/blob/3.1/master/build.xml#L172). - -If you're looking for more info on running the core kohana tests then please see our [dev wiki](https://github.com/kohana/kohana/wiki/Unit-Testing-Kohana) \ No newline at end of file +If you use the `tests.php` testsuite loader then it will only whitelist the +highest files. see `config/unittest.php` for details on configuring the +`tests.php` whitelist. diff --git a/includes/kohana/modules/unittest/bootstrap.php b/includes/kohana/modules/unittest/bootstrap.php index adec04a8..177dae15 100644 --- a/includes/kohana/modules/unittest/bootstrap.php +++ b/includes/kohana/modules/unittest/bootstrap.php @@ -1,40 +1,48 @@ 0) + { + ob_end_flush(); + } + else + { + ob_end_clean(); + } +} + // Enable the unittest module -Kohana::modules(Kohana::modules() + array('unittest' => MODPATH.'unittest')); +Kohana::modules(Kohana::modules() + array('unittest' => MODPATH.'unittest')); \ No newline at end of file diff --git a/includes/kohana/modules/unittest/bootstrap_all_modules.php b/includes/kohana/modules/unittest/bootstrap_all_modules.php new file mode 100644 index 00000000..9a591a2a --- /dev/null +++ b/includes/kohana/modules/unittest/bootstrap_all_modules.php @@ -0,0 +1,20 @@ +isDir()) + { + $modules[$module->getFilename()] = MODPATH.$module->getFilename(); + } +} + +Kohana::modules(Kohana::modules() + $modules); + +unset ($modules_iterator, $modules, $module); diff --git a/includes/kohana/modules/unittest/classes/kohana/unittest/database/testcase.php b/includes/kohana/modules/unittest/classes/Kohana/Unittest/Database/TestCase.php similarity index 86% rename from includes/kohana/modules/unittest/classes/kohana/unittest/database/testcase.php rename to includes/kohana/modules/unittest/classes/Kohana/Unittest/Database/TestCase.php index ae825c77..e39e366c 100644 --- a/includes/kohana/modules/unittest/classes/kohana/unittest/database/testcase.php +++ b/includes/kohana/modules/unittest/classes/Kohana/Unittest/Database/TestCase.php @@ -8,8 +8,7 @@ * @copyright (c) 2008-2009 Kohana Team * @license http://kohanaphp.com/license */ -// @codingStandardsIgnoreFile -abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Database_TestCase { +abstract class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Database_TestCase { /** * Whether we should enable work arounds to make the tests compatible with phpunit 3.4 @@ -21,7 +20,7 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data * Make sure PHPUnit backs up globals * @var boolean */ - protected $backupGlobals = TRUE; + protected $backupGlobals = FALSE; /** * A set of unittest helpers that are shared between normal / database @@ -36,6 +35,12 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ protected $environmentDefault = array(); + /** + * The kohana database connection that PHPUnit should use for this test + * @var string + */ + protected $_database_connection = 'default'; + /** * Creates a predefined environment using the default environment * @@ -44,9 +49,9 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ public function setUp() { - if (self::$_assert_type_compatability === NULL) + if(self::$_assert_type_compatability === NULL) { - if ( ! class_exists('PHPUnit_Runner_Version')) + if( ! class_exists('PHPUnit_Runner_Version')) { require_once 'PHPUnit/Runner/Version.php'; } @@ -82,8 +87,7 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data public function getConnection() { // Get the unittesting db connection - $config = Kohana::config('database') - ->{Kohana::config('unittest')->db_connection}; + $config = Kohana::$config->load('database.'.$this->_database_connection); if($config['type'] !== 'pdo') { @@ -101,15 +105,15 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data return $this->createDefaultDBConnection($pdo, $config['connection']['database']); } - /** - * Gets a connection to the unittest database - * - * @return Kohana_Database The database connection - */ - public function getKohanaConnection() - { - return Database::instance(Kohana::config('unittest')->db_connection); - } + /** + * Gets a connection to the unittest database + * + * @return Kohana_Database The database connection + */ + public function getKohanaConnection() + { + return Database::instance(Kohana::$config->load('unittest')->db_connection); + } /** * Removes all kohana related cache files in the cache directory @@ -167,8 +171,10 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ public static function assertInstanceOf($expected, $actual, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertType($expected, $actual, $message); + } return parent::assertInstanceOf($expected, $actual, $message); } @@ -184,8 +190,10 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ public static function assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertAttributeType($expected, $attributeName, $classOrObject, $message); + } return parent::assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message); } @@ -200,8 +208,10 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ public static function assertNotInstanceOf($expected, $actual, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertNotType($expected, $actual, $message); + } return self::assertNotInstanceOf($expected, $actual, $message); } @@ -217,8 +227,10 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ public static function assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertAttributeNotType($expected, $attributeName, $classOrObject, $message); + } return self::assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message); } @@ -233,8 +245,10 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ public static function assertInternalType($expected, $actual, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertType($expected, $actual, $message); + } return parent::assertInternalType($expected, $actual, $message); } @@ -250,8 +264,10 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ public static function assertAttributeInternalType($expected, $attributeName, $classOrObject, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertAttributeType($expected, $attributeName, $classOrObject, $message); + } return self::assertAttributeInternalType($expected, $attributeName, $classOrObject, $message); } @@ -266,8 +282,10 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ public static function assertNotInternalType($expected, $actual, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertNotType($expected, $actual, $message); + } return self::assertNotInternalType($expected, $actual, $message); } @@ -283,8 +301,10 @@ abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Data */ public static function assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertAttributeNotType($expected, $attributeName, $classOrObject, $message); + } return self::assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message); } diff --git a/includes/kohana/modules/unittest/classes/kohana/unittest/helpers.php b/includes/kohana/modules/unittest/classes/Kohana/Unittest/Helpers.php similarity index 94% rename from includes/kohana/modules/unittest/classes/kohana/unittest/helpers.php rename to includes/kohana/modules/unittest/classes/Kohana/Unittest/Helpers.php index 25ef3d66..410090e0 100644 --- a/includes/kohana/modules/unittest/classes/kohana/unittest/helpers.php +++ b/includes/kohana/modules/unittest/classes/Kohana/Unittest/Helpers.php @@ -132,7 +132,7 @@ class Kohana_Unittest_Helpers { $class->setStaticPropertyValue($var, $value); } // If this is an environment variable - elseif (preg_match('/^[A-Z_-]+$/D', $option) OR isset($_SERVER[$option])) + elseif (preg_match('/^[A-Z_-]+$/', $option) OR isset($_SERVER[$option])) { if ($backup_needed) { @@ -146,12 +146,12 @@ class Kohana_Unittest_Helpers { { if ($backup_needed) { - $this->_environment_backup[$option] = Kohana::config($option); + $this->_environment_backup[$option] = Kohana::$config->load($option); } list($group, $var) = explode('.', $option, 2); - Kohana::config($group)->set($var, $value); + Kohana::$config->load($group)->set($var, $value); } } } diff --git a/includes/kohana/modules/unittest/classes/kohana/unittest/testcase.php b/includes/kohana/modules/unittest/classes/Kohana/Unittest/TestCase.php similarity index 92% rename from includes/kohana/modules/unittest/classes/kohana/unittest/testcase.php rename to includes/kohana/modules/unittest/classes/Kohana/Unittest/TestCase.php index 7d6e777b..d47d69fb 100644 --- a/includes/kohana/modules/unittest/classes/kohana/unittest/testcase.php +++ b/includes/kohana/modules/unittest/classes/Kohana/Unittest/TestCase.php @@ -4,7 +4,6 @@ * A version of the stock PHPUnit testcase that includes some extra helpers * and default settings */ -// @codingStandardsIgnoreFile abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { /** @@ -17,7 +16,7 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { * Make sure PHPUnit backs up globals * @var boolean */ - protected $backupGlobals = TRUE; + protected $backupGlobals = FALSE; /** * A set of unittest helpers that are shared between normal / database @@ -40,9 +39,9 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { */ public function setUp() { - if (self::$_assert_type_compatability === NULL) + if(self::$_assert_type_compatability === NULL) { - if ( ! class_exists('PHPUnit_Runner_Version')) + if( ! class_exists('PHPUnit_Runner_Version')) { require_once 'PHPUnit/Runner/Version.php'; } @@ -122,8 +121,10 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { */ public static function assertInstanceOf($expected, $actual, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertType($expected, $actual, $message); + } return parent::assertInstanceOf($expected, $actual, $message); } @@ -139,8 +140,10 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { */ public static function assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertAttributeType($expected, $attributeName, $classOrObject, $message); + } return parent::assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message); } @@ -155,8 +158,10 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { */ public static function assertNotInstanceOf($expected, $actual, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertNotType($expected, $actual, $message); + } return self::assertNotInstanceOf($expected, $actual, $message); } @@ -172,8 +177,10 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { */ public static function assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertAttributeNotType($expected, $attributeName, $classOrObject, $message); + } return self::assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message); } @@ -188,8 +195,10 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { */ public static function assertInternalType($expected, $actual, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertType($expected, $actual, $message); + } return parent::assertInternalType($expected, $actual, $message); } @@ -205,8 +214,10 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { */ public static function assertAttributeInternalType($expected, $attributeName, $classOrObject, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertAttributeType($expected, $attributeName, $classOrObject, $message); + } return self::assertAttributeInternalType($expected, $attributeName, $classOrObject, $message); } @@ -221,8 +232,10 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { */ public static function assertNotInternalType($expected, $actual, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertNotType($expected, $actual, $message); + } return self::assertNotInternalType($expected, $actual, $message); } @@ -238,8 +251,10 @@ abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase { */ public static function assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message = '') { - if (self::$_assert_type_compatability) + if(self::$_assert_type_compatability) + { return self::assertAttributeNotType($expected, $attributeName, $classOrObject, $message); + } return self::assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message); } diff --git a/includes/kohana/modules/unittest/classes/Kohana/Unittest/TestSuite.php b/includes/kohana/modules/unittest/classes/Kohana/Unittest/TestSuite.php new file mode 100644 index 00000000..c8533732 --- /dev/null +++ b/includes/kohana/modules/unittest/classes/Kohana/Unittest/TestSuite.php @@ -0,0 +1,80 @@ + array(), + 'addDirectoryToBlacklist' => array(), + 'addFileToWhitelist' => array()); + + /** + * Runs the tests and collects their result in a TestResult. + * + * @param PHPUnit_Framework_TestResult $result + * @param mixed $filter + * @param array $groups + * @param array $excludeGroups + * @param boolean $processIsolation + * @return PHPUnit_Framework_TestResult + * @throws InvalidArgumentException + */ + public function run(PHPUnit_Framework_TestResult $result = NULL, $filter = FALSE, array $groups = array(), array $excludeGroups = array(), $processIsolation = FALSE) + { + + // Get the code coverage filter from the suite's result object + $coverage = $result->getCodeCoverage(); + + if ($coverage) + { + $coverage_filter = $coverage->filter(); + + // Apply the white and blacklisting + foreach ($this->_filter_calls as $method => $args) + { + foreach ($args as $arg) + { + $coverage_filter->$method($arg); + } + } + } + + return parent::run($result, $filter, $groups, $excludeGroups, $processIsolation); + } + + /** + * Queues a file to be added to the code coverage blacklist when the suite runs + * @param string $file + */ + public function addFileToBlacklist($file) + { + $this->_filter_calls['addFileToBlacklist'][] = $file; + } + + /** + * Queues a directory to be added to the code coverage blacklist when the suite runs + * @param string $dir + */ + public function addDirectoryToBlacklist($dir) + { + $this->_filter_calls['addDirectoryToBlacklist'][] = $dir; + } + + /** + * Queues a file to be added to the code coverage whitelist when the suite runs + * @param string $file + */ + public function addFileToWhitelist($file) + { + $this->_filter_calls['addFileToWhitelist'][] = $file; + } +} diff --git a/includes/kohana/modules/unittest/classes/kohana/unittest/tests.php b/includes/kohana/modules/unittest/classes/Kohana/Unittest/Tests.php similarity index 61% rename from includes/kohana/modules/unittest/classes/kohana/unittest/tests.php rename to includes/kohana/modules/unittest/classes/Kohana/Unittest/Tests.php index dda38e0a..308cd4ac 100644 --- a/includes/kohana/modules/unittest/classes/kohana/unittest/tests.php +++ b/includes/kohana/modules/unittest/classes/Kohana/Unittest/Tests.php @@ -13,13 +13,6 @@ class Kohana_Unittest_Tests { static protected $cache = array(); - /** - * Flag to identify whether the installed version of phpunit - * is greater than or equal to 3.5 - * @var boolean - */ - static protected $phpunit_v35 = FALSE; - /** * Loads test files if they cannot be found by kohana * @param $class @@ -45,65 +38,19 @@ class Kohana_Unittest_Tests { */ static public function configure_environment($do_whitelist = TRUE, $do_blacklist = TRUE) { - // During a webui request we need to manually load PHPUnit - if ( ! class_exists('PHPUnit_Util_Filter', FALSE) AND ! function_exists('phpunit_autoload')) - { - try - { - include_once 'PHPUnit/Autoload.php'; - } - catch (ErrorException $e) - { - include_once 'PHPUnit/Framework.php'; - } - } - - // Allow PHPUnit to handle exceptions and errors - if (Kohana::$is_cli) - { - restore_exception_handler(); - restore_error_handler(); - } + restore_exception_handler(); + restore_error_handler(); spl_autoload_register(array('Unittest_tests', 'autoload')); - // As of PHPUnit v3.5 there are slight differences in the way files are black|whitelisted - self::$phpunit_v35 = function_exists('phpunit_autoload'); - Unittest_tests::$cache = (($cache = Kohana::cache('unittest_whitelist_cache')) === NULL) ? array() : $cache; - $config = Kohana::config('unittest'); - - if ($do_whitelist AND $config->use_whitelist) - { - self::whitelist(); - } - - if ($do_blacklist AND count($config['blacklist'])) - { - Unittest_tests::blacklist($config->blacklist); - } - } - - /** - * Helper function to see if unittest is enabled in the config - * - * @return boolean - */ - static function enabled() - { - $p_environment = Kohana::config('unittest.environment'); - $k_environment = Kohana::$environment; - - return (is_array($p_environment) AND in_array($k_environment, $p_environment)) - OR - ($k_environment === $p_environment); } /** * Creates the test suite for kohana * - * @return PHPUnit_Framework_TestSuite + * @return Unittest_TestSuite */ static function suite() { @@ -114,10 +61,25 @@ class Kohana_Unittest_Tests { return $suite; } + Unittest_Tests::configure_environment(); + + $suite = new Unittest_TestSuite; + + // Load the whitelist and blacklist for code coverage + $config = Kohana::$config->load('unittest'); + + if ($config->use_whitelist) + { + Unittest_Tests::whitelist(NULL, $suite); + } + + if (count($config['blacklist'])) + { + Unittest_Tests::blacklist($config->blacklist, $suite); + } + + // Add tests $files = Kohana::list_files('tests'); - - $suite = new PHPUnit_Framework_TestSuite; - self::addTests($suite, $files); return $suite; @@ -128,23 +90,20 @@ class Kohana_Unittest_Tests { * * Uses recursion to scan subdirectories * - * @param PHPUnit_Framework_TestSuite $suite The test suite to add to + * @param Unittest_TestSuite $suite The test suite to add to * @param array $files Array of files to test */ - // @codingStandardsIgnoreStart - static function addTests(PHPUnit_Framework_TestSuite $suite, array $files) - // @codingStandardsIgnoreEnd + static function addTests(Unittest_TestSuite $suite, array $files) { - if (self::$phpunit_v35) - { - $filter = PHP_CodeCoverage_Filter::getInstance(); - } - foreach ($files as $file) + foreach ($files as $path => $file) { if (is_array($file)) { - self::addTests($suite, $file); + if ($path != 'tests'.DIRECTORY_SEPARATOR.'test_data') + { + self::addTests($suite, $file); + } } else { @@ -161,14 +120,7 @@ class Kohana_Unittest_Tests { require_once($file); } - if (isset($filter)) - { - $filter->addFileToBlacklist($file); - } - else - { - PHPUnit_Util_Filter::addFileToFilter($file); - } + $suite->addFileToBlacklist($file); } } } @@ -177,38 +129,20 @@ class Kohana_Unittest_Tests { /** * Blacklist a set of files in PHPUnit code coverage * - * @param array A set of files to blacklist + * @param array $blacklist_items A set of files to blacklist + * @param Unittest_TestSuite $suite The test suite */ - static public function blacklist(array $blacklist_items) + static public function blacklist(array $blacklist_items, Unittest_TestSuite $suite = NULL) { - if (self::$phpunit_v35) + foreach ($blacklist_items as $item) { - $filter = PHP_CodeCoverage_Filter::getInstance(); - - foreach ($blacklist_items as $item) + if (is_dir($item)) { - if (is_dir($item)) - { - $filter->addDirectoryToBlacklist($item); - } - else - { - $filter->addFileToBlacklist($item); - } + $suite->addDirectoryToBlacklist($item); } - } - else - { - foreach ($blacklist_items as $item) + else { - if (is_dir($item)) - { - PHPUnit_Util_Filter::addDirectoryToFilter($item); - } - else - { - PHPUnit_Util_Filter::addFileToFilter($item); - } + $suite->addFileToBlacklist($item); } } } @@ -220,8 +154,9 @@ class Kohana_Unittest_Tests { * set in the config file * * @param array $directories Optional directories to whitelist + * @param Unittest_Testsuite $suite Suite to load the whitelist into */ - static public function whitelist(array $directories = NULL) + static public function whitelist(array $directories = NULL, Unittest_TestSuite $suite = NULL) { if (empty($directories)) { @@ -236,7 +171,7 @@ class Kohana_Unittest_Tests { } // Only whitelist the "top" files in the cascading filesystem - self::set_whitelist(Kohana::list_files('classes', $directories)); + self::set_whitelist(Kohana::list_files('classes', $directories), $suite); } } @@ -248,7 +183,7 @@ class Kohana_Unittest_Tests { */ static protected function get_config_whitelist() { - $config = Kohana::config('unittest'); + $config = Kohana::$config->load('unittest'); $directories = array(); if ($config->whitelist['app']) @@ -292,19 +227,16 @@ class Kohana_Unittest_Tests { * Recursively whitelists an array of files * * @param array $files Array of files to whitelist + * @param Unittest_TestSuite $suite Suite to load the whitelist into */ - static protected function set_whitelist($files) + static protected function set_whitelist($files, Unittest_TestSuite $suite = NULL) { - if (self::$phpunit_v35) - { - $filter = PHP_CodeCoverage_Filter::getInstance(); - } foreach ($files as $file) { if (is_array($file)) { - self::set_whitelist($file); + self::set_whitelist($file, $suite); } else { @@ -320,9 +252,9 @@ class Kohana_Unittest_Tests { if (Unittest_tests::$cache[$file]) { - if (isset($filter)) + if (isset($suite)) { - $filter->addFileToWhitelist($file); + $suite->addFileToWhitelist($file); } else { @@ -332,5 +264,4 @@ class Kohana_Unittest_Tests { } } } - } diff --git a/includes/kohana/modules/unittest/classes/Unittest/Database/TestCase.php b/includes/kohana/modules/unittest/classes/Unittest/Database/TestCase.php new file mode 100644 index 00000000..baeaa0a8 --- /dev/null +++ b/includes/kohana/modules/unittest/classes/Unittest/Database/TestCase.php @@ -0,0 +1,17 @@ + - * @author Paul Banks - * @copyright (c) 2008-2009 Kohana Team - * @license http://kohanaphp.com/license - */ - -class Controller_UnitTest extends Controller_Template -{ - /** - * Whether the archive module is available - * @var boolean - */ - protected $cc_archive_available = FALSE; - - /** - * Unittest config - * @var Config - */ - protected $config = NULL; - - /** - * The uri by which the report uri will be executed - * @var string - */ - protected $report_uri = ''; - - /** - * The uri by which the run action will be executed - * @var string - */ - protected $run_uri = ''; - - /** - * Is the XDEBUG extension loaded? - * @var boolean - */ - protected $xdebug_loaded = FALSE; - - /** - * Template - * @var string - */ - public $template = 'unittest/layout'; - - /** - * Loads test suite - */ - public function before() - { - parent::before(); - - if ( ! Unittest_tests::enabled()) - { - // Pretend this is a normal 404 error... - $this->status = 404; - - throw new Kohana_Request_Exception('Unable to find a route to match the URI: :uri', - array(':uri' => $this->request->uri())); - } - - // Prevent the whitelist from being autoloaded, but allow the blacklist - // to be loaded - Unittest_Tests::configure_environment(FALSE); - - $this->config = Kohana::config('unittest'); - - // This just stops some very very long lines - $route = Route::get('unittest'); - $this->report_uri = $route->uri(array('action' => 'report')); - $this->run_uri = $route->uri(array('action' => 'run')); - - // Switch used to disable cc settings - $this->xdebug_loaded = extension_loaded('xdebug'); - $this->cc_archive_enabled = class_exists('Archive'); - - Kohana_View::set_global('xdebug_enabled', $this->xdebug_loaded); - Kohana_View::set_global('cc_archive_enabled', $this->cc_archive_enabled); - } - - /** - * Handles index page for /unittest/ and /unittest/index/ - */ - public function action_index() - { - $this->template->body = View::factory('unittest/index') - ->set('run_uri', $this->run_uri) - ->set('report_uri', $this->report_uri) - ->set('whitelistable_items', $this->get_whitelistable_items()) - ->set('groups', $this->get_groups_list(Unittest_tests::suite())); - } - - /** - * Handles report generation - */ - public function action_report() - { - // Fairly foolproof - if ( ! $this->config->cc_report_path AND ! class_exists('Archive')) - throw new Kohana_Exception('Cannot generate report'); - - // We don't want to use the HTML layout, we're sending the user 100111011100110010101100 - $this->auto_render = FALSE; - - $suite = Unittest_tests::suite(); - $temp_path = rtrim($this->config->temp_path, '/').'/'; - $group = (array) Arr::get($_GET, 'group', array()); - - // Stop unittest from interpretting "all groups" as "no groups" - if (empty($group) OR empty($group[0])) - { - $group = array(); - } - - if (Arr::get($_GET, 'use_whitelist', FALSE)) - { - $this->whitelist(Arr::get($_GET, 'whitelist', array())); - } - - $runner = new Kohana_Unittest_Runner($suite); - - // If the user wants to download a report - if ($this->cc_archive_enabled AND Arr::get($_GET, 'archive') === '1') - { - // $report is the actual directory of the report, - // $folder is the name component of directory - list($report, $folder) = $runner->generate_report($group, $temp_path); - - $archive = Archive::factory('zip'); - - // TODO: Include the test results? - $archive->add($report, 'report', TRUE); - - $filename = $folder.'.zip'; - - $archive->save($temp_path.$filename); - - // It'd be nice to clear up afterwards but by deleting the report dir we corrupt the archive - // And once the archive has been sent to the user Request stops the script so we can't delete anything - // It'll be up to the user to delete files periodically - $this->request->send_file($temp_path.$filename, $filename); - } - else - { - $folder = trim($this->config->cc_report_path, '/').'/'; - $path = DOCROOT.$folder; - - if ( ! file_exists($path)) - throw new Kohana_Exception('Report directory :dir does not exist', array(':dir' => $path)); - - if ( ! is_writable($path)) - throw new Kohana_Exception('Script doesn\'t have permission to write to report dir :dir ', array(':dir' => $path)); - - $runner->generate_report($group, $path, FALSE); - - $this->request->redirect(URL::site($folder.'index.html', $this->request)); - } - } - - /** - * Handles test running interface - */ - public function action_run() - { - $this->template->body = View::factory('unittest/results'); - - // Get the test suite and work out which groups we're testing - $suite = Unittest_tests::suite(); - $group = (array) Arr::get($_GET, 'group', array()); - - - // Stop phpunit from interpretting "all groups" as "no groups" - if (empty($group) OR empty($group[0])) - { - $group = array(); - } - - // Only collect code coverage if the user asked for it - $collect_cc = (bool) Arr::get($_GET, 'collect_cc', FALSE); - - if ($collect_cc AND Arr::get($_GET, 'use_whitelist', FALSE)) - { - $whitelist = $this->whitelist(Arr::get($_GET, 'whitelist', array())); - } - - $runner = new Kohana_Unittest_Runner($suite); - - try - { - $runner->run($group, $collect_cc); - - if ($collect_cc) - { - $this->template->body->set('coverage', $runner->calculate_cc_percentage()); - } - - if (isset($whitelist)) - { - $this->template->body->set('coverage_explanation', $this->nice_whitelist_explanation($whitelist)); - } - } - catch(Kohana_Exception $e) - { - // Code coverage is not allowed, possibly xdebug disabled? - // TODO: Tell the user this? - $runner->run($group); - } - - // Show some results - $this->template->body - ->set('results', $runner->results) - ->set('totals', $runner->totals) - ->set('time', $this->nice_time($runner->time)) - - // Sets group to the currently selected group, or default all groups - ->set('group', Arr::get($this->get_groups_list($suite), reset($group), 'All groups')) - ->set('groups', $this->get_groups_list($suite)) - - ->set('run_uri', $this->request->uri()) - ->set('report_uri', $this->report_uri.url::query()) - - // Whitelist related stuff - ->set('whitelistable_items', $this->get_whitelistable_items()) - ->set('whitelisted_items', isset($whitelist) ? array_keys($whitelist) : array()) - ->set('whitelist', ! empty($whitelist)); - } - - /** - * Get the list of groups from the test suite, sorted with 'All groups' prefixed - * - * @return array Array of groups in the test suite - */ - protected function get_groups_list($suite) - { - // Make groups aray suitable for drop down - $groups = $suite->getGroups(); - if (count($groups) > 0) - { - sort($groups); - $groups = array_combine($groups, $groups); - } - return array('' => 'All Groups') + $groups; - } - - /** - * Gets a list of items that are whitelistable - * - * @return array - */ - protected function get_whitelistable_items() - { - static $whitelist; - - if (count($whitelist)) - return $whitelist; - - $whitelist = array(); - - $whitelist['k_app'] = 'Application'; - - $k_modules = array_keys(Kohana::modules()); - - $whitelist += array_map('ucfirst', array_combine($k_modules, $k_modules)); - - $whitelist['k_sys'] = 'Kohana Core'; - - return $whitelist; - } - - /** - * Whitelists a specified set of modules specified by the user - * - * @param array $modules - */ - protected function whitelist(array $modules) - { - $k_modules = Kohana::modules(); - $whitelist = array(); - - // Make sure our whitelist is valid - foreach ($modules as $item) - { - if (isset($k_modules[$item])) - { - $whitelist[$item] = $k_modules[$item]; - } - elseif ($item === 'k_app') - { - $whitelist[$item] = APPPATH; - } - elseif ($item === 'k_sys') - { - $whitelist[$item] = SYSPATH; - } - } - - if (count($whitelist)) - { - Unittest_tests::whitelist($whitelist); - } - - return $whitelist; - } - - /** - * Prettifies the list of whitelisted modules - * - * @param array Array of whitelisted items - * @return string - */ - protected function nice_whitelist_explanation(array $whitelist) - { - $items = array_intersect_key($this->get_whitelistable_items(), $whitelist); - - return implode(', ', $items); - } - - protected function nice_time($time) - { - $parts = array(); - - if ($time > DATE::DAY) - { - $parts[] = floor($time/DATE::DAY).'d'; - $time = $time % DATE::DAY; - } - - if ($time > DATE::HOUR) - { - $parts[] = floor($time/DATE::HOUR).'h'; - $time = $time % DATE::HOUR; - } - - if ($time > DATE::MINUTE) - { - $parts[] = floor($time/DATE::MINUTE).'m'; - $time = $time % DATE::MINUTE; - } - - if ($time > 0) - { - $parts[] = round($time, 1).'s'; - } - - return implode(' ', $parts); - } -} // End Controller_PHPUnit diff --git a/includes/kohana/modules/unittest/classes/kohana/unittest/runner.php b/includes/kohana/modules/unittest/classes/kohana/unittest/runner.php deleted file mode 100644 index d85733ff..00000000 --- a/includes/kohana/modules/unittest/classes/kohana/unittest/runner.php +++ /dev/null @@ -1,313 +0,0 @@ - - * @author Paul Banks - * @copyright (c) 2008-2009 Kohana Team - * @license http://kohanaphp.com/license - */ -class Kohana_Unittest_Runner implements PHPUnit_Framework_TestListener { - /** - * Results - * @var array - */ - protected $results = array( - 'errors' => array(), - 'failures' => array(), - 'skipped' => array(), - 'incomplete' => array(), - ); - - /** - * Test result totals - * @var array - */ - protected $totals = array( - 'tests' => 0, - 'passed' => 0, - 'errors' => 0, - 'failures' => 0, - 'skipped' => 0, - 'incomplete' => 0, - 'assertions' => 0, - ); - - /** - * Info about the current test running - * @var array - */ - protected $current = array(); - - /** - * Time for tests to run (seconds) - * @var float - */ - protected $time = 0; - - /** - * Result collector - * @var PHPUnit_Framework_TestResult - */ - protected $result = NULL; - - /** - * the test suite to run - * @var PHPUnit_Framework_TestSuite - */ - protected $suite = NULL; - - /** - * Constructor - * - * @param PHPUnit_Framework_TestSuite $suite The suite to test - * @param PHPUnit_Framework_TestResult $result Optional result object to use - */ - function __construct(PHPUnit_Framework_TestSuite $suite, PHPUnit_Framework_TestResult $result = NULL) - { - if ($result === NULL) - { - $result = new PHPUnit_Framework_TestResult; - } - - $result->addListener($this); - - $this->suite = $suite; - $this->result = $result; - } - - /** - * Magic getter to allow access to member variables - * - * @param string $var Variable to get - * @return mixed - */ - function __get($var) - { - return $this->$var; - } - - /** - * Calcualtes stats for each file covered by the code testing - * - * Each member of the returned array is formatted like so: - * - * - * array( - * 'coverage' => $coverage_percent_for_file, - * 'loc' => $lines_of_code, - * 'locExecutable' => $lines_of_executable_code, - * 'locExecuted' => $lines_of_code_executed - * ); - * - * - * @return array Statistics for code coverage of each file - */ - public function calculate_cc() - { - if ($this->result->getCollectCodeCoverageInformation()) - { - $coverage = $this->result->getCodeCoverageInformation(); - - $coverage_summary = PHPUnit_Util_CodeCoverage::getSummary($coverage); - - $stats = array(); - - foreach ($coverage_summary as $file => $_lines) - { - $stats[$file] = PHPUnit_Util_CodeCoverage::getStatistics($coverage_summary, $file); - } - - return $stats; - } - - return FALSE; - } - - /** - * Calculates the percentage code coverage information - * - * @return boolean|float FALSE if cc is not enabled, float for coverage percent - */ - public function calculate_cc_percentage() - { - if ($stats = $this->calculate_cc()) - { - $executable = 0; - $executed = 0; - - foreach ($stats as $stat) - { - $executable += $stat['locExecutable']; - $executed += $stat['locExecuted']; - } - - return ($executable > 0) ? ($executed * 100 / $executable) : 100; - } - - return FALSE; - } - - /** - * Generate a report using the specified $temp_path - * - * @param array $groups Groups to test - * @param string $temp_path Temporary path to use while generating report - */ - public function generate_report(array $groups, $temp_path, $create_sub_dir = TRUE) - { - if ( ! is_writable($temp_path)) - throw new Kohana_Exception('Temp path :path does not exist or is not writable by the webserver', array(':path' => $temp_path)); - - $folder_path = $temp_path; - - if ($create_sub_dir === TRUE) - { - // Icky, highly unlikely, but do it anyway - // Basically adds "(n)" to the end of the filename until there's a free file - $count = 0; - do - { - $folder_name = date('Y-m-d_H:i:s') - .(empty($groups) ? '' : ('['.implode(',', $groups).']')) - .(($count > 0) ? ('('.$count.')') : ''); - ++$count; - } - while (is_dir($folder_path.$folder_name)); - - $folder_path .= $folder_name; - - mkdir($folder_path, 0777); - } - else - { - $folder_name = basename($folder_path); - } - - $this->run($groups, TRUE); - - require_once 'PHPUnit/Runner/Version.php'; - require_once 'PHPUnit/Util/Report.php'; - - PHPUnit_Util_Report::render($this->result, $folder_path); - - return array($folder_path, $folder_name); - } - - /** - * Runs the test suite using the result specified in the constructor - * - * @param array $groups Optional array of groups to test - * @param bool $collect_cc Optional, Should code coverage be collected? - * @return Kohana_PHPUnit Instance of $this - */ - public function run(array $groups = array(), $collect_cc = FALSE) - { - if ($collect_cc AND ! extension_loaded('xdebug')) - throw new Kohana_Exception('Code coverage cannot be collected because the xdebug extension is not loaded'); - - $this->result->collectCodeCoverageInformation( (bool) $collect_cc); - - // Run the tests. - $this->suite->run($this->result, FALSE, $groups); - - return $this; - } - - // @codingStandardsIgnoreStart - public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) - // @codingStandardsIgnoreEnd - { - $this->totals['errors']++; - $this->current['result'] = 'errors'; - $this->current['message'] = $test->getStatusMessage(); - } - - // @codingStandardsIgnoreStart - public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) - // @codingStandardsIgnoreEnd - { - $this->totals['failures']++; - $this->current['result'] = 'failures'; - $this->current['message'] = $test->getStatusMessage(); - } - - // @codingStandardsIgnoreStart - public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) - // @codingStandardsIgnoreEnd - { - $this->totals['incomplete']++; - $this->current['result'] = 'incomplete'; - $this->current['message'] = $test->getStatusMessage(); - } - - // @codingStandardsIgnoreStart - public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) - // @codingStandardsIgnoreEnd - { - $this->totals['skipped']++; - $this->current['result'] = 'skipped'; - $this->current['message'] = $test->getStatusMessage(); - } - - // @codingStandardsIgnoreStart - public function startTest(PHPUnit_Framework_Test $test) - // @codingStandardsIgnoreEnd - { - $this->current['name'] = $test->getName(FALSE); - $this->current['description'] = $test->toString(); - $this->current['result'] = 'passed'; - } - - // @codingStandardsIgnoreStart - public function endTest(PHPUnit_Framework_Test $test, $time) - // @codingStandardsIgnoreEnd - { - // Add totals - $this->totals['tests']++; - $this->totals['assertions'] += $test->getNumAssertions(); - - // Handle passed tests - if ($this->current['result'] == 'passed') - { - // Add to total - $this->totals['passed']++; - } - else - { - // Add to results - $this->results[$this->current['result']][] = $this->current; - } - - $this->current = array(); - - $this->time += $time; - } - - // @codingStandardsIgnoreStart - public function startTestSuite(PHPUnit_Framework_TestSuite $suite) {} - // @codingStandardsIgnoreEnd - - // @codingStandardsIgnoreStart - public function endTestSuite(PHPUnit_Framework_TestSuite $suite) - // @codingStandardsIgnoreEnd - { - // Parse test descriptions to make them look nicer - foreach ($this->results as $case => $testresults) - { - foreach ($testresults as $type => $result) - { - preg_match('/^(?:([a-z0-9_]+?)::)?([a-z0-9_]+)(?: with data set (#\d+ \(.*?\)))?/i', $result['description'], $m); - - $this->results[$case][$type] += array( - 'class' => $m[1], - 'test' => $m[2], - 'data_set' => isset($m[3]) ? $m[3] : FALSE, - ); - } - } - } -} diff --git a/includes/kohana/modules/unittest/classes/unittest/runner.php b/includes/kohana/modules/unittest/classes/unittest/runner.php deleted file mode 100644 index 02544b2a..00000000 --- a/includes/kohana/modules/unittest/classes/unittest/runner.php +++ /dev/null @@ -1,3 +0,0 @@ - Kohana::DEVELOPMENT, - - // This is the folder where we generate and zip all the reports for downloading - // Needs to be readable and writable - 'temp_path' => Kohana::$cache_dir.'/unittest', - - // Path from DOCROOT (i.e. http://yourdomain/) to the folder where HTML cc reports can be published. - // If you'd prefer not to allow users to do this then simply set the value to FALSE. - // Example value of 'cc_report_path' would allow devs to see report at http://yourdomain/report/ - 'cc_report_path' => 'report', - // If you don't use a whitelist then only files included during the request will be counted // If you do, then only whitelisted items will be counted 'use_whitelist' => TRUE, // Items to whitelist, only used in cli - // Web runner ui allows user to choose which items to whitelist 'whitelist' => array( // Should the app be whitelisted? @@ -46,8 +31,4 @@ return array( // List of individual files/folders to blacklist 'blacklist' => array( ), - - // A database connection that can be used when testing - // This doesn't overwrite anything, tests will have to use this value manually - 'db_connection' => 'unittest', ); diff --git a/includes/kohana/modules/unittest/config/userguide.php b/includes/kohana/modules/unittest/config/userguide.php index d8094844..a286a261 100644 --- a/includes/kohana/modules/unittest/config/userguide.php +++ b/includes/kohana/modules/unittest/config/userguide.php @@ -1,23 +1,13 @@ - array( - - // This should be the path to this modules userguide pages, without the 'guide/'. Ex: '/guide/modulename/' would be 'modulename' 'unittest' => array( - - // Whether this modules userguide pages should be shown 'enabled' => TRUE, - - // The name that should show up on the userguide index page 'name' => 'Unittest', - - // A short description of this module, shown on the index page - 'description' => 'Kohana unit testing.', - - // Copyright message, shown in the footer for this module - 'copyright' => '© 2008–2010 Kohana Team', - ) + 'description' => 'Unit testing module', + 'copyright' => '© 2009-2011 Kohana Team', + ) ) ); \ No newline at end of file diff --git a/includes/kohana/modules/unittest/guide/unittest/index.md b/includes/kohana/modules/unittest/guide/unittest/index.md index fb594e25..9f7e314c 100644 --- a/includes/kohana/modules/unittest/guide/unittest/index.md +++ b/includes/kohana/modules/unittest/guide/unittest/index.md @@ -1,2 +1,3 @@ -# Unittest +# UnitTest +Unit tests for Kohana \ No newline at end of file diff --git a/includes/kohana/modules/unittest/guide/unittest/menu.md b/includes/kohana/modules/unittest/guide/unittest/menu.md index dcc6dfba..1a707cc7 100644 --- a/includes/kohana/modules/unittest/guide/unittest/menu.md +++ b/includes/kohana/modules/unittest/guide/unittest/menu.md @@ -1,5 +1,5 @@ ## [UnitTest]() - - [Testing](testing) - - [Mock Objects](mockobjects) - - [Troubleshooting](troubleshooting) - - [Testing workflows](testing_workflows) \ No newline at end of file + - [Mock Objects](mockobjects) + - [Testing](testing) + - [Testing workflows](testing_workflows) + - [Troubleshooting](troubleshooting) diff --git a/includes/kohana/modules/unittest/guide/unittest/testing.md b/includes/kohana/modules/unittest/guide/unittest/testing.md index 504478ad..41682f83 100644 --- a/includes/kohana/modules/unittest/guide/unittest/testing.md +++ b/includes/kohana/modules/unittest/guide/unittest/testing.md @@ -1,12 +1,12 @@ -### From the command line +# Usage - $ phpunit --bootstrap=index.php modules/unittest/tests.php + $ phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php -Of course, you'll need to make sure the path to the tests.php file is correct. If you want you can copy it to a more accessible location +Alternatively you can use a phpunit.xml to have a more fine grained control over which tests are included and which files are whitelisted. -### From the web +Make sure you only whitelist the highest files in the cascading filesystem, else you could end up with a lot of "class cannot be redefined" errors. -Just navigate to http://example.com/unittest. You may need to use http://example.com/index.php/unittest if you have not enabled url rewriting in your .htaccess. +If you use the tests.php testsuite loader then it will only whitelist the highest files. see config/unittest.php for details on configuring the tests.php whitelist. ## Writing tests @@ -103,13 +103,13 @@ This functionality can be used to record which bug reports a test is for: To see all groups that are available in your code run: - $ phpunit --boostrap=index.php --list-groups modules/unittest/tests.php + $ phpunit --boostrap=modules/unittest/bootstrap.php --list-groups modules/unittest/tests.php *Note:* the `--list-groups` switch should appear before the path to the test suite loader You can also exclude groups while testing using the `--exclude-group` switch. This can be useful if you want to ignore all kohana tests: - $ phpunit --bootstrap=index.php --exclude-group=kohana modules/unittest/tests.php + $ phpunit --bootstrap=modules/unittest/bootstrap.php --exclude-group=kohana modules/unittest/tests.php For more info see: diff --git a/includes/kohana/modules/unittest/guide/unittest/testing_workflows.md b/includes/kohana/modules/unittest/guide/unittest/testing_workflows.md index fa98cee8..893ab622 100644 --- a/includes/kohana/modules/unittest/guide/unittest/testing_workflows.md +++ b/includes/kohana/modules/unittest/guide/unittest/testing_workflows.md @@ -2,16 +2,6 @@ Having unittests for your application is a nice idea, but unless you actually use them they're about as useful as a chocolate firegaurd. There are quite a few ways of getting tests "into" your development process and this guide aims to cover a few of them. -## Testing through the webui - -The web ui is a fairly temporary solution, aimed at helping developers get into unittesting and code coverage. Eventually it's hoped that people migrate on to the termainl & CI servers. - -To access it goto - - http://example.com/unittest/ - -*Note:* Your site will need to be in the correct environment in order to use the webui. See the config file for more details. You may also need to use http://example.com/index.php/unittest/ - ## Integrating with IDEs Modern IDEs have come a long way in the last couple of years and ones like netbeans have pretty decent PHP / PHPUnit support. diff --git a/includes/kohana/modules/unittest/init.php b/includes/kohana/modules/unittest/init.php deleted file mode 100644 index c40ba428..00000000 --- a/includes/kohana/modules/unittest/init.php +++ /dev/null @@ -1,16 +0,0 @@ -)') - ->defaults(array( - 'controller' => 'unittest', - 'action' => 'index', - )); diff --git a/includes/kohana/modules/unittest/tests.php b/includes/kohana/modules/unittest/tests.php index dfc21cd1..4421fc97 100644 --- a/includes/kohana/modules/unittest/tests.php +++ b/includes/kohana/modules/unittest/tests.php @@ -5,16 +5,16 @@ if ( ! class_exists('Kohana')) die('Please include the kohana bootstrap file (see README.markdown)'); } -if ($file = Kohana::find_file('classes', 'unittest/tests')) +if ($file = Kohana::find_file('classes', 'Unittest/Tests')) { require_once $file; // PHPUnit requires a test suite class to be in this file, // so we create a faux one that uses the kohana base - Class TestSuite extends Unittest_Tests + class TestSuite extends Unittest_Tests {} } else { die('Could not include the test suite'); -} \ No newline at end of file +} diff --git a/includes/kohana/modules/unittest/views/unittest/index.php b/includes/kohana/modules/unittest/views/unittest/index.php deleted file mode 100644 index b4750fd8..00000000 --- a/includes/kohana/modules/unittest/views/unittest/index.php +++ /dev/null @@ -1,66 +0,0 @@ - - - diff --git a/includes/kohana/modules/unittest/views/unittest/layout.php b/includes/kohana/modules/unittest/views/unittest/layout.php deleted file mode 100644 index 5132c70a..00000000 --- a/includes/kohana/modules/unittest/views/unittest/layout.php +++ /dev/null @@ -1,255 +0,0 @@ - - - - PHPUnit for Kohana - - - - - - - - - - - - diff --git a/includes/kohana/modules/unittest/views/unittest/results.php b/includes/kohana/modules/unittest/views/unittest/results.php deleted file mode 100644 index 33b15f06..00000000 --- a/includes/kohana/modules/unittest/views/unittest/results.php +++ /dev/null @@ -1,83 +0,0 @@ - - - -
- -
No tests in group
- -
Tests Passed
- - - $tests):?> - - -
-

- - []

-
    - -
  1. - :: - - - - -
  2. - -
-
- - -
diff --git a/includes/kohana/modules/userguide/README.md b/includes/kohana/modules/userguide/README.md index 17401742..f79888d2 100644 --- a/includes/kohana/modules/userguide/README.md +++ b/includes/kohana/modules/userguide/README.md @@ -30,42 +30,6 @@ Any images used in the userguide pages must be in `media/guide//`. For The API browser is generated from the actual source code. The descriptions for classes, constants, properties, and methods is extracted from the comments and parsed in Markdown. For example if you look in the comment for [Kohana_Core::init](http://github.com/kohana/core/blob/c443c44922ef13421f4a/classes/kohana/core.php#L5) you can see a markdown list and table. These are parsed and show correctly in the API browser. `@param`, `@uses`, `@throws`, `@returns` and other tags are parsed as well. -## How to Contribute - -### If you don't know git, or you don't feel like you are a good documentation writer: - -Just submit a [bug report](http://dev.kohanaframework.org/projects/userguide3/issues/new) and explain what you think can be improved. If you are a good writer but don't know git, just provide some content in your bug report and we will merge it in. - -### If you know git: - -**Short version**: Create a ticket on redmine for your changes, fork the module whose docs you wish to improve (e.g. `git://github.com/kohana/orm.git` or `git://github.com/kohana/core.git`), checkout the appropriate branch, make changes, and then send a pull request with the ticket number. - -**Long version:** (This still assumes you at least know your way around git, especially how submodules work.) - - 1. Create a ticket on redmine for your changes. - - 2. Fork the specific repo you want to contribute to on github. (For example go to http://github.com/kohana/core and click the fork button.) - - 3. Now go into the repo of the area of docs you want to contribute to and add your forked repo as a new remote, and push to it. - - cd system - - # make sure we are up to date - git checkout 3.1/develop - git pull - - # add your repository as a new remote - git remote add git@github.com:/core.git - - # (make some changes to the docs) - - # now commit the changes and push to your repo - git commit - git push 3.1/develop - - 4. Send a pull request on github containing the ticket number, and update the ticket with a link to the pull request. - - # What the userguide adds to markdown: In addition to the features and syntax of [Markdown](http://daringfireball.net/projects/markdown/) and [Markdown Extra](http://michelf.com/projects/php-markdown/extra/) the following apply to userguide pages and api documentation: @@ -96,10 +60,10 @@ You can make links to the api browser by wrapping any class name in brackets. Y If you want to have parameters, only put the brackets around the class and function (not the params), and put a backslash in front of the opening parenthesis. - [Kohana::config]\('foobar','baz') + [Kohana::$config]\('foobar','baz') ### Including Views You may include a view by putting the name of the view in double curly brackets. **If the view is not found, no exception or error will be shown!** The curly brackets and view will simply be shown an the page as is. - {{some/view}} + {{some/view}} \ No newline at end of file diff --git a/includes/kohana/modules/userguide/classes/Controller/Userguide.php b/includes/kohana/modules/userguide/classes/Controller/Userguide.php new file mode 100644 index 00000000..e3196143 --- /dev/null +++ b/includes/kohana/modules/userguide/classes/Controller/Userguide.php @@ -0,0 +1,3 @@ +request->action() === 'media') { // Do not template media files @@ -28,28 +30,15 @@ class Controller_Userguide extends Controller_Template { $this->media = Route::get('docs/media'); $this->guide = Route::get('docs/guide'); - if (defined('MARKDOWN_PARSER_CLASS')) - { - throw new Kohana_Exception('Markdown parser already registered. Live documentation will not work in your environment.'); - } - - // Use customized Markdown parser - define('MARKDOWN_PARSER_CLASS', 'Kodoc_Markdown'); - - if ( ! class_exists('Markdown', FALSE)) - { - // Load Markdown support - require Kohana::find_file('vendor', 'markdown/markdown'); - } - // Set the base URL for links and images Kodoc_Markdown::$base_url = URL::site($this->guide->uri()).'/'; Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/'; } - parent::before(); + // Default show_comments to config value + $this->template->show_comments = Kohana::$config->load('userguide.show_comments'); } - + // List all modules that have userguides public function index() { @@ -57,32 +46,32 @@ class Controller_Userguide extends Controller_Template { $this->template->breadcrumb = array('User Guide'); $this->template->content = View::factory('userguide/index', array('modules' => $this->_modules())); $this->template->menu = View::factory('userguide/menu', array('modules' => $this->_modules())); - + // Don't show disqus on the index page - $this->template->hide_disqus = TRUE; + $this->template->show_comments = FALSE; } - + // Display an error if a page isn't found public function error($message) { $this->response->status(404); $this->template->title = "Userguide - Error"; $this->template->content = View::factory('userguide/error',array('message' => $message)); - + // Don't show disqus on error pages - $this->template->hide_disqus = TRUE; + $this->template->show_comments = FALSE; // If we are in a module and that module has a menu, show that - if ($module = $this->request->param('module') AND $menu = $this->file($module.'/menu') AND Kohana::config('userguide.modules.'.$module.'.enabled')) + if ($module = $this->request->param('module') AND $menu = $this->file($module.'/menu') AND Kohana::$config->load('userguide.modules.'.$module.'.enabled')) { // Namespace the markdown parser Kodoc_Markdown::$base_url = URL::site($this->guide->uri()).'/'.$module.'/'; Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/'.$module.'/'; - $this->template->menu = Markdown($this->_get_all_menu_markdown()); + $this->template->menu = Kodoc_Markdown::markdown($this->_get_all_menu_markdown()); $this->template->breadcrumb = array( $this->guide->uri() => 'User Guide', - $this->guide->uri(array('module' => $module)) => Kohana::config('userguide.modules.'.$module.'.name'), + $this->guide->uri(array('module' => $module)) => Kohana::$config->load('userguide.modules.'.$module.'.name'), 'Error' ); } @@ -119,19 +108,19 @@ class Controller_Userguide extends Controller_Template { { return $this->index(); } - + // If this module's userguide pages are disabled, show the error page - if ( ! Kohana::config('userguide.modules.'.$module.'.enabled')) + if ( ! Kohana::$config->load('userguide.modules.'.$module.'.enabled')) { - return $this->error(__('That module doesn\'t exist, or has userguide pages disabled.')); + return $this->error('That module doesn\'t exist, or has userguide pages disabled.'); } - + // Prevent "guide/module" and "guide/module/index" from having duplicate content if ( $page == 'index') { - return $this->error(__('Userguide page not found')); + return $this->error('Userguide page not found'); } - + // If a module is set, but no page was provided in the url, show the index page if ( ! $page ) { @@ -144,37 +133,37 @@ class Controller_Userguide extends Controller_Template { // If it's not found, show the error page if ( ! $file) { - return $this->error(__('Userguide page not found')); + return $this->error('Userguide page not found'); } - + // Namespace the markdown parser Kodoc_Markdown::$base_url = URL::site($this->guide->uri()).'/'.$module.'/'; Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/'.$module.'/'; // Set the page title - $this->template->title = $page == 'index' ? Kohana::config('userguide.modules.'.$module.'.name') : $this->title($page); + $this->template->title = $page == 'index' ? Kohana::$config->load('userguide.modules.'.$module.'.name') : $this->title($page); // Parse the page contents into the template Kodoc_Markdown::$show_toc = true; - $this->template->content = Markdown(file_get_contents($file)); + $this->template->content = Kodoc_Markdown::markdown(file_get_contents($file)); Kodoc_Markdown::$show_toc = false; // Attach this module's menu to the template - $this->template->menu = Markdown($this->_get_all_menu_markdown()); + $this->template->menu = Kodoc_Markdown::markdown($this->_get_all_menu_markdown()); // Bind the breadcrumb $this->template->bind('breadcrumb', $breadcrumb); - + // Bind the copyright - $this->template->copyright = Kohana::config('userguide.modules.'.$module.'.copyright'); + $this->template->copyright = Kohana::$config->load('userguide.modules.'.$module.'.copyright'); // Add the breadcrumb trail $breadcrumb = array(); - $breadcrumb[$this->guide->uri()] = __('User Guide'); - $breadcrumb[$this->guide->uri(array('module' => $module))] = Kohana::config('userguide.modules.'.$module.'.name'); - + $breadcrumb[$this->guide->uri()] = 'User Guide'; + $breadcrumb[$this->guide->uri(array('module' => $module))] = Kohana::$config->load('userguide.modules.'.$module.'.name'); + // TODO try and get parent category names (from menu). Regex magic or javascript dom stuff perhaps? - + // Only add the current page title to breadcrumbs if it isn't the index, otherwise we get repeats. if ($page != 'index') { @@ -194,7 +183,7 @@ class Controller_Userguide extends Controller_Template { // If no class was passed to the url, display the API index page if ( ! $class) { - $this->template->title = __('Table of Contents'); + $this->template->title = 'Table of Contents'; $this->template->content = View::factory('userguide/api/toc') ->set('classes', Kodoc::class_methods()) @@ -204,7 +193,7 @@ class Controller_Userguide extends Controller_Template { { // Create the Kodoc_Class version of this class. $_class = Kodoc_Class::factory($class); - + // If the class requested and the actual class name are different // (different case, orm vs ORM, auth vs Auth) redirect if ($_class->class->name != $class) @@ -219,12 +208,12 @@ class Controller_Userguide extends Controller_Template { // If this classes package has been disabled via the config, 404 if ( ! Kodoc::show_class($_class)) return $this->error('That class is in package that is hidden. Check the api_packages config setting.'); - + // Everything is fine, display the class. $this->template->title = $class; $this->template->content = View::factory('userguide/api/class') - ->set('doc', Kodoc::factory($class)) + ->set('doc', $_class) ->set('route', $this->request->route()); } @@ -234,12 +223,9 @@ class Controller_Userguide extends Controller_Template { // Bind the breadcrumb $this->template->bind('breadcrumb', $breadcrumb); - // Get the docs URI - $guide = Route::get('docs/guide'); - // Add the breadcrumb $breadcrumb = array(); - $breadcrumb[$this->guide->uri(array('page' => NULL))] = __('User Guide'); + $breadcrumb[$this->guide->uri(array('page' => NULL))] = 'User Guide'; $breadcrumb[$this->request->route()->uri()] = 'API Browser'; $breadcrumb[] = $this->template->title; } @@ -258,8 +244,8 @@ class Controller_Userguide extends Controller_Template { if ($file = Kohana::find_file('media/guide', $file, $ext)) { // Check if the browser sent an "if-none-match: " header, and tell if the file hasn't changed - $this->response->check_cache(sha1($this->request->uri()).filemtime($file), $this->request); - + $this->check_cache(sha1($this->request->uri()).filemtime($file)); + // Send the file content as the response $this->response->body(file_get_contents($file)); @@ -307,83 +293,105 @@ class Controller_Userguide extends Controller_Template { return parent::after(); } + /** + * Locates the appropriate markdown file for a given guide page. Page URLS + * can be specified in one of three forms: + * + * * userguide/adding + * * userguide/adding.md + * * userguide/adding.markdown + * + * In every case, the userguide will search the cascading file system paths + * for the file guide/userguide/adding.md. + * + * @param string $page The relative URL of the guide page + * @return string + */ public function file($page) { + + // Strip optional .md or .markdown suffix from the passed filename + $info = pathinfo($page); + if (isset($info['extension']) + AND (($info['extension'] === 'md') OR ($info['extension'] === 'markdown'))) + { + $page = $info['dirname'].DIRECTORY_SEPARATOR.$info['filename']; + } return Kohana::find_file('guide', $page, 'md'); } public function section($page) { $markdown = $this->_get_all_menu_markdown(); - + if (preg_match('~\*{2}(.+?)\*{2}[^*]+\[[^\]]+\]\('.preg_quote($page).'\)~mu', $markdown, $matches)) { return $matches[1]; } - + return $page; } public function title($page) { $markdown = $this->_get_all_menu_markdown(); - + if (preg_match('~\[([^\]]+)\]\('.preg_quote($page).'\)~mu', $markdown, $matches)) { // Found a title for this link return $matches[1]; } - + return $page; } - + protected function _get_all_menu_markdown() { // Only do this once per request... static $markdown = ''; - + if (empty($markdown)) { // Get menu items $file = $this->file($this->request->param('module').'/menu'); - + if ($file AND $text = file_get_contents($file)) { // Add spans around non-link categories. This is a terrible hack. - //echo Kohana::debug($text); - + //echo Debug::vars($text); + //$text = preg_replace('/(\s*[\-\*\+]\s*)(.*)/','$1$2',$text); $text = preg_replace('/^(\s*[\-\*\+]\s*)([^\[\]]+)$/m','$1$2',$text); - //echo Kohana::debug($text); + //echo Debug::vars($text); $markdown .= $text; } - + } - + return $markdown; } - + // Get the list of modules from the config, and reverses it so it displays in the order the modules are added, but move Kohana to the top. protected function _modules() { - $modules = array_reverse(Kohana::config('userguide.modules')); - + $modules = array_reverse(Kohana::$config->load('userguide.modules')); + if (isset($modules['kohana'])) { $kohana = $modules['kohana']; unset($modules['kohana']); $modules = array_merge(array('kohana' => $kohana), $modules); } - + // Remove modules that have been disabled via config foreach ($modules as $key => $value) { - if ( ! Kohana::config('userguide.modules.'.$key.'.enabled')) + if ( ! Kohana::$config->load('userguide.modules.'.$key.'.enabled')) { unset($modules[$key]); } } - + return $modules; } diff --git a/includes/kohana/modules/userguide/classes/kohana/kodoc.php b/includes/kohana/modules/userguide/classes/Kohana/Kodoc.php similarity index 52% rename from includes/kohana/modules/userguide/classes/kohana/kodoc.php rename to includes/kohana/modules/userguide/classes/Kohana/Kodoc.php index 8bdd9dc9..acd9cbcf 100644 --- a/includes/kohana/modules/userguide/classes/kohana/kodoc.php +++ b/includes/kohana/modules/userguide/classes/Kohana/Kodoc.php @@ -5,7 +5,7 @@ * @package Kohana/Userguide * @category Base * @author Kohana Team - * @copyright (c) 2008-2009 Kohana Team + * @copyright (c) 2008-2012 Kohana Team * @license http://kohanaphp.com/license */ class Kohana_Kodoc { @@ -57,15 +57,6 @@ class Kohana_Kodoc { { $classes = Kodoc::classes(); - foreach ($classes as $class) - { - if (isset($classes['kohana_'.$class])) - { - // Remove extended classes - unset($classes['kohana_'.$class]); - } - } - ksort($classes); $menu = array(); @@ -74,6 +65,9 @@ class Kohana_Kodoc { foreach ($classes as $class) { + if (Kodoc::is_transparent($class, $classes)) + continue; + $class = Kodoc_Class::factory($class); // Test if we should show this class @@ -113,9 +107,7 @@ class Kohana_Kodoc { } /** - * Returns an array of all the classes available, built by listing all files in the classes folder and then trying to create that class. - * - * This means any empty class files (as in complety empty) will cause an exception + * Returns an array of all the classes available, built by listing all files in the classes folder. * * @param array array of files, obtained using Kohana::list_files * @return array an array of all the class names @@ -129,19 +121,22 @@ class Kohana_Kodoc { $classes = array(); + // This will be used a lot! + $ext_length = strlen(EXT); + foreach ($list as $name => $path) { if (is_array($path)) { $classes += Kodoc::classes($path); } - else + elseif (substr($name, -$ext_length) === EXT) { // Remove "classes/" and the extension - $class = substr($name, 8, -(strlen(EXT))); + $class = substr($name, 8, -$ext_length); // Convert slashes to underscores - $class = str_replace(DIRECTORY_SEPARATOR, '_', strtolower($class)); + $class = str_replace(DIRECTORY_SEPARATOR, '_', $class); $classes[$class] = $class; } @@ -166,13 +161,11 @@ class Kohana_Kodoc { foreach ($list as $class) { - $_class = new ReflectionClass($class); - - if (stripos($_class->name, 'Kohana_') === 0) - { - // Skip transparent extension classes + // Skip transparent extension classes + if (Kodoc::is_transparent($class)) continue; - } + + $_class = new ReflectionClass($class); $methods = array(); @@ -180,10 +173,10 @@ class Kohana_Kodoc { { $declares = $_method->getDeclaringClass()->name; - if (stripos($declares, 'Kohana_') === 0) + // Remove the transparent prefix from declaring classes + if ($child = Kodoc::is_transparent($declares)) { - // Remove "Kohana_" - $declares = substr($declares, 7); + $declares = $child; } if ($declares === $_class->name OR $declares === "Core") @@ -200,93 +193,149 @@ class Kohana_Kodoc { return $classes; } + /** + * Generate HTML for the content of a tag. + * + * @param string $tag Name of the tag without @ + * @param string $text Content of the tag + * @return string HTML + */ + public static function format_tag($tag, $text) + { + if ($tag === 'license') + { + if (strpos($text, '://') !== FALSE) + return HTML::anchor($text); + } + elseif ($tag === 'link') + { + $split = preg_split('/\s+/', $text, 2); + + return HTML::anchor( + $split[0], + isset($split[1]) ? $split[1] : $split[0] + ); + } + elseif ($tag === 'copyright') + { + // Convert the copyright symbol + return str_replace('(c)', '©', $text); + } + elseif ($tag === 'throws') + { + $route = Route::get('docs/api'); + + if (preg_match('/^(\w+)\W(.*)$/D', $text, $matches)) + { + return HTML::anchor( + $route->uri(array('class' => $matches[1])), + $matches[1] + ).' '.$matches[2]; + } + + return HTML::anchor( + $route->uri(array('class' => $text)), + $text + ); + } + elseif ($tag === 'see' OR $tag === 'uses') + { + if (preg_match('/^'.Kodoc::$regex_class_member.'/', $text, $matches)) + return Kodoc::link_class_member($matches); + } + + return $text; + } + /** * Parse a comment to extract the description and the tags * - * @param string the comment retreived using ReflectionClass->getDocComment() + * [!!] Converting the output to HTML in this method is deprecated in 3.3 + * + * @param string $comment The DocBlock to parse + * @param boolean $html Whether or not to convert the return values + * to HTML (deprecated) * @return array array(string $description, array $tags) */ - public static function parse($comment) + public static function parse($comment, $html = TRUE) { // Normalize all new lines to \n $comment = str_replace(array("\r\n", "\n"), "\n", $comment); - // Remove the phpdoc open/close tags and split - $comment = array_slice(explode("\n", $comment), 1, -1); + // Split into lines while capturing without leading whitespace + preg_match_all('/^\s*\* ?(.*)\n/m', $comment, $lines); // Tag content $tags = array(); - foreach ($comment as $i => $line) + /** + * Process a tag and add it to $tags + * + * @param string $tag Name of the tag without @ + * @param string $text Content of the tag + * @return void + */ + $add_tag = function($tag, $text) use ($html, &$tags) { - // Remove all leading whitespace - $line = preg_replace('/^\s*\* ?/m', '', $line); - - // Search this line for a tag - if (preg_match('/^@(\S+)(?:\s*(.+))?$/', $line, $matches)) + // Don't show @access lines, they are shown elsewhere + if ($tag !== 'access') { - // This is a tag line - unset($comment[$i]); - - $name = $matches[1]; - $text = isset($matches[2]) ? $matches[2] : ''; - - switch ($name) + if ($html) { - case 'license': - if (strpos($text, '://') !== FALSE) - { - // Convert the lincense into a link - $text = HTML::anchor($text); - } - break; - case 'link': - $text = preg_split('/\s+/', $text, 2); - $text = HTML::anchor($text[0], isset($text[1]) ? $text[1] : $text[0]); - break; - case 'copyright': - if (strpos($text, '(c)') !== FALSE) - { - // Convert the copyright sign - $text = str_replace('(c)', '©', $text); - } - break; - case 'throws': - if (preg_match('/^(\w+)\W(.*)$/', $text, $matches)) - { - $text = HTML::anchor(Route::get('docs/api')->uri(array('class' => $matches[1])), $matches[1]).' '.$matches[2]; - } - else - { - $text = HTML::anchor(Route::get('docs/api')->uri(array('class' => $text)), $text); - } - break; - case 'uses': - if (preg_match('/^'.Kodoc::$regex_class_member.'$/i', $text, $matches)) - { - $text = Kodoc::link_class_member($matches); - } - break; - // Don't show @access lines, they are shown elsewhere - case 'access': - continue 2; + $text = Kodoc::format_tag($tag, $text); } // Add the tag - $tags[$name][] = $text; + $tags[$tag][] = $text; + } + }; + + $comment = $tag = null; + $end = count($lines[1]) - 1; + + foreach ($lines[1] as $i => $line) + { + // Search this line for a tag + if (preg_match('/^@(\S+)\s*(.+)?$/', $line, $matches)) + { + if ($tag) + { + // Previous tag is finished + $add_tag($tag, $text); + } + + $tag = $matches[1]; + $text = isset($matches[2]) ? $matches[2] : ''; + + if ($i === $end) + { + // No more lines + $add_tag($tag, $text); + } + } + elseif ($tag) + { + // This is the continuation of the previous tag + $text .= "\n".$line; + + if ($i === $end) + { + // No more lines + $add_tag($tag, $text); + } } else { - // Overwrite the comment line - $comment[$i] = (string) $line; + $comment .= "\n".$line; } } - // Concat the comment lines back to a block of text - if ($comment = trim(implode("\n", $comment))) + $comment = trim($comment, "\n"); + + if ($comment AND $html) { // Parse the comment with Markdown - $comment = Markdown($comment); + $comment = Kodoc_Markdown::markdown($comment); } return array($comment, $tags); @@ -328,7 +377,7 @@ class Kohana_Kodoc { */ public static function show_class(Kodoc_Class $class) { - $api_packages = Kohana::config('userguide.api_packages'); + $api_packages = Kohana::$config->load('userguide.api_packages'); // If api_packages is true, all packages should be shown if ($api_packages === TRUE) @@ -350,5 +399,68 @@ class Kohana_Kodoc { return $show_this; } + /** + * Checks whether a class is a transparent extension class or not. + * + * This method takes an optional $classes parameter, a list of all defined + * class names. If provided, the method will return false unless the extension + * class exists. If not, the method will only check known transparent class + * prefixes. + * + * Transparent prefixes are defined in the userguide.php config file: + * + * 'transparent_prefixes' => array( + * 'Kohana' => TRUE, + * ); + * + * Module developers can therefore add their own transparent extension + * namespaces and exclude them from the userguide. + * + * @param string $class The name of the class to check for transparency + * @param array $classes An optional list of all defined classes + * @return false If this is not a transparent extension class + * @return string The name of the class that extends this (in the case provided) + * @throws InvalidArgumentException If the $classes array is provided and the $class variable is not lowercase + */ + public static function is_transparent($class, $classes = NULL) + { + + static $transparent_prefixes = NULL; + + if ( ! $transparent_prefixes) + { + $transparent_prefixes = Kohana::$config->load('userguide.transparent_prefixes'); + } + + // Split the class name at the first underscore + $segments = explode('_',$class,2); + + if ((count($segments) == 2) AND (isset($transparent_prefixes[$segments[0]]))) + { + if ($segments[1] === 'Core') + { + // Cater for Module extends Module_Core naming + $child_class = $segments[0]; + } + else + { + // Cater for Foo extends Module_Foo naming + $child_class = $segments[1]; + } + + // It is only a transparent class if the unprefixed class also exists + if ($classes AND ! isset($classes[$child_class])) + return FALSE; + + // Return the name of the child class + return $child_class; + } + else + { + // Not a transparent class + return FALSE; + } + } + } // End Kodoc diff --git a/includes/kohana/modules/userguide/classes/kohana/kodoc/class.php b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Class.php similarity index 69% rename from includes/kohana/modules/userguide/classes/kohana/kodoc/class.php rename to includes/kohana/modules/userguide/classes/Kohana/Kodoc/Class.php index c6b182df..2c2f2f23 100644 --- a/includes/kohana/modules/userguide/classes/kohana/kodoc/class.php +++ b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Class.php @@ -5,7 +5,7 @@ * @package Kohana/Userguide * @category Base * @author Kohana Team - * @copyright (c) 2009 Kohana Team + * @copyright (c) 2009-2012 Kohana Team * @license http://kohanaphp.com/license */ class Kohana_Kodoc_Class extends Kodoc { @@ -35,6 +35,11 @@ class Kohana_Kodoc_Class extends Kodoc { */ public $constants = array(); + /** + * @var array Parent classes/interfaces of this class/interface + */ + public $parents = array(); + /** * Loads a class and uses [reflection](http://php.net/reflection) to parse * the class. Reads the class modifiers, constants and comment. Parses the @@ -52,43 +57,80 @@ class Kohana_Kodoc_Class extends Kodoc { $this->modifiers = ''.implode(' ', Reflection::getModifierNames($modifiers)).' '; } - if ($constants = $this->class->getConstants()) + $this->constants = $this->class->getConstants(); + + // If ReflectionClass::getParentClass() won't work if the class in + // question is an interface + if ($this->class->isInterface()) { - foreach ($constants as $name => $value) + $this->parents = $this->class->getInterfaces(); + } + else + { + $parent = $this->class; + + while ($parent = $parent->getParentClass()) { - $this->constants[$name] = Debug::vars($value); + $this->parents[] = $parent; } } - $parent = $this->class; - - do + if ( ! $comment = $this->class->getDocComment()) { - if ($comment = $parent->getDocComment()) + foreach ($this->parents as $parent) { - // Found a description for this class - break; + if ($comment = $parent->getDocComment()) + { + // Found a description for this class + break; + } } } - while ($parent = $parent->getParentClass()); - list($this->description, $this->tags) = Kodoc::parse($comment); - + list($this->description, $this->tags) = Kodoc::parse($comment, FALSE); + } + + /** + * Gets the constants of this class as HTML. + * + * @return array + */ + public function constants() + { + $result = array(); + + foreach ($this->constants as $name => $value) + { + $result[$name] = Debug::vars($value); + } + + return $result; + } + + /** + * Get the description of this class as HTML. Includes a warning when the + * class or one of its parents could not be found. + * + * @return string HTML + */ + public function description() + { + $result = $this->description; + // If this class extends Kodoc_Missing, add a warning about possible // incomplete documentation - $parent = $this->class; - - while ($parent = $parent->getParentClass()) + foreach ($this->parents as $parent) { if ($parent->name == 'Kodoc_Missing') { - $warning = "[!!] **This class, or a class parent, could not be + $result .= "[!!] **This class, or a class parent, could not be found or loaded. This could be caused by a missing - module or other dependancy. The documentation for - class may not be complete!**"; - $this->description = Markdown($warning).$this->description; + module or other dependancy. The documentation for + class may not be complete!**"; } } + + return Kodoc_Markdown::markdown($result); } /** @@ -100,12 +142,14 @@ class Kohana_Kodoc_Class extends Kodoc { { $props = $this->class->getProperties(); + $defaults = $this->class->getDefaultProperties(); + usort($props, array($this,'_prop_sort')); foreach ($props as $key => $property) { // Create Kodoc Properties for each property - $props[$key] = new Kodoc_Property($this->class->name, $property->name); + $props[$key] = new Kodoc_Property($this->class->name, $property->name, Arr::get($defaults, $property->name)); } return $props; @@ -174,7 +218,7 @@ class Kohana_Kodoc_Class extends Kodoc { /* - echo kohana::debug('a is '.$a->class.'::'.$a->name,'b is '.$b->class.'::'.$b->name, + echo Debug::vars('a is '.$a->class.'::'.$a->name,'b is '.$b->class.'::'.$b->name, 'are the classes the same?', $a->class == $b->class,'if they are, the result is:',strcmp($a->name, $b->name), 'is a this class?', $a->name == $this->class->name,-1, 'is b this class?', $b->name == $this->class->name,1, @@ -213,4 +257,23 @@ class Kohana_Kodoc_Class extends Kodoc { return $bdepth - $adepth; } -} // End Kodac_Class + /** + * Get the tags of this class as HTML. + * + * @return array + */ + public function tags() + { + $result = array(); + + foreach ($this->tags as $name => $set) + { + foreach ($set as $text) + { + $result[$name][] = Kodoc::format_tag($name, $text); + } + } + + return $result; + } +} diff --git a/includes/kohana/modules/userguide/classes/kohana/kodoc/markdown.php b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Markdown.php similarity index 86% rename from includes/kohana/modules/userguide/classes/kohana/kodoc/markdown.php rename to includes/kohana/modules/userguide/classes/Kohana/Kodoc/Markdown.php index 3562126a..856be950 100644 --- a/includes/kohana/modules/userguide/classes/kohana/kodoc/markdown.php +++ b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Markdown.php @@ -5,7 +5,7 @@ * @package Kohana/Userguide * @category Base * @author Kohana Team - * @copyright (c) 2009 Kohana Team + * @copyright (c) 2009-2012 Kohana Team * @license http://kohanaphp.com/license */ class Kohana_Kodoc_Markdown extends MarkdownExtra_Parser { @@ -38,6 +38,26 @@ class Kohana_Kodoc_Markdown extends MarkdownExtra_Parser { */ public static $show_toc = false; + /** + * Transform some text using [Kodoc_Markdown] + * + * @see Markdown() + * + * @param string Text to parse + * @return string Transformed text + */ + public static function markdown($text) + { + static $instance; + + if ($instance === NULL) + { + $instance = new Kodoc_Markdown; + } + + return $instance->transform($text); + } + public function __construct() { // doImage is 10, add image url just before @@ -107,7 +127,7 @@ class Kohana_Kodoc_Markdown extends MarkdownExtra_Parser { $attr = ' id="'.$this->make_heading_id($matches[2]).'"'; // Add this header to the page toc - $this->_add_to_toc($level,$matches[2],$this->make_heading_id($matches[2])); + $this->_add_to_toc($level, $matches[2], $this->make_heading_id(empty($matches[3]) ? $matches[2] : $matches[3])); $block = "".$this->runSpanGamut($matches[2]).""; return "\n" . $this->hashBlock($block) . "\n\n"; @@ -152,18 +172,23 @@ class Kohana_Kodoc_Markdown extends MarkdownExtra_Parser { { list($search, $view) = $set; - try + if (Kohana::find_file('views', $view)) { - $replace[$search] = View::factory($view)->render(); - } - catch (Exception $e) - { - ob_start(); + try + { + $replace[$search] = View::factory($view)->render(); + } + catch (Exception $e) + { + /** + * Capture the exception handler output and insert it instead. + * + * NOTE: Is this really the correct way to handle an exception? + */ + $response = Kohana_exception::_handler($e); - // Capture the exception handler output and insert it instead - Kohana_exception::handler($e); - - $replace[$search] = ob_get_clean(); + $replace[$search] = $response->body(); + } } } diff --git a/includes/kohana/modules/userguide/classes/kohana/kodoc/method.php b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Method.php similarity index 98% rename from includes/kohana/modules/userguide/classes/kohana/kodoc/method.php rename to includes/kohana/modules/userguide/classes/Kohana/Kodoc/Method.php index 025055fc..2e673d4b 100644 --- a/includes/kohana/modules/userguide/classes/kohana/kodoc/method.php +++ b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Method.php @@ -68,7 +68,7 @@ class Kohana_Kodoc_Method extends Kodoc { if (isset($tags['param'][$i])) { - preg_match('/^(\S+)(?:\s*(?:\$'.$param->name.'\s*)?(.+))?$/', $tags['param'][$i], $matches); + preg_match('/^(\S+)(?:\s*(?:\$'.$param->name.'\s*)?(.+))?$/s', $tags['param'][$i], $matches); $param->type = $matches[1]; @@ -138,4 +138,4 @@ class Kohana_Kodoc_Method extends Kodoc { return $out; } -} // End Kodoc_Method \ No newline at end of file +} // End Kodoc_Method diff --git a/includes/kohana/modules/userguide/classes/kohana/kodoc/method/param.php b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Method/Param.php similarity index 93% rename from includes/kohana/modules/userguide/classes/kohana/kodoc/method/param.php rename to includes/kohana/modules/userguide/classes/Kohana/Kodoc/Method/Param.php index 33ff5921..7b5976fc 100644 --- a/includes/kohana/modules/userguide/classes/kohana/kodoc/method/param.php +++ b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Method/Param.php @@ -83,7 +83,7 @@ class Kohana_Kodoc_Method_Param extends Kodoc { if ($this->description) { - $display .= '$'.$this->name.' '; + $display .= '$'.$this->name.' '; } else { diff --git a/includes/kohana/modules/userguide/classes/kohana/kodoc/missing.php b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Missing.php similarity index 100% rename from includes/kohana/modules/userguide/classes/kohana/kodoc/missing.php rename to includes/kohana/modules/userguide/classes/Kohana/Kodoc/Missing.php diff --git a/includes/kohana/modules/userguide/classes/kohana/kodoc/property.php b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Property.php similarity index 81% rename from includes/kohana/modules/userguide/classes/kohana/kodoc/property.php rename to includes/kohana/modules/userguide/classes/Kohana/Kodoc/Property.php index 1c873692..af2cbee4 100644 --- a/includes/kohana/modules/userguide/classes/kohana/kodoc/property.php +++ b/includes/kohana/modules/userguide/classes/Kohana/Kodoc/Property.php @@ -5,7 +5,7 @@ * @package Kohana/Userguide * @category Base * @author Kohana Team - * @copyright (c) 2009 Kohana Team + * @copyright (c) 2009-2012 Kohana Team * @license http://kohanaphp.com/license */ class Kohana_Kodoc_Property extends Kodoc { @@ -30,7 +30,12 @@ class Kohana_Kodoc_Property extends Kodoc { */ public $value; - public function __construct($class, $property) + /** + * @var string default value of the property + */ + public $default; + + public function __construct($class, $property, $default = NULL) { $property = new ReflectionProperty($class, $property); @@ -45,19 +50,19 @@ class Kohana_Kodoc_Property extends Kodoc { if (isset($tags['var'])) { - if (preg_match('/^(\S*)(?:\s*(.+?))?$/', $tags['var'][0], $matches)) + if (preg_match('/^(\S*)(?:\s*(.+?))?$/s', $tags['var'][0], $matches)) { $this->type = $matches[1]; if (isset($matches[2])) { - $this->description = Markdown($matches[2]); + $this->description = Kodoc_Markdown::markdown($matches[2]); } } } $this->property = $property; - + // Show the value of static properties, but only if they are public or we are php 5.3 or higher and can force them to be accessible if ($property->isStatic() AND ($property->isPublic() OR version_compare(PHP_VERSION, '5.3', '>='))) { @@ -66,7 +71,7 @@ class Kohana_Kodoc_Property extends Kodoc { { $property->setAccessible(TRUE); } - + // Don't debug the entire object, just say what kind of object it is if (is_object($property->getValue($class))) { @@ -77,7 +82,9 @@ class Kohana_Kodoc_Property extends Kodoc { $this->value = Debug::vars($property->getValue($class)); } } - + + // Store the defult property + $this->default = Debug::vars($default);; } } // End Kodoc_Property diff --git a/includes/kohana/modules/userguide/config/userguide.php b/includes/kohana/modules/userguide/config/userguide.php index 9b5d2522..0258f694 100644 --- a/includes/kohana/modules/userguide/config/userguide.php +++ b/includes/kohana/modules/userguide/config/userguide.php @@ -4,11 +4,14 @@ return array ( // Enable the API browser. TRUE or FALSE 'api_browser' => TRUE, - + // Enable these packages in the API browser. TRUE for all packages, or a string of comma seperated packages, using 'None' for a class with no @package // Example: 'api_packages' => 'Kohana,Kohana/Database,Kohana/ORM,None', 'api_packages' => TRUE, - + + // Enables Disqus comments on the API and User Guide pages + 'show_comments' => Kohana::$environment === Kohana::PRODUCTION, + // Leave this alone 'modules' => array( @@ -25,7 +28,12 @@ return array 'description' => 'Documentation viewer and api generation.', // Copyright message, shown in the footer for this module - 'copyright' => '© 2008–2011 Kohana Team', + 'copyright' => '© 2008–2012 Kohana Team', ) + ), + + // Set transparent class name segments + 'transparent_prefixes' => array( + 'Kohana' => TRUE, ) ); diff --git a/includes/kohana/modules/userguide/guide/userguide/adding.md b/includes/kohana/modules/userguide/guide/userguide/adding.md index 7a5ec689..156eca82 100644 --- a/includes/kohana/modules/userguide/guide/userguide/adding.md +++ b/includes/kohana/modules/userguide/guide/userguide/adding.md @@ -24,6 +24,20 @@ First, copy this config and place in it `/config/userguide.php`, replaci // Copyright message, shown in the footer for this module 'copyright' => '© 2010–2011 ', ) + ), + + /* + * If you use transparent extension outside the Kohana_ namespace, + * add your class prefix here. Both common Kohana naming conventions are + * excluded: + * - Modulename extends Modulename_Core + * - Foo extends Modulename_Foo + * + * For example, if you use Modulename_ for your base classes + * then you would define: + */ + 'transparent_prefixes' => array( + 'Modulename' => TRUE, ) ); diff --git a/includes/kohana/modules/userguide/guide/userguide/config.md b/includes/kohana/modules/userguide/guide/userguide/config.md index a566040f..338abc13 100644 --- a/includes/kohana/modules/userguide/guide/userguide/config.md +++ b/includes/kohana/modules/userguide/guide/userguide/config.md @@ -6,17 +6,17 @@ The userguide has the following config options, available in `config/userguide.p ( // Enable the API browser. TRUE or FALSE 'api_browser' => TRUE, - + // Enable these packages in the API browser. TRUE for all packages, or a string of comma seperated packages, using 'None' for a class with no @package // Example: 'api_packages' => 'Kohana,Kohana/Database,Kohana/ORM,None', 'api_packages' => TRUE, - + ); -You can enable or disabled the entire api browser, or limit it to only show certain packages. To disable a module from showing pages in the userguide, simply change that modules `enabled` option using the cascading filesystem. For example: +You can enable or disable the entire API browser, or limit it to only show certain packages. To disable a module from showing pages in the userguide, simply change that module's `enabled` option using the cascading filesystem. For example: `application/config/userguide.php` - + return array ( 'modules' => array @@ -31,5 +31,5 @@ You can enable or disabled the entire api browser, or limit it to only show cert ) ) ) - -Using this you could make the userguide only show your modules and your classes in the api browser, if you wanted to host your own documentation on your site. Feel free to change the styles and views as well, but be sure to give credit where credit is due! \ No newline at end of file + +Using this you could make the userguide only show your modules and classes in the API browser, if you wanted to host your own documentation on your site. Feel free to change the styles and views as well, but be sure to give credit where credit is due! \ No newline at end of file diff --git a/includes/kohana/modules/userguide/guide/userguide/contributing.md b/includes/kohana/modules/userguide/guide/userguide/contributing.md index fcf4e613..b5bf2559 100644 --- a/includes/kohana/modules/userguide/guide/userguide/contributing.md +++ b/includes/kohana/modules/userguide/guide/userguide/contributing.md @@ -1,5 +1,3 @@ -[!!] When the docs get merged these images/links should be update - # Contributing Kohana is community driven, and we rely on community contributions for the documentation. @@ -18,7 +16,7 @@ To quickly point out something that needs improvement, report a [bug report](htt If you want to contribute some changes, you can do so right from your browser without even knowing git! -First create an account on [Github](https://github.com/signup/free). +First create an account on [GitHub](https://github.com/signup/free). You will need to fork the module for the area you want to improve. For example, to improve the [ORM documentation](../orm) fork . To improve the [Kohana documentation](../kohana), fork , etc. So, find the module you want to improve and click on the Fork button in the top right. @@ -36,31 +34,42 @@ After you have made your changes, send a pull request so your improvements can b Once your pull request has been accepted, you can delete your repository if you want. Your commit will have been copied to the official branch. -## If you know git +## If you know Git -**Short version**: Create a ticket on redmine for your changes, fork the module whose docs you wish to improve (e.g. `git://github.com/kohana/orm.git` or `git://github.com/kohana/core.git`), checkout the appropriate branch, make changes, and then send a pull request with the ticket number. +### Short version -**Long version:** (This still assumes you at least know your way around git, especially how submodules work.) +Fork the module whose docs you wish to improve (e.g. `git://github.com/kohana/orm.git` or `git://github.com/kohana/core.git`), checkout the `3.2/develop` branch (for the 3.2 docs), make changes, and then send a pull request. - 1. Create a ticket on redmine for your changes. +### Long version - 2. Fork the specific repo you want to contribute to on github. (For example go to http://github.com/kohana/core and click the fork button.) +(This still assumes you at least know your way around Git, especially how submodules work.) + + 1. Fork the specific repo you want to contribute to on GitHub. (For example, go to http://github.com/kohana/core and click the fork button.) + + 1. Now you need to add your fork as a "git remote" to your application and ensure you are on the right branch. An example for the [ORM](../orm) module and 3.2 docs: + + cd my-kohana-app/modules/orm - 3. Now go into the repo of the area of docs you want to contribute to and add your forked repo as a new remote, and push to it. - - cd system - - # make sure we are up to date - git checkout 3.1/develop - git pull - # add your repository as a new remote - git remote add git@github.com:/core.git - - # (make some changes to the docs) - - # now commit the changes and push to your repo - git commit - git push 3.1/develop + git remote add git://github.com//orm.git - 4. Send a pull request on github containing the ticket number, and update the ticket with a link to the pull request. + # Get the correct branch + git checkout 3.2/develop + + 1. Now go into the repo of the area of docs you want to contribute to and add your forked repo as a new remote, and push to it. + + cd my-kohana-app/modules/orm + + # Make some changes to the docs + nano file.md + + # Commit your changes - Use a descriptive commit message! If there is a redmine ticket for the changes you are making include "Fixes #XXXXX" in the commit message so its tracked. + git commit -a -m "Corrected a typo in the ORM docs. Fixes #12345." + + # make sure we are up to date with the latest changes + git merge origin/3.2/develop + + # Now push your changes to your fork. + git push 3.2/develop + + 1. Finally, send a pull request on GitHub. \ No newline at end of file diff --git a/includes/kohana/modules/userguide/guide/userguide/markdown.md b/includes/kohana/modules/userguide/guide/userguide/markdown.md index 3367fbd5..d4c1d709 100644 --- a/includes/kohana/modules/userguide/guide/userguide/markdown.md +++ b/includes/kohana/modules/userguide/guide/userguide/markdown.md @@ -192,6 +192,8 @@ To link to page in a different module, prefix your url with `../` and the module **Images are also namespaced**, using `![Alt Text](imagename.jpg)` would look for `media/guide//imagename.jpg`. +[!!] If you want your userguide pages to be browsable on github or similar sites outside Kohana's own userguide module, specify the optional .md file extension in your links + ## API Links You can make links to the api browser by wrapping any class name in brackets. You may also include a function name, or propery name to link to that specifically. All of the following will link to the API browser: @@ -208,9 +210,9 @@ You can make links to the api browser by wrapping any class name in brackets. Y If you want to have parameters and have the function be clickable, only put the brackets around the class and function (not the params), and put a backslash in front of the opening parenthesis. - [Kohana::config]\('foobar','baz') + [Kohana::$config]\('foobar','baz') -[Kohana::config]\('foobar','baz') +[Kohana::$config]\('foobar','baz') ## Notes diff --git a/includes/kohana/modules/userguide/i18n/de.php b/includes/kohana/modules/userguide/i18n/de.php deleted file mode 100644 index 836f6635..00000000 --- a/includes/kohana/modules/userguide/i18n/de.php +++ /dev/null @@ -1,6 +0,0 @@ - 'Handbuch' -); diff --git a/includes/kohana/modules/userguide/i18n/es.php b/includes/kohana/modules/userguide/i18n/es.php deleted file mode 100644 index 22175dc5..00000000 --- a/includes/kohana/modules/userguide/i18n/es.php +++ /dev/null @@ -1,6 +0,0 @@ - 'Guía de Usuario' -); diff --git a/includes/kohana/modules/userguide/i18n/fr.php b/includes/kohana/modules/userguide/i18n/fr.php deleted file mode 100644 index 017d6a3a..00000000 --- a/includes/kohana/modules/userguide/i18n/fr.php +++ /dev/null @@ -1,7 +0,0 @@ - 'Guide Utilisateur' -); - diff --git a/includes/kohana/modules/userguide/i18n/he.php b/includes/kohana/modules/userguide/i18n/he.php deleted file mode 100644 index 0d392ed5..00000000 --- a/includes/kohana/modules/userguide/i18n/he.php +++ /dev/null @@ -1,6 +0,0 @@ - 'מדריך למשתמש' -); diff --git a/includes/kohana/modules/userguide/i18n/nl.php b/includes/kohana/modules/userguide/i18n/nl.php deleted file mode 100644 index c93ebf52..00000000 --- a/includes/kohana/modules/userguide/i18n/nl.php +++ /dev/null @@ -1,6 +0,0 @@ - 'Gebruiksaanwijzing' -); \ No newline at end of file diff --git a/includes/kohana/modules/userguide/i18n/ru.php b/includes/kohana/modules/userguide/i18n/ru.php deleted file mode 100644 index ecf4ce08..00000000 --- a/includes/kohana/modules/userguide/i18n/ru.php +++ /dev/null @@ -1,7 +0,0 @@ - 'Руководство пользователя' -); - diff --git a/includes/kohana/modules/userguide/i18n/zh.php b/includes/kohana/modules/userguide/i18n/zh.php deleted file mode 100644 index 6025fdda..00000000 --- a/includes/kohana/modules/userguide/i18n/zh.php +++ /dev/null @@ -1,28 +0,0 @@ - '用户手册', - - // Errors - 'Error' => '错误', - 'Userguide page not found' => '用户手册页面无法找到', - 'API Reference: Class not found.' => 'API 参考: 没有找到此类。', - 'That class is hidden' => '那个是隐藏类', - - // API - 'Table of Contents' => '目录', - 'Available Classes' => '可用的类', - 'Class Contents' => '类列表', - 'Constants' => '常量', - 'Properties' => '属性', - 'Methods' => '方法', - 'None' => '无', - 'Parameters' => '参数', - 'Parameter' => '参数', - 'Type' => '类型', - 'Description' => '描述', - 'Default' => '默认', - 'Return Values' => '返回值', - 'Source Code' => '源代码', -); diff --git a/includes/kohana/modules/userguide/init.php b/includes/kohana/modules/userguide/init.php index a812316b..fc083e4e 100644 --- a/includes/kohana/modules/userguide/init.php +++ b/includes/kohana/modules/userguide/init.php @@ -1,19 +1,19 @@ )', array('file' => '.+')) +Route::set('docs/media', 'guide-media(/)', array('file' => '.+')) ->defaults(array( - 'controller' => 'userguide', + 'controller' => 'Userguide', 'action' => 'media', 'file' => NULL, )); // API Browser, if enabled -if (Kohana::config('userguide.api_browser') === TRUE) +if (Kohana::$config->load('userguide.api_browser') === TRUE) { - Route::set('docs/api', 'guide/api(/)', array('class' => '[a-zA-Z0-9_]+')) + Route::set('docs/api', 'guide-api(/)', array('class' => '[a-zA-Z0-9_]+')) ->defaults(array( - 'controller' => 'userguide', + 'controller' => 'Userguide', 'action' => 'api', 'class' => NULL, )); @@ -24,7 +24,21 @@ Route::set('docs/guide', 'guide(/(/))', array( 'page' => '.+', )) ->defaults(array( - 'controller' => 'userguide', + 'controller' => 'Userguide', 'action' => 'docs', 'module' => '', - )); \ No newline at end of file + )); + +// Simple autoloader used to encourage PHPUnit to behave itself. +class Markdown_Autoloader { + public static function autoload($class) + { + if ($class == 'Markdown_Parser' OR $class == 'MarkdownExtra_Parser') + { + include_once Kohana::find_file('vendor', 'markdown/markdown'); + } + } +} + +// Register the autoloader +spl_autoload_register(array('Markdown_Autoloader', 'autoload')); diff --git a/includes/kohana/modules/userguide/media/guide/css/kodoc.css b/includes/kohana/modules/userguide/media/guide/css/kodoc.css index 391c2d7a..0807bcc9 100644 --- a/includes/kohana/modules/userguide/media/guide/css/kodoc.css +++ b/includes/kohana/modules/userguide/media/guide/css/kodoc.css @@ -95,48 +95,48 @@ h6:hover a.permalink { } -#header, -#content, -#footer { float: left; clear: both; width: 100%; } +#kodoc-header, +#kodoc-content, +#kodoc-footer { float: left; clear: both; width: 100%; } -#header { padding: 58px 0 2em; background: #77c244 url(../img/header.png) center top repeat-x; } - #logo { display: block; float: left; } - #menu { float: right; margin-top: 12px; background: #113c32; -moz-border-radius: 5px; -webkit-border-radius: 5px; } - #menu ul { float: left; margin: 0; padding: 0 0.5em 0 0; } - #menu li { display: block; float: left; margin: 0; padding: 0; } - #menu li.first { padding-left: 0.5em; } - #menu li a { display: block; height: 32px; line-height: 32px; padding: 0 0.8em; border-right: solid 1px #0f362d; border-left: solid 1px #144539; letter-spacing: 0.05em; text-decoration: none; text-transform: uppercase; color: #efefef; font-size: 90%; } - #menu li.first a { border-left: 0; } - #menu li.last a { border-right: 0; } - #menu li a:hover { background: #164e41; border-left-color: #195a4b; color: #fff; text-shadow: #fff 0 0 1px; } +#kodoc-header { padding: 58px 0 2em; background: #77c244 url(../img/header.png) center top repeat-x; } + #kodoc-logo { display: block; float: left; } + #kodoc-menu { float: right; margin-top: 12px; background: #113c32; -moz-border-radius: 5px; -webkit-border-radius: 5px; } + #kodoc-menu ul { float: left; margin: 0; padding: 0 0.5em 0 0; } + #kodoc-menu li { display: block; float: left; margin: 0; padding: 0; } + #kodoc-menu li.first { padding-left: 0.5em; } + #kodoc-menu li a { display: block; height: 32px; line-height: 32px; padding: 0 0.8em; border-right: solid 1px #0f362d; border-left: solid 1px #144539; letter-spacing: 0.05em; text-decoration: none; text-transform: uppercase; color: #efefef; font-size: 90%; } + #kodoc-menu li.first a { border-left: 0; } + #kodoc-menu li.last a { border-right: 0; } + #kodoc-menu li a:hover { background: #164e41; border-left-color: #195a4b; color: #fff; text-shadow: #fff 0 0 1px; } -#content { background: #f1f8db url(../img/content.png) center top repeat-x; } - #content .wrapper { min-height: 390px; padding: 1em 0; background: transparent url(../img/wrapper.png) center top no-repeat; } - #content div.page-toc { float: right; margin: 1em; margin-top: 0; padding: 1em; background: #fff; border: solid 0.1em #e8efcf; border-radius: 0.6em; } - #content p.intro { padding: 1em 20px; padding-left: 20px; margin: 0 -20px; font-size: 1.2em; } - #content a { color: #004352; } - #content a:hover { color: #00758f; } - #content a:active { text-decoration: none; } +#kodoc-content { background: #f1f8db url(../img/content.png) center top repeat-x; } + #kodoc-content .wrapper { min-height: 390px; padding: 1em 0; background: transparent url(../img/wrapper.png) center top no-repeat; } + #kodoc-content div.page-toc { float: right; margin: 1em; margin-top: 0; padding: 1em; background: #fff; border: solid 0.1em #e8efcf; border-radius: 0.6em; } + #kodoc-content p.intro { padding: 1em 20px; padding-left: 20px; margin: 0 -20px; font-size: 1.2em; } + #kodoc-content a { color: #004352; } + #kodoc-content a:hover { color: #00758f; } + #kodoc-content a:active { text-decoration: none; } -#breadcrumb { margin: 0 0 1em; padding: 0 0 0.5em; list-style: none; border-bottom: solid 1px #e8efcf; } - #breadcrumb li { display: inline-block; margin: 0; padding: 0 0.4em 0 0; text-transform: uppercase; font-size: 11px; } - #breadcrumb li:before { content: '»'; padding-right: 0.4em; } - #breadcrumb li a { color: #999; text-decoration: none; } +#kodoc-breadcrumb { margin: 0 0 1em; padding: 0 0 0.5em; list-style: none; border-bottom: solid 1px #e8efcf; } + #kodoc-breadcrumb li { display: inline-block; margin: 0; padding: 0 0.4em 0 0; text-transform: uppercase; font-size: 11px; } + #kodoc-breadcrumb li:before { content: '»'; padding-right: 0.4em; } + #kodoc-breadcrumb li a { color: #999; text-decoration: none; } -#topics { } - #topics ul, - #topics ol { list-style-type:none; margin: 0; padding: 0;} - #topics ul li, - #topics ol li { margin:0; padding: 0; margin-left: 1em; } - #topics ul li a.current, - #topics ol li a.current { font-weight: bold; } - #topics span, - #topics a { display: block; padding: 0; margin: 0; } - #topics span { cursor: pointer; } - #topics span.toggle { display: block; float: left; width: 1em; padding-right: 0.4em; margin-left: -1.4em; text-align: center; } +#kodoc-topics { } + #kodoc-topics ul, + #kodoc-topics ol { list-style-type:none; margin: 0; padding: 0;} + #kodoc-topics ul li, + #kodoc-topics ol li { margin:0; padding: 0; margin-left: 1em; } + #kodoc-topics ul li a.current, + #kodoc-topics ol li a.current { font-weight: bold; } + #kodoc-topics span, + #kodoc-topics a { display: block; padding: 0; margin: 0; } + #kodoc-topics span { cursor: pointer; } + #kodoc-topics span.toggle { display: block; float: left; width: 1em; padding-right: 0.4em; margin-left: -1.4em; text-align: center; } - #topics li span { cursor:pointer; } + #kodoc-topics li span { cursor:pointer; } -#footer { padding: 1em 0; background: #00262f; color: #405c63; text-shadow: #00262f 0.1em 0.1em 1px; font-size: 0.9em; } - #footer a { color: #809397; } - #footer div.last { text-align: right; } \ No newline at end of file +#kodoc-footer { padding: 1em 0; background: #00262f; color: #405c63; text-shadow: #00262f 0.1em 0.1em 1px; font-size: 0.9em; } + #kodoc-footer a { color: #809397; } + #kodoc-footer div.last { text-align: right; } \ No newline at end of file diff --git a/includes/kohana/modules/userguide/media/guide/js/kodoc.js b/includes/kohana/modules/userguide/media/guide/js/kodoc.js index 70d8b488..597ebf9b 100644 --- a/includes/kohana/modules/userguide/media/guide/js/kodoc.js +++ b/includes/kohana/modules/userguide/media/guide/js/kodoc.js @@ -17,10 +17,10 @@ $(document).ready(function() $('a[href="'+ window.location.pathname +'"]').addClass('current'); // Breadcrumbs magic - $('#breadcrumb li.last').each(function() + $('#kodoc-breadcrumb li.last').each(function() { var $this = $(this); - var $topics = $('#topics li').has('a.current').slice(0, -1); + var $topics = $('#kodoc-topics li').has('a.current').slice(0, -1); $topics.each(function() { @@ -33,7 +33,7 @@ $(document).ready(function() }); // Collapsing menus - $('#topics li:has(li)').each(function() + $('#kodoc-topics li:has(li)').each(function() { var $this = $(this); var toggle = $(''); @@ -88,7 +88,7 @@ $(document).ready(function() }); // "Link to this" link that appears when you hover over a header - $('#body') + $('#kodoc-body') .find('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]') .append(function(){ var $this = $(this); diff --git a/includes/kohana/modules/userguide/tests/KodocTest.php b/includes/kohana/modules/userguide/tests/KodocTest.php new file mode 100644 index 00000000..8258f51b --- /dev/null +++ b/includes/kohana/modules/userguide/tests/KodocTest.php @@ -0,0 +1,368 @@ +Description

\n", array()), + ), + array( +<<<'COMMENT' +/** + * Description spanning + * multiple lines + */ +COMMENT +, + array("

Description spanning\nmultiple lines

\n", array()), + ), + array( +<<<'COMMENT' +/** + * Description including + * + * a code block + */ +COMMENT +, + array("

Description including

\n\n
a code block\n
\n", array()), + ), + array( +<<<'COMMENT' + /** + * Indented + */ +COMMENT +, + array("

Indented

\n", array()), + ), + array( +<<<'COMMENT' +/** + * @tag Content + */ +COMMENT +, + array('', array('tag' => array('Content'))), + ), + array( +<<<'COMMENT' +/** + * @tag Multiple + * @tag Tags + */ +COMMENT +, + array('', array('tag' => array('Multiple', 'Tags'))), + ), + array( +<<<'COMMENT' +/** + * Description with tag + * @tag Content + */ +COMMENT +, + array( + "

Description with tag

\n", + array('tag' => array('Content')), + ), + ), + array( +<<<'COMMENT' +/** + * @trailingspace + */ +COMMENT +, + array('', array('trailingspace' => array(''))), + ), + array( +<<<'COMMENT' +/** + * @tag Content that spans + * multiple lines + */ +COMMENT +, + array( + '', + array('tag' => array("Content that spans\nmultiple lines")), + ), + ), + array( +<<<'COMMENT' +/** + * @tag Content that spans + * multiple lines indented + */ +COMMENT +, + array( + '', + array('tag' => array("Content that spans\n multiple lines indented")), + ), + ), + ); + } + + /** + * @covers Kohana_Kodoc::parse + * + * @dataProvider provider_parse_basic + * + * @param string $comment Argument to the method + * @param array $expected Expected result + */ + public function test_parse_basic($comment, $expected) + { + $this->assertSame($expected, Kodoc::parse($comment)); + } + + public function provider_parse_tags() + { + $route_api = Route::get('docs/api'); + + return array( + array( +<<<'COMMENT' +/** + * @access public + */ +COMMENT +, + array('', array()), + ), + array( +<<<'COMMENT' +/** + * @copyright Some plain text + */ +COMMENT +, + array('', array('copyright' => array('Some plain text'))), + ), + array( +<<<'COMMENT' +/** + * @copyright (c) 2012 Kohana Team + */ +COMMENT +, + array('', array('copyright' => array('© 2012 Kohana Team'))), + ), + array( +<<<'COMMENT' +/** + * @license Kohana + */ +COMMENT +, + array('', array('license' => array('Kohana'))), + ), + array( +<<<'COMMENT' +/** + * @license http://kohanaframework.org/license + */ +COMMENT +, + array('', array('license' => array('http://kohanaframework.org/license'))), + ), + array( +<<<'COMMENT' +/** + * @link http://kohanaframework.org + */ +COMMENT +, + array('', array('link' => array('http://kohanaframework.org'))), + ), + array( +<<<'COMMENT' +/** + * @link http://kohanaframework.org Description + */ +COMMENT +, + array('', array('link' => array('Description'))), + ), + array( +<<<'COMMENT' +/** + * @see MyClass + */ +COMMENT +, + array( + '', + array( + 'see' => array( + 'MyClass', + ), + ), + ), + ), + array( +<<<'COMMENT' +/** + * @see MyClass::method() + */ +COMMENT +, + array( + '', + array( + 'see' => array( + 'MyClass::method()', + ), + ), + ), + ), + array( +<<<'COMMENT' +/** + * @throws Exception + */ +COMMENT +, + array( + '', + array( + 'throws' => array( + 'Exception', + ), + ), + ), + ), + array( +<<<'COMMENT' +/** + * @throws Exception During failure + */ +COMMENT +, + array( + '', + array( + 'throws' => array( + 'Exception During failure', + ), + ), + ), + ), + array( +<<<'COMMENT' +/** + * @uses MyClass + */ +COMMENT +, + array( + '', + array( + 'uses' => array( + 'MyClass', + ), + ), + ), + ), + array( +<<<'COMMENT' +/** + * @uses MyClass::method() + */ +COMMENT +, + array( + '', + array( + 'uses' => array( + 'MyClass::method()', + ), + ), + ), + ), + ); + } + + /** + * @covers Kohana_Kodoc::format_tag + * @covers Kohana_Kodoc::parse + * + * @dataProvider provider_parse_tags + * + * @param string $comment Argument to the method + * @param array $expected Expected result + */ + public function test_parse_tags($comment, $expected) + { + $this->assertSame($expected, Kodoc::parse($comment)); + } + + /** + * Provides test data for test_transparent_classes + * @return array + */ + public function provider_transparent_classes() + { + return array( + // Kohana_Core is a special case + array('Kohana','Kohana_Core',NULL), + array('Controller_Template','Kohana_Controller_Template',NULL), + array('Controller_Template','Kohana_Controller_Template', + array('Kohana_Controller_Template'=>'Kohana_Controller_Template', + 'Controller_Template'=>'Controller_Template') + ), + array(FALSE,'Kohana_Controller_Template', + array('Kohana_Controller_Template'=>'Kohana_Controller_Template')), + array(FALSE,'Controller_Template',NULL), + ); + } + + /** + * Tests Kodoc::is_transparent + * + * Checks that a selection of transparent and non-transparent classes give expected results + * + * @group kohana.userguide.3529-configurable-transparent-classes + * @dataProvider provider_transparent_classes + * @param mixed $expected + * @param string $class + * @param array $classes + */ + public function test_transparent_classes($expected, $class, $classes) + { + $result = Kodoc::is_transparent($class, $classes); + $this->assertSame($expected,$result); + } +} diff --git a/includes/kohana/modules/userguide/tests/userguide/ControllerTest.php b/includes/kohana/modules/userguide/tests/userguide/ControllerTest.php new file mode 100644 index 00000000..30ef4755 --- /dev/null +++ b/includes/kohana/modules/userguide/tests/userguide/ControllerTest.php @@ -0,0 +1,45 @@ +getMock('Controller_Userguide', array('__construct'), array(), '', FALSE); + $path = $controller->file($page); + + // Only verify trailing segments to avoid problems if file overwritten in CFS + $expected_len = strlen($expected_file); + $file = substr($path, -$expected_len, $expected_len); + + $this->assertEquals($expected_file, $file); + } +} diff --git a/includes/kohana/modules/userguide/views/userguide/api/class.php b/includes/kohana/modules/userguide/views/userguide/api/class.php index 0b54712a..08afd9d4 100644 --- a/includes/kohana/modules/userguide/views/userguide/api/class.php +++ b/includes/kohana/modules/userguide/views/userguide/api/class.php @@ -1,16 +1,34 @@

modifiers, $doc->class->name ?> - class; ?> - getParentClass()): ?> + parents as $parent): ?>
extends uri(array('class' => $parent->name)), $parent->name, NULL, NULL, TRUE) ?> - +

-description ?> +class->getInterfaceNames()): ?> +

+Implements: +uri(array('class' => $interfaces[$i])), $interfaces[$i], NULL, NULL, TRUE); +} +?> +

+ + +is_transparent($doc->class->name)):?> +

+This class is a transparent base class for uri(array('class'=>$child)),$child) ?> and +should not be accessed directly. +

+ + +description() ?> tags): ?>
-tags as $name => $set): ?> +tags() as $name => $set): ?>
@@ -29,38 +47,38 @@ Class is not declared in a file, it is probably an internal
-

+

    constants): ?> constants as $name => $value): ?>
  • -
  • +
-

+

-

+

@@ -70,9 +88,9 @@ Class is not declared in a file, it is probably an internal constants): ?>
-

+

-constants as $name => $value): ?> +constants() as $name => $value): ?>

@@ -81,20 +99,23 @@ Class is not declared in a file, it is probably an internal properties()): ?> -

+

modifiers ?> type ?> $property->name ?>

description ?>
value ?>
+default !== $prop->value): ?> +

default ?>
+
methods()): ?> -

+

set('doc', $method)->set('route', $route) ?> diff --git a/includes/kohana/modules/userguide/views/userguide/api/method.php b/includes/kohana/modules/userguide/views/userguide/api/method.php index 521dfde7..3dcd3329 100644 --- a/includes/kohana/modules/userguide/views/userguide/api/method.php +++ b/includes/kohana/modules/userguide/views/userguide/api/method.php @@ -27,7 +27,7 @@ tags) echo View::factory('userguide/api/tags')->set('tags', $doc->tags) ?> return): ?> -

+

    return as $set): list($type, $text) = $set; ?>
  • @@ -37,7 +37,7 @@ source): ?>
    -

    +

    source) ?>
    diff --git a/includes/kohana/modules/userguide/views/userguide/api/toc.php b/includes/kohana/modules/userguide/views/userguide/api/toc.php index 2a690053..88075d9e 100644 --- a/includes/kohana/modules/userguide/views/userguide/api/toc.php +++ b/includes/kohana/modules/userguide/views/userguide/api/toc.php @@ -1,62 +1,56 @@ -

    +

    - +
    diff --git a/includes/kohana/modules/userguide/views/userguide/error.php b/includes/kohana/modules/userguide/views/userguide/error.php index 1f7b97b7..1fb7cbb1 100644 --- a/includes/kohana/modules/userguide/views/userguide/error.php +++ b/includes/kohana/modules/userguide/views/userguide/error.php @@ -1,3 +1,3 @@ -

    Kodoc -

    +

    Kodoc -

    \ No newline at end of file diff --git a/includes/kohana/modules/userguide/views/userguide/template.php b/includes/kohana/modules/userguide/views/userguide/template.php index 8868d80f..d94efc4e 100644 --- a/includes/kohana/modules/userguide/views/userguide/template.php +++ b/includes/kohana/modules/userguide/views/userguide/template.php @@ -1,9 +1,9 @@ - + -<?php echo $title ?> | Kohana <?php echo __('User Guide'); ?> +<?php echo $title ?> | Kohana <?php echo 'User Guide'; ?> $media) echo HTML::style($style, array('media' => $media), NULL, TRUE), "\n" ?> @@ -15,29 +15,31 @@ -
PHP Version Kohana requires PHP 5.2.3 or newer, this version is .Kohana requires PHP 5.3.3 or newer, this version is .
System Directory The configured system directory does not exist or does not contain required files.