From 1dd63fc0441ff9e9ee10702f27786e54ee8244b9 Mon Sep 17 00:00:00 2001 From: Deon George Date: Wed, 20 Jul 2011 22:57:07 +1000 Subject: [PATCH] Init with KH 3.1.3.1 --- .htaccess | 21 + LICENSE | 674 ++++ application/bootstrap.php | 138 + application/cache/.htaccess | 2 + application/classes/auth/orm.php | 23 + application/classes/block.php | 4 + application/classes/breadcrumb.php | 4 + application/classes/config.php | 4 + application/classes/controller/account.php | 64 + .../classes/controller/account/welcome.php | 33 + .../classes/controller/admin/welcome.php | 33 + application/classes/controller/default.php | 4 + .../classes/controller/lnapp/default.php | 90 + .../classes/controller/lnapp/login.php | 199 ++ .../classes/controller/lnapp/logout.php | 26 + .../controller/lnapp/templatedefault.php | 284 ++ application/classes/controller/lnapp/tree.php | 107 + application/classes/controller/login.php | 4 + application/classes/controller/logout.php | 4 + application/classes/controller/redir.php | 20 + .../classes/controller/templatedefault.php | 36 + application/classes/controller/tree.php | 4 + application/classes/controller/welcome.php | 41 + application/classes/database/mysql.php | 20 + application/classes/editor.php | 53 + application/classes/form.php | 19 + application/classes/headimages.php | 4 + application/classes/html.php | 4 + application/classes/htmlrender.php | 4 + application/classes/http/exception/404.php | 30 + application/classes/lnapp/block.php | 81 + application/classes/lnapp/breadcrumb.php | 64 + application/classes/lnapp/config.php | 102 + application/classes/lnapp/headimages.php | 45 + application/classes/lnapp/html.php | 21 + application/classes/lnapp/htmlrender.php | 94 + application/classes/lnapp/meta.php | 34 + application/classes/lnapp/script.php | 59 + application/classes/lnapp/sort.php | 105 + application/classes/lnapp/style.php | 54 + application/classes/lnapp/systemmessage.php | 129 + application/classes/meta.php | 4 + application/classes/orm.php | 70 + application/classes/response.php | 19 + application/classes/script.php | 4 + application/classes/sort.php | 4 + application/classes/style.php | 4 + application/classes/systemmessage.php | 4 + application/classes/valid.php | 27 + application/config/auth.php | 21 + application/config/cache.php | 23 + application/config/config.php | 37 + application/config/database.php | 42 + application/i18n/.htaccess | 2 + application/media/css/default.css | 296 ++ application/media/css/jquery.gritter.css | 92 + application/media/css/jquery.jstree.css | 56 + application/media/css/login.css | 43 + .../media/img/address-book-new-big.png | Bin 0 -> 3787 bytes application/media/img/address-book-new.png | Bin 0 -> 1305 bytes application/media/img/ajax-progress.gif | Bin 0 -> 7685 bytes application/media/img/bug-big.png | Bin 0 -> 928 bytes application/media/img/dialog-error-big.png | Bin 0 -> 2383 bytes application/media/img/dialog-error.png | Bin 0 -> 1126 bytes .../media/img/dialog-information-big.png | Bin 0 -> 3165 bytes application/media/img/dialog-information.png | Bin 0 -> 1242 bytes application/media/img/dialog-password-big.png | Bin 0 -> 2992 bytes application/media/img/dialog-password.png | Bin 0 -> 1196 bytes application/media/img/dialog-question-big.png | Bin 0 -> 2502 bytes application/media/img/dialog-question.png | Bin 0 -> 1086 bytes application/media/img/dialog-warning-big.png | Bin 0 -> 2844 bytes application/media/img/dialog-warning.png | Bin 0 -> 1074 bytes application/media/img/forum-big.png | Bin 0 -> 1615 bytes application/media/img/gritter-close-ie6.gif | Bin 0 -> 718 bytes application/media/img/gritter.png | Bin 0 -> 4880 bytes application/media/img/help-about-big.png | Bin 0 -> 2978 bytes application/media/img/help-about.png | Bin 0 -> 981 bytes application/media/img/help-big.png | Bin 0 -> 1615 bytes application/media/img/help-faq-big.png | Bin 0 -> 2421 bytes application/media/img/help-faq.png | Bin 0 -> 1118 bytes application/media/img/help.png | Bin 0 -> 738 bytes .../media/img/jquery.gritter.close-ie6.gif | Bin 0 -> 718 bytes application/media/img/jquery.gritter.png | Bin 0 -> 4880 bytes application/media/img/jquery.jstree.d.png | Bin 0 -> 7635 bytes .../media/img/jquery.jstree.throbber.gif | Bin 0 -> 1849 bytes application/media/img/login.user.png | Bin 0 -> 654 bytes application/media/img/logo-small.png | Bin 0 -> 3978 bytes application/media/img/logo.png | Bin 0 -> 10495 bytes application/media/img/request-feature-big.png | Bin 0 -> 1095 bytes application/media/img/smile-big.png | Bin 0 -> 1332 bytes application/media/img/toggle-closed.png | Bin 0 -> 102 bytes application/media/img/toggle-open.png | Bin 0 -> 98 bytes application/media/js/jquery-1.4.2.js | 154 + application/media/js/jquery.cookie.js | 96 + application/media/js/jquery.gritter-1.5.js | 21 + application/media/js/jquery.jstree-1.0rc.js | 142 + .../media/js/jquery.stickyfloat-1.0.js | 48 + application/media/js/themes/default/d.png | Bin 0 -> 7635 bytes .../media/js/themes/default/dot_for_ie.gif | Bin 0 -> 43 bytes application/media/js/themes/default/style.css | 56 + .../media/js/themes/default/throbber.gif | Bin 0 -> 1849 bytes application/messages/.htaccess | 2 + application/views/.htaccess | 2 + application/views/lnapp/default.php | 93 + application/views/login.php | 16 + application/views/login_reset.php | 14 + application/views/login_reset_sent.php | 13 + application/views/template.php | 1 + application/views/userguide/template.php | 108 + includes/kohana/LICENSE.md | 14 + includes/kohana/README.md | 3 + includes/kohana/application/bootstrap.php | 118 + .../classes/controller/welcome.php | 10 + includes/kohana/install.php | 217 ++ includes/kohana/modules/auth/README.md | 13 + includes/kohana/modules/auth/classes/auth.php | 3 + .../kohana/modules/auth/classes/auth/file.php | 3 + .../modules/auth/classes/kohana/auth.php | 175 + .../modules/auth/classes/kohana/auth/file.php | 88 + includes/kohana/modules/auth/config/auth.php | 16 + .../kohana/modules/auth/guide/auth/config.md | 0 .../kohana/modules/auth/guide/auth/edit.md | 0 .../kohana/modules/auth/guide/auth/index.md | 0 .../kohana/modules/auth/guide/auth/login.md | 0 .../kohana/modules/auth/guide/auth/menu.md | 7 + .../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 | 61 + .../kohana/modules/cache/classes/cache.php | 3 + .../modules/cache/classes/cache/apc.php | 3 + .../cache/classes/cache/eaccelerator.php | 3 + .../modules/cache/classes/cache/file.php | 3 + .../modules/cache/classes/cache/memcache.php | 3 + .../cache/classes/cache/memcachetag.php | 3 + .../modules/cache/classes/cache/sqlite.php | 3 + .../modules/cache/classes/cache/wincache.php | 3 + .../modules/cache/classes/cache/xcache.php | 3 + .../modules/cache/classes/kohana/cache.php | 256 ++ .../cache/classes/kohana/cache/apc.php | 135 + .../classes/kohana/cache/eaccelerator.php | 133 + .../cache/classes/kohana/cache/exception.php | 11 + .../cache/classes/kohana/cache/file.php | 463 +++ .../classes/kohana/cache/garbagecollect.php | 23 + .../cache/classes/kohana/cache/memcache.php | 324 ++ .../classes/kohana/cache/memcachetag.php | 76 + .../cache/classes/kohana/cache/sqlite.php | 336 ++ .../cache/classes/kohana/cache/tagging.php | 42 + .../cache/classes/kohana/cache/wincache.php | 140 + .../cache/classes/kohana/cache/xcache.php | 84 + .../kohana/modules/cache/config/cache.php | 76 + .../kohana/modules/cache/config/userguide.php | 23 + .../kohana/modules/cache/guide/cache.usage.md | 219 ++ .../modules/cache/guide/cache/config.md | 168 + .../modules/cache/guide/cache/examples.md | 0 .../kohana/modules/cache/guide/cache/index.md | 59 + .../kohana/modules/cache/guide/cache/menu.md | 3 + .../kohana/modules/cache/guide/cache/usage.md | 219 ++ .../cache/tests/cache/KohanaCacheTest.php | 91 + .../kohana/modules/cache/tests/phpunit.xml | 16 + .../codebench/classes/bench/arrcallback.php | 57 + .../classes/bench/autolinkemails.php | 70 + .../codebench/classes/bench/datespan.php | 186 ++ .../codebench/classes/bench/explodelimit.php | 34 + .../codebench/classes/bench/gruberurl.php | 61 + .../codebench/classes/bench/ltrimdigits.php | 28 + .../codebench/classes/bench/mddobaseurl.php | 66 + .../codebench/classes/bench/mddoimageurl.php | 66 + .../classes/bench/mddoincludeviews.php | 50 + .../classes/bench/stripnullbytes.php | 37 + .../codebench/classes/bench/transliterate.php | 65 + .../codebench/classes/bench/urlsite.php | 123 + .../codebench/classes/bench/userfuncarray.php | 58 + .../codebench/classes/bench/validcolor.php | 116 + .../codebench/classes/bench/validurl.php | 105 + .../modules/codebench/classes/codebench.php | 3 + .../classes/controller/codebench.php | 34 + .../codebench/classes/kohana/codebench.php | 217 ++ .../modules/codebench/config/codebench.php | 16 + .../modules/codebench/config/userguide.php | 23 + .../codebench/guide/codebench/index.md | 76 + .../modules/codebench/guide/codebench/menu.md | 1 + includes/kohana/modules/codebench/init.php | 8 + .../guide/codebench/codebench_screenshot1.png | Bin 0 -> 15827 bytes .../guide/codebench/codebench_screenshot2.png | Bin 0 -> 13575 bytes .../modules/codebench/views/codebench.php | 260 ++ .../database/classes/config/database.php | 3 + .../modules/database/classes/database.php | 3 + .../database/classes/database/exception.php | 3 + .../database/classes/database/expression.php | 3 + .../database/classes/database/mysql.php | 3 + .../classes/database/mysql/result.php | 3 + .../modules/database/classes/database/pdo.php | 3 + .../database/classes/database/query.php | 3 + .../classes/database/query/builder.php | 3 + .../classes/database/query/builder/delete.php | 3 + .../classes/database/query/builder/insert.php | 3 + .../classes/database/query/builder/join.php | 3 + .../classes/database/query/builder/select.php | 3 + .../classes/database/query/builder/update.php | 3 + .../classes/database/query/builder/where.php | 3 + .../database/classes/database/result.php | 3 + .../classes/database/result/cached.php | 3 + .../kohana/modules/database/classes/db.php | 3 + .../classes/kohana/config/database.php | 97 + .../database/classes/kohana/database.php | 699 ++++ .../classes/kohana/database/exception.php | 11 + .../classes/kohana/database/expression.php | 62 + .../classes/kohana/database/mysql.php | 432 +++ .../classes/kohana/database/mysql/result.php | 71 + .../database/classes/kohana/database/pdo.php | 208 ++ .../classes/kohana/database/query.php | 236 ++ .../classes/kohana/database/query/builder.php | 209 ++ .../kohana/database/query/builder/delete.php | 93 + .../kohana/database/query/builder/insert.php | 175 + .../kohana/database/query/builder/join.php | 144 + .../kohana/database/query/builder/select.php | 445 +++ .../kohana/database/query/builder/update.php | 134 + .../kohana/database/query/builder/where.php | 160 + .../classes/kohana/database/result.php | 331 ++ .../classes/kohana/database/result/cached.php | 51 + .../modules/database/classes/kohana/db.php | 139 + .../classes/kohana/model/database.php | 58 + .../classes/kohana/session/database.php | 229 ++ .../database/classes/model/database.php | 3 + .../database/classes/session/database.php | 3 + .../modules/database/config/database.php | 57 + .../modules/database/config/session.php | 27 + .../modules/database/config/userguide.php | 23 + .../modules/database/guide/database/config.md | 118 + .../database/guide/database/examples.md | 52 + .../modules/database/guide/database/index.md | 17 + .../modules/database/guide/database/menu.md | 7 + .../modules/database/guide/database/query.md | 5 + .../database/guide/database/query/builder.md | 253 ++ .../database/guide/database/query/prepared.md | 67 + .../database/guide/database/results.md | 105 + includes/kohana/modules/email/README.markdown | 39 + .../kohana/modules/email/classes/email.php | 145 + .../kohana/modules/email/config/email.php | 29 + .../kohana/modules/email/vendor/swift/CHANGES | 61 + .../kohana/modules/email/vendor/swift/LICENSE | 165 + .../kohana/modules/email/vendor/swift/README | 30 + .../kohana/modules/email/vendor/swift/VERSION | 1 + .../email/vendor/swift/classes/Swift.php | 57 + .../vendor/swift/classes/Swift/Attachment.php | 75 + .../AbstractFilterableInputStream.php | 178 + .../Swift/ByteStream/ArrayByteStream.php | 190 ++ .../Swift/ByteStream/FileByteStream.php | 177 + .../swift/classes/Swift/CharacterReader.php | 60 + .../GenericFixedWidthReader.php | 96 + .../Swift/CharacterReader/UsAsciiReader.php | 83 + .../Swift/CharacterReader/Utf8Reader.php | 183 ++ .../classes/Swift/CharacterReaderFactory.php | 29 + .../SimpleCharacterReaderFactory.php | 119 + .../swift/classes/Swift/CharacterStream.php | 86 + .../CharacterStream/ArrayCharacterStream.php | 319 ++ .../CharacterStream/NgCharacterStream.php | 300 ++ .../classes/Swift/DependencyContainer.php | 349 ++ .../classes/Swift/DependencyException.php | 30 + .../swift/classes/Swift/EmbeddedFile.php | 73 + .../vendor/swift/classes/Swift/Encoder.php | 32 + .../classes/Swift/Encoder/Base64Encoder.php | 63 + .../swift/classes/Swift/Encoder/QpEncoder.php | 263 ++ .../classes/Swift/Encoder/Rfc2231Encoder.php | 89 + .../vendor/swift/classes/Swift/Encoding.php | 70 + .../classes/Swift/Events/CommandEvent.php | 67 + .../classes/Swift/Events/CommandListener.php | 29 + .../swift/classes/Swift/Events/Event.php | 39 + .../classes/Swift/Events/EventDispatcher.php | 81 + .../classes/Swift/Events/EventListener.php | 19 + .../classes/Swift/Events/EventObject.php | 65 + .../classes/Swift/Events/ResponseEvent.php | 65 + .../classes/Swift/Events/ResponseListener.php | 29 + .../swift/classes/Swift/Events/SendEvent.php | 127 + .../classes/Swift/Events/SendListener.php | 35 + .../Swift/Events/SimpleEventDispatcher.php | 175 + .../Swift/Events/TransportChangeEvent.php | 31 + .../Swift/Events/TransportChangeListener.php | 53 + .../Swift/Events/TransportExceptionEvent.php | 50 + .../Events/TransportExceptionListener.php | 30 + .../swift/classes/Swift/FailoverTransport.php | 48 + .../vendor/swift/classes/Swift/FileStream.php | 28 + .../vendor/swift/classes/Swift/Filterable.php | 34 + .../vendor/swift/classes/Swift/Image.php | 62 + .../swift/classes/Swift/InputByteStream.php | 72 + .../swift/classes/Swift/IoException.php | 30 + .../vendor/swift/classes/Swift/KeyCache.php | 99 + .../classes/Swift/KeyCache/ArrayKeyCache.php | 209 ++ .../classes/Swift/KeyCache/DiskKeyCache.php | 316 ++ .../Swift/KeyCache/KeyCacheInputStream.php | 53 + .../classes/Swift/KeyCache/NullKeyCache.php | 110 + .../KeyCache/SimpleKeyCacheInputStream.php | 131 + .../classes/Swift/LoadBalancedTransport.php | 48 + .../swift/classes/Swift/MailTransport.php | 48 + .../vendor/swift/classes/Swift/Mailer.php | 173 + .../Swift/Mailer/ArrayRecipientIterator.php | 59 + .../Swift/Mailer/RecipientIterator.php | 34 + .../vendor/swift/classes/Swift/Message.php | 82 + .../swift/classes/Swift/Mime/Attachment.php | 143 + .../classes/Swift/Mime/CharsetObserver.php | 26 + .../classes/Swift/Mime/ContentEncoder.php | 41 + .../ContentEncoder/Base64ContentEncoder.php | 81 + .../ContentEncoder/PlainContentEncoder.php | 175 + .../Mime/ContentEncoder/QpContentEncoder.php | 117 + .../swift/classes/Swift/Mime/EmbeddedFile.php | 51 + .../classes/Swift/Mime/EncodingObserver.php | 28 + .../swift/classes/Swift/Mime/Header.php | 85 + .../classes/Swift/Mime/HeaderEncoder.php | 28 + .../HeaderEncoder/Base64HeaderEncoder.php | 36 + .../Mime/HeaderEncoder/QpHeaderEncoder.php | 99 + .../classes/Swift/Mime/HeaderFactory.php | 72 + .../swift/classes/Swift/Mime/HeaderSet.php | 170 + .../Swift/Mime/Headers/AbstractHeader.php | 596 ++++ .../classes/Swift/Mime/Headers/DateHeader.php | 118 + .../Mime/Headers/IdentificationHeader.php | 161 + .../Swift/Mime/Headers/MailboxHeader.php | 316 ++ .../Mime/Headers/ParameterizedHeader.php | 274 ++ .../classes/Swift/Mime/Headers/PathHeader.php | 126 + .../Swift/Mime/Headers/UnstructuredHeader.php | 108 + .../swift/classes/Swift/Mime/Message.php | 230 ++ .../swift/classes/Swift/Mime/MimeEntity.php | 108 + .../swift/classes/Swift/Mime/MimePart.php | 196 ++ .../Swift/Mime/ParameterizedHeader.php | 35 + .../Swift/Mime/SimpleHeaderFactory.php | 187 ++ .../classes/Swift/Mime/SimpleHeaderSet.php | 396 +++ .../classes/Swift/Mime/SimpleMessage.php | 609 ++++ .../classes/Swift/Mime/SimpleMimeEntity.php | 803 +++++ .../vendor/swift/classes/Swift/MimePart.php | 65 + .../swift/classes/Swift/OutputByteStream.php | 41 + .../classes/Swift/Plugins/AntiFloodPlugin.php | 147 + .../Swift/Plugins/BandwidthMonitorPlugin.php | 173 + .../Swift/Plugins/Decorator/Replacements.php | 36 + .../classes/Swift/Plugins/DecoratorPlugin.php | 201 ++ .../swift/classes/Swift/Plugins/Logger.php | 37 + .../classes/Swift/Plugins/LoggerPlugin.php | 160 + .../Swift/Plugins/Loggers/ArrayLogger.php | 73 + .../Swift/Plugins/Loggers/EchoLogger.php | 64 + .../Swift/Plugins/Pop/Pop3Connection.php | 36 + .../Swift/Plugins/Pop/Pop3Exception.php | 34 + .../Swift/Plugins/PopBeforeSmtpPlugin.php | 288 ++ .../swift/classes/Swift/Plugins/Reporter.php | 36 + .../classes/Swift/Plugins/ReporterPlugin.php | 82 + .../Swift/Plugins/Reporters/HitReporter.php | 63 + .../Swift/Plugins/Reporters/HtmlReporter.php | 47 + .../swift/classes/Swift/Plugins/Sleeper.php | 26 + .../classes/Swift/Plugins/ThrottlerPlugin.php | 188 ++ .../swift/classes/Swift/Plugins/Timer.php | 26 + .../swift/classes/Swift/Preferences.php | 76 + .../Swift/ReplacementFilterFactory.php | 27 + .../classes/Swift/RfcComplianceException.php | 30 + .../swift/classes/Swift/SendmailTransport.php | 48 + .../swift/classes/Swift/SmtpTransport.php | 56 + .../swift/classes/Swift/StreamFilter.php | 33 + .../ByteArrayReplacementFilter.php | 188 ++ .../StreamFilters/StringReplacementFilter.php | 66 + .../StringReplacementFilterFactory.php | 53 + .../swift/classes/Swift/SwiftException.php | 28 + .../vendor/swift/classes/Swift/Transport.php | 60 + .../Swift/Transport/AbstractSmtpTransport.php | 543 +++ .../Esmtp/Auth/CramMd5Authenticator.php | 88 + .../Esmtp/Auth/LoginAuthenticator.php | 58 + .../Esmtp/Auth/PlainAuthenticator.php | 57 + .../Swift/Transport/Esmtp/AuthHandler.php | 262 ++ .../Swift/Transport/Esmtp/Authenticator.php | 38 + .../classes/Swift/Transport/EsmtpHandler.php | 82 + .../Swift/Transport/EsmtpTransport.php | 340 ++ .../Swift/Transport/FailoverTransport.php | 97 + .../classes/Swift/Transport/IoBuffer.php | 65 + .../Swift/Transport/LoadBalancedTransport.php | 188 ++ .../classes/Swift/Transport/MailInvoker.php | 36 + .../classes/Swift/Transport/MailTransport.php | 242 ++ .../Swift/Transport/SendmailTransport.php | 173 + .../Swift/Transport/SimpleMailInvoker.php | 58 + .../classes/Swift/Transport/SmtpAgent.php | 36 + .../classes/Swift/Transport/StreamBuffer.php | 276 ++ .../classes/Swift/TransportException.php | 31 + .../swift/dependency_maps/cache_deps.php | 25 + .../swift/dependency_maps/mime_deps.php | 97 + .../swift/dependency_maps/transport_deps.php | 62 + .../modules/email/vendor/swift/mime_types.php | 76 + .../email/vendor/swift/preferences.php | 20 + .../modules/email/vendor/swift/swift_init.php | 21 + .../email/vendor/swift/swift_required.php | 22 + .../vendor/swift/swift_required_pear.php | 22 + includes/kohana/modules/image/README.markdown | 0 .../kohana/modules/image/classes/image.php | 3 + .../kohana/modules/image/classes/image/gd.php | 3 + .../modules/image/classes/kohana/image.php | 747 +++++ .../modules/image/classes/kohana/image/gd.php | 584 ++++ .../kohana/modules/image/config/userguide.php | 23 + .../modules/image/guide/image/examples.md | 0 .../kohana/modules/image/guide/image/index.md | 0 .../kohana/modules/image/guide/image/menu.md | 3 + .../kohana/modules/image/guide/image/using.md | 0 .../kohana/modules/orm/auth-schema-mysql.sql | 49 + .../modules/orm/auth-schema-postgresql.sql | 53 + .../kohana/modules/orm/classes/auth/orm.php | 3 + .../modules/orm/classes/kohana/auth/orm.php | 277 ++ .../kohana/modules/orm/classes/kohana/orm.php | 1688 ++++++++++ .../kohana/orm/validation/exception.php | 182 ++ .../modules/orm/classes/model/auth/role.php | 29 + .../modules/orm/classes/model/auth/user.php | 240 ++ .../orm/classes/model/auth/user/token.php | 70 + .../kohana/modules/orm/classes/model/role.php | 7 + .../kohana/modules/orm/classes/model/user.php | 7 + .../modules/orm/classes/model/user/token.php | 7 + includes/kohana/modules/orm/classes/orm.php | 3 + .../orm/classes/orm/validation/exception.php | 10 + .../kohana/modules/orm/config/userguide.php | 23 + .../kohana/modules/orm/guide/orm/examples.md | 13 + .../modules/orm/guide/orm/examples/simple.md | 119 + .../orm/guide/orm/examples/validation.md | 137 + .../kohana/modules/orm/guide/orm/filters.md | 22 + .../kohana/modules/orm/guide/orm/index.md | 22 + includes/kohana/modules/orm/guide/orm/menu.md | 9 + .../kohana/modules/orm/guide/orm/models.md | 28 + .../modules/orm/guide/orm/relationships.md | 123 + .../kohana/modules/orm/guide/orm/tutorials.md | 0 .../kohana/modules/orm/guide/orm/using.md | 75 + .../modules/orm/guide/orm/validation.md | 111 + .../kohana/modules/unittest/README.markdown | 36 + .../kohana/modules/unittest/bootstrap.php | 104 + .../unittest/classes/controller/unittest.php | 352 ++ .../kohana/unittest/database/testcase.php | 291 ++ .../classes/kohana/unittest/helpers.php | 169 + .../classes/kohana/unittest/runner.php | 313 ++ .../classes/kohana/unittest/testcase.php | 246 ++ .../classes/kohana/unittest/tests.php | 336 ++ .../unittest/classes/unittest/helpers.php | 3 + .../unittest/classes/unittest/runner.php | 3 + .../unittest/classes/unittest/testcase.php | 3 + .../unittest/classes/unittest/tests.php | 3 + .../modules/unittest/config/unittest.php | 53 + .../modules/unittest/config/userguide.php | 23 + .../modules/unittest/example.phpunit.xml | 16 + .../modules/unittest/guide/unittest/index.md | 2 + .../modules/unittest/guide/unittest/menu.md | 5 + .../unittest/guide/unittest/mockobjects.md | 265 ++ .../unittest/guide/unittest/testing.md | 117 + .../guide/unittest/testing_workflows.md | 51 + .../guide/unittest/troubleshooting.md | 21 + includes/kohana/modules/unittest/init.php | 16 + includes/kohana/modules/unittest/tests.php | 20 + .../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 | 105 + .../classes/controller/userguide.php | 390 +++ .../modules/userguide/classes/kodoc.php | 3 + .../modules/userguide/classes/kodoc/class.php | 3 + .../userguide/classes/kodoc/markdown.php | 3 + .../userguide/classes/kodoc/method.php | 3 + .../userguide/classes/kodoc/method/param.php | 3 + .../userguide/classes/kodoc/missing.php | 3 + .../userguide/classes/kodoc/property.php | 3 + .../userguide/classes/kohana/kodoc.php | 354 ++ .../userguide/classes/kohana/kodoc/class.php | 216 ++ .../classes/kohana/kodoc/markdown.php | 264 ++ .../userguide/classes/kohana/kodoc/method.php | 141 + .../classes/kohana/kodoc/method/param.php | 101 + .../classes/kohana/kodoc/missing.php | 36 + .../classes/kohana/kodoc/property.php | 83 + .../modules/userguide/config/userguide.php | 31 + .../guide/de-de/about.conventions.md | 300 ++ .../userguide/guide/de-de/about.kohana.md | 15 + .../modules/userguide/guide/developers.md | 150 + .../userguide/guide/userguide/adding.md | 41 + .../userguide/guide/userguide/config.md | 35 + .../userguide/guide/userguide/contributing.md | 66 + .../userguide/guide/userguide/index.md | 3 + .../userguide/guide/userguide/markdown.md | 235 ++ .../modules/userguide/guide/userguide/menu.md | 7 + .../userguide/guide/userguide/modules.md | 0 .../userguide/guide/userguide/using.md | 1 + .../userguide/guide/userguide/works.md | 25 + 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 | 30 + .../modules/userguide/media/guide/css/api.css | 34 + .../userguide/media/guide/css/kodoc.css | 142 + .../userguide/media/guide/css/print.css | 50 + .../userguide/media/guide/css/screen.css | 267 ++ .../userguide/media/guide/css/shCore.css | 226 ++ .../media/guide/css/shThemeDefault.css | 117 + .../media/guide/css/shThemeKodoc.css | 183 ++ .../userguide/media/guide/img/arrows.png | Bin 0 -> 357 bytes .../userguide/media/guide/img/breadcrumbs.png | Bin 0 -> 195 bytes .../userguide/media/guide/img/content.png | Bin 0 -> 2983 bytes .../userguide/media/guide/img/ext_link.png | Bin 0 -> 144 bytes .../userguide/media/guide/img/h2_line.png | Bin 0 -> 184 bytes .../userguide/media/guide/img/h3_line.png | Bin 0 -> 184 bytes .../userguide/media/guide/img/header.png | Bin 0 -> 2998 bytes .../userguide/media/guide/img/kohana.png | Bin 0 -> 8678 bytes .../media/guide/img/lightbulb_48.png | Bin 0 -> 4843 bytes .../userguide/media/guide/img/lines.png | Bin 0 -> 181 bytes .../userguide/media/guide/img/orange-tab.png | Bin 0 -> 215 bytes .../userguide/media/guide/img/wrapper.png | Bin 0 -> 12347 bytes .../userguide/media/guide/js/jquery.cookie.js | 96 + .../userguide/media/guide/js/jquery.min.js | 167 + .../modules/userguide/media/guide/js/kodoc.js | 97 + .../userguide/media/guide/js/shBrushPhp.js | 88 + .../userguide/media/guide/js/shCore.js | 17 + .../userguide/media/guide/js/sizzle.js | 1068 ++++++ .../guide/userguide/contrib-github-edit.png | Bin 0 -> 9681 bytes .../guide/userguide/contrib-github-fork.png | Bin 0 -> 8278 bytes .../guide/userguide/contrib-github-pull.png | Bin 0 -> 8839 bytes .../modules/userguide/messages/userguide.php | 14 + .../userguide/vendor/markdown/License.text | 36 + .../userguide/vendor/markdown/markdown.php | 2909 +++++++++++++++++ .../userguide/views/userguide/api/class.php | 103 + .../userguide/views/userguide/api/menu.php | 18 + .../userguide/views/userguide/api/method.php | 45 + .../userguide/views/userguide/api/tags.php | 6 + .../userguide/views/userguide/api/toc.php | 75 + .../userguide/views/userguide/error.php | 3 + .../views/userguide/examples/error.php | 6 + .../userguide/examples/hello_world_error.php | 696 ++++ .../userguide/views/userguide/index.php | 20 + .../userguide/views/userguide/menu.php | 17 + .../userguide/views/userguide/page-toc.php | 10 + .../userguide/views/userguide/template.php | 108 + 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 + .../system/classes/controller/template.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 + .../system/classes/http/exception/400.php | 3 + .../system/classes/http/exception/401.php | 3 + .../system/classes/http/exception/402.php | 3 + .../system/classes/http/exception/403.php | 3 + .../system/classes/http/exception/404.php | 3 + .../system/classes/http/exception/405.php | 3 + .../system/classes/http/exception/406.php | 3 + .../system/classes/http/exception/407.php | 3 + .../system/classes/http/exception/408.php | 3 + .../system/classes/http/exception/409.php | 3 + .../system/classes/http/exception/410.php | 3 + .../system/classes/http/exception/411.php | 3 + .../system/classes/http/exception/412.php | 3 + .../system/classes/http/exception/413.php | 3 + .../system/classes/http/exception/414.php | 3 + .../system/classes/http/exception/415.php | 3 + .../system/classes/http/exception/416.php | 3 + .../system/classes/http/exception/417.php | 3 + .../system/classes/http/exception/500.php | 3 + .../system/classes/http/exception/501.php | 3 + .../system/classes/http/exception/502.php | 3 + .../system/classes/http/exception/503.php | 3 + .../system/classes/http/exception/504.php | 3 + .../system/classes/http/exception/505.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/arr.php | 574 ++++ includes/kohana/system/classes/kohana/cli.php | 75 + .../kohana/system/classes/kohana/config.php | 157 + .../system/classes/kohana/config/file.php | 60 + .../system/classes/kohana/config/reader.php | 115 + .../system/classes/kohana/controller.php | 75 + .../system/classes/kohana/controller/rest.php | 96 + .../classes/kohana/controller/template.php | 50 + .../kohana/system/classes/kohana/cookie.php | 161 + .../kohana/system/classes/kohana/core.php | 1040 ++++++ .../kohana/system/classes/kohana/date.php | 598 ++++ .../kohana/system/classes/kohana/debug.php | 468 +++ .../kohana/system/classes/kohana/encrypt.php | 213 ++ .../system/classes/kohana/exception.php | 3 + .../kohana/system/classes/kohana/feed.php | 176 + .../kohana/system/classes/kohana/file.php | 243 ++ .../kohana/system/classes/kohana/form.php | 434 +++ .../kohana/system/classes/kohana/fragment.php | 147 + .../kohana/system/classes/kohana/html.php | 386 +++ .../kohana/system/classes/kohana/http.php | 160 + .../system/classes/kohana/http/exception.php | 34 + .../classes/kohana/http/exception/400.php | 10 + .../classes/kohana/http/exception/401.php | 10 + .../classes/kohana/http/exception/402.php | 10 + .../classes/kohana/http/exception/403.php | 10 + .../classes/kohana/http/exception/404.php | 10 + .../classes/kohana/http/exception/405.php | 10 + .../classes/kohana/http/exception/406.php | 10 + .../classes/kohana/http/exception/407.php | 10 + .../classes/kohana/http/exception/408.php | 10 + .../classes/kohana/http/exception/409.php | 10 + .../classes/kohana/http/exception/410.php | 10 + .../classes/kohana/http/exception/411.php | 10 + .../classes/kohana/http/exception/412.php | 10 + .../classes/kohana/http/exception/413.php | 10 + .../classes/kohana/http/exception/414.php | 10 + .../classes/kohana/http/exception/415.php | 10 + .../classes/kohana/http/exception/416.php | 10 + .../classes/kohana/http/exception/417.php | 10 + .../classes/kohana/http/exception/500.php | 10 + .../classes/kohana/http/exception/501.php | 10 + .../classes/kohana/http/exception/502.php | 10 + .../classes/kohana/http/exception/503.php | 10 + .../classes/kohana/http/exception/504.php | 10 + .../classes/kohana/http/exception/505.php | 10 + .../system/classes/kohana/http/header.php | 310 ++ .../classes/kohana/http/header/value.php | 219 ++ .../classes/kohana/http/interaction.php | 56 + .../system/classes/kohana/http/request.php | 64 + .../system/classes/kohana/http/response.php | 31 + .../kohana/system/classes/kohana/i18n.php | 166 + .../system/classes/kohana/inflector.php | 269 ++ .../classes/kohana/kohana/exception.php | 206 ++ includes/kohana/system/classes/kohana/log.php | 205 ++ .../kohana/system/classes/kohana/log/file.php | 95 + .../system/classes/kohana/log/stderr.php | 31 + .../system/classes/kohana/log/stdout.php | 31 + .../system/classes/kohana/log/syslog.php | 70 + .../system/classes/kohana/log/writer.php | 49 + .../kohana/system/classes/kohana/model.php | 29 + includes/kohana/system/classes/kohana/num.php | 234 ++ .../kohana/system/classes/kohana/profiler.php | 385 +++ .../kohana/system/classes/kohana/request.php | 1501 +++++++++ .../system/classes/kohana/request/client.php | 328 ++ .../kohana/request/client/external.php | 427 +++ .../kohana/request/client/internal.php | 179 + .../classes/kohana/request/exception.php | 9 + .../kohana/system/classes/kohana/response.php | 933 ++++++ .../kohana/system/classes/kohana/route.php | 541 +++ .../kohana/system/classes/kohana/security.php | 103 + .../kohana/system/classes/kohana/session.php | 434 +++ .../system/classes/kohana/session/cookie.php | 47 + .../system/classes/kohana/session/native.php | 93 + .../kohana/system/classes/kohana/text.php | 590 ++++ .../kohana/system/classes/kohana/upload.php | 190 ++ includes/kohana/system/classes/kohana/url.php | 194 ++ .../kohana/system/classes/kohana/utf8.php | 767 +++++ .../kohana/system/classes/kohana/valid.php | 518 +++ .../system/classes/kohana/validation.php | 518 +++ .../classes/kohana/validation/exception.php | 29 + .../kohana/system/classes/kohana/view.php | 346 ++ .../system/classes/kohana/view/exception.php | 9 + 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 + .../kohana/system/classes/request/client.php | 3 + .../classes/request/client/external.php | 3 + .../classes/request/client/internal.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 + .../system/classes/validation/exception.php | 3 + includes/kohana/system/classes/view.php | 3 + .../kohana/system/config/credit_cards.php | 60 + includes/kohana/system/config/curl.php | 9 + includes/kohana/system/config/encrypt.php | 17 + includes/kohana/system/config/inflector.php | 71 + includes/kohana/system/config/mimes.php | 226 ++ includes/kohana/system/config/session.php | 7 + includes/kohana/system/config/user_agents.php | 106 + includes/kohana/system/config/userguide.php | 23 + .../kohana/system/guide/kohana/autoloading.md | 69 + .../kohana/system/guide/kohana/bootstrap.md | 111 + .../kohana/system/guide/kohana/controllers.md | 1 + .../kohana/system/guide/kohana/conventions.md | 417 +++ .../kohana/system/guide/kohana/cookies.md | 100 + .../kohana/system/guide/kohana/debugging.md | 20 + includes/kohana/system/guide/kohana/errors.md | 76 + .../kohana/system/guide/kohana/extension.md | 101 + includes/kohana/system/guide/kohana/files.md | 83 + .../system/guide/kohana/files/classes.md | 41 + .../system/guide/kohana/files/config.md | 102 + .../kohana/system/guide/kohana/files/i18n.md | 67 + .../system/guide/kohana/files/messages.md | 36 + includes/kohana/system/guide/kohana/flow.md | 27 + .../kohana/system/guide/kohana/fragments.md | 135 + .../kohana/system/guide/kohana/helpers.md | 53 + includes/kohana/system/guide/kohana/index.md | 19 + .../kohana/system/guide/kohana/install.md | 26 + includes/kohana/system/guide/kohana/menu.md | 47 + .../kohana/system/guide/kohana/modules.md | 40 + includes/kohana/system/guide/kohana/mvc.md | 3 + .../system/guide/kohana/mvc/controllers.md | 182 ++ .../kohana/system/guide/kohana/mvc/models.md | 35 + .../kohana/system/guide/kohana/mvc/views.md | 153 + .../kohana/system/guide/kohana/profiling.md | 54 + .../kohana/system/guide/kohana/requests.md | 65 + .../kohana/system/guide/kohana/routing.md | 271 ++ .../kohana/system/guide/kohana/security.md | 1 + .../system/guide/kohana/security/cookies.md | 3 + .../system/guide/kohana/security/database.md | 5 + .../system/guide/kohana/security/deploying.md | 61 + .../guide/kohana/security/encryption.md | 1 + .../guide/kohana/security/validation.md | 234 ++ .../system/guide/kohana/security/xss.md | 17 + .../kohana/system/guide/kohana/sessions.md | 167 + includes/kohana/system/guide/kohana/tips.md | 33 + .../kohana/system/guide/kohana/tutorials.md | 17 + .../guide/kohana/tutorials/clean-urls.md | 80 + .../guide/kohana/tutorials/error-pages.md | 154 + .../system/guide/kohana/tutorials/git.md | 143 + .../guide/kohana/tutorials/hello-world.md | 106 + .../guide/kohana/tutorials/sharing-kohana.md | 54 + .../guide/kohana/tutorials/simple-mvc.md | 1 + .../guide/kohana/tutorials/templates.md | 7 + .../guide/kohana/tutorials/translation.md | 5 + .../kohana/system/guide/kohana/upgrading.md | 93 + includes/kohana/system/i18n/en.php | 3 + includes/kohana/system/i18n/es.php | 7 + includes/kohana/system/i18n/fr.php | 7 + .../guide/kohana/cascading_filesystem.png | Bin 0 -> 61164 bytes .../media/guide/kohana/hello_world_1.png | Bin 0 -> 1423 bytes .../media/guide/kohana/hello_world_2.png | Bin 0 -> 6681 bytes .../guide/kohana/hello_world_2_error.png | Bin 0 -> 84148 bytes .../system/media/guide/kohana/install.png | Bin 0 -> 71146 bytes .../system/media/guide/kohana/welcome.png | Bin 0 -> 754 bytes .../kohana/system/messages/validation.php | 27 + .../kohana/system/tests/kohana/ArrTest.php | 554 ++++ .../kohana/system/tests/kohana/CLITest.php | 168 + .../kohana/system/tests/kohana/ConfigTest.php | 245 ++ .../kohana/system/tests/kohana/CookieTest.php | 160 + .../kohana/system/tests/kohana/CoreTest.php | 326 ++ .../kohana/system/tests/kohana/DateTest.php | 780 +++++ .../kohana/system/tests/kohana/DebugTest.php | 113 + .../system/tests/kohana/ExceptionTest.php | 92 + .../kohana/system/tests/kohana/FeedTest.php | 125 + .../kohana/system/tests/kohana/FileTest.php | 77 + .../kohana/system/tests/kohana/FormTest.php | 393 +++ .../kohana/system/tests/kohana/HTMLTest.php | 308 ++ .../tests/kohana/Http/Header/ValueTest.php | 164 + .../system/tests/kohana/Http/HeaderTest.php | 129 + .../kohana/system/tests/kohana/I18nTest.php | 80 + .../system/tests/kohana/InflectorTest.php | 185 ++ .../kohana/system/tests/kohana/LogTest.php | 110 + .../kohana/system/tests/kohana/ModelTest.php | 35 + .../kohana/system/tests/kohana/NumTest.php | 200 ++ .../system/tests/kohana/RequestTest.php | 853 +++++ .../system/tests/kohana/ResponseTest.php | 244 ++ .../kohana/system/tests/kohana/RouteTest.php | 715 ++++ .../system/tests/kohana/SecurityTest.php | 93 + .../system/tests/kohana/SessionTest.php | 499 +++ .../kohana/system/tests/kohana/TextTest.php | 600 ++++ .../kohana/system/tests/kohana/URLTest.php | 272 ++ .../kohana/system/tests/kohana/UTF8Test.php | 630 ++++ .../kohana/system/tests/kohana/UploadTest.php | 224 ++ .../kohana/system/tests/kohana/ValidTest.php | 882 +++++ .../system/tests/kohana/ValidationTest.php | 495 +++ .../kohana/system/tests/kohana/ViewTest.php | 78 + .../tests/kohana/request/client/internal.php | 89 + .../tests/test_data/callback_routes.php | 78 + .../kohana/system/tests/test_data/github.png | Bin 0 -> 5101 bytes .../system/tests/test_data/views/test.css.php | 1 + includes/kohana/system/utf8/from_unicode.php | 68 + includes/kohana/system/utf8/ltrim.php | 22 + includes/kohana/system/utf8/ord.php | 76 + includes/kohana/system/utf8/rtrim.php | 22 + includes/kohana/system/utf8/str_ireplace.php | 70 + includes/kohana/system/utf8/str_pad.php | 50 + includes/kohana/system/utf8/str_split.php | 27 + includes/kohana/system/utf8/strcasecmp.php | 19 + includes/kohana/system/utf8/strcspn.php | 30 + includes/kohana/system/utf8/stristr.php | 28 + includes/kohana/system/utf8/strlen.php | 17 + includes/kohana/system/utf8/strpos.php | 27 + includes/kohana/system/utf8/strrev.php | 18 + includes/kohana/system/utf8/strrpos.php | 27 + includes/kohana/system/utf8/strspn.php | 30 + includes/kohana/system/utf8/strtolower.php | 81 + includes/kohana/system/utf8/strtoupper.php | 81 + includes/kohana/system/utf8/substr.php | 72 + .../kohana/system/utf8/substr_replace.php | 22 + includes/kohana/system/utf8/to_unicode.php | 144 + .../system/utf8/transliterate_to_ascii.php | 77 + includes/kohana/system/utf8/trim.php | 17 + includes/kohana/system/utf8/ucfirst.php | 18 + includes/kohana/system/utf8/ucwords.php | 23 + includes/kohana/system/views/kohana/error.php | 128 + .../system/views/kohana/generate_logo.php | 14 + includes/kohana/system/views/kohana/logo.php | 8 + .../kohana/system/views/profiler/stats.php | 74 + .../kohana/system/views/profiler/style.css | 27 + index.php | 121 + 816 files changed, 79597 insertions(+) create mode 100644 .htaccess create mode 100644 LICENSE create mode 100644 application/bootstrap.php create mode 100644 application/cache/.htaccess create mode 100644 application/classes/auth/orm.php create mode 100644 application/classes/block.php create mode 100644 application/classes/breadcrumb.php create mode 100644 application/classes/config.php create mode 100644 application/classes/controller/account.php create mode 100644 application/classes/controller/account/welcome.php create mode 100644 application/classes/controller/admin/welcome.php create mode 100644 application/classes/controller/default.php create mode 100644 application/classes/controller/lnapp/default.php create mode 100644 application/classes/controller/lnapp/login.php create mode 100644 application/classes/controller/lnapp/logout.php create mode 100644 application/classes/controller/lnapp/templatedefault.php create mode 100644 application/classes/controller/lnapp/tree.php create mode 100644 application/classes/controller/login.php create mode 100644 application/classes/controller/logout.php create mode 100644 application/classes/controller/redir.php create mode 100644 application/classes/controller/templatedefault.php create mode 100644 application/classes/controller/tree.php create mode 100644 application/classes/controller/welcome.php create mode 100644 application/classes/database/mysql.php create mode 100644 application/classes/editor.php create mode 100644 application/classes/form.php create mode 100644 application/classes/headimages.php create mode 100644 application/classes/html.php create mode 100644 application/classes/htmlrender.php create mode 100644 application/classes/http/exception/404.php create mode 100644 application/classes/lnapp/block.php create mode 100644 application/classes/lnapp/breadcrumb.php create mode 100644 application/classes/lnapp/config.php create mode 100644 application/classes/lnapp/headimages.php create mode 100644 application/classes/lnapp/html.php create mode 100644 application/classes/lnapp/htmlrender.php create mode 100644 application/classes/lnapp/meta.php create mode 100644 application/classes/lnapp/script.php create mode 100644 application/classes/lnapp/sort.php create mode 100644 application/classes/lnapp/style.php create mode 100644 application/classes/lnapp/systemmessage.php create mode 100644 application/classes/meta.php create mode 100644 application/classes/orm.php create mode 100644 application/classes/response.php create mode 100644 application/classes/script.php create mode 100644 application/classes/sort.php create mode 100644 application/classes/style.php create mode 100644 application/classes/systemmessage.php create mode 100644 application/classes/valid.php create mode 100644 application/config/auth.php create mode 100644 application/config/cache.php create mode 100644 application/config/config.php create mode 100644 application/config/database.php create mode 100644 application/i18n/.htaccess create mode 100644 application/media/css/default.css create mode 100644 application/media/css/jquery.gritter.css create mode 100644 application/media/css/jquery.jstree.css create mode 100644 application/media/css/login.css create mode 100644 application/media/img/address-book-new-big.png create mode 100644 application/media/img/address-book-new.png create mode 100644 application/media/img/ajax-progress.gif create mode 100644 application/media/img/bug-big.png create mode 100644 application/media/img/dialog-error-big.png create mode 100644 application/media/img/dialog-error.png create mode 100644 application/media/img/dialog-information-big.png create mode 100644 application/media/img/dialog-information.png create mode 100644 application/media/img/dialog-password-big.png create mode 100644 application/media/img/dialog-password.png create mode 100644 application/media/img/dialog-question-big.png create mode 100644 application/media/img/dialog-question.png create mode 100644 application/media/img/dialog-warning-big.png create mode 100644 application/media/img/dialog-warning.png create mode 100644 application/media/img/forum-big.png create mode 100644 application/media/img/gritter-close-ie6.gif create mode 100755 application/media/img/gritter.png create mode 100644 application/media/img/help-about-big.png create mode 100644 application/media/img/help-about.png create mode 100644 application/media/img/help-big.png create mode 100644 application/media/img/help-faq-big.png create mode 100644 application/media/img/help-faq.png create mode 100644 application/media/img/help.png create mode 100644 application/media/img/jquery.gritter.close-ie6.gif create mode 100644 application/media/img/jquery.gritter.png create mode 100644 application/media/img/jquery.jstree.d.png create mode 100644 application/media/img/jquery.jstree.throbber.gif create mode 100644 application/media/img/login.user.png create mode 100644 application/media/img/logo-small.png create mode 100644 application/media/img/logo.png create mode 100644 application/media/img/request-feature-big.png create mode 100644 application/media/img/smile-big.png create mode 100644 application/media/img/toggle-closed.png create mode 100644 application/media/img/toggle-open.png create mode 100644 application/media/js/jquery-1.4.2.js create mode 100644 application/media/js/jquery.cookie.js create mode 100644 application/media/js/jquery.gritter-1.5.js create mode 100644 application/media/js/jquery.jstree-1.0rc.js create mode 100644 application/media/js/jquery.stickyfloat-1.0.js create mode 100644 application/media/js/themes/default/d.png create mode 100644 application/media/js/themes/default/dot_for_ie.gif create mode 100644 application/media/js/themes/default/style.css create mode 100644 application/media/js/themes/default/throbber.gif create mode 100644 application/messages/.htaccess create mode 100644 application/views/.htaccess create mode 100644 application/views/lnapp/default.php create mode 100644 application/views/login.php create mode 100644 application/views/login_reset.php create mode 100644 application/views/login_reset_sent.php create mode 100644 application/views/template.php create mode 100644 application/views/userguide/template.php create mode 100644 includes/kohana/LICENSE.md create mode 100644 includes/kohana/README.md create mode 100644 includes/kohana/application/bootstrap.php create mode 100644 includes/kohana/application/classes/controller/welcome.php create mode 100644 includes/kohana/install.php create mode 100644 includes/kohana/modules/auth/README.md create mode 100644 includes/kohana/modules/auth/classes/auth.php create mode 100644 includes/kohana/modules/auth/classes/auth/file.php create mode 100644 includes/kohana/modules/auth/classes/kohana/auth.php create mode 100644 includes/kohana/modules/auth/classes/kohana/auth/file.php create mode 100644 includes/kohana/modules/auth/config/auth.php create mode 100644 includes/kohana/modules/auth/guide/auth/config.md create mode 100644 includes/kohana/modules/auth/guide/auth/edit.md create mode 100644 includes/kohana/modules/auth/guide/auth/index.md create mode 100644 includes/kohana/modules/auth/guide/auth/login.md create mode 100644 includes/kohana/modules/auth/guide/auth/menu.md create mode 100644 includes/kohana/modules/auth/guide/auth/register.md create mode 100644 includes/kohana/modules/auth/guide/auth/roles.md create mode 100644 includes/kohana/modules/auth/guide/auth/user.md create mode 100644 includes/kohana/modules/cache/README.md create mode 100644 includes/kohana/modules/cache/classes/cache.php create mode 100644 includes/kohana/modules/cache/classes/cache/apc.php create mode 100644 includes/kohana/modules/cache/classes/cache/eaccelerator.php create mode 100644 includes/kohana/modules/cache/classes/cache/file.php create mode 100644 includes/kohana/modules/cache/classes/cache/memcache.php create mode 100644 includes/kohana/modules/cache/classes/cache/memcachetag.php create mode 100644 includes/kohana/modules/cache/classes/cache/sqlite.php create mode 100644 includes/kohana/modules/cache/classes/cache/wincache.php create mode 100644 includes/kohana/modules/cache/classes/cache/xcache.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/apc.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/eaccelerator.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/exception.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/file.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/garbagecollect.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/memcache.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/memcachetag.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/sqlite.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/tagging.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/wincache.php create mode 100644 includes/kohana/modules/cache/classes/kohana/cache/xcache.php create mode 100644 includes/kohana/modules/cache/config/cache.php create mode 100644 includes/kohana/modules/cache/config/userguide.php create mode 100644 includes/kohana/modules/cache/guide/cache.usage.md create mode 100644 includes/kohana/modules/cache/guide/cache/config.md create mode 100644 includes/kohana/modules/cache/guide/cache/examples.md create mode 100644 includes/kohana/modules/cache/guide/cache/index.md create mode 100644 includes/kohana/modules/cache/guide/cache/menu.md create mode 100644 includes/kohana/modules/cache/guide/cache/usage.md create mode 100644 includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php create mode 100644 includes/kohana/modules/cache/tests/phpunit.xml create mode 100644 includes/kohana/modules/codebench/classes/bench/arrcallback.php create mode 100644 includes/kohana/modules/codebench/classes/bench/autolinkemails.php create mode 100644 includes/kohana/modules/codebench/classes/bench/datespan.php create mode 100644 includes/kohana/modules/codebench/classes/bench/explodelimit.php create mode 100644 includes/kohana/modules/codebench/classes/bench/gruberurl.php create mode 100644 includes/kohana/modules/codebench/classes/bench/ltrimdigits.php create mode 100644 includes/kohana/modules/codebench/classes/bench/mddobaseurl.php create mode 100644 includes/kohana/modules/codebench/classes/bench/mddoimageurl.php create mode 100644 includes/kohana/modules/codebench/classes/bench/mddoincludeviews.php create mode 100644 includes/kohana/modules/codebench/classes/bench/stripnullbytes.php create mode 100644 includes/kohana/modules/codebench/classes/bench/transliterate.php create mode 100644 includes/kohana/modules/codebench/classes/bench/urlsite.php create mode 100644 includes/kohana/modules/codebench/classes/bench/userfuncarray.php create mode 100644 includes/kohana/modules/codebench/classes/bench/validcolor.php create mode 100644 includes/kohana/modules/codebench/classes/bench/validurl.php create mode 100644 includes/kohana/modules/codebench/classes/codebench.php create mode 100644 includes/kohana/modules/codebench/classes/controller/codebench.php create mode 100644 includes/kohana/modules/codebench/classes/kohana/codebench.php create mode 100644 includes/kohana/modules/codebench/config/codebench.php create mode 100644 includes/kohana/modules/codebench/config/userguide.php create mode 100644 includes/kohana/modules/codebench/guide/codebench/index.md create mode 100644 includes/kohana/modules/codebench/guide/codebench/menu.md create mode 100644 includes/kohana/modules/codebench/init.php create mode 100644 includes/kohana/modules/codebench/media/guide/codebench/codebench_screenshot1.png create mode 100644 includes/kohana/modules/codebench/media/guide/codebench/codebench_screenshot2.png create mode 100644 includes/kohana/modules/codebench/views/codebench.php create mode 100644 includes/kohana/modules/database/classes/config/database.php create mode 100644 includes/kohana/modules/database/classes/database.php create mode 100644 includes/kohana/modules/database/classes/database/exception.php create mode 100644 includes/kohana/modules/database/classes/database/expression.php create mode 100644 includes/kohana/modules/database/classes/database/mysql.php create mode 100644 includes/kohana/modules/database/classes/database/mysql/result.php create mode 100644 includes/kohana/modules/database/classes/database/pdo.php create mode 100644 includes/kohana/modules/database/classes/database/query.php create mode 100644 includes/kohana/modules/database/classes/database/query/builder.php create mode 100644 includes/kohana/modules/database/classes/database/query/builder/delete.php create mode 100644 includes/kohana/modules/database/classes/database/query/builder/insert.php create mode 100644 includes/kohana/modules/database/classes/database/query/builder/join.php create mode 100644 includes/kohana/modules/database/classes/database/query/builder/select.php create mode 100644 includes/kohana/modules/database/classes/database/query/builder/update.php create mode 100644 includes/kohana/modules/database/classes/database/query/builder/where.php create mode 100644 includes/kohana/modules/database/classes/database/result.php create mode 100644 includes/kohana/modules/database/classes/database/result/cached.php create mode 100644 includes/kohana/modules/database/classes/db.php create mode 100644 includes/kohana/modules/database/classes/kohana/config/database.php create mode 100644 includes/kohana/modules/database/classes/kohana/database.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/exception.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/expression.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/mysql.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/mysql/result.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/pdo.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/query.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/query/builder.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/query/builder/delete.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/query/builder/insert.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/query/builder/join.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/query/builder/select.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/query/builder/update.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/query/builder/where.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/result.php create mode 100644 includes/kohana/modules/database/classes/kohana/database/result/cached.php create mode 100644 includes/kohana/modules/database/classes/kohana/db.php create mode 100644 includes/kohana/modules/database/classes/kohana/model/database.php create mode 100644 includes/kohana/modules/database/classes/kohana/session/database.php create mode 100644 includes/kohana/modules/database/classes/model/database.php create mode 100644 includes/kohana/modules/database/classes/session/database.php create mode 100644 includes/kohana/modules/database/config/database.php create mode 100644 includes/kohana/modules/database/config/session.php create mode 100644 includes/kohana/modules/database/config/userguide.php create mode 100644 includes/kohana/modules/database/guide/database/config.md create mode 100644 includes/kohana/modules/database/guide/database/examples.md create mode 100644 includes/kohana/modules/database/guide/database/index.md create mode 100644 includes/kohana/modules/database/guide/database/menu.md create mode 100644 includes/kohana/modules/database/guide/database/query.md create mode 100644 includes/kohana/modules/database/guide/database/query/builder.md create mode 100644 includes/kohana/modules/database/guide/database/query/prepared.md create mode 100644 includes/kohana/modules/database/guide/database/results.md create mode 100644 includes/kohana/modules/email/README.markdown create mode 100644 includes/kohana/modules/email/classes/email.php create mode 100644 includes/kohana/modules/email/config/email.php create mode 100644 includes/kohana/modules/email/vendor/swift/CHANGES create mode 100644 includes/kohana/modules/email/vendor/swift/LICENSE create mode 100644 includes/kohana/modules/email/vendor/swift/README create mode 100644 includes/kohana/modules/email/vendor/swift/VERSION create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Attachment.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/ByteStream/AbstractFilterableInputStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/ByteStream/ArrayByteStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/ByteStream/FileByteStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/CharacterReader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/CharacterReader/GenericFixedWidthReader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/CharacterReader/UsAsciiReader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/CharacterReader/Utf8Reader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/CharacterReaderFactory.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/CharacterReaderFactory/SimpleCharacterReaderFactory.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/CharacterStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/CharacterStream/ArrayCharacterStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/CharacterStream/NgCharacterStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/DependencyContainer.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/DependencyException.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/EmbeddedFile.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Encoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Encoder/Base64Encoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Encoder/QpEncoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Encoder/Rfc2231Encoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Encoding.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/CommandEvent.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/CommandListener.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/Event.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/EventDispatcher.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/EventListener.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/EventObject.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/ResponseEvent.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/ResponseListener.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/SendEvent.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/SendListener.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/SimpleEventDispatcher.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/TransportChangeEvent.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/TransportChangeListener.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/TransportExceptionEvent.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Events/TransportExceptionListener.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/FailoverTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/FileStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Filterable.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Image.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/InputByteStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/IoException.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/KeyCache.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/KeyCache/ArrayKeyCache.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/KeyCache/DiskKeyCache.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/KeyCache/KeyCacheInputStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/KeyCache/NullKeyCache.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/KeyCache/SimpleKeyCacheInputStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/LoadBalancedTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/MailTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mailer.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mailer/ArrayRecipientIterator.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mailer/RecipientIterator.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Message.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Attachment.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/CharsetObserver.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/ContentEncoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/EmbeddedFile.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/EncodingObserver.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Header.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/HeaderEncoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/HeaderEncoder/Base64HeaderEncoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/HeaderFactory.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/HeaderSet.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Headers/AbstractHeader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Headers/DateHeader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Headers/IdentificationHeader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Headers/MailboxHeader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Headers/ParameterizedHeader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Headers/PathHeader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Headers/UnstructuredHeader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/Message.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/MimeEntity.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/MimePart.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/ParameterizedHeader.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/SimpleHeaderFactory.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/SimpleHeaderSet.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/SimpleMessage.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Mime/SimpleMimeEntity.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/MimePart.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/OutputByteStream.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/AntiFloodPlugin.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/BandwidthMonitorPlugin.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Decorator/Replacements.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/DecoratorPlugin.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Logger.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/LoggerPlugin.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Loggers/ArrayLogger.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Loggers/EchoLogger.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Pop/Pop3Connection.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Pop/Pop3Exception.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/PopBeforeSmtpPlugin.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Reporter.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/ReporterPlugin.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Reporters/HitReporter.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Reporters/HtmlReporter.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Sleeper.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/ThrottlerPlugin.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Plugins/Timer.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Preferences.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/ReplacementFilterFactory.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/RfcComplianceException.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/SendmailTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/SmtpTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/StreamFilter.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/StreamFilters/ByteArrayReplacementFilter.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/StreamFilters/StringReplacementFilter.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/StreamFilters/StringReplacementFilterFactory.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/SwiftException.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/AbstractSmtpTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/Esmtp/AuthHandler.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/Esmtp/Authenticator.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/EsmtpHandler.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/EsmtpTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/FailoverTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/IoBuffer.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/LoadBalancedTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/MailInvoker.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/MailTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/SendmailTransport.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/SimpleMailInvoker.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/SmtpAgent.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/Transport/StreamBuffer.php create mode 100644 includes/kohana/modules/email/vendor/swift/classes/Swift/TransportException.php create mode 100644 includes/kohana/modules/email/vendor/swift/dependency_maps/cache_deps.php create mode 100644 includes/kohana/modules/email/vendor/swift/dependency_maps/mime_deps.php create mode 100644 includes/kohana/modules/email/vendor/swift/dependency_maps/transport_deps.php create mode 100644 includes/kohana/modules/email/vendor/swift/mime_types.php create mode 100644 includes/kohana/modules/email/vendor/swift/preferences.php create mode 100644 includes/kohana/modules/email/vendor/swift/swift_init.php create mode 100644 includes/kohana/modules/email/vendor/swift/swift_required.php create mode 100644 includes/kohana/modules/email/vendor/swift/swift_required_pear.php create mode 100644 includes/kohana/modules/image/README.markdown 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/kohana/image.php create mode 100644 includes/kohana/modules/image/classes/kohana/image/gd.php create mode 100644 includes/kohana/modules/image/config/userguide.php create mode 100644 includes/kohana/modules/image/guide/image/examples.md create mode 100644 includes/kohana/modules/image/guide/image/index.md create mode 100644 includes/kohana/modules/image/guide/image/menu.md create mode 100644 includes/kohana/modules/image/guide/image/using.md create mode 100644 includes/kohana/modules/orm/auth-schema-mysql.sql create mode 100644 includes/kohana/modules/orm/auth-schema-postgresql.sql create mode 100644 includes/kohana/modules/orm/classes/auth/orm.php create mode 100644 includes/kohana/modules/orm/classes/kohana/auth/orm.php create mode 100644 includes/kohana/modules/orm/classes/kohana/orm.php create mode 100644 includes/kohana/modules/orm/classes/kohana/orm/validation/exception.php create mode 100644 includes/kohana/modules/orm/classes/model/auth/role.php create mode 100644 includes/kohana/modules/orm/classes/model/auth/user.php create mode 100644 includes/kohana/modules/orm/classes/model/auth/user/token.php create mode 100644 includes/kohana/modules/orm/classes/model/role.php create mode 100644 includes/kohana/modules/orm/classes/model/user.php create mode 100644 includes/kohana/modules/orm/classes/model/user/token.php create mode 100644 includes/kohana/modules/orm/classes/orm.php create mode 100644 includes/kohana/modules/orm/classes/orm/validation/exception.php create mode 100644 includes/kohana/modules/orm/config/userguide.php create mode 100644 includes/kohana/modules/orm/guide/orm/examples.md create mode 100644 includes/kohana/modules/orm/guide/orm/examples/simple.md create mode 100644 includes/kohana/modules/orm/guide/orm/examples/validation.md create mode 100644 includes/kohana/modules/orm/guide/orm/filters.md create mode 100644 includes/kohana/modules/orm/guide/orm/index.md create mode 100644 includes/kohana/modules/orm/guide/orm/menu.md create mode 100644 includes/kohana/modules/orm/guide/orm/models.md create mode 100644 includes/kohana/modules/orm/guide/orm/relationships.md create mode 100644 includes/kohana/modules/orm/guide/orm/tutorials.md create mode 100644 includes/kohana/modules/orm/guide/orm/using.md create mode 100644 includes/kohana/modules/orm/guide/orm/validation.md create mode 100644 includes/kohana/modules/unittest/README.markdown create mode 100644 includes/kohana/modules/unittest/bootstrap.php create mode 100644 includes/kohana/modules/unittest/classes/controller/unittest.php create mode 100644 includes/kohana/modules/unittest/classes/kohana/unittest/database/testcase.php create mode 100644 includes/kohana/modules/unittest/classes/kohana/unittest/helpers.php create mode 100644 includes/kohana/modules/unittest/classes/kohana/unittest/runner.php create mode 100644 includes/kohana/modules/unittest/classes/kohana/unittest/testcase.php create mode 100644 includes/kohana/modules/unittest/classes/kohana/unittest/tests.php create mode 100644 includes/kohana/modules/unittest/classes/unittest/helpers.php create mode 100644 includes/kohana/modules/unittest/classes/unittest/runner.php create mode 100644 includes/kohana/modules/unittest/classes/unittest/testcase.php create mode 100644 includes/kohana/modules/unittest/classes/unittest/tests.php create mode 100644 includes/kohana/modules/unittest/config/unittest.php create mode 100644 includes/kohana/modules/unittest/config/userguide.php create mode 100644 includes/kohana/modules/unittest/example.phpunit.xml create mode 100644 includes/kohana/modules/unittest/guide/unittest/index.md create mode 100644 includes/kohana/modules/unittest/guide/unittest/menu.md create mode 100644 includes/kohana/modules/unittest/guide/unittest/mockobjects.md create mode 100644 includes/kohana/modules/unittest/guide/unittest/testing.md create mode 100644 includes/kohana/modules/unittest/guide/unittest/testing_workflows.md create mode 100644 includes/kohana/modules/unittest/guide/unittest/troubleshooting.md create mode 100644 includes/kohana/modules/unittest/init.php create mode 100644 includes/kohana/modules/unittest/tests.php create mode 100644 includes/kohana/modules/unittest/views/unittest/index.php create mode 100644 includes/kohana/modules/unittest/views/unittest/layout.php create mode 100644 includes/kohana/modules/unittest/views/unittest/results.php create mode 100644 includes/kohana/modules/userguide/README.md create mode 100644 includes/kohana/modules/userguide/classes/controller/userguide.php create mode 100644 includes/kohana/modules/userguide/classes/kodoc.php create mode 100644 includes/kohana/modules/userguide/classes/kodoc/class.php create mode 100644 includes/kohana/modules/userguide/classes/kodoc/markdown.php create mode 100644 includes/kohana/modules/userguide/classes/kodoc/method.php create mode 100644 includes/kohana/modules/userguide/classes/kodoc/method/param.php create mode 100644 includes/kohana/modules/userguide/classes/kodoc/missing.php create mode 100644 includes/kohana/modules/userguide/classes/kodoc/property.php create mode 100644 includes/kohana/modules/userguide/classes/kohana/kodoc.php create mode 100644 includes/kohana/modules/userguide/classes/kohana/kodoc/class.php create mode 100644 includes/kohana/modules/userguide/classes/kohana/kodoc/markdown.php create mode 100644 includes/kohana/modules/userguide/classes/kohana/kodoc/method.php create mode 100644 includes/kohana/modules/userguide/classes/kohana/kodoc/method/param.php create mode 100644 includes/kohana/modules/userguide/classes/kohana/kodoc/missing.php create mode 100644 includes/kohana/modules/userguide/classes/kohana/kodoc/property.php create mode 100644 includes/kohana/modules/userguide/config/userguide.php create mode 100644 includes/kohana/modules/userguide/guide/de-de/about.conventions.md create mode 100644 includes/kohana/modules/userguide/guide/de-de/about.kohana.md create mode 100644 includes/kohana/modules/userguide/guide/developers.md create mode 100644 includes/kohana/modules/userguide/guide/userguide/adding.md create mode 100644 includes/kohana/modules/userguide/guide/userguide/config.md create mode 100644 includes/kohana/modules/userguide/guide/userguide/contributing.md create mode 100644 includes/kohana/modules/userguide/guide/userguide/index.md create mode 100644 includes/kohana/modules/userguide/guide/userguide/markdown.md create mode 100644 includes/kohana/modules/userguide/guide/userguide/menu.md create mode 100644 includes/kohana/modules/userguide/guide/userguide/modules.md create mode 100644 includes/kohana/modules/userguide/guide/userguide/using.md create mode 100644 includes/kohana/modules/userguide/guide/userguide/works.md create mode 100644 includes/kohana/modules/userguide/i18n/de.php create mode 100644 includes/kohana/modules/userguide/i18n/es.php create mode 100644 includes/kohana/modules/userguide/i18n/fr.php create mode 100644 includes/kohana/modules/userguide/i18n/he.php create mode 100644 includes/kohana/modules/userguide/i18n/nl.php create mode 100644 includes/kohana/modules/userguide/i18n/ru.php create mode 100644 includes/kohana/modules/userguide/i18n/zh.php create mode 100644 includes/kohana/modules/userguide/init.php create mode 100644 includes/kohana/modules/userguide/media/guide/css/api.css create mode 100644 includes/kohana/modules/userguide/media/guide/css/kodoc.css create mode 100644 includes/kohana/modules/userguide/media/guide/css/print.css create mode 100644 includes/kohana/modules/userguide/media/guide/css/screen.css create mode 100644 includes/kohana/modules/userguide/media/guide/css/shCore.css create mode 100644 includes/kohana/modules/userguide/media/guide/css/shThemeDefault.css create mode 100644 includes/kohana/modules/userguide/media/guide/css/shThemeKodoc.css create mode 100644 includes/kohana/modules/userguide/media/guide/img/arrows.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/breadcrumbs.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/content.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/ext_link.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/h2_line.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/h3_line.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/header.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/kohana.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/lightbulb_48.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/lines.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/orange-tab.png create mode 100644 includes/kohana/modules/userguide/media/guide/img/wrapper.png create mode 100644 includes/kohana/modules/userguide/media/guide/js/jquery.cookie.js create mode 100644 includes/kohana/modules/userguide/media/guide/js/jquery.min.js create mode 100644 includes/kohana/modules/userguide/media/guide/js/kodoc.js create mode 100644 includes/kohana/modules/userguide/media/guide/js/shBrushPhp.js create mode 100644 includes/kohana/modules/userguide/media/guide/js/shCore.js create mode 100644 includes/kohana/modules/userguide/media/guide/js/sizzle.js create mode 100644 includes/kohana/modules/userguide/media/guide/userguide/contrib-github-edit.png create mode 100644 includes/kohana/modules/userguide/media/guide/userguide/contrib-github-fork.png create mode 100644 includes/kohana/modules/userguide/media/guide/userguide/contrib-github-pull.png create mode 100644 includes/kohana/modules/userguide/messages/userguide.php create mode 100644 includes/kohana/modules/userguide/vendor/markdown/License.text create mode 100644 includes/kohana/modules/userguide/vendor/markdown/markdown.php create mode 100644 includes/kohana/modules/userguide/views/userguide/api/class.php create mode 100644 includes/kohana/modules/userguide/views/userguide/api/menu.php create mode 100644 includes/kohana/modules/userguide/views/userguide/api/method.php create mode 100644 includes/kohana/modules/userguide/views/userguide/api/tags.php create mode 100644 includes/kohana/modules/userguide/views/userguide/api/toc.php create mode 100644 includes/kohana/modules/userguide/views/userguide/error.php create mode 100644 includes/kohana/modules/userguide/views/userguide/examples/error.php create mode 100644 includes/kohana/modules/userguide/views/userguide/examples/hello_world_error.php create mode 100644 includes/kohana/modules/userguide/views/userguide/index.php create mode 100644 includes/kohana/modules/userguide/views/userguide/menu.php create mode 100644 includes/kohana/modules/userguide/views/userguide/page-toc.php create mode 100644 includes/kohana/modules/userguide/views/userguide/template.php create mode 100644 includes/kohana/system/classes/arr.php create mode 100644 includes/kohana/system/classes/cli.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/reader.php create mode 100644 includes/kohana/system/classes/controller.php create mode 100644 includes/kohana/system/classes/controller/rest.php create mode 100644 includes/kohana/system/classes/controller/template.php 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/400.php create mode 100644 includes/kohana/system/classes/http/exception/401.php create mode 100644 includes/kohana/system/classes/http/exception/402.php create mode 100644 includes/kohana/system/classes/http/exception/403.php create mode 100644 includes/kohana/system/classes/http/exception/404.php create mode 100644 includes/kohana/system/classes/http/exception/405.php create mode 100644 includes/kohana/system/classes/http/exception/406.php create mode 100644 includes/kohana/system/classes/http/exception/407.php create mode 100644 includes/kohana/system/classes/http/exception/408.php create mode 100644 includes/kohana/system/classes/http/exception/409.php create mode 100644 includes/kohana/system/classes/http/exception/410.php create mode 100644 includes/kohana/system/classes/http/exception/411.php create mode 100644 includes/kohana/system/classes/http/exception/412.php create mode 100644 includes/kohana/system/classes/http/exception/413.php create mode 100644 includes/kohana/system/classes/http/exception/414.php create mode 100644 includes/kohana/system/classes/http/exception/415.php create mode 100644 includes/kohana/system/classes/http/exception/416.php create mode 100644 includes/kohana/system/classes/http/exception/417.php create mode 100644 includes/kohana/system/classes/http/exception/500.php create mode 100644 includes/kohana/system/classes/http/exception/501.php create mode 100644 includes/kohana/system/classes/http/exception/502.php create mode 100644 includes/kohana/system/classes/http/exception/503.php create mode 100644 includes/kohana/system/classes/http/exception/504.php create mode 100644 includes/kohana/system/classes/http/exception/505.php create mode 100644 includes/kohana/system/classes/http/header.php create mode 100644 includes/kohana/system/classes/http/header/value.php create mode 100644 includes/kohana/system/classes/http/interaction.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 create mode 100644 includes/kohana/system/classes/kohana/arr.php create mode 100644 includes/kohana/system/classes/kohana/cli.php create mode 100644 includes/kohana/system/classes/kohana/config.php create mode 100644 includes/kohana/system/classes/kohana/config/file.php create mode 100644 includes/kohana/system/classes/kohana/config/reader.php create mode 100644 includes/kohana/system/classes/kohana/controller.php create mode 100644 includes/kohana/system/classes/kohana/controller/rest.php create mode 100644 includes/kohana/system/classes/kohana/controller/template.php create mode 100644 includes/kohana/system/classes/kohana/cookie.php create mode 100644 includes/kohana/system/classes/kohana/core.php create mode 100644 includes/kohana/system/classes/kohana/date.php create mode 100644 includes/kohana/system/classes/kohana/debug.php create mode 100644 includes/kohana/system/classes/kohana/encrypt.php create mode 100644 includes/kohana/system/classes/kohana/exception.php create mode 100644 includes/kohana/system/classes/kohana/feed.php create mode 100644 includes/kohana/system/classes/kohana/file.php create mode 100644 includes/kohana/system/classes/kohana/form.php create mode 100644 includes/kohana/system/classes/kohana/fragment.php create mode 100644 includes/kohana/system/classes/kohana/html.php create mode 100644 includes/kohana/system/classes/kohana/http.php create mode 100644 includes/kohana/system/classes/kohana/http/exception.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/400.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/401.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/402.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/403.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/404.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/405.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/406.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/407.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/408.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/409.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/410.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/411.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/412.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/413.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/414.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/415.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/416.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/417.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/500.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/501.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/502.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/503.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/504.php create mode 100644 includes/kohana/system/classes/kohana/http/exception/505.php create mode 100644 includes/kohana/system/classes/kohana/http/header.php create mode 100644 includes/kohana/system/classes/kohana/http/header/value.php create mode 100644 includes/kohana/system/classes/kohana/http/interaction.php create mode 100644 includes/kohana/system/classes/kohana/http/request.php create mode 100644 includes/kohana/system/classes/kohana/http/response.php create mode 100644 includes/kohana/system/classes/kohana/i18n.php create mode 100644 includes/kohana/system/classes/kohana/inflector.php create mode 100644 includes/kohana/system/classes/kohana/kohana/exception.php create mode 100644 includes/kohana/system/classes/kohana/log.php create mode 100644 includes/kohana/system/classes/kohana/log/file.php create mode 100644 includes/kohana/system/classes/kohana/log/stderr.php create mode 100644 includes/kohana/system/classes/kohana/log/stdout.php create mode 100644 includes/kohana/system/classes/kohana/log/syslog.php create mode 100644 includes/kohana/system/classes/kohana/log/writer.php create mode 100644 includes/kohana/system/classes/kohana/model.php create mode 100644 includes/kohana/system/classes/kohana/num.php create mode 100644 includes/kohana/system/classes/kohana/profiler.php create mode 100644 includes/kohana/system/classes/kohana/request.php create mode 100644 includes/kohana/system/classes/kohana/request/client.php create mode 100644 includes/kohana/system/classes/kohana/request/client/external.php create mode 100644 includes/kohana/system/classes/kohana/request/client/internal.php create mode 100644 includes/kohana/system/classes/kohana/request/exception.php create mode 100644 includes/kohana/system/classes/kohana/response.php create mode 100644 includes/kohana/system/classes/kohana/route.php create mode 100644 includes/kohana/system/classes/kohana/security.php create mode 100644 includes/kohana/system/classes/kohana/session.php create mode 100644 includes/kohana/system/classes/kohana/session/cookie.php create mode 100644 includes/kohana/system/classes/kohana/session/native.php create mode 100644 includes/kohana/system/classes/kohana/text.php create mode 100644 includes/kohana/system/classes/kohana/upload.php create mode 100644 includes/kohana/system/classes/kohana/url.php create mode 100644 includes/kohana/system/classes/kohana/utf8.php create mode 100644 includes/kohana/system/classes/kohana/valid.php create mode 100644 includes/kohana/system/classes/kohana/validation.php create mode 100644 includes/kohana/system/classes/kohana/validation/exception.php create mode 100644 includes/kohana/system/classes/kohana/view.php create mode 100644 includes/kohana/system/classes/kohana/view/exception.php 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 create mode 100644 includes/kohana/system/classes/request/client.php create mode 100644 includes/kohana/system/classes/request/client/external.php create mode 100644 includes/kohana/system/classes/request/client/internal.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/native.php create mode 100644 includes/kohana/system/classes/text.php create mode 100644 includes/kohana/system/classes/upload.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/valid.php create mode 100644 includes/kohana/system/classes/validation.php create mode 100644 includes/kohana/system/classes/validation/exception.php create mode 100644 includes/kohana/system/classes/view.php create mode 100644 includes/kohana/system/config/credit_cards.php create mode 100644 includes/kohana/system/config/curl.php create mode 100644 includes/kohana/system/config/encrypt.php create mode 100644 includes/kohana/system/config/inflector.php create mode 100644 includes/kohana/system/config/mimes.php create mode 100644 includes/kohana/system/config/session.php create mode 100644 includes/kohana/system/config/user_agents.php create mode 100644 includes/kohana/system/config/userguide.php create mode 100644 includes/kohana/system/guide/kohana/autoloading.md create mode 100644 includes/kohana/system/guide/kohana/bootstrap.md create mode 100644 includes/kohana/system/guide/kohana/controllers.md create mode 100644 includes/kohana/system/guide/kohana/conventions.md create mode 100644 includes/kohana/system/guide/kohana/cookies.md create mode 100644 includes/kohana/system/guide/kohana/debugging.md create mode 100644 includes/kohana/system/guide/kohana/errors.md create mode 100644 includes/kohana/system/guide/kohana/extension.md create mode 100644 includes/kohana/system/guide/kohana/files.md create mode 100644 includes/kohana/system/guide/kohana/files/classes.md create mode 100644 includes/kohana/system/guide/kohana/files/config.md create mode 100644 includes/kohana/system/guide/kohana/files/i18n.md create mode 100644 includes/kohana/system/guide/kohana/files/messages.md create mode 100644 includes/kohana/system/guide/kohana/flow.md create mode 100644 includes/kohana/system/guide/kohana/fragments.md create mode 100644 includes/kohana/system/guide/kohana/helpers.md create mode 100644 includes/kohana/system/guide/kohana/index.md create mode 100644 includes/kohana/system/guide/kohana/install.md create mode 100644 includes/kohana/system/guide/kohana/menu.md create mode 100644 includes/kohana/system/guide/kohana/modules.md create mode 100644 includes/kohana/system/guide/kohana/mvc.md create mode 100644 includes/kohana/system/guide/kohana/mvc/controllers.md create mode 100644 includes/kohana/system/guide/kohana/mvc/models.md create mode 100644 includes/kohana/system/guide/kohana/mvc/views.md create mode 100644 includes/kohana/system/guide/kohana/profiling.md create mode 100644 includes/kohana/system/guide/kohana/requests.md create mode 100644 includes/kohana/system/guide/kohana/routing.md create mode 100644 includes/kohana/system/guide/kohana/security.md create mode 100644 includes/kohana/system/guide/kohana/security/cookies.md create mode 100644 includes/kohana/system/guide/kohana/security/database.md create mode 100644 includes/kohana/system/guide/kohana/security/deploying.md create mode 100644 includes/kohana/system/guide/kohana/security/encryption.md create mode 100644 includes/kohana/system/guide/kohana/security/validation.md create mode 100644 includes/kohana/system/guide/kohana/security/xss.md create mode 100644 includes/kohana/system/guide/kohana/sessions.md create mode 100644 includes/kohana/system/guide/kohana/tips.md create mode 100644 includes/kohana/system/guide/kohana/tutorials.md create mode 100644 includes/kohana/system/guide/kohana/tutorials/clean-urls.md create mode 100644 includes/kohana/system/guide/kohana/tutorials/error-pages.md create mode 100644 includes/kohana/system/guide/kohana/tutorials/git.md create mode 100644 includes/kohana/system/guide/kohana/tutorials/hello-world.md create mode 100644 includes/kohana/system/guide/kohana/tutorials/sharing-kohana.md create mode 100644 includes/kohana/system/guide/kohana/tutorials/simple-mvc.md create mode 100644 includes/kohana/system/guide/kohana/tutorials/templates.md create mode 100644 includes/kohana/system/guide/kohana/tutorials/translation.md create mode 100644 includes/kohana/system/guide/kohana/upgrading.md create mode 100644 includes/kohana/system/i18n/en.php create mode 100644 includes/kohana/system/i18n/es.php create mode 100644 includes/kohana/system/i18n/fr.php create mode 100644 includes/kohana/system/media/guide/kohana/cascading_filesystem.png create mode 100644 includes/kohana/system/media/guide/kohana/hello_world_1.png create mode 100644 includes/kohana/system/media/guide/kohana/hello_world_2.png create mode 100644 includes/kohana/system/media/guide/kohana/hello_world_2_error.png create mode 100644 includes/kohana/system/media/guide/kohana/install.png create mode 100644 includes/kohana/system/media/guide/kohana/welcome.png create mode 100644 includes/kohana/system/messages/validation.php create mode 100644 includes/kohana/system/tests/kohana/ArrTest.php create mode 100644 includes/kohana/system/tests/kohana/CLITest.php create mode 100644 includes/kohana/system/tests/kohana/ConfigTest.php create mode 100644 includes/kohana/system/tests/kohana/CookieTest.php create mode 100644 includes/kohana/system/tests/kohana/CoreTest.php create mode 100644 includes/kohana/system/tests/kohana/DateTest.php create mode 100644 includes/kohana/system/tests/kohana/DebugTest.php create mode 100644 includes/kohana/system/tests/kohana/ExceptionTest.php create mode 100644 includes/kohana/system/tests/kohana/FeedTest.php create mode 100644 includes/kohana/system/tests/kohana/FileTest.php create mode 100644 includes/kohana/system/tests/kohana/FormTest.php create mode 100644 includes/kohana/system/tests/kohana/HTMLTest.php create mode 100644 includes/kohana/system/tests/kohana/Http/Header/ValueTest.php create mode 100644 includes/kohana/system/tests/kohana/Http/HeaderTest.php create mode 100644 includes/kohana/system/tests/kohana/I18nTest.php create mode 100644 includes/kohana/system/tests/kohana/InflectorTest.php create mode 100644 includes/kohana/system/tests/kohana/LogTest.php create mode 100644 includes/kohana/system/tests/kohana/ModelTest.php create mode 100644 includes/kohana/system/tests/kohana/NumTest.php create mode 100644 includes/kohana/system/tests/kohana/RequestTest.php create mode 100644 includes/kohana/system/tests/kohana/ResponseTest.php create mode 100644 includes/kohana/system/tests/kohana/RouteTest.php create mode 100644 includes/kohana/system/tests/kohana/SecurityTest.php create mode 100644 includes/kohana/system/tests/kohana/SessionTest.php create mode 100644 includes/kohana/system/tests/kohana/TextTest.php create mode 100644 includes/kohana/system/tests/kohana/URLTest.php create mode 100644 includes/kohana/system/tests/kohana/UTF8Test.php create mode 100644 includes/kohana/system/tests/kohana/UploadTest.php create mode 100644 includes/kohana/system/tests/kohana/ValidTest.php create mode 100644 includes/kohana/system/tests/kohana/ValidationTest.php create mode 100644 includes/kohana/system/tests/kohana/ViewTest.php create mode 100644 includes/kohana/system/tests/kohana/request/client/internal.php create mode 100644 includes/kohana/system/tests/test_data/callback_routes.php create mode 100644 includes/kohana/system/tests/test_data/github.png create mode 100644 includes/kohana/system/tests/test_data/views/test.css.php create mode 100644 includes/kohana/system/utf8/from_unicode.php create mode 100644 includes/kohana/system/utf8/ltrim.php create mode 100644 includes/kohana/system/utf8/ord.php create mode 100644 includes/kohana/system/utf8/rtrim.php create mode 100644 includes/kohana/system/utf8/str_ireplace.php create mode 100644 includes/kohana/system/utf8/str_pad.php create mode 100644 includes/kohana/system/utf8/str_split.php create mode 100644 includes/kohana/system/utf8/strcasecmp.php create mode 100644 includes/kohana/system/utf8/strcspn.php create mode 100644 includes/kohana/system/utf8/stristr.php create mode 100644 includes/kohana/system/utf8/strlen.php create mode 100644 includes/kohana/system/utf8/strpos.php create mode 100644 includes/kohana/system/utf8/strrev.php create mode 100644 includes/kohana/system/utf8/strrpos.php create mode 100644 includes/kohana/system/utf8/strspn.php create mode 100644 includes/kohana/system/utf8/strtolower.php create mode 100644 includes/kohana/system/utf8/strtoupper.php create mode 100644 includes/kohana/system/utf8/substr.php create mode 100644 includes/kohana/system/utf8/substr_replace.php create mode 100644 includes/kohana/system/utf8/to_unicode.php create mode 100644 includes/kohana/system/utf8/transliterate_to_ascii.php create mode 100644 includes/kohana/system/utf8/trim.php create mode 100644 includes/kohana/system/utf8/ucfirst.php create mode 100644 includes/kohana/system/utf8/ucwords.php create mode 100644 includes/kohana/system/views/kohana/error.php create mode 100644 includes/kohana/system/views/kohana/generate_logo.php create mode 100644 includes/kohana/system/views/kohana/logo.php create mode 100644 includes/kohana/system/views/profiler/stats.php create mode 100644 includes/kohana/system/views/profiler/style.css create mode 100644 index.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..c8dada9 --- /dev/null +++ b/.htaccess @@ -0,0 +1,21 @@ +# Turn on URL rewriting +RewriteEngine On + +# Installation directory +RewriteBase / + +# Protect hidden files from being viewed + + Order Deny,Allow + Deny From All + + +# Protect application and system files from being viewed +RewriteRule ^(?:application|modules|includes/kohana)\b.* index.php/$0 [L] + +# Allow any files or directories that exist to be displayed directly +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d + +# Rewrite all other URLs to index.php/URL +RewriteRule .* index.php/$0 [PT] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/application/bootstrap.php b/application/bootstrap.php new file mode 100644 index 0000000..772327e --- /dev/null +++ b/application/bootstrap.php @@ -0,0 +1,138 @@ +" + */ +if (isset($_SERVER['KOHANA_ENV'])) +{ + Kohana::$environment = constant('Kohana::'.strtoupper($_SERVER['KOHANA_ENV'])); +} + +/** + * Initialize Kohana, setting the default options. + * + * The following options are available: + * + * - string base_url path, and optionally domain, of your application NULL + * - 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 + * - boolean errors enable or disable error handling TRUE + * - boolean profile enable or disable internal profiling TRUE + * - boolean caching enable or disable internal caching FALSE + */ +Kohana::init(array( + 'base_url' => '/', + 'index_file' => '', + 'caching' => TRUE, + 'cache_dir' => '/dev/shm/lnapp', +)); + +/** + * Attach the file write to logging. Multiple writers are supported. + */ +Kohana::$log->attach(new Log_File(APPPATH.'logs')); + +/** + * Attach a file reader to config. Multiple readers are supported. + */ +Kohana::$config->attach(new Config_File); + +/** + * Enable modules. Modules are referenced by a relative or absolute path. + */ +Kohana::modules(array( + 'auth' => SMDPATH.'auth', // Basic authentication + 'cache' => SMDPATH.'cache', // Caching with multiple backends + // 'codebench' => SMDPATH.'codebench', // Benchmarking tool + 'database' => SMDPATH.'database', // Database access + // 'image' => SMDPATH.'image', // Image manipulation + 'orm' => SMDPATH.'orm', // Object Relationship Mapping + // 'unittest' => SMDPATH.'unittest', // Unit testing + 'userguide' => SMDPATH.'userguide', // User guide and API documentation + )); + +/** + * Enable specalised interfaces + */ +Route::set('sections', '/(/(/(/)))', + array( + 'directory' => '('.implode('|',Kohana::config('config.method_directory')).')' + )); + +// Static file serving (CSS, JS, images) +Route::set('default/media', 'media(/)', array('file' => '.+')) + ->defaults(array( + 'controller' => 'welcome', + 'action' => 'media', + 'file' => NULL, + )); + +/** + * Set the routes. Each route must have a minimum of a name, a URI and a set of + * defaults for the URI. + */ +Route::set('default', '((/(/)))', array('id' => '[a-zA-Z0-9_.-]+')) + ->defaults(array( + 'controller' => 'welcome', + 'action' => 'index', + )); +?> diff --git a/application/cache/.htaccess b/application/cache/.htaccess new file mode 100644 index 0000000..281d5c3 --- /dev/null +++ b/application/cache/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/application/classes/auth/orm.php b/application/classes/auth/orm.php new file mode 100644 index 0000000..02e3ac3 --- /dev/null +++ b/application/classes/auth/orm.php @@ -0,0 +1,23 @@ +_config['hash_method']) { + case '' : return $str; + case 'md5': return md5($str); + default: return hash_hmac($this->_config['hash_method'], $str, $this->_config['hash_key']); + } + } +} +?> diff --git a/application/classes/block.php b/application/classes/block.php new file mode 100644 index 0000000..0b6b536 --- /dev/null +++ b/application/classes/block.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/breadcrumb.php b/application/classes/breadcrumb.php new file mode 100644 index 0000000..61fc697 --- /dev/null +++ b/application/classes/breadcrumb.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/config.php b/application/classes/config.php new file mode 100644 index 0000000..fd71789 --- /dev/null +++ b/application/classes/config.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/controller/account.php b/application/classes/controller/account.php new file mode 100644 index 0000000..2b8e3c9 --- /dev/null +++ b/application/classes/controller/account.php @@ -0,0 +1,64 @@ +logged_in()!= 0) { + # Redirect to the user account + Request::instance()->redirect('welcome/index'); + } + + Block::add(array( + 'title'=>_('Login to server'), + 'body'=>View::factory('blogin'), + 'style'=>array('css/blogin.css'=>'screen'), + )); + + # @todo this is not being updated - want to make this automatic + $this->template->control = sprintf('%s -> %s', + HTML::anchor(URL::site(),'Home'), + HTML::anchor($this->request->uri(),'Login',array('id'=>'ajxbody')) + ); + + $this->template->content = Block::factory(); + + # If there is a post and $_POST is not empty + if ($_POST) { + # Instantiate a new user + $user = ORM::factory('account'); + + # Check Auth + $status = $user->login($_POST); + + # If the post data validates using the rules setup in the user model + if ($status) { + # Redirect to the user account + Request::instance()->redirect('welcome/index'); + + } else { + # @todo Get errors for display in view + #$this->template->content = $_POST->errors('signin'); + } + } + + $js = ''; + $js .= ''; + $this->template->content .= $js; + } + + public function action_logout() { + # If user already signed-in + if (Auth::instance()->logged_in()!= 0) { + Auth::instance()->logout(); + + Request::instance()->redirect('account/login'); + } + + Request::instance()->redirect('welcome/index'); + } +} // End Welcome diff --git a/application/classes/controller/account/welcome.php b/application/classes/controller/account/welcome.php new file mode 100644 index 0000000..665c550 --- /dev/null +++ b/application/classes/controller/account/welcome.php @@ -0,0 +1,33 @@ +'Welcome to lnApp (account)', + 'subtitle'=>'Using lnApp', + 'body'=>'Sample lnApp application', + 'footer'=>'lnApp makes building websites easy! '.time(), + )); + + $this->template->control = sprintf('%s -> %s -> %s -> %s', + HTML::anchor(URL::site(),_('Home')), + HTML::anchor($this->request->uri(),'Body',array('id'=>'ajxbody')), + HTML::anchor($this->request->uri(),'Left',array('id'=>'ajxleft')), + HTML::anchor($this->request->uri(),'Right',array('id'=>'ajxright')) + ); + + $this->template->content = Block::factory(); + + // @todo Change this to Script::add(); + $js = ''; + $js .= ''; + $this->template->content .= $js; + } +} // End Welcome diff --git a/application/classes/controller/admin/welcome.php b/application/classes/controller/admin/welcome.php new file mode 100644 index 0000000..eddbe7f --- /dev/null +++ b/application/classes/controller/admin/welcome.php @@ -0,0 +1,33 @@ +'Welcome to lnApp (admin)', + 'subtitle'=>'Using lnApp', + 'body'=>'Sample lnApp application', + 'footer'=>'lnApp makes building websites easy! '.time(), + )); + + $this->template->control = sprintf('%s -> %s -> %s -> %s', + HTML::anchor(URL::site(),_('Home')), + HTML::anchor($this->request->uri(),'Body',array('id'=>'ajxbody')), + HTML::anchor($this->request->uri(),'Left',array('id'=>'ajxleft')), + HTML::anchor($this->request->uri(),'Right',array('id'=>'ajxright')) + ); + + $this->template->content = Block::factory(); + + // @todo Change this to Script::add(); + $js = ''; + $js .= ''; + $this->template->content .= $js; + } +} // End Welcome diff --git a/application/classes/controller/default.php b/application/classes/controller/default.php new file mode 100644 index 0000000..478f8ee --- /dev/null +++ b/application/classes/controller/default.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/controller/lnapp/default.php b/application/classes/controller/lnapp/default.php new file mode 100644 index 0000000..b004078 --- /dev/null +++ b/application/classes/controller/lnapp/default.php @@ -0,0 +1,90 @@ + 'admin' will only allow users with the role admin to access action_adminpanel + * 'moderatorpanel' => array('login', 'moderator') will only allow users with the roles login and moderator to access action_moderatorpanel + * + * @var array actions that require a valid user + */ + protected $secure_actions = array(); + + /** + * Check and see if this controller needs authentication + * + * if $this->auth_required is TRUE, then the user must be logged in only. + * if $this->auth_required is FALSE, AND $this->secure_actions has an array of + * methods set to TRUE, then the user must be logged in AND a member of the + * role. + * + * @return boolean + */ + protected function _auth_required() { + // If our global configurable is disabled, then continue + if (! Kohana::Config('config.method_security')) + return FALSE; + + return (($this->auth_required !== FALSE && Auth::instance()->logged_in() === FALSE) || + (is_array($this->secure_actions) && array_key_exists($this->request->action(),$this->secure_actions) && + Auth::instance()->logged_in($this->secure_actions[$this->request->action()]) === FALSE)); + } + + public function before() { + parent::before(); + + // Check user auth and role + if ($this->_auth_required()) { + // For AJAX/JSON requests, authorisation is controlled in the method. + if (Request::current()->is_ajax() && $this->request->action() === 'json') { + // Nothing required. + + // For no AJAX/JSON requests, display an access page + } elseif (Auth::instance()->logged_in(NULL,get_class($this).'|'.__METHOD__)) { + Request::current()->redirect('login/noaccess'); + + } else { + Session::instance()->set('afterlogin',Request::detect_uri()); + Request::current()->redirect($this->noauth_redirect); + } + } + } + + public function after() { + parent::after(); + + // Generate and check the ETag for this file + $this->response->check_cache(NULL,$this->request); + } +} +?> diff --git a/application/classes/controller/lnapp/login.php b/application/classes/controller/lnapp/login.php new file mode 100644 index 0000000..4c8405d --- /dev/null +++ b/application/classes/controller/lnapp/login.php @@ -0,0 +1,199 @@ +logged_in()!= 0) { + // Redirect to the user account + Request::current()->redirect('welcome/index'); + } + + // If there is a post and $_POST is not empty + if ($_POST) { + // Store our details in a session key + Session::instance()->set(Kohana::config('auth.session_key'),$_POST['username']); + Session::instance()->set('password',$_POST['password']); + + // If the post data validates using the rules setup in the user model + if (Auth::instance()->login($_POST['username'],$_POST['password'])) { + // Redirect to the user account + if ($redir = Session::instance()->get('afterlogin')) { + Session::instance()->delete('afterlogin'); + Request::current()->redirect($redir); + + } else + Request::current()->redirect('welcome/index'); + + } else { + SystemMessage::add(array( + 'title'=>_('Invalid username or password'), + 'type'=>'error', + 'body'=>_('The username or password was invalid.') + )); + } + } + + Block::add(array( + 'title'=>_('Login to server'), + 'body'=>View::factory('login'), + 'style'=>array('css/login.css'=>'screen'), + )); + + Script::add(array('type'=>'stdin','data'=>' + $(document).ready(function() { + $("#ajxbody").click(function() {$("#ajBODY").load("'.$this->request->uri().'/"); return false;}); + });' + )); + } + + public function action_register() { + // If user already signed-in + if (Auth::instance()->logged_in()!= 0) { + // Redirect to the user account + Request::current()->redirect('welcome/index'); + } + + // Instantiate a new user + $account = ORM::factory('account'); + + // If there is a post and $_POST is not empty + if ($_POST) { + // Check Auth + $status = $account->values($_POST)->check(); + + if (! $status) { + foreach ($account->validation()->errors('form/register') as $f => $r) { + // $r[0] has our reason for validation failure + switch ($r[0]) { + // Generic validation reason + default: + SystemMessage::add(array( + 'title'=>_('Validation failed'), + 'type'=>'error', + 'body'=>sprintf(_('The defaults on your submission were not valid for field %s (%s).'),$f,$r) + )); + } + } + } + + $ido = ORM::factory('module') + ->where('name','=','account') + ->find(); + + $account->id = $ido->record_id->next_id($ido->id); + // Save the user details + if ($account->save()) {} + + } + + SystemMessage::add(array( + 'title'=>_('Already have an account?'), + 'type'=>'info', + 'body'=>_('If you already have an account, please login..') + )); + + Block::add(array( + 'title'=>_('Register'), + 'body'=>View::factory('bregister') + ->set('account',$account) + ->set('errors',$account->validation()->errors('form/register')), + )); + + $this->template->left = HTML::anchor('login','Login').'...'; + } + + /** + * Enable user password reset + */ + public function action_reset() { + // If user already signed-in + if (Auth::instance()->logged_in()!= 0) { + // Redirect to the user account + Request::current()->redirect('welcome/index'); + } + + // If the user posted their details to reset their password + if ($_POST) { + // If the email address is correct, create a method token + if (! empty($_POST['email']) AND ($ao=ORM::factory('account',array('email'=>$_POST['email']))) AND $ao->loaded()) { + $mt = ORM::factory('module_method_token'); + + // Find out our password reset method id + // @todo move this to a more generic method, so that it can be called by other methods + $mo = ORM::factory('module',array('name'=>'account')); + $mmo = ORM::factory('module_method',array('name'=>'user_resetpassword','module_id'=>$mo->id)); + + // Check to see if there is already a token, if so, do nothing. + if ($mt->where('account_id','=',$ao->id)->and_where('method_id','=',$mmo->id)->find()) { + if ($mt->date_expire < time()) { + $mt->delete(); + $mt->clear(); + } + } + + if (! $mt->loaded()) { + $mt->account_id = $ao->id; + $mt->method_id = $mmo->id; + $mt->date_expire = time() + 15*3600; + $mt->token = md5(sprintf('%s:%s:%s',$mt->account_id,$mt->method_id,$mt->date_expire)); + $mt->save(); + + // Send our email with the token + $et = EmailTemplate::instance('account_reset_password'); + $et->to = array($mt->account->email=>sprintf('%s %s',$mt->account->first_name,$mt->account->last_name)); + $et->variables = array( + 'SITE'=>URL::base(TRUE,TRUE), + 'SITE_ADMIN'=>Config::sitename(), + 'SITE_NAME'=>Config::sitename(), + 'TOKEN'=>$mt->token, + 'USER_NAME'=>sprintf('%s %s',$mt->account->first_name,$mt->account->last_name), + ); + $et->send(); + } + + // Redirect to our password reset, the Auth will validate the token. + } elseif (! empty($_REQUEST['token'])) { + Request::current()->redirect(sprintf('user/account/resetpassword?token=%s',$_REQUEST['token'])); + } + + // Show our token screen even if the email was invalid. + if (isset($_POST['email'])) + Block::add(array( + 'title'=>_('Reset your password'), + 'body'=>View::factory('login_reset_sent'), + 'style'=>array('css/login.css'=>'screen'), + )); + else + Request::current()->redirect('login'); + + } else { + Block::add(array( + 'title'=>_('Reset your password'), + 'body'=>View::factory('login_reset'), + 'style'=>array('css/login.css'=>'screen'), + )); + } + } + + public function action_noaccess() { + SystemMessage::add(array( + 'title'=>_('No access to requested resource'), + 'type'=>'error', + 'body'=>_('You do not have access to the requested resource, please contact your administrator.') + )); + } +} +?> diff --git a/application/classes/controller/lnapp/logout.php b/application/classes/controller/lnapp/logout.php new file mode 100644 index 0000000..1613d17 --- /dev/null +++ b/application/classes/controller/lnapp/logout.php @@ -0,0 +1,26 @@ +logged_in()!= 0) { + Auth::instance()->logout(); + + Request::current()->redirect('login'); + } + + Request::current()->redirect('welcome/index'); + } +} +?> diff --git a/application/classes/controller/lnapp/templatedefault.php b/application/classes/controller/lnapp/templatedefault.php new file mode 100644 index 0000000..83620d1 --- /dev/null +++ b/application/classes/controller/lnapp/templatedefault.php @@ -0,0 +1,284 @@ + 'admin' will only allow users with the role admin to access action_adminpanel + * 'moderatorpanel' => array('login', 'moderator') will only allow users with the roles login and moderator to access action_moderatorpanel + * + * @var array actions that require a valid user + */ + protected $secure_actions = array( + 'menu' => TRUE, + ); + + /** + * Check and see if this controller needs authentication + * + * if $this->auth_required is TRUE, then the user must be logged in only. + * if $this->auth_required is FALSE, AND $this->secure_actions has an array of + * methods set to TRUE, then the user must be logged in AND a member of the + * role. + * + * @return boolean + */ + protected function _auth_required() { + // If our global configurable is disabled, then continue + if (! Kohana::Config('config.method_security')) + return FALSE; + + return (($this->auth_required !== FALSE && Auth::instance()->logged_in(NULL,get_class($this).'|'.__METHOD__) === FALSE) || + (is_array($this->secure_actions) && array_key_exists($this->request->action(),$this->secure_actions) && + Auth::instance()->logged_in($this->secure_actions[$this->request->action()],get_class($this).'|'.__METHOD__) === FALSE)); + } + + /** + * Loads the template [View] object. + * + * Page information is provided by [meta]. + * @uses meta + */ + public function before() { + // Do not template media files + if ($this->request->action() === 'media') { + $this->auto_render = FALSE; + return; + } + + parent::before(); + + // Check user auth and role + if ($this->_auth_required()) { + if (Kohana::$is_cli) + throw new Kohana_Exception('Cant run :method, authentication not possible',array(':method'=>$this->request->action())); + + // If auth is required and the user is logged in, then they dont have access. + // (We have already checked authorisation.) + if (Auth::instance()->logged_in(NULL,get_class($this).'|'.__METHOD__)) { + if (Config::sitemode() == Kohana::DEVELOPMENT) + SystemMessage::add(array( + 'title'=>_('Insufficient Access'), + 'type'=>'debug', + 'body'=>Kohana::debug(array('required'=>$this->auth_required,'action'=>$this->request->action(),'user'=>Auth::instance()->get_user()->username)), + )); + + // @todo Login No Access redirects are not handled in JS? + if ($this->request->is_ajax()) { + echo _('You dont have enough permissions.'); + die(); + } else + Request::current()->redirect('login/noaccess'); + + } else { + Session::instance()->set('afterlogin',Request::detect_uri()); + Request::current()->redirect($this->noauth_redirect); + } + } + + // For AJAX calls, we dont need to render the complete page. + if ($this->request->is_ajax()) { + $this->auto_render = FALSE; + return; + } + + // Bind our template meta variable + $this->meta = new meta; + View::bind_global('meta',$this->meta); + + // Our default style sheet + Style::add(array( + 'type'=>'file', + 'data'=>'css/default.css', + )); + + // Our default scripts + // This is in a reverse list, since we push them to the beginging of the scripts to render. + foreach (array('file'=>array( + 'js/jquery.cookie.js', + 'js/jquery.jstree-1.0rc.js', + 'js/jquery-1.4.2.js', + )) as $type => $datas) { + + foreach ($datas as $data) { + Script::add(array( + 'type'=>$type, + 'data'=>$data, + ),TRUE); + } + } + + // Initialise our content + $this->template->left = ''; + $this->template->content = ''; + $this->template->right = ''; + } + + public function after() { + if (! is_string($this->template) AND empty($this->template->content)) + $this->template->content = Block::factory(); + + if ($this->auto_render) { + // Application Title + $this->meta->title = 'Application Title'; + $this->template->title = ''; + + // Style Sheets Properties + $this->meta->styles = Style::factory(); + + // Script Properties + $this->meta->scripts = Script::factory(); + + // Application logo + $this->template->logo = Config::logo(); + + // Link images on the header line + $this->template->headimages = $this->_headimages(); + + // Control Line + $this->template->control = $this->_control(); + + // System Messages line + $this->template->sysmsg = $this->_sysmsg(); + + // Left Item + $this->template->left = $this->_left(); + + // Right Item + $this->template->right = $this->_right(); + + // Footer + $this->template->footer = $this->_footer(); + + // For any ajax rendered actions, we'll need to capture the content and put it in the response + } elseif ($this->request->is_ajax() && isset($this->template->content) && ! $this->response->body()) { + // @todo move this formatting to a view? + if ($s = $this->_sysmsg() AND (string)$s) + $this->response->body(sprintf('
%s
',$s)); + + # In case there any style sheets or scrpits for this render. + $this->response->bodyadd(Style::factory()); + + # Get the response body + $this->response->bodyadd(sprintf('
%s
',$this->template->content)); + } + + parent::after(); + + // Generate and check the ETag for this file + $this->response->check_cache(NULL,$this->request); + } + + /** + * Default Method to call from the tree menu + */ + public function action_menu() { + $this->template->content = 'See menu on tree'; + } + + protected function _headimages() { + HeadImages::add(array( + 'url'=>'http://dev.leenooks.net', + 'img'=>'img/forum-big.png', + 'attrs'=>array('onclick'=>"target='_blank';",'title'=>'Link') + )); + + return HeadImages::factory(); + } + + /** + * Render our control menu bar + */ + protected function _control() { + return Breadcrumb::factory(); + } + + protected function _sysmsg() { + return SystemMessage::factory(); + } + + protected function _left() { + return empty($this->template->left) ? Controller_Tree::js() : $this->template->left; + } + + protected function _right() { + return empty($this->template->right) ? '' : $this->template->right; + } + + public function _footer() { + return sprintf('© %s',Config::SiteName()); + } + + /** + * This action will render all the media related files for a page + * @return void + */ + final public function action_media() { + // Get the file path from the request + $file = $this->request->param('file'); + + // Find the file extension + $ext = pathinfo($file, PATHINFO_EXTENSION); + + // Remove the extension from the filename + $file = substr($file, 0, -(strlen($ext) + 1)); + $f = ''; + + // If our file is pathed with session, our file is in our session. + if ($fd = Session::instance()->get_once($this->request->param('file'))) { + $this->response->body($fd); + + // First try and find media files for the site_id + } elseif ($f = Kohana::find_file(sprintf('media/%s',Config::siteid()), $file, $ext)) { + // Send the file content as the response + $this->response->body(file_get_contents($f)); + + // If not found try a default media file + } elseif ($f = Kohana::find_file('media', $file, $ext)) { + // Send the file content as the response + $this->response->body(file_get_contents($f)); + + } else { + // Return a 404 status + $this->response->status(404); + } + + // Generate and check the ETag for this file + $this->response->check_cache(NULL,$this->request); + + // Set the proper headers to allow caching + $this->response->headers('Content-Type',File::mime_by_ext($ext)); + $this->response->headers('Content-Length',(string)$this->response->content_length()); + $this->response->headers('Last-Modified',date('r', $f ? filemtime($f) : time())); + } +} +?> diff --git a/application/classes/controller/lnapp/tree.php b/application/classes/controller/lnapp/tree.php new file mode 100644 index 0000000..bc04e00 --- /dev/null +++ b/application/classes/controller/lnapp/tree.php @@ -0,0 +1,107 @@ +response->headers('Content-Type','application/json'); + $this->response->body(sprintf('[%s]',json_encode($this->output))); + } + + public static function js() { + $mediapath = Route::get(static::$jsmediaroute); + + return ' +
+'; + } + + /** + * Draw the Tree Menu + * + * The incoming ID is either a Branch B_x or a Node N_x + * Where X is actually the module. + * + * @param id + */ + public function action_json($id=null,array $data=array()) { + if ($this->_auth_required() AND ! Auth::instance()->logged_in()) { + $this->output = array('attr'=>array('id'=>'a_login'), + 'data'=>array('title'=>_('Please Login').'...','attr'=>array('id'=>'N_login','href'=>URL::site('/login')))); + + return; + } + + $this->output = array(); + + foreach ($data as $branch) { + array_push($this->output,array( + 'attr'=>array('id'=>sprintf('B_%s',$branch['id'])), + 'state'=>$branch['state'], + 'data'=>array('title'=>$branch['name']), + 'attr'=>array('id'=>sprintf('N_%s',$branch['id']),'href'=>empty($branch['attr_href']) ? URL::site(sprintf('/%s/menu',$branch['name'])) : $branch['attr_href']), + ) + ); + } + } +} +?> diff --git a/application/classes/controller/login.php b/application/classes/controller/login.php new file mode 100644 index 0000000..59a728d --- /dev/null +++ b/application/classes/controller/login.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/controller/logout.php b/application/classes/controller/logout.php new file mode 100644 index 0000000..671e24d --- /dev/null +++ b/application/classes/controller/logout.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/controller/redir.php b/application/classes/controller/redir.php new file mode 100644 index 0000000..26b70d6 --- /dev/null +++ b/application/classes/controller/redir.php @@ -0,0 +1,20 @@ + diff --git a/application/classes/controller/templatedefault.php b/application/classes/controller/templatedefault.php new file mode 100644 index 0000000..969d035 --- /dev/null +++ b/application/classes/controller/templatedefault.php @@ -0,0 +1,36 @@ +template->left) + return $this->template->left; + + elseif (Auth::instance()->logged_in(NULL,get_class($this).'|'.__METHOD__)) + return Controller_Tree::js(); + } + + protected function _right() { + if ($this->template->right) + return $this->template->right; + else + return $this->_cart(); + } + + private function _cart() { + if (! Cart::instance()->contents()->reset(FALSE)->count_all()) + return ''; + + return Cart::instance()->cart_block(); + } +} +?> diff --git a/application/classes/controller/tree.php b/application/classes/controller/tree.php new file mode 100644 index 0000000..eeebcf6 --- /dev/null +++ b/application/classes/controller/tree.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/controller/welcome.php b/application/classes/controller/welcome.php new file mode 100644 index 0000000..6a55a29 --- /dev/null +++ b/application/classes/controller/welcome.php @@ -0,0 +1,41 @@ +redirect('guide/app'); + + if (! Auth::instance()->logged_in()) + Request::current()->redirect('login'); + + Block::add(array( + 'title'=>'Welcome to lnApp (public)!', + 'subtitle'=>'Using lnApp', + 'body'=>'Sample lnApp application', + 'footer'=>'lnApp makes building websites easy! '.time(), + )); + + // @todo Show a login/logout on the breadcrumb + if (! Auth::instance()->logged_in()) { + Script::add(array('type'=>'stdin','data'=>' + $(document).ready(function() { + $("#ajxbody").click(function() {$("#ajBODY").load("'.URL::site('/login').'",null,function(x,s,r) {}); return false;}); + $("#ajBODY").ajaxSend(function() {$(this).html(\''.sprintf('%s %s<\/span>...',HTML::image('media/img/ajax-progress.gif',array('alt'=>_('Loading Login').'...')),_('Loading Login')).'\');return true;}); + });' + )); + } + } +} +?> diff --git a/application/classes/database/mysql.php b/application/classes/database/mysql.php new file mode 100644 index 0000000..4cf17ba --- /dev/null +++ b/application/classes/database/mysql.php @@ -0,0 +1,20 @@ + diff --git a/application/classes/editor.php b/application/classes/editor.php new file mode 100644 index 0000000..20432e3 --- /dev/null +++ b/application/classes/editor.php @@ -0,0 +1,53 @@ +'file', + 'data'=>'js/jquery-1.4.2.js', + )); + Script::add(array( + 'type'=>'file', + 'data'=>'js/tiny_mce/tiny_mce.js', + )); + Script::add(array( + 'type'=>'stdin', + 'data'=>' +tinyMCE.init({ + mode : "specific_textareas", + editor_selector : "mceEditor", + theme : "advanced", + plugins : "table,save,advhr,advimage,advlink,emotions,iespell,insertdatetime,preview,media,searchreplace,print", + theme_advanced_buttons1_add : "fontselect,fontsizeselect", + theme_advanced_buttons2_add : "separator,insertdate,inserttime,preview,separator,forecolor,backcolor", + theme_advanced_buttons2_add_before: "cut,copy,paste,separator,search,replace,separator", + theme_advanced_buttons3_add_before : "tablecontrols,separator", + theme_advanced_buttons3_add : "iespell,media,advhr", + theme_advanced_toolbar_location : "bottom", + theme_advanced_toolbar_align : "center", + plugin_insertdate_dateFormat : "%Y-%m-%d", + plugin_insertdate_timeFormat : "%H:%M:%S", + extended_valid_elements : "a[name|href|target|title|onclick],img[class|src|border=0|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name],hr[class|width|size|noshade],font[face|size|color|style],span[class|align|style]", + relative_urls: "true", + width : "100%" +});')); + } +} +?> diff --git a/application/classes/form.php b/application/classes/form.php new file mode 100644 index 0000000..cabca21 --- /dev/null +++ b/application/classes/form.php @@ -0,0 +1,19 @@ + diff --git a/application/classes/headimages.php b/application/classes/headimages.php new file mode 100644 index 0000000..74d735e --- /dev/null +++ b/application/classes/headimages.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/html.php b/application/classes/html.php new file mode 100644 index 0000000..8cad46f --- /dev/null +++ b/application/classes/html.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/htmlrender.php b/application/classes/htmlrender.php new file mode 100644 index 0000000..e887d1b --- /dev/null +++ b/application/classes/htmlrender.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/http/exception/404.php b/application/classes/http/exception/404.php new file mode 100644 index 0000000..a72b75d --- /dev/null +++ b/application/classes/http/exception/404.php @@ -0,0 +1,30 @@ +_('Page not found'), + 'type'=>'error', + 'body'=>sprintf(_('The page [%s] you requested was not found?'),Request::detect_uri()), + )); + + Request::factory()->redirect('welcome'); + } +} diff --git a/application/classes/lnapp/block.php b/application/classes/lnapp/block.php new file mode 100644 index 0000000..1d73847 --- /dev/null +++ b/application/classes/lnapp/block.php @@ -0,0 +1,81 @@ + '; + protected static $_required_keys = array('body'); + + /** + * Add a block to be rendered + * + * @param array Block attributes + */ + public static function add($block,$prepend=FALSE) { + parent::add($block); + + // Detect any style sheets. + if (! empty($block['style']) && is_array($block['style'])) + foreach ($block['style'] as $data=>$media) + Style::add(array( + 'type'=>'file', + 'data'=>$data, + 'media'=>$media, + )); + } + + /** + * Return an instance of this class + * + * @return Block + */ + public static function factory() { + return new Block; + } + + /** + * Render this block + * + * @see HTMLRender::render() + */ + protected function render() { + $output = ''; + $styles = array(); + + $i = 0; + foreach (static::$_data as $value) { + if ($i++) + $output .= static::$_spacer; + + $output .= ''; + + if (! empty($value['title'])) + $output .= sprintf('',$value['title']); + + if (! empty($value['subtitle'])) + $output .= sprintf('',$value['subtitle']); + + $output .= sprintf('',$value['body']); + + if (! empty($value['footer'])) + $output .= sprintf('',$value['footer']); + + $output .= '
%s
%s
%s
'; + } + + return $output; + } +} +?> diff --git a/application/classes/lnapp/breadcrumb.php b/application/classes/lnapp/breadcrumb.php new file mode 100644 index 0000000..04125f5 --- /dev/null +++ b/application/classes/lnapp/breadcrumb.php @@ -0,0 +1,64 @@ + $v) { + $output .= static::$_spacer; + + $p = join('/',array_slice($data,0,$k+1)); + $output .= HTML::anchor($p,empty(static::$_data['name'][$p]) ? ucfirst($v) : static::$_data['name'][$p]); + } + + return $output; + } +} +?> diff --git a/application/classes/lnapp/config.php b/application/classes/lnapp/config.php new file mode 100644 index 0000000..33369cf --- /dev/null +++ b/application/classes/lnapp/config.php @@ -0,0 +1,102 @@ +uri(array('file'=>'img/logo-small.png'),array('alt'=>static::sitename())); + + return HTML::image($logo,array('class'=>'headlogo','alt'=>_('Logo'))); + } + + /** + * Return our caching mechanism + */ + public static function cachetype() { + return is_null(Kohana::config('config.cache_type')) ? 'file' : Kohana::config('config.cache_type'); + } + + /** + * Show a date using a site configured format + */ + public static function date($date) { + return date(Kohana::config('config.date_format'),$date); + } + + /** + * See if our emails for the template should be sent to configured admin(s) + * + * @param string template - Template to test for + * @return mixed|array - Email to send test emails to + */ + public static function testmail($template) { + $config = Kohana::config('config.email_admin_only'); + + if (is_null($config) OR ! is_array($config) OR empty($config[$template])) + return FALSE; + else + return $config[$template]; + } +} +?> diff --git a/application/classes/lnapp/headimages.php b/application/classes/lnapp/headimages.php new file mode 100644 index 0000000..21f3ac7 --- /dev/null +++ b/application/classes/lnapp/headimages.php @@ -0,0 +1,45 @@ +uri(array('file'=>$value['img'])),array('alt'=>isset($value['attrs']['title']) ? $value['attrs']['title'] : '')); + $output .= HTML::anchor($value['url'],$i,(isset($value['attrs']) && is_array($value['attrs'])) ? $value['attrs'] : null); + $output .= static::$_spacer; + } + + return $output; + } +} +?> diff --git a/application/classes/lnapp/html.php b/application/classes/lnapp/html.php new file mode 100644 index 0000000..7c43317 --- /dev/null +++ b/application/classes/lnapp/html.php @@ -0,0 +1,21 @@ + diff --git a/application/classes/lnapp/htmlrender.php b/application/classes/lnapp/htmlrender.php new file mode 100644 index 0000000..b140401 --- /dev/null +++ b/application/classes/lnapp/htmlrender.php @@ -0,0 +1,94 @@ +get_called_class())); + } + + /** + * Add an item to be rendered + * + * @param array Item to be added + */ + public static function add($item,$prepend=FALSE) { + foreach (static::$_required_keys as $key) + if (! isset($item[$key])) + throw new Kohana_Exception('Missing key :key for image',array(':key'=>$key)); + + // Check for unique keys + if (static::$_unique_vals) + foreach (static::$_unique_vals as $v=>$u) + foreach (static::$_data as $d) + if (isset($d[$u]) && $d['data'] == $item['data']) + return; + + if ($prepend) + array_unshift(static::$_data,$item); + else + array_push(static::$_data,$item); + } + + /** + * Set the space used between rendering output + */ + public static function setSpacer($spacer) { + static::$_spacer = $spacer; + } + + /** + * Set the Kohana Media Path, used to determine where to find additional + * HTML content required for rendering. + */ + public static function setMediaPath($path) { + static::$_media_path = $path; + } + + /** + * Factory instance method must be declared by the child class + */ + public static function factory() { + throw new Kohana_Exception(':class is calling :method, when it should have its own method', + array(':class'=>get_called_class(),':method'=>__METHOD__)); + } + + /** + * Return the HTML to render the header images + */ + public function __toString() { + try { + return static::render(); + } + + // Display the exception message + catch (Exception $e) { + Kohana::exception_handler($e); + + return ''; + } + } + + /** + * Rendering must be declared by the child class + */ + protected function render() { + throw new Kohana_Exception(':class is calling :method, when it should have its own method', + array(':class'=>get_called_class(),':method'=>__METHOD__)); + } +} +?> diff --git a/application/classes/lnapp/meta.php b/application/classes/lnapp/meta.php new file mode 100644 index 0000000..4fcce6d --- /dev/null +++ b/application/classes/lnapp/meta.php @@ -0,0 +1,34 @@ +_array_keys) && empty($this->_data[$key])) + return array(); + + if (empty($this->_data[$key])) + return null; + else + return $this->_data[$key]; + } + + public function __set($key,$value) { + if (in_array($key,$this->_array_keys) && ! is_array($value)) + throw new Kohana_Exception('Key :key must be an array',array(':key'=>$key)); + + $this->_data[$key] = $value; + } +} +?> diff --git a/application/classes/lnapp/script.php b/application/classes/lnapp/script.php new file mode 100644 index 0000000..0763512 --- /dev/null +++ b/application/classes/lnapp/script.php @@ -0,0 +1,59 @@ +'type'); + + /** + * Return an instance of this class + * + * @return Script + */ + public static function factory() { + return new Script; + } + + /** + * Render the script tag + * + * @see HTMLRender::render() + */ + protected function render() { + $foutput = $soutput = ''; + $mediapath = Route::get(static::$_media_path); + + $i = $j = 0; + foreach (static::$_data as $value) { + + switch ($value['type']) { + case 'file': + $foutput .= HTML::script($mediapath->uri(array('file'=>$value['data']))); + if ($i++) + $foutput .= static::$_spacer; + break; + case 'stdin': + $soutput .= sprintf("",$value['data']); + if ($j++) + $soutput .= static::$_spacer; + break; + default: + throw new Kohana_Exception('Unknown style type :type',array(':type'=>$value['type'])); + } + } + + return $foutput.static::$_spacer.$soutput; + } +} +?> diff --git a/application/classes/lnapp/sort.php b/application/classes/lnapp/sort.php new file mode 100644 index 0000000..7fe6259 --- /dev/null +++ b/application/classes/lnapp/sort.php @@ -0,0 +1,105 @@ +$key)) {\n"; + $code .= " asort(\$a->$key);\n"; + $code .= " \$aa = array_shift(\$a->$key);\n"; + $code .= " } else\n"; + $code .= " \$aa = \$a->$key;\n"; + + $code .= " if (is_array(\$b->$key)) {\n"; + $code .= " asort(\$b->$key);\n"; + $code .= " \$bb = array_shift(\$b->$key);\n"; + $code .= " } else\n"; + $code .= " \$bb = \$b->$key;\n"; + + $code .= " if (\$aa != \$bb)"; + if ($rev) + $code .= " return (\$aa < \$bb ? 1 : -1);\n"; + else + $code .= " return (\$aa > \$bb ? 1 : -1);\n"; + + $code .= "} else {\n"; + + $code .= " \$a = array_change_key_case(\$a);\n"; + $code .= " \$b = array_change_key_case(\$b);\n"; + + $key = strtolower($key); + + $code .= " if ((! isset(\$a['$key'])) && isset(\$b['$key'])) return 1;\n"; + $code .= " if (isset(\$a['$key']) && (! isset(\$b['$key']))) return -1;\n"; + + $code .= " if ((isset(\$a['$key'])) && (isset(\$b['$key']))) {\n"; + $code .= " if (is_array(\$a['$key'])) {\n"; + $code .= " asort(\$a['$key']);\n"; + $code .= " \$aa = array_shift(\$a['$key']);\n"; + $code .= " } else\n"; + $code .= " \$aa = \$a['$key'];\n"; + + $code .= " if (is_array(\$b['$key'])) {\n"; + $code .= " asort(\$b['$key']);\n"; + $code .= " \$bb = array_shift(\$b['$key']);\n"; + $code .= " } else\n"; + $code .= " \$bb = \$b['$key'];\n"; + + $code .= " if (\$aa != \$bb)\n"; + $code .= " if (is_numeric(\$aa) && is_numeric(\$bb)) {\n"; + + if ($rev) + $code .= " return (\$aa < \$bb ? 1 : -1);\n"; + else + $code .= " return (\$aa > \$bb ? 1 : -1);\n"; + + $code .= " } else {\n"; + + if ($rev) + $code .= " if ( (\$c = strcasecmp(\$bb,\$aa)) != 0 ) return \$c;\n"; + else + $code .= " if ( (\$c = strcasecmp(\$aa,\$bb)) != 0 ) return \$c;\n"; + + $code .= " }\n"; + $code .= " }\n"; + $code .= "}\n"; + } + + $code .= 'return $c;'; + + $MASORT_CACHE[$sortby] = create_function('$a, $b',$code); + } + + uasort($data,$MASORT_CACHE[$sortby]); + } +} +?> diff --git a/application/classes/lnapp/style.php b/application/classes/lnapp/style.php new file mode 100644 index 0000000..1481567 --- /dev/null +++ b/application/classes/lnapp/style.php @@ -0,0 +1,54 @@ +uri(array('file'=>$value['data'])), + array('media'=>(! empty($value['media'])) ? $value['media'] : 'screen'),TRUE); + break; + default: + throw new Kohana_Exception('Unknown style type :type',array(':type'=>$value['type'])); + } + } + + return $output; + } +} +?> diff --git a/application/classes/lnapp/systemmessage.php b/application/classes/lnapp/systemmessage.php new file mode 100644 index 0000000..237bf01 --- /dev/null +++ b/application/classes/lnapp/systemmessage.php @@ -0,0 +1,129 @@ + '; + protected static $_required_keys = array('title','body','type'); + + /** + * Add a system message to be rendered + * + * @param array System Message attributes + */ + public static function add($msg,$prepend=FALSE) { + if ($msgs = Session::instance()->get('sessionmsgs')) { + static::$_data = $msgs; + } + + parent::add($msg); + + // Add a gribber popup + Style::add(array( + 'type'=>'file', + 'data'=>'css/jquery.gritter.css', + 'media'=>'screen', + )); + Script::add(array( + 'type'=>'file', + 'data'=>'js/jquery.gritter-1.5.js', + )); + Script::add(array( + 'type'=>'stdin', + 'data'=>sprintf( +'$(document).ready(function() { + $.extend($.gritter.options, { + fade_in_speed: "medium", + fade_out_speed: 2000, + time: "3000", + sticky: false, + }); + $.gritter.add({ + title: "%s", + text: "%s", + image: "%s", +});});',$msg['title'],$msg['body'],URL::site().static::image($msg['type'],true)))); + + // Save our messages in our session, so that we get them for redirects + Session::instance()->set('sessionmsgs',static::$_data); + } + + /** + * Return an instance of this class + * + * @return SystemMessage + */ + public static function factory() { + return new SystemMessage; + } + + /** + * Render an image for the System Message + */ + private static function image($type,$raw=false,$big=false,$alt='') { + $mediapath = Route::get(static::$_media_path); + + switch ($type) { + case 'error': + $file = sprintf('img/dialog-error%s.png',$big ? '-big' : ''); + break; + case 'info': + $file = sprintf('img/dialog-information%s.png',$big ? '-big' : ''); + break; + case 'warning': + $file = sprintf('img/dialog-warning%s.png',$big ? '-big' : ''); + break; + case 'debug': + $file = sprintf('img/dialog-question%s.png',$big ? '-big' : ''); + break; + default: + throw new Kohana_Exception('Unknown system message type :type',array(':type'=>$value['type'])); + } + + if ($raw) + return $mediapath->uri(array('file'=>$file)); + else + return HTML::image($mediapath->uri(array('file'=>$file)),array('alt'=>$alt ? $alt : '','class'=>'sysicon')); + } + + /** + * Render this system message + * + * @see HTMLRender::render() + */ + protected function render() { + $output = ''; + $mediapath = Route::get(static::$_media_path); + + // Reload our message from the session + if ($msgs = Session::instance()->get('sessionmsgs')) { + Session::instance()->delete('sessionmsgs'); + static::$_data = $msgs; + } + + $i = 0; + foreach (static::$_data as $value) { + if ($i++) + $output .= static::$_spacer; + + $output .= ''; + $output .= sprintf('',static::image($value['type'],false,false,isset($value['alt']) ? $value['alt'] : '')); + $output .= sprintf('',$value['title']); + $output .= ''; + $output .= sprintf('',$value['body']); + $output .= '
%s%s
%s
'; + } + + return $output; + } +} +?> diff --git a/application/classes/meta.php b/application/classes/meta.php new file mode 100644 index 0000000..006de23 --- /dev/null +++ b/application/classes/meta.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/orm.php b/application/classes/orm.php new file mode 100644 index 0000000..a71c8ad --- /dev/null +++ b/application/classes/orm.php @@ -0,0 +1,70 @@ +check()); + + // Always build a new validation object + $this->_validation(); + + $array = $this->_validation; + + if (($this->_valid = $array->check()) === FALSE OR $extra_errors) + { + return FALSE; + } + + return $this; + } + + /** + * Format fields for display purposes + * + * @param string column name + * @return mixed + */ + private function _format() { + foreach ($this->_display_filters as $column => $formats) + $this->_object_formated[$column] = $this->run_filter($column,$this->__get($column),array($column=>$formats)); + + $this->_formated = TRUE; + } + + /** + * Return a formated columns, as per the model definition + */ + public function display($column) { + // Trigger a load of the record. + $value = $this->__get($column); + + // If some of our fields need to be formated for display purposes. + if ($this->_loaded AND ! $this->_formated AND $this->_display_filters) + $this->_format(); + + if (isset($this->_object_formated[$column])) + return $this->_object_formated[$column]; + else + return HTML::nbsp($value); + } +} +?> diff --git a/application/classes/response.php b/application/classes/response.php new file mode 100644 index 0000000..62f0e8c --- /dev/null +++ b/application/classes/response.php @@ -0,0 +1,19 @@ +_body .= (string) $content; + } +} +?> diff --git a/application/classes/script.php b/application/classes/script.php new file mode 100644 index 0000000..1e03000 --- /dev/null +++ b/application/classes/script.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/sort.php b/application/classes/sort.php new file mode 100644 index 0000000..cccb437 --- /dev/null +++ b/application/classes/sort.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/style.php b/application/classes/style.php new file mode 100644 index 0000000..18e54da --- /dev/null +++ b/application/classes/style.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/systemmessage.php b/application/classes/systemmessage.php new file mode 100644 index 0000000..6753b7c --- /dev/null +++ b/application/classes/systemmessage.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/valid.php b/application/classes/valid.php new file mode 100644 index 0000000..fa0d3ec --- /dev/null +++ b/application/classes/valid.php @@ -0,0 +1,27 @@ + diff --git a/application/config/auth.php b/application/config/auth.php new file mode 100644 index 0000000..acfe24f --- /dev/null +++ b/application/config/auth.php @@ -0,0 +1,21 @@ + 'ORM', + 'hash_method' => 'md5', + 'salt_pattern' => '1, 3, 5, 9, 14, 15, 20, 21, 28, 30', + 'lifetime' => 1209600, + 'session_key' => 'auth_user', + 'forced_key' => 'auth_forced', +); +?> diff --git a/application/config/cache.php b/application/config/cache.php new file mode 100644 index 0000000..c7946c3 --- /dev/null +++ b/application/config/cache.php @@ -0,0 +1,23 @@ + array + ( + 'driver' => 'file', + 'cache_dir' => Kohana::$cache_dir ? Kohana::$cache_dir : '/dev/shm/lnapp', + 'default_expire' => 3600, + ) +); +?> diff --git a/application/config/config.php b/application/config/config.php new file mode 100644 index 0000000..cd0ecf6 --- /dev/null +++ b/application/config/config.php @@ -0,0 +1,37 @@ + 'file', + 'date_format' => 'd-M-Y', + 'email_admin_only'=> array( + 'adsl_traffic_notice'=>array('deon@c5t61p.leenooks.vpn'=>'Deon George'), + ), + 'method_directory'=> array( // Out method paths for the different functions + 'admin', + 'affiliate', + 'reseller', + 'task', + 'user', + ), + 'method_security' => TRUE, // Enables Method Security. Setting to false means any method can be run without authentication + 'site' => array( + '172.31.9.4'=>1, + 'www.graytech.net.au'=>1, + ), + 'site_debug' => FALSE, + 'site_mode' => array( + '172.31.10.200'=>Kohana::DEVELOPMENT, + 'www.graytech.net.au'=>Kohana::PRODUCTION, + ), + 'site_name' => 'lnApp', +); +?> diff --git a/application/config/database.php b/application/config/database.php new file mode 100644 index 0000000..cdefb2f --- /dev/null +++ b/application/config/database.php @@ -0,0 +1,42 @@ + array + ( + 'type' => 'mysql', + 'connection' => array( + /** + * The following options are available for MySQL: + * + * string hostname server hostname, or socket + * string database database name + * string username database username + * string password database password + * boolean persistent use persistent connections? + * + * Ports and sockets may be appended to the hostname. + */ + 'hostname' => 'localhost', + 'username' => FALSE, + 'password' => FALSE, + 'persistent' => FALSE, + 'database' => 'kohana', + ), + 'table_prefix' => '', + 'charset' => 'utf8', + 'caching' => FALSE, + 'profiling' => TRUE, + ), +); +?> diff --git a/application/i18n/.htaccess b/application/i18n/.htaccess new file mode 100644 index 0000000..281d5c3 --- /dev/null +++ b/application/i18n/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/application/media/css/default.css b/application/media/css/default.css new file mode 100644 index 0000000..2a4dc0b --- /dev/null +++ b/application/media/css/default.css @@ -0,0 +1,296 @@ +/* Default Template CSS */ + +table.box-left { border: 1px solid #AAAACC; margin-right: auto; } +table.box-center { border: 1px solid #AAAACC; margin-left: auto; margin-right: auto; } +table.box-full { border: 1px solid #AAAACC; margin-right: auto; width: 100%; } +tr.head { font-weight: bold; } +tr.subhead { background-color: #BBBBDD; } +td.head { font-weight: bold; } +td.bold { font-weight: bold; } +td.bold-right { font-weight: bold; text-align: right; } +td.data { font-weight: bold; } +span.data { font-weight: bold; } +td.data-right { font-weight: bold; text-align: right; } +td.right { text-align: right; } +tr.odd { background-color: #FCFCFE; } +tr.even { background-color: #F6F6F8; } + +/* Global Page */ +table.page { + width: 100%; + border: 0px solid #000000; + font-weight: normal; + color: #000000; + + font-family: "bitstream vera sans","luxi sans",verdana,geneva,arial,helvetica,sans-serif; + background-color: #FFFFFF; + font-size: 13px; + empty-cells: hide; +} + +/* Page Header - Logo & Title */ +table.page tr.pagehead td table.pagehead { + width: 100%; +} + +table.page tr.pagehead td table.pagehead td.headlogo { + width: 100px; + height: 50px; + vertical-align: bottom; + text-align: center; +} + +table.page tr.pagehead td table.pagehead img.headlogo { + border: 0px; + max-width: 100px; + max-height: 50px; +} + +table.page tr.pagehead td table.pagehead td.headtitle { + vertical-align: bottom; + text-align: left; + padding-left: 5px; + font-weight: bold; +} + +table.page tr.pagehead td table.pagehead td.headimages { + vertical-align: bottom; + text-align: right; +} + +table.page tr.pagehead td table.pagehead td.headimages img { + border: 0px; +} + +/* Page Control */ +table.page tr.pagecontrol td { + border-top: 1px solid #AAAACC; + border-bottom: 1px solid #AAAACC; +} + +table.page tr.pagecontrol td table.pagecontrol { + width: 100%; +} + +table.page tr.pagecontrol td table.pagecontrol td.none { + border-top: 0px; + border-bottom: 0px; + font-size: 11px; + text-align: left; + vertical-align: top; + font-weight: bold; + color: #000000; + + padding: 0px; + padding-top: 0px; + padding-bottom: 0px; +} + +table.page tr.pagecontrol td table.pagecontrol a { + text-decoration: none; + color: #000000; +} + +table.page tr.pagecontrol td table.pagecontrol a:hover { + text-decoration: none; + background-color: #FFFFFF; + color: #0000AA; +} + +/* Main Page */ +/** LEFT **/ +table.page tr.pagemain td.pageleft { + background-color: #FCFCFE; +/* display: show; */ + vertical-align: top; + border-right: 1px solid #88AACC; +} + +table.page tr.pagemain td.pageleft table.pageleft table { + width: 100%; +} + +table.page tr.pagemain td.pageleft table.pageleft tr.title { + text-align: center; + margin: 0px; + padding: 10px; + border: 1px solid #000000; + font-weight: bold; + font-size: 110%; +} + +table.page tr.pagemain td.pageleft table.pageleft tr.footer { + font-size: 75%; +} + +table.page tr.pagemain td.pageleft table.pageleft tr.footer td { + border-top: 1px solid #AAAACC; + border-bottom: 1px solid #AAAACC; +} + +table.page tr.pagemain td.pageleft table.pageleft tr.spacer { + font-size: 5px; +} + +table.page tr.pagemain td.pageleft table.pageleft tr.spacer td { + border-top: 2px solid #AAAACC; +} + +/** BODY **/ +table.page tr.pagemain td.pagebody { + background-color: #FCFCFE; + width: 85%; + vertical-align: top; + font-size: 8pt; +} + +/*** Body System Message ***/ +table.page tr.pagemain td.pagebody table.sysmsg { + border-bottom: 1px solid #88AACC; + width: 100%; +} + +table.page tr.pagemain td.pagebody table.sysmsg table { + width: 100%; +} + +table.page tr.pagemain td.pagebody table.sysmsg td.icon { + text-align: center; + vertical-align: top; + width: 25px; + height: 50px; +} + +table.page tr.pagemain td.pagebody table.sysmsg td.icon img.sysicon { + border: 5px; + max-width: 25px; + max-height: 50px; +} + +table.page tr.pagemain td.pagebody table.sysmsg td.head { + font-size: small; + text-align: left; + font-weight: bold; +} + +table.page tr.pagemain td.pagebody table.sysmsg td.body { + font-weight: normal; +} + +table.page tr.pagemain td.pagebody table.sysmsg tr.spacer { + font-size: 5px; +} + +table.page tr.pagemain td.pagebody table.sysmsg tr.spacer td { + border-top: 1px solid #AAAACC; +} + +/*** Body Content ***/ +table.page tr.pagemain td.pagebody table.content { + font-weight: normal; + background-color: #FCFCFE; + width: 100%; + color: #000000; +} + +table.page tr.pagemain td.pagebody table.content table.block { + width: 100%; +} + +table.page tr.pagemain td.pagebody table.content tr.title { + text-align: left; + margin: 0px; + color: #000000; + background-color: #F8F8FA; + border: 1px solid #000000; + font-weight: bold; + font-size: 150%; +} + +table.page tr.pagemain td.pagebody table.content tr.title td { + padding: 5px; + border-bottom: 1px solid #AAAACC; + color: #000000; +} + +table.page tr.pagemain td.pagebody table.content tr.subtitle { + text-align: left; + margin: 0px; + margin-bottom: 15px; + font-size: 75%; + color: #000000; + border-bottom: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + background: #FFFFFF; + font-weight: normal; +} + +table.page tr.pagemain td.pagebody table.content tr.subtitle td { + padding: 5px; +} + +table.page tr.pagemain td.pagebody table.content tr.footer { + font-size: 75%; +} + +table.page tr.pagemain td.pagebody table.content tr.footer td { + border-top: 1px solid #AAAACC; + border-bottom: 1px solid #AAAACC; + padding: 5px; +} + +table.page tr.pagemain td.pagebody table.content tr.spacer { + font-size: 5px; +} + +table.page tr.pagemain td.pagebody table.content tr.spacer td { + border: 0px solid #AAAACC; +} + +/** RIGHT **/ +table.page tr.pagemain td.pageright { + background-color: #FCFCFE; +/* display: none; */ + vertical-align: top; + border-left: 1px solid #88AACC; +} + +table.page tr.pagemain td.pageright table.pageright table { + width: 100%; +} + +table.page tr.pagemain td.pageright table.pageright tr.title { + text-align: center; + margin: 0px; + padding: 10px; + border: 1px solid #000000; + font-weight: bold; + font-size: 110%; +} + +table.page tr.pagemain td.pageright table.pageright tr.footer { + font-size: 75%; +} + +table.page tr.pagemain td.pageright table.pageright tr.footer td { + border-top: 1px solid #AAAACC; + border-bottom: 1px solid #AAAACC; +} + +table.page tr.pagemain td.pageright table.pageright tr.spacer { + font-size: 5px; +} + +table.page tr.pagemain td.pageright table.pageright tr.spacer td { + border-top: 2px solid #AAAACC; +} + +/* Footer */ +table.page tr.pagefoot td { + border-top: 1px solid #AAAACC; + font-weight: bold; + font-size: 10px; + text-align: right; + color: #000000; +} diff --git a/application/media/css/jquery.gritter.css b/application/media/css/jquery.gritter.css new file mode 100644 index 0000000..0d6cba5 --- /dev/null +++ b/application/media/css/jquery.gritter.css @@ -0,0 +1,92 @@ +/* ie6 trash */ +* html #gritter-notice-wrapper { + position:absolute; +} +* html .gritter-top { + margin-bottom:-10px; +} +* html .gritter-item { + padding-bottom:0; +} +* html .gritter-bottom { + margin-bottom:0; +} +* html .gritter-close { + background:url(../img/jquery.gritter-close-ie6.gif); + width:22px; + height:22px; + top:7px; + left:7px; +} + +/* the norm */ +#gritter-notice-wrapper { + position:fixed; + top:20px; + right:20px; + width:301px; + z-index:9999; +} +.gritter-item-wrapper { + position:relative; + margin:0 0 10px 0; +} +.gritter-top { + background:url(../img/jquery.gritter.png) no-repeat left -30px; + height:10px; +} +.hover .gritter-top { + background-position:right -30px; +} +.gritter-bottom { + background:url(../img/jquery.gritter.png) no-repeat left bottom; + height:8px; + margin:0; +} +.hover .gritter-bottom { + background-position: bottom right; +} +.gritter-item { + display:block; + background:url(../img/jquery.gritter.png) no-repeat left -40px; + color:#eee; + padding:2px 11px 8px 11px; + font-size: 11px; + font-family:verdana; +} +.hover .gritter-item { + background-position:right -40px; +} +.gritter-item p { + padding:0; + margin:0; +} +.gritter-close { + position:absolute; + top:5px; + left:3px; + background:url(../img/jquery.gritter.png) no-repeat left top; + cursor:pointer; + width:30px; + height:30px; +} +.gritter-title { + font-size:14px; + font-weight:bold; + padding:0 0 7px 0; + display:block; + text-shadow:1px 1px #000; /* Not supported by IE :( */ +} +.gritter-image { + width:24px; + height:24px; + float:left; +} +.gritter-with-image, +.gritter-without-image { + padding:0 0 0px 0; +} +.gritter-with-image { + width:250px; + float:right; +} diff --git a/application/media/css/jquery.jstree.css b/application/media/css/jquery.jstree.css new file mode 100644 index 0000000..633edee --- /dev/null +++ b/application/media/css/jquery.jstree.css @@ -0,0 +1,56 @@ +/* + * jsTree default theme 1.0 + * Supported features: dots/no-dots, icons/no-icons, focused, loading + * Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search + */ + +.jstree-default li, +.jstree-default ins { background-image:url("../img/jquery.jstree.d.png"); background-repeat:no-repeat; background-color:transparent; } +.jstree-default li { background-position:-90px 0; background-repeat:repeat-y; } +.jstree-default li.jstree-last { background:transparent; } +.jstree-default .jstree-open > ins { background-position:-72px 0; } +.jstree-default .jstree-closed > ins { background-position:-54px 0; } +.jstree-default .jstree-leaf > ins { background-position:-36px 0; } + +.jstree-default .jstree-hovered { background:#FFF0C0; border:1px solid #841212; padding:0 2px 0 1px; color: #841212;} +.jstree-default .jstree-clicked { background:#FCFCFC; border:1px solid #FCFCFC; padding:0 2px 0 1px; color: #841212;} +.jstree-default a .jstree-icon { background-position:-56px -19px; } +.jstree-default a.jstree-loading .jstree-icon { background:url("../img/jquery.jstree.throbber.gif") center center no-repeat !important; } + +.jstree-default.jstree-focused { background:#FCFCFC; } + +.jstree-default .jstree-no-dots li, +.jstree-default .jstree-no-dots .jstree-leaf > ins { background:transparent; } +.jstree-default .jstree-no-dots .jstree-open > ins { background-position:-18px 0; } +.jstree-default .jstree-no-dots .jstree-closed > ins { background-position:0 0; } + +.jstree-default .jstree-no-icons a .jstree-icon { display:none; } + +.jstree-default .jstree-search { font-style:italic; } + +.jstree-default .jstree-no-icons .checkbox { display:inline-block; } +.jstree-default .jstree-no-checkboxes .checkbox { display:none !important; } +.jstree-default .jstree-checked > a > .checkbox { background-position:-38px -19px; } +.jstree-default .jstree-unchecked > a > .checkbox { background-position:-2px -19px; } +.jstree-default .jstree-undetermined > a > .checkbox { background-position:-20px -19px; } +.jstree-default .jstree-checked > a > .checkbox:hover { background-position:-38px -37px; } +.jstree-default .jstree-unchecked > a > .checkbox:hover { background-position:-2px -37px; } +.jstree-default .jstree-undetermined > a > .checkbox:hover { background-position:-20px -37px; } + +#vakata-dragged.jstree-default ins { background:transparent !important; } +#vakata-dragged.jstree-default .jstree-ok { background:url("../img/jquery.jstree.d.png") -2px -53px no-repeat !important; } +#vakata-dragged.jstree-default .jstree-invalid { background:url("../img/jquery.jstree.d.png") -18px -53px no-repeat !important; } +#jstree-marker.jstree-default { background:url("../img/jquery.jstree.d.png") -41px -57px no-repeat !important; } + +.jstree-default a.jstree-search { color:aqua; } + +#vakata-contextmenu.jstree-default-context, +#vakata-contextmenu.jstree-default-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; } +#vakata-contextmenu.jstree-default-context li { } +#vakata-contextmenu.jstree-default-context a { color:black; } +#vakata-contextmenu.jstree-default-context a:hover, +#vakata-contextmenu.jstree-default-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; } +#vakata-contextmenu.jstree-default-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; } +#vakata-contextmenu.jstree-default-context li ul { margin-left:-4px; } + +/* TODO: IE6 support - the `>` selectors */ diff --git a/application/media/css/login.css b/application/media/css/login.css new file mode 100644 index 0000000..baabd9e --- /dev/null +++ b/application/media/css/login.css @@ -0,0 +1,43 @@ +/** Login Style Sheet **/ + +table.login { + margin-left: auto; + margin-right: auto; + background-color: #F9F9FA; + border: 1px solid #AAAACC; + padding: 10px; +} + +#login-uid { + background: url('../img/login.user.png') no-repeat 0 1px; + background-color: #FAFAFF; + color: #000000; + padding-left: 24px; +} + +#login-uid:focus { + background-color: #F0F0FF; + color: #000000; +} + +#login-uid:disabled { + background-color: #DDDDFF; + color: #000000; +} + +#login-pwd { + background: url('../img/dialog-password.png') no-repeat 0 1px; + background-color: #FAFAFF; + color: #000000; + padding-left: 24px; +} + +#login-pwd:focus { + background-color: #F0F0FF; + color: #000000; +} + +#login-pwd:disabled { + background-color: #DDDDFF; + color: #000000; +} diff --git a/application/media/img/address-book-new-big.png b/application/media/img/address-book-new-big.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7c4e016c9a9f73b0de9857e3df5fb576257c73 GIT binary patch literal 3787 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 BGL`@U literal 0 HcmV?d00001 diff --git a/application/media/img/address-book-new.png b/application/media/img/address-book-new.png new file mode 100644 index 0000000000000000000000000000000000000000..e37385cc152e1728bc16c20ed1a8ce242fa30ada GIT binary patch literal 1305 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 TRS@nf!FPIzm+~vs(zpKy%_B!@ literal 0 HcmV?d00001 diff --git a/application/media/img/bug-big.png b/application/media/img/bug-big.png new file mode 100644 index 0000000000000000000000000000000000000000..0758a85529421df9045a6d866f7a2a8b3bab744b GIT binary patch literal 928 zcmV;R17G}!P)>=r{{DUqGC4Wf-rnxFAt0r6m$*mPAnrc5aHoFZPXX!tIW#Fkh-@}1 zgcunaIdE;Q)w-{*uc@iYd$HDLGMSl~nJ}5cMx2Ab0?2uvK5<5XZ4IbS9y%Yc`wJT07%ZDz#lGybn3JqSRL~ z0UMA74b4&(;i^(TLHHouW@2KZv$OMExB)%z3;YY;Loal|6x@ch@X6Si?-HL4j@D=l zeuOzN@HzCtAbbO5cnohq+|yIjbjFy_Ph%X%fYJIiybU2dgwNm{+<-yYgcaC>^-^hI zU_eS41c9}7c6JumZ^`EVsK0+4wqYKC3+K=OaORAut5fhdT!DJ9)^2Za*F-0SAOIR0 z8@s!^fxX>b;4yp%z}&riy}2BhE=kyhKVc7q5G^e&9UUE+OvblfsFVuBuu`cws9wq? zSc40vPR(7r_U6`>U0-Ks2awfe0uWa2VGs(+I&9m+vkw8USUYBzkBrPwS0PwY4>8<_XtR z?QyfF^}_so6h-NDI*OvUwl-_+^wd-oMXzJg!&ysCO%38{Fr7}nPRAG{+`UUx;q}6|pRv$CJeep*C0000e#& z+t{Av&YgSD$HR=Bn^`>Lr70Zg(>cEP`kw#)Th96JcSeZ_|If|Z&X+x@)U^ieQ;=1V z1%?zD8b$>s4VVbv!hIqaw`)$dRSP_&)Iou-n{ZrN)4lkBS7?DIG5{bucl%iZL z`sG5Q>=p_^X?fY!mCBsJ(;7~F6Hc=Q5Su3OTcz$Y;c+JxOZN^9MSF&ZtYm*b;`@l_ zA)bdWmq7$05(StW=BVh<$S&*@cnnVZ%$-}hp9|V;7v_|)}YiHz$^N2Tb(O3r`_L=vuhW*3l}*5=9>kt zSbVVz_dY1%ZX1ComFltK#q>4T+<4s`cf@oqhc`M3K~M$S3HVkdL{Nr-JvfNj*T?zS zUUTQh#?A+D=e;6RTPAQqsRI`LIeY!}J-ZJbvdZVq5iBn^2VB>y5D{qtyuLS*Numc1 zF!kPh!G(9E3Ccp{U{UO#y7py`f`v|nAevx(r2jL>((4QAh-J=gm1>?2AU z9TPaI)Yl^M_*aLI9ErLgd~jLw>%~i}dz;*6q*B2>dzQV24@ctZ^zFm&U`H|Vs#4Yz ze0bpY+Xv&0gEuzT;ODAM@NPDU*PaVqv$I($j>EgJyfW{>VEyviV1WsEDAnEFy<>0? zZ+yIkQl&#rkTvP_`TACwnj)Ub&@(U)i^9X}THpnxB8xD6)6t`8t60RJnQ5=xX8S<9 zjjY8=)XGX`CWG3!lQS>8kheqzkBKZbwZH=0nM|k6csh+gJ-wlzYYDI&_zqeyKaU-a z(%sjG2VZIFLJIELnaw8sTyFD0SPwMp(_467Fh5W4;9$~(`9i57tXv7M*@Ww9eN~gKq)xNpYc#tTy6@-n zBvPq_f}0vXDHUXG+l~gFM>{W7VQqUA6ZSR`Fkqh( zjhcjaU8SloQ;N)8cd_fS$F52y@y_sYBS8@Y*TqRDkq`kRq}4rEG)myQSD0_BDZ!@F zh!*o{*Tsp)QE+XuszgiyTI)s^`WQ+t%}9vWC_qEB8G$jcSUeo->RNM~uzv@xi$6Da zm6B1pa#dO-;y9G^c?_6pARutQQYwkcWK@Ipm15z!=U9CH`SnwVjpY8V_Ko^|A-s6l zHeN1=z-R*j4dZ3cD~nQ5sb5_}no)I@Sqt`hAnkIYPBypjxKcqE3V4Nr2%K+bfpNE3 z^!%lzXnA}bOcP}oDATNtO%r8VSB#ZuHn>_Vu(A;=gcPPrC4%K;bg6`y$>6$fQQ*S{ z0ulI2VPz#Mj)NEm;(49bWmd`9cGDii=*S8ZTI;pzqRVA;sf76c+B{||MegibBY>A0 z2pkhxdRnQs@{5a~iF9>QX%VpX(@PLk%PTFum)9(3SylHl43uG1Ev7Z1b@d%uBb7>Z z++VtDiT4ZQY_8QRe)z&61ZDr60LtXKQrUm{r#JoY%2s?{f|8` zz~an|ANc;C?iD%Lt}5}vg~i1{nS*336P9)47@wlN~m%w$=9VUNkzUw#`u^47NPN7(o`IQxaTI=_J4Zk@DV|AKe z9WO}TNoLCvnwk}wN#Zsbz8fCsGmQPoXw*tXB8F{Q=yJJw%o+sM^8~G{nPw<8SGJ82 zi4X(<#d2AeN+oY02qsU#Q?J80piq;1NLFg#{S6&4DWz;64J2w35B2e*@QEYv`Q7j- z+c2UD$1x(7Wf_KnSrdRjgg|Sw)|7oeD0rS91c5&a@B9^Be+k|LNG?|IUC^$nL@Bso2&^)U8QPo{_&v8L_!9Z(f z<4sQPty^_|_1$y7dr3s_e>Ce~-w^3?u{bXxa}1Q2c}9$ZVP1nVs}b<1cyRC#BBo<{ zC&kB8Q_}*hP`-Q2^!B8sH?LHGAD9RzpTL})aJ>IKhz^goS>*Q3a(Uyw!9?nySo}cS z_8p9mra7Ne=J*uwD-h2E1W&xcY9q&1XMQ0=}I z)D9xx%mbaCAw4;fCd*p+ibyBm-wTC#IL;_#GD;4Qk|afR0n|E~zZ@Wf+1-cTlUH!v zu_>+R6R?QPA>EaRUjemi-cD|-yb5f8)2{oehpz5imk`YXEgy?WNu?akVF)FH!OcxT z>kjVh(Bc?-#UdmKU}aqmqEe!mnP>U__U6t4zeXbW{mtKMoX>*>`oZT`>>L2- zl|gWSAG~MJYIUMd-kY7>q}J(C=bKJcDwUiMr>8&LH|EphU!DZbupw$}1Ss?axh%x8 zAhreMvY^2Nba5QbW>ZdnS_`*h{^I?anPp>)yRfjJ06;G-ErE#C`ue&ZDwTdX@aof8 zH-M6EPZ-Yo9>yjQQfO@)NM9{2M+27r7ct=O&FK-Ng{P}3{n@FS4u&hu;QDbp& zkx4|_igl%8xyG*c5_3Ks$Nlf_?v{pv;8vm8e6zdR9035&arO>Usg2!C=IPfrhQF=b z>XgJjF)A*H$H!+|TU$*Lk>oxmB3fEnqUGgfJI-d)y+j$Kbq->=ijZc-DJ?=V5EFAK sj#Jkn6&*!Y0QFoh*IZp)HPQn91H!bc#XW9h5C8xG07*qoM6N<$fYzVCCsZ|1ws)pLoB?KqBM9M`FuhNQmb)|OUPl(aqN>Sv}R>vzFK~O4`)J$+8i=v2&B1`i;vzf_7zu#GM9{*Ml{fEbn z9czE20O!t~8y}_p=cJTh-n;wWbt%Mnx7&-OB(hnSx;!ha6aqNsjxbsfAX&LoGEp+3 zgHi7Yc>I%IC;G-S&pcCn=m4+0{?ZY&@OQiB7Dl_K=ccdUxW2Kuxse7zP|;e4LI?#w z7yzKP);j01yvT>9FzrCAgfc$L@@P6uld`w$(Z?TuJpRxC&YnFxmgd7B-+$kdrIZEl zUVL|1DOD80OBI*}`P$V<(<^ z8USwF8G0wwX)*lsnVI>RG0xBb==>XjR#|zhoCcvDXsx?ppu0-Tu2Q-4{;3{Nk6waO_j_yB3<~-}=Gn zv2r<;TKAO@EzWq0akdEnEefZQNX6T_Z@( zGsb@7^vf@PXK`y2qguq^+jFpNrDWsHA2qD3P^qfjWFNtH4bEE(Wh(5Q4=|^$Y%deBF*dn*qdWwj~7$lzf zjAR}Gt@o~Q&Qfa)rIazn6#>SGWDE%*&Y*GL6X!e-?~G-H@jTBh08q}EMg+HQ0N&%3 zQ9R589h9vzkfnx{86#o26)@nC`euR#5fRaO?>J)y2ml8L1>xX<5JkF5ORH=)mgmM> zj~>yZp@b4^jYhzgZ3Cc`UWi7cIVn|$0KyYbN~KgU-P3!P3T@Zgz1IfuTq`rx6cGUt z!CDJ|3YoO8%IQ|)dmrG#|04}jpsEX#IDr3L3Q zaD@{T4XaIm^3kwzVtQis((!KVhu^t!eeLxE8zbk&ETb6<Y%U*oNAWYR1x%H>c~%6PZeX#*k4+ZO`_{v$8)FO*AVBL&x#QaM_QSIF8S5Yc={WuYB+K2^C-5y?D>sx%1zDG0W4A3WKcM>+$uKjqud*eW?&gy$>8A zjY6qda2_j7L!Y|P{KG$&vXgAOD+Tp{8KmEoVuf6ujuYT&GOYdC>fZ*VXQ=k6L zZ7t?F4Zh`UzB`H&GC#j(v0Sc{wFvZ{g#}v_h3X7S`7oauS!||;2xm++Pu%W)Y-Wd} zBrpI#5iz*`{`)_jFsOPtVeZhJL)?AWo05cUaC?Kr3FDf(E7VNrvRqhq52IH47Ff`Q0e)9|^;-u0^P& z1{4j zfWJR^^2xV9GynjA$BsYss`vQ)Znw?q)oKkG10k54o0((I`I=yCsvL%MK^W}TTK`f! ziuWxpEH*6w6YUQNrph& zr6AA<)YDr<$Na)#c4Ku_d+@B^>+`YkF$Vwy5S%m4L{XH5rC^eci5EXo04++d7S@E6 zQcnpw0E$VHC?SOIcDl2p!Dy<}>5MC>xN}bDMedBvlU8ek2q~`|``EG0M+)%p!w-*~ zw<}qeSs;WCdcD%d#`^w2e{dj*qJ=EWW)X2wX|>?JpB)Z|EKbrW$`a2RedkWJpu5fk z|FcSkchVxA)FgyBj`i%^Tu6{0A{G-9%`gl@S*cV25n<)#ib~ToMrU&%S&Dg<`y@$?b8g5O?Pqxwho!K4_UzfwPBrFY+(ec}XsZf68B0CYE7zc*Gc^X5bowAR14Z=D6_ z93*KBg~>q(4vj_~j5VKm?rYEO+A#pm*`KYhtU#~Vfh)`JL6#;!2nF(s=MJ28;JpJ= z6kv_-hG5ej1K=Ir7X%^HDs@=;!5R7(QZ*dAr~4ys)-<lH zN4Hvxh-RnEfW9>gApiuongjp_01f~O0LTC!{rTtvK#F(iz?Q|pEeHw#0szzpoTmWb zKLAkxfB^u9Tk)sK7XEJYK@jra>EVMbE!>Lhtuy*xFjh5 z)LTp(RTu~G|CyPc-Pv7c*$b50vMf+26^bpPF)`JRluhyS(xeYHzBF1NH0h(QzSJf* zG1ixwXpD)P)X0NKgGmL0U@#hOs+iW&i?Fa23Y5L?&g{<4?93dW)OOi!^t_$Pe82hU zo9`S#DMh6KlDi#AEpio?=<{ zWW8?(@31=+K@f@-j@OEsq89RbNU~&0XVRicjdPiV{PvL}N3?R7D}PYytNFm=_Po#$ z?6OF5E}qGz7ZZtiq@XBw1wmjPc0qKz+ySq*F0j6__A0!@58!y!#Iv)LhineVcz^Fc z`|ZUW%aSb5_jK>}xkR^*xAIMfZY(RRa^w2K)fwJm?QW=V2(SDZ=`l3z=+My9-`o!~ z_is!>UiUW#>~}ZTa+yr}R`<>xkGt0E;W$oa2y@M1v1CQ3*t&1;V4sxD{+Q28m%VQ9 zs>zU(<%u7FDd=87up^mFZo2I2n%nw)D@wPM77IH=2>F2!@)Jr68Jm9bxkmzD*Jh;b zRhuBh2*smSdrZI(T`z$WGaqcYxl8bz>!RAVE>rq&7r;+ZGu_Hq2g8i>DC)#-53gpK z&p{Z;qz?wH@bklntuY<`IdW<} z5O5!i#TM;gF;Nv}z<(smx#Ns>7#PyM0jYV>ivq|Jm6KJ!UAyshaam5dsC=}J12a@lS6UQpT-2nKqedUCiF zY;PN6cxJyzm;?9Xn*qZpsXXTxEjKE{BO@a!WwO`DLe5K2wIZ}q2_Byt z!S*&r)zlUM{~u;pW`25lTCbFNWjU%7LgpVK?$WU0nWdvR@$1j;^PcBC=Q(e70Kks_=lIeOdkmd5ef`G7(6DuR z)HyO23`5wM+0aR2}JfLZ7bPw6AFgrU9(P#wX@i@?Hbu|f7(;@JU zx#k^CyVz>AK6l>%sOzqKw5hcLLQ_G&lZSq*8QfkcZhjSFu_z>y30PZOg~jj!_xq0j_$bZgdITN{72>GNHlG0)Ef0iS9j=ugit%wc9? zFfkDTQ=cK+scS#J?Eq{x+wK;v+UpzhU>(c=ZYtTVQ6Js9&gF7F?{e8yPS*&`&CkHX z;yj~Vdza>VQ&ZDdwpE2R8s#%B+QzBz2|vuwGZ4_MRm)T=)t;?uhlU1TwGLW_U7k@0 zhZn&yY6EqXaz#-j@8ZO{4MLINIwuhbg%aV9vIc;nwYC0R%^KCDSdF#!JE z+1lFh+U9i}$34sEOOUFFIn3_cE3?1!t@E+@q@(X?19;&`5a zM>7M_Yw7`gZ#M(s8*_sjX+}VX?L#S94T*7#X$03ch!_N7E9XGi43Ht!!xPU%GJVoL$$`1q9^W`4U*g=0;grTVI2wW;Ljq z>Yxwnfkc2BW4=NP>2w;X& z_z~wRi0hP@X;iDw)Z_*TTt{>YFD;@<^(6Adf}>dkATAyA`@oJ3s<@<(NhfWLWRo^O zF*(jCMRY*}h?H`$&!(}#O<~a5Ag&3F`(b%yi82(=DgaKB`?k5yv^I%mPa+esS%F+B zZJzi)G(3pev0(QdU|pEHvd8OUvjnZ1!8wE8)Ay<9t81&4IWG4|)&Pjb!k?5^UXRj` z%L`=8jZ!~oYtukicPI4r>cP_A2ZIR2?y$3h_4+)FX4K@!!4eR%<&-@|p*(?hZzqt~%Qw%cvc-Q5L7V=pUWn~hH20bZ{gEr^eCp9-0VA#$!!tP{*y zTAnYh%c_iS7&zhYtLtvYgTWw>##0BT==8-OB!-@Cd za2OS585U7C>2e?#oJh&@C2cu5IlHzE0FzXno1~kIUXva^Lkm$@l+S2G#*SWOAE-XQ zFnY6dvyempB&&RZ^kUZR^uNjgClMy;rh;Trr|&{Ztbk9?6a!9Upj6goK%{R4)d0-| z1PB3ywiN({M3$G^(b1M5k3WdB0{Q=%x|?7y=%M6HQJN%{_)b|MJ3%uxO>Fc!fAP#6 zd7!%rKr9yg0R2vi0FbxoLHF6wsbxb88ogvyb!CDwM5j+yo8;4h(n_E#mcArr@Qv*P zfGQ*|DlE>AA$`(Rm<}9Nv~4Y5F!nG*ck1*<2?ThQM$VBD`&tU?fy|qw-^?rojhXoh zUbQU%@??2GEWLCgMjKrWFJj?3a4xXoboM#xyySZ%8J~|*dDSsGvQ9LL#iI@$}4X~FiS6POAX*)^9xj)2>Bo?%(nIx93Rz8VJ?+jh)N`a!)VgpGMbD@ zW~;Hu#S$?#da4^6n5;S+_H|B@cVgR$7z_9veXlM-O^Vcq?t#eUp*k%QH8}3{cf``X zSlMT#F}+?FrE@6YGLYxHIlfKi>^DDPH7%*GWDiO9;p!N zrrKmOKy6(Wlh4ksj@Z@#1wrhV@#5Of?)D@pR-eTLE!t+LRw{LU6v4fcH2^ZXSb=tV zl{P{rPIItE(+s#d2B_$h!bC2g`^=`z@55~Aaq>#8V>4S=oMSzMC`WSY^Lf)KwfR{C zKv#lvt&&K@(Ou3Vef+qYDk@0k@eyEP^Ln9B_@ul*!psrze{y;PCZ{H#R#}bC)x`!P zq@*W%BPAB5gy$US_fyeml+C=@aTyqo*PRwixsrbZEf=>{*W8Se@k_2I7yvEWa1;QR zwZh_p1OdJ00Hku!dqtlpV&mh1G`j~~m}Q#YjPgm8QmY$K*M+~lJ@DM$iKU_lInssj zJkU+Hs!<7)Nu(dvP3~{*IRK=`G!-ueo%n|SYp{tKnRP}*NfPI8@0(5^Dk$RP)F&td z4yO&>YbER2bd$jsNXqXy031TBbo$B6z%nyC1)rmB_WQ@!s3?<52EGJ*liTY`Q5|^w zE*8p$I_6^Oc~V|HyJN?W2QYTtH30N5j0Z9HVC+41><`al!9}Pc(Om2Ec@RJi7Gi{E z;PumjkA<`@v;M)&&yew)g>EB=GNI`pxP7D0q0>TlPY1iH!0B!n*FBo~{==Ey-Fe3V z@U;tLcjkeQVI07C@-KgV=Z&%}rE#hOn|&C1dN%I=&tE(nf9tJduVvOfwrL|2dq2!M z`A4jQn6|woxwIT+|6NPNz6x!Hs;PeU#EHM1#C1<$9NhGM`u$yZ3gE%akMG0SkMRV? z*D$`GbLzuV?6%4BifeJB(U3ZOz9fF~gZEEmK7S&!e&42`lRa?w@Zsm)Kl$F@B~nr7 z%C*Ze>=~VeGrUH`Fq mYou!?%3zeFt4YZUR7b;x1fW{b? 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 literal 0 HcmV?d00001 diff --git a/application/media/img/dialog-question.png b/application/media/img/dialog-question.png new file mode 100644 index 0000000000000000000000000000000000000000..ec0776145dd1f81ce086d6a742b97ff7672253e8 GIT binary patch literal 1086 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 literal 0 HcmV?d00001 diff --git a/application/media/img/forum-big.png b/application/media/img/forum-big.png new file mode 100644 index 0000000000000000000000000000000000000000..bbda895a99d6cb9d1a10e4a698fb8f939ab02871 GIT binary patch literal 1615 zcmV-V2C(^wP)8wQ zK~#9!m6mI4Tt^jvzx(p8?W`ZM<2X%>A4%&+OyZ~&;Hs^Ks6b6xf`Wq5{sDyeL4Op1 z_yMSdN7WS61|lJ70il9gMWD1zDhP$Rv~KH~*p1`HapTx=yz$yzuOGW!@8iDa&b|EL zx>!M|%8{;SG@5h1d*+-uXB2KtlnwLXRuyiRO$}}PTB<5zPnDIG-60*b!gU-~Hw@p` zqWN@Y^_pv2k1xirOaW`R1ZZg6x2JYX-E;Td)7|~hg9oUqsU}uYNoi>*bEzfP3Kq%a z0x!REhSB#g4z6XEA4|_(8wTV@0y;j?^Yv0yKKM&x=qzPT zG+k#UXHs+}uJBMbm8w`7mhG~A$2L0mbQ!}J-n+kOiBm7oB{15U_PX zjm2Vj8<7Za42)1;SIba;A7k&Fpt`Od#0r7$BC_vdxXaYnYWT92=Z_!aiRVu6%NI|R z%bOG|7t6AVltch!FHiwwbwKev*<4w@1(_={HMfMKsA$pKu(KabC77@McJ_np{t;@YydML3k3+lW4j| zHkZej9zhUzfDWiS5cr-OFJxEt=_L_tSJ1Qn5W1$J=_fS15wd|R@Fq&@o|K}b;HkbC@f6O z0AoNN@YVsN>FGRKB++W%|%T6ooeTwh=RtyC(t zzWz7hPhrgRKqfSr4x!@a*M{;DFAfce0MT$<_gIGB+F@hy!gT*1`8P&w9Xc2aXUzZr N002ovPDHLkV1f>g7GVGY literal 0 HcmV?d00001 diff --git a/application/media/img/gritter-close-ie6.gif b/application/media/img/gritter-close-ie6.gif new file mode 100644 index 0000000000000000000000000000000000000000..aea6e427ede71a9f7f709aeb7daa48e6fb5f07e0 GIT binary patch literal 718 zcmZ?wbhEHb6k`x$c$UP#%F258?%jtEA8y~i-NM4c)YNp%nl%Rx9<;W$uCK3;i;Fvd z{`~Xj&v|%wTwPs_jErPuWxc$-G&D4(PoG{>Q?p{liYHH=T)upH)~s3P=H@9WDTaoI zot>RNK0a1fRb&dSOvEG*2;&HeM|kAi~2*|TTgy?gib=g&uv9_8ib855-6)ej#& ztX;cy+O%l`0s?k+cG=n485tSJj~{n%aEOYEI(6z)Wo2bpSlIgY>lZIx{Nu-u_V)Iw zswxo?ksCK|?Afzt-MV#$4joEJNSHEZNCMki?(^G9DBs4DA0KlA4-XUF{g-;~8~>S)2Qt zr4^YHrt(BjO<>ZDo6y8!o1iYtm$)pEH7Y*AOKt*-K?1YN@~w$NZVB8R69W^p8MbX> z2v1<=Z;IJ;@X(=<1m7f9COv`W{2W_#B6*itXfVlfC0H&un##G%Tr5F&xm1FqNdn7q zmDLHH)^8GImisBQu{=oBX0p?n%41+0%5+gUQE*yHP=>bWBJM*fH78PBXE3_T=XfRr zWU^RA$1;U5B%i&)KPiEy-n&Lx;gRF-Jl+UR$iBA%yu2tBQB0m*KZV klO;q&G-@>)84n8x&q`bHRhy%cqp_2ThePmiumXcM0E(Ob2mk;8 literal 0 HcmV?d00001 diff --git a/application/media/img/gritter.png b/application/media/img/gritter.png new file mode 100755 index 0000000000000000000000000000000000000000..0ca3bc0a0f8068194082db9719d6e20a8645985f GIT binary patch literal 4880 zcmeI0`8Qj6AIC49ZdA3?#VD1|W2S1WstH<4TGLvKmc|nMR$BX5QcFZTEuGdg?WDx)$W4Lb*_@?=xsSEf}j=ky@>l}G27U}m#5O6s#(m&{wO}JlhkW-Lf zU_$giL8bukW1Y>F%Qx?`7RFH-aDH*uia`wL`Mn!ygw7jo^Sf?_g;qYUK78Up;hi#z z0St?nllk&5`)cZ65uGlBg_2T|JrP}TgBkLo=tSJ@XAfU$&EL7YzxtA`LU^g^nKMq$ z{n8TJl2YE%rgwiJwF-ytCD8spQYde5nv?yk5tp=7swE19F~}KkJ3~`03NlI38_7@Ddgo5^^R>&8%f}dyS!(+urDQF{{E&=N15T)nOd>5$6YQ z7%x&j-XS>RCJLpj+;o?|{$i6S4=pA)F&K=6nukenKlZu(^Yi$oIPUOLrixKKO|;aC z5^nH0U6e!?b>G>WN-S)O+P%Y%U}Qfk2$CAd!l_gbFAW zN;*C+b zN?x%aY+OB~I~Dr+`sB5xQ#D?#dk(4^Kg}o3_b@jW$2Q)L2IEk6&d$yo`MMqg-gv7K zjaqoWx-DYAF5r8m!d$0ikQo;q9-hg;p(Rb#x;%=4g)6*Q9}f0oq$$dFloAT z?0oNW;G9J+g#FYUTC-g(}blG#-ka#OW_af#=An@unq4dw7g}TX+la z1^1irXuwtul!(F+4TrE|gW&$p>u>&4G#{a(M1s;rc~FJ!wD?6VPyI4=d+o#{Sz zYqRr-5)%G?m_-du{|~p4P%_^toGnVyq6afd%sN3k9c*uiog38Kv8eZs__ZjQq9Xa} z_nD!pu$1kl^(hlxTg*GhpCq$J^f|}cA{qEaX~@!c5X z=&6CRhXp<T` z%=Focl-W5NTbW$sb>6+;l7v~(`WxNG&rTJ|!0oN0Q-gTBR{B~oFV=Npvn1jv&LG%< zvpUn86w$WweyInN!X-5xv>Xa0(;9v?*}SsZK8Wsho&*ixEyS^J|C7C+&*w{x!O(jS z=E*$#nTDfeuoyWpy7nhz`@bCN>YwE1Sqvs!EwQ4stZaI*(wQLf2hTk6s~#cyRC^KR zWT*d1>PJODwRiAEo(qBU8$Pd!R$m{($WB==gU%^Nw#=2c9KD7Uq?e=1IT8^M(sn_T zW2Ke=%_fcuBR2?A%Q@)1>-*mZ0)gNs5-dKvQD(Vl!X?(<@{Au1 zf_D1rd(aa3>~%dmG}nV&B^)BBzR`_I|eO-JpJDZEduLt1ELPKR?r|E7oo!s!>Y&U}NMeN>4B|)ybwuu{}-yAsS zZ=qdz_9PYm#r85(%cLnP^K7T9v*x=V$9sBgYqtC2D#2im8fO45N2CNQ1Rl$dJG7IH z@}$*J&4xR+Rtb|@kT^WE7w2|u<&&#i)zafK!g`{x**!fsZ$K8X^bdizduP?Gl4sZu z3955Y|N3EgUNk?a>c^eWD3GKKwzx>nb|=a%P#pfvezH1^-7dsJj$qH8y57PtFhfS7 zQcMdVWK@sTCZ!i{fV<%hGO*Z$>I?CxXb$my>>klfD zo~=zUZ5Mt`mU(-r#aH|rEJdVW8rKcw&Jl3^YG9LvEwZSivwcOyW~Z*%+1VL}4im#B z-#i>OYG27DQmOn$Ok&S~t+R-pC*d9PHEuS8xu_mT{LUmQdEfn6B-x{|hwqZJNTL^=2JWQDfnaL12k|{h3MpBgAWF z9eDd02H#ek`~u}prbGI{UgG@=ynRn)dKWE@)CPRR@k*0rY85X8p1u| znO?{X>K(7@BH8T=jr5Z(N|I_#iUSgnTJ~;z6&fnu;`i@4r$h2={$po-^^m4a){jSe zn8fi`q=`z$D+<_{rJ*izBn06H%pP1c)#z++R>md6*uAVnO^`4`BV4Ng=(NM zF6$st2W0p8e~^7}{L-N($4^m!`)(AVboZfmFLv+KO+9s7_fm+=549mO;F?EQ<-afa zPVBp2zWe2STzpT<|EDaXC=Z5X9Ck(J-mOgTL%j06S1)tG&Msd^x#bpqfQ5k+6(9d{ zX4zASfd=*DE~KeML|LZ#;oIBITbJL+r`Nd8G#-?WSIVR{yRHQU%s{!UP;%e={5)3K zcjWFuZCBk9N7LwU82CXFVQqTDLGj!*{%n_?R=Zl;tv>7p1CLt&hxNM-$oJ;!_JY%6 zELi5Mh+V=JvaptUTQ;o}H{0cL%tz6yewKm5k-TXai=d(T^*3soJn?}z#RY}icK*0x*m$+nzzb@R!~_!{?)SFN|i*}yKK Zf3P5-;PGuo@D~?gbJgKWjfL<1{{eTAuA%?{ literal 0 HcmV?d00001 diff --git a/application/media/img/help-about-big.png b/application/media/img/help-about-big.png new file mode 100644 index 0000000000000000000000000000000000000000..45b5d620625fcf15fffa10e5a6b33427886969c3 GIT binary patch literal 2978 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 literal 0 HcmV?d00001 diff --git a/application/media/img/help-about.png b/application/media/img/help-about.png new file mode 100644 index 0000000000000000000000000000000000000000..435ea0eeaae4e0f4fd7f35cec165cf184e96f937 GIT binary patch literal 981 zcmV;`11kK9P)0=OBTd6P1FeF#9cDLHp>8UQ zAn0g8g-ryfUWqVvU!jxYh2m~xf52$R2D_*fS{ca1U9^Fw7Al8kXM22Obio z92raO{h&!XGRBbnqwZgUCp+j5w{$nepFBt;mTZ{pp#QOewN`^|T?fO)a)NG>eFj(ygfAUM_$4X4$J`CyeiZ`O=5>($3%^QIj3? zYexokAlkL7^Q};<{T+qLAU1~^+y#K48PN%r)8CrK*^y#pJ#*Fb%oi^&FwW=JYyqc+ zrB^w6ED}#PL=(@2BP~g+zk>0X(7Fycxcg6VrIf+}4`@qlN6^g`DyI zxA%W!FN(F6=`+&kdn%oH_T8rD#6Bve48`mPlma){h8t`{2jVz-mC_xM zFp>CSl#a5zaF&Hj->f>?wr{Oi>#<-s7KydL5Q((*pp?ch-@=+zwA+kw>Ni^97ZAN0 zHD9A_)~`*C{kYg8Bsw1>Y83hB*NVr zr%mR@_XUi(=J~dEk88R@F(4|?x98Qb1Eu^${K_J3sGDHqMP|=`QnuEcFviw&1=iXf zN9q)#b5pPQOE*8WH-CECuH5>{&ZS=SQ>SEfC*8IWL`0N`IN2P>m(tS(L zm?~s*_PB^RBDFuHhbCns9KZ$Y4;|)6uaryv{DC7}2kHU0x`Av9 zt}ZGdP!%-+v6(C87U_tM28wQ zK~#9!m6mI4Tt^jvzx(p8?W`ZM<2X%>A4%&+OyZ~&;Hs^Ks6b6xf`Wq5{sDyeL4Op1 z_yMSdN7WS61|lJ70il9gMWD1zDhP$Rv~KH~*p1`HapTx=yz$yzuOGW!@8iDa&b|EL zx>!M|%8{;SG@5h1d*+-uXB2KtlnwLXRuyiRO$}}PTB<5zPnDIG-60*b!gU-~Hw@p` zqWN@Y^_pv2k1xirOaW`R1ZZg6x2JYX-E;Td)7|~hg9oUqsU}uYNoi>*bEzfP3Kq%a z0x!REhSB#g4z6XEA4|_(8wTV@0y;j?^Yv0yKKM&x=qzPT zG+k#UXHs+}uJBMbm8w`7mhG~A$2L0mbQ!}J-n+kOiBm7oB{15U_PX zjm2Vj8<7Za42)1;SIba;A7k&Fpt`Od#0r7$BC_vdxXaYnYWT92=Z_!aiRVu6%NI|R z%bOG|7t6AVltch!FHiwwbwKev*<4w@1(_={HMfMKsA$pKu(KabC77@McJ_np{t;@YydML3k3+lW4j| zHkZej9zhUzfDWiS5cr-OFJxEt=_L_tSJ1Qn5W1$J=_fS15wd|R@Fq&@o|K}b;HkbC@f6O z0AoNN@YVsN>FGRKB++W%|%T6ooeTwh=RtyC(t zzWz7hPhrgRKqfSr4x!@a*M{;DFAfce0MT$<_gIGB+F@hy!gT*1`8P&w9Xc2aXUzZr N002ovPDHLkV1f>g7GVGY literal 0 HcmV?d00001 diff --git a/application/media/img/help-faq-big.png b/application/media/img/help-faq-big.png new file mode 100644 index 0000000000000000000000000000000000000000..1d57544fcfda07203ab62a1bb0f0bc87e716c83b GIT binary patch literal 2421 zcmV-*35xcKP)_O?|2fWbkhH`5;0jjp)m)ZtayBgX>V`;falG7tTyf-Cz#KgSOdvL9B;0b>_RC#?6=^7OiutvlJE`& z9}Wk{9RN~tm%UV~bvUjzLTZwckr4u&<|Mo}5{Zzjj;nQ|m8)RclGkJa9C(2W##-OO z?5TD#J1U$^8R=l^|JlRz-1DZq=F?9r4|jESHVzIB<{Wf74G#~J&dys6Rx8Ws0%+-e z$dsJ9$((caFxOr8WL1CF!xWUa$Uq2~e9eJF_L^8MMuI{AxC4YjeuDS5+W2D$0IV44 zBnOq^co}iY5Y8UYsB;#6znr_{9lR>Mx0y3GCID~{KZ|-EdYN67PIv=1mD2h2d8{1S z06bnV`KzOv!a#QNC#=S;wJ9<-mC!FhXx?5Ti3h2Oc4Z+X?4%*OW8&b zKugy{VyI~+UxHICQwo!;6+-Gi#)|NR+t8$BuszI{(FIu23j= zuD?G@w2~TK{W~{pGQSdS&^$oTEMqh&xA%#q5I*7WTO$h+I-9AIG3h(0duLUSO zWbcD&LW9A;xC4Yj0gCrNa`a&I@#Dt?h3SK}U)|59gq1)~4GV-Nf|dQ<<;$11;3R|q z7H+?s@Z%l4D%_wC99JY4^~d^&RpL%ef^9Nt)v8s~jjZv9Ry+R-Tw-G7$OZsYJ^kEq zhgust=&b9-lrelIkRh%U1!WfbtYgN9h3oFGIo0 z&_7@8p^nb`vZ}wjO$&~|+sQ2% z!s-4`+G|w+D6okoV>aO$prZ14Ez6sCZru3Gg-X0nQV29vIWSENfP?s1bo+sq?y7Q9 z@G=yPwe0s8xW0 zfdO3tFGGtU_w}~d04xA4RD>`uuUN5SnbBDC`%jJ@IE$Z9v=Cr%2rrt8l>DET_LH3o z0KP+)`hrmkUPgC&4Zu9me2rdGf9HRY{{ul_k2W=3IIDddoM@@Rc}q_BYyjw6^hksh zGS&V|cY6)M9MCru&D99^=PX*ZXs*HV@mr;3(ixA(BM%_wao=TQUH|u@mqe zx`gS2wO?<0#Y&7QgK?%(8k41-n^90u@G38{d|iF*nM+NV8u6ns)&74VS&~g~qZ7q7 zz(i;uHb7xEeW;f0G@7O;Pbn^wInMM3rVx0yV;KSPYx1qmj^QVZk?{>4H(1yV!YZv) zQ~jnWK~7Pm9-o{Xo84YK&%znDavZlLEr7{nS|C_CFD^dD8=||rYZ#oQmth#JI#cK~ z{68Q`lQTBhXWI8VKs>m7_6G|5Z#l=Uk8 z3B};8UAuO!(NwYn^7U_!cRo%q$FyR3(;#@60bD9(0>RDM-Zr$)u-B?o8PyJ8j!&H^ zfN4r6vz1QgD-#aRoZkfDQ(V4nTR|ZJMtbhJTiInjJpKbaJmzpk)2v&Q-WC;_dS-Z-)&a&orFomF;-gzMaDbh>pV^q_ndYY78hUbeHnK(l^ n*p=_TP=K)tJpEXFkgWd#=zh}tL(H{K00000NkvXXu0mjfj>wB7 literal 0 HcmV?d00001 diff --git a/application/media/img/help-faq.png b/application/media/img/help-faq.png new file mode 100644 index 0000000000000000000000000000000000000000..69e0ce1e958fecf4830943d1e6bc29add6ba1293 GIT binary patch literal 1118 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6%*9TgAsieWw;%dH0CG7CJR*yM z%CCbkqm#z$3ZS55iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa}?p8|YB zT>t<74^-dK&;TS?ty%>nApjx*VH+D8!-NsEgM$N{lbxLnXY=szz}XN+Mn*KST2f0#OJ84iPk(1;XIpJUZFOD6 zCLq|psiV8KySL-ryEk9HeEI$zh`s}z4OH{y&C90NhK}yG=GMlYySH_9we?Nts&A<6 z>~8I!(B0G9HFf%=n!3uGhN_7Z`=(BxykXPYO`F#5Ik4y8!2_qxoSHDPXY01jPo6w^ z{rY9uq6_V7uXc8|_Vjhm*nWM{o?DCd-CA|@-kPKL_MCrw;KJkkPhPxx_x8i551&4L z`26|v*RP+yfBy<}$&Vl3fBpLP=g*(NfBynq_4@Uz=9Y%tdw1^KwWF)M9q69^i9JBF z59pr0uHOEx8MCHMn?8BVwoTi%Zvi@KRZ(nAE#@%f`n~9>018 zby7!HE6_a)_X6E>d)1M5>bN1ZOPZV8 zJ32ZiPMI5CJ zO}Wfe`CpHxufMF({`A>JoUx)juWSB%+rV^e|N78tstWS5>)tnJy{}6QH9B&4)zZxA zuED)YF?mNNPP(uy)a5)W(XnL8r&V8qm5$ApNLHDAp~rCp_uX8zL?#xlx|lQ}z4?v1 ze5+Oe?3wYbYSKp;v(4Xcv%ls3S?KZiZ6KH80w4RB^XYe&N&U6<4_~@}=KVA7hrT>H zU3K-$w3UI&zg~Sl+5i5x_s0?qEplgVn;o0;_OGu~S7z-so~p+;k3UP?d~b{RS%a5e zFL$l8tN(T3ca(TRu==j6Sy#7hUvAAFexFg6VNV_V1N+d&JyVYB92Eg&K~Gmdmvv4F FO#t}{G$8;0 literal 0 HcmV?d00001 diff --git a/application/media/img/help.png b/application/media/img/help.png new file mode 100644 index 0000000000000000000000000000000000000000..674a8e92b7f20890210e7f9e52c3bc44733dffa8 GIT binary patch literal 738 zcmV<80v-K{P)nJY(i5b*a|{viz&fDK@p;)lY?EeSqdF8xKuX>ow^r?;1CFf z(zR|ajxHHoN*y~$Kok)|(xfU$Ufz4TdmVg9W2)lN@ATui?}z*SJNQo$J@Gvt2|T`| zw+4I%8h}w9U^<!n|*(9aPSGJcR;dQt!9$R zQzhr+``n9vO!`$2)nM?+y6vh~|))-^1+*iGjcDvoJl`#fm z45?I#v9U3<)_9(W)*8oga9#IiP_x;*JX8wHvJgU0tJP?=T1-z*<2VkE<6zr1wryjK z`4{AQ-tSVWL?jX+6bf}=OG`^wmPMn{!1KH==;-K(dcE!d2Jkx|Q>)cJtgo*>4~N4~ zrIe!6-o?em@cjHdo12^Z?Ch+iweC!I`SkR3J6MswpklH3qgX7y0v-i>Ct|VKlgY`+ zFO^D#-QC@vp!2P}-<4$@3db%707*qoM6N<$g2Gft=>Px# literal 0 HcmV?d00001 diff --git a/application/media/img/jquery.gritter.close-ie6.gif b/application/media/img/jquery.gritter.close-ie6.gif new file mode 100644 index 0000000000000000000000000000000000000000..aea6e427ede71a9f7f709aeb7daa48e6fb5f07e0 GIT binary patch literal 718 zcmZ?wbhEHb6k`x$c$UP#%F258?%jtEA8y~i-NM4c)YNp%nl%Rx9<;W$uCK3;i;Fvd z{`~Xj&v|%wTwPs_jErPuWxc$-G&D4(PoG{>Q?p{liYHH=T)upH)~s3P=H@9WDTaoI zot>RNK0a1fRb&dSOvEG*2;&HeM|kAi~2*|TTgy?gib=g&uv9_8ib855-6)ej#& ztX;cy+O%l`0s?k+cG=n485tSJj~{n%aEOYEI(6z)Wo2bpSlIgY>lZIx{Nu-u_V)Iw zswxo?ksCK|?Afzt-MV#$4joEJNSHEZNCMki?(^G9DBs4DA0KlA4-XUF{g-;~8~>S)2Qt zr4^YHrt(BjO<>ZDo6y8!o1iYtm$)pEH7Y*AOKt*-K?1YN@~w$NZVB8R69W^p8MbX> z2v1<=Z;IJ;@X(=<1m7f9COv`W{2W_#B6*itXfVlfC0H&un##G%Tr5F&xm1FqNdn7q zmDLHH)^8GImisBQu{=oBX0p?n%41+0%5+gUQE*yHP=>bWBJM*fH78PBXE3_T=XfRr zWU^RA$1;U5B%i&)KPiEy-n&Lx;gRF-Jl+UR$iBA%yu2tBQB0m*KZV klO;q&G-@>)84n8x&q`bHRhy%cqp_2ThePmiumXcM0E(Ob2mk;8 literal 0 HcmV?d00001 diff --git a/application/media/img/jquery.gritter.png b/application/media/img/jquery.gritter.png new file mode 100644 index 0000000000000000000000000000000000000000..0ca3bc0a0f8068194082db9719d6e20a8645985f GIT binary patch literal 4880 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 Zf3P5-;PGuo@D~?gbJgKWjfL<1{{eTAuA%?{ literal 0 HcmV?d00001 diff --git a/application/media/img/jquery.jstree.d.png b/application/media/img/jquery.jstree.d.png new file mode 100644 index 0000000000000000000000000000000000000000..8540175a04b0cd303e3966d1727f30ee9b8a7254 GIT binary patch literal 7635 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 EAHN$8j{pDw literal 0 HcmV?d00001 diff --git a/application/media/img/login.user.png b/application/media/img/login.user.png new file mode 100644 index 0000000000000000000000000000000000000000..7be48fb8cd09d6adfc48954e1907f38d3bb0c3b1 GIT binary patch literal 654 zcmV;90&)F`P)jL%;Pn)6~ zp+z7;99L)q8&6i(@;pDhvS!nc7x4PcUW2hXn?r(7S4e~~4wNS)&tLR1=iDr~xz%s_ zpCYEMNeNI9QX-`U1SCOHp6#ty*-X@(cLimD^%ZdQkQYW-ZbURRu<80K^-R;In0T@uAMdL%H@{m-*2~J%XyJ%VN zE@#($GnW&I^uH7h;s`cp6e}0&OCWbNaqnkKr2DFgq@H3 zGts7?ud1dvE>`|~(zIvbA{ONS6{SQaYpg6)B3o60TPim(N oN=PK6hSf)_xH~Spv5tS$Z>|sW3LxX?DF6Tf07*qoM6N<$f^GL0UH||9 literal 0 HcmV?d00001 diff --git a/application/media/img/logo-small.png b/application/media/img/logo-small.png new file mode 100644 index 0000000000000000000000000000000000000000..726933b130f4c934d8d48f3dab6d7e91ba332731 GIT binary patch literal 3978 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 N002ovPDHLkV1lZ2^SA&2 literal 0 HcmV?d00001 diff --git a/application/media/img/smile-big.png b/application/media/img/smile-big.png new file mode 100644 index 0000000000000000000000000000000000000000..22fba5ffce41d5eb25b1267871a8091ce49cd1f1 GIT binary patch literal 1332 zcmV-41Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOb@ z2reP?;x3T@00gv2L_t(Y$HkRfh*eb>$A8~md+&43c$}*x*4M*1 z6VsSdFSTI9y6pA+zP0{*{{{bN6Tp*mtjj-(@wd&B`l@p*0&Wg{S?>vp6mY6zNy>g; zrzea7IQ*e4S-YZ1^BKWf%fwW~{=;S2wzp6iIvTxFiFAuV zMgIxF6L89kh4X@KFFx42ZqqfLPC=-p>YP<;ElHY)sFkT&q_2Ni@ScCSaB#ff?h)Zz zokcOTX$n34J(=yhp6kEpigg`HZ3^?v4`t5l%1N3yvCc`x7dd#Oj4{ZHg?_rS*DM}B zR=c!NVGt<(CE=L^twWx9`Qar4tNQX0u@}7g(eG@0>?`?VY@UR3J0%x58Tzg!o1Q%` zJBNHUyC~@G(&&MWa|eKD&bX23j$c2^`bTc-+kM||457Evrk;f3diZsVG8GqB!xvj8HHT4o@asDH zrg4FQG1GcEK~ohGiLL5DpE0?RTdkW7XbhHgha1)`&k~hlQRfu3lBV^)lf(iJa3o26 z&Z)s6ViVPJ%}GzEN5@>*FtbMfqCse6SvG(;wUWs>Ax>DcJPQbG)?E%Z1941Ww-3NA z*R7>mDU%72I917KAq-^MIYicsx|H7wG52j9W%5*sr#3HO-@9A5abSvQ{Cmn}!wnZ1 z-g)6JZojgbk&$7xyr-0^Dfuk;hRj(3ajGXz6k>9La>23%`sk}=Y+U7vr9(^|9VCux zl%tedDqO$fI3EqZ#nv5${{H2f4ZvE5b9!=SOrJlcgJQ*w938Fow&s1k_cAMQH+A#y zYZdzPdwA&P`4ox?Nn$xMneg}?h!+-t&cl5_R^+@^yEZEy4$b^zD>P- zU)z@jKGWG&F>Mj6mjpYWyrZRmPR_HTucourXs%N>|d-UD0U-S+UhNmrKrWbjhV4U*!Ef;6E+0}v+6489l(*><6?|hQT;IMsOgjZFmK;30$UXds)7C3QaENxCww|eP(4^^kds%W8V z)U{JabiC`;yw}KV}(k??52Rpzq9MbKsV3>%m-EhYgaAymUOmA zd&b8Y4-$+0ZBkEv{i7QN4gtr33E(GS6!^8#KLd~f769{rE`er+xeaJ55H|M2jVvbw zqDJNuz&LOc_!)@K_M!zq7xlo+z#LBq5Im_DrU0S}PSHl7QUh27ext6#x!>eQm3K5w q6*lxTGxpfVmo!dvdOGX>wEY3UCM(zdn1^Ek00003VS!P@cil)z4*}Q$iB}d>0za literal 0 HcmV?d00001 diff --git a/application/media/js/jquery-1.4.2.js b/application/media/js/jquery-1.4.2.js new file mode 100644 index 0000000..7c24308 --- /dev/null +++ b/application/media/js/jquery-1.4.2.js @@ -0,0 +1,154 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/application/media/js/jquery.cookie.js b/application/media/js/jquery.cookie.js new file mode 100644 index 0000000..6036754 --- /dev/null +++ b/application/media/js/jquery.cookie.js @@ -0,0 +1,96 @@ +/** + * Cookie plugin + * + * Copyright (c) 2006 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +/** + * Create a cookie with the given name and value and other optional parameters. + * + * @example $.cookie('the_cookie', 'the_value'); + * @desc Set the value of a cookie. + * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); + * @desc Create a cookie with all available options. + * @example $.cookie('the_cookie', 'the_value'); + * @desc Create a session cookie. + * @example $.cookie('the_cookie', null); + * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain + * used when the cookie was set. + * + * @param String name The name of the cookie. + * @param String value The value of the cookie. + * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. + * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. + * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie and will not be retained + * when the the browser exits. + * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). + * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). + * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will + * require a secure protocol (like HTTPS). + * @type undefined + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ + +/** + * Get the value of a cookie with the given name. + * + * @example $.cookie('the_cookie'); + * @desc Get the value of a cookie. + * + * @param String name The name of the cookie. + * @return The value of the cookie. + * @type String + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ +jQuery.cookie = function(name, value, options) { + if (typeof value != 'undefined') { // name and value given, set cookie + options = options || {}; + if (value === null) { + value = ''; + options.expires = -1; + } + var expires = ''; + if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { + var date; + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else { + date = options.expires; + } + expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE + } + // CAUTION: Needed to parenthesize options.path and options.domain + // in the following expressions, otherwise they evaluate to undefined + // in the packed version for some reason... + var path = options.path ? '; path=' + (options.path) : ''; + var domain = options.domain ? '; domain=' + (options.domain) : ''; + var secure = options.secure ? '; secure' : ''; + document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); + } else { // only name given, get cookie + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}; \ No newline at end of file diff --git a/application/media/js/jquery.gritter-1.5.js b/application/media/js/jquery.gritter-1.5.js new file mode 100644 index 0000000..bf3bd47 --- /dev/null +++ b/application/media/js/jquery.gritter-1.5.js @@ -0,0 +1,21 @@ +(function($){$.gritter={};$.gritter.options={fade_in_speed:'medium',fade_out_speed:2000,time:6000} +$.gritter.add=function(params){try{if(!params.title||!params.text){throw'You need to fill out the first 2 params: "title" and "text"';}}catch(e){alert('Gritter Error: '+e);} +return Gritter.add(params);} +$.gritter.remove=function(id,params){Gritter.removeSpecific(id,params||'');} +$.gritter.removeAll=function(params){Gritter.stop(params||'');} +var Gritter={fade_in_speed:'',fade_out_speed:'',time:'',_custom_timer:0,_item_count:0,_is_setup:0,_tpl_close:'
',_tpl_item:'',_tpl_wrap:'
',add:function(params){if(!this._is_setup){this._runSetup();} +var user=params.title,text=params.text,image=params.image||'',sticky=params.sticky||false,time_alive=params.time||'';this._verifyWrapper();this._item_count++;var number=this._item_count,tmp=this._tpl_item;this['_before_open_'+number]=($.isFunction(params.before_open))?params.before_open:function(){};this['_after_open_'+number]=($.isFunction(params.after_open))?params.after_open:function(){};this['_before_close_'+number]=($.isFunction(params.before_close))?params.before_close:function(){};this['_after_close_'+number]=($.isFunction(params.after_close))?params.after_close:function(){};this._custom_timer=0;if(time_alive){this._custom_timer=time_alive;} +var image_str=(image!='')?'':'',class_name=(image!='')?'gritter-with-image':'gritter-without-image';tmp=this._str_replace(['[[username]]','[[text]]','[[image]]','[[number]]','[[class_name]]'],[user,text,image_str,this._item_count,class_name],tmp);this['_before_open_'+number]();$('#gritter-notice-wrapper').append(tmp);var item=$('#gritter-item-'+this._item_count);item.fadeIn(this.fade_in_speed,function(){Gritter['_after_open_'+number]($(this));});if(!sticky){this._setFadeTimer(item,number);} +$(item).bind('mouseenter mouseleave',function(event){if(event.type=='mouseenter'){if(!sticky){Gritter.restoreItemIfFading(this,number);}} +else{if(!sticky){Gritter._setFadeTimer(this,number);}} +Gritter._hoverState(this,event.type);});return number;},_countRemoveWrapper:function(unique_id){this['_after_close_'+unique_id]($('#gritter-item-'+unique_id));if($('.gritter-item-wrapper').length==0){$('#gritter-notice-wrapper').remove();}},_fade:function(e,unique_id){Gritter['_before_close_'+unique_id]($(e));$(e).animate({opacity:0},Gritter.fade_out_speed,function(){$(e).animate({height:0},300,function(){$(e).remove();Gritter._countRemoveWrapper(unique_id);})})},_hoverState:function(e,type){if(type=='mouseenter'){$(e).addClass('hover');if($(e).find('img').length){$(e).find('img').before(this._tpl_close);} +else{$(e).find('span').before(this._tpl_close);} +$(e).find('.gritter-close').click(function(){Gritter._remove(this);});} +else{$(e).removeClass('hover');$(e).find('.gritter-close').remove();}},_remove:function(e){var gritter_wrap=$(e).parents('.gritter-item-wrapper');var unique_id=gritter_wrap.attr('id').split('-')[2];this['_before_close_'+unique_id](gritter_wrap);gritter_wrap.fadeOut('medium',function(){$(this).remove();Gritter._countRemoveWrapper(unique_id);});},removeSpecific:function(unique_id,params){var e=$('#gritter-item-'+unique_id);this['_before_close_'+unique_id](e);if(typeof(params)==='object'){if(params.fade){var speed=this.fade_out_speed;if(params.speed){speed=params.speed;} +e.fadeOut(speed,function(){e.remove();});}} +else{e.remove();} +this._countRemoveWrapper(unique_id);},restoreItemIfFading:function(e,number){window.clearTimeout(Gritter['_int_id_'+number]);$(e).stop().css({opacity:1});},_runSetup:function(){for(opt in $.gritter.options){this[opt]=$.gritter.options[opt];} +this._is_setup=1;},_setFadeTimer:function(item,number){var timer_str=(this._custom_timer)?this._custom_timer:this.time;Gritter['_int_id_'+number]=window.setTimeout(function(){Gritter._fade(item,number);},timer_str);},stop:function(params){var before_close=($.isFunction(params.before_close))?params.before_close:function(){};var after_close=($.isFunction(params.after_close))?params.after_close:function(){};var wrap=$('#gritter-notice-wrapper');before_close(wrap);wrap.fadeOut(function(){$(this).remove();after_close();});},_str_replace:function(search,replace,subject,count){var i=0,j=0,temp='',repl='',sl=0,fl=0,f=[].concat(search),r=[].concat(replace),s=subject,ra=r instanceof Array,sa=s instanceof Array;s=[].concat(s);if(count){this.window[count]=0;} +for(i=0,sl=s.length;if.length+5)return false;if(f[b].selectorText&&f[b].selectorText.toLowerCase()==a)if(d===true){g.removeRule&&g.removeRule(b);g.deleteRule&&g.deleteRule(b);return true}else return f[b]}while(f[++b]);return false},add_css:function(a,d){if(c.jstree.css.get_css(a,false,d))return false;d.insertRule?d.insertRule(a+" { }",0):d.addRule(a,null,0);return c.vakata.css.get_css(a)},remove_css:function(a, +d){return c.vakata.css.get_css(a,true,d)},add_sheet:function(a){var d;if(a.str){d=document.createElement("style");d.setAttribute("type","text/css");if(d.styleSheet){document.getElementsByTagName("head")[0].appendChild(d);d.styleSheet.cssText=a.str}else{d.appendChild(document.createTextNode(a.str));document.getElementsByTagName("head")[0].appendChild(d)}return d.sheet||d.styleSheet}if(a.url)if(document.createStyleSheet)try{document.createStyleSheet(a.url)}catch(g){}else{d=document.createElement("link"); +d.rel="stylesheet";d.type="text/css";d.media="all";d.href=a.url;document.getElementsByTagName("head")[0].appendChild(d);return d.styleSheet}}}})(jQuery); +(function(c){var a=[],d=-1,g={},f={};c.fn.jstree=function(b){var e=typeof b=="string",h=Array.prototype.slice.call(arguments,1),k=this;!e&&c.meta&&h.push(c.metadata.get(this).jstree);b=!e&&h.length?c.extend.apply(null,[true,b].concat(h)):b;if(e&&b.substring(0,1)=="_")return k;e?this.each(function(){var j=a[c.data(this,"jstree-instance-id")];j=j&&c.isFunction(j[b])?j[b].apply(j,h):j;if(typeof j!=="undefined"&&j!==true&&j!==false){k=j;return false}}):this.each(function(){var j=c.data(this,"jstree-instance-id"), +i=false;j&&a[j]&&a[j].destroy();j=parseInt(a.push({}),10)-1;c.data(this,"jstree-instance-id",j);b.plugins=c.isArray(b.plugins)?b.plugins:c.jstree.defaults.plugins;c.inArray("core",b.plugins)===-1&&b.plugins.unshift("core");i=c.extend(true,{},c.jstree.defaults,b);i.plugins=b.plugins;a[j]=new c.jstree._instance(j,c(this).addClass("jstree jstree-"+j),i);c.each(a[j].get_settings().plugins,function(m,l){a[j].data[l]={}});c.each(a[j].get_settings().plugins,function(m,l){g[l]&&g[l].__init.apply(a[j])}); +a[j].init()});return k};c.jstree={defaults:{plugins:[]},_focused:function(){return a[d]||null},_reference:function(b){if(a[b])return a[b];var e=c(b);if(!e.length&&typeof b==="string")e=c("#"+b);if(!e.length)return null;return a[e.closest(".jstree").data("jstree-instance-id")]||null},_instance:function(b,e,h){this.data={core:{}};this.get_settings=function(){return c.extend(true,{},h)};this.get_index=function(){return b};this.get_container=function(){return e};this._set_settings=function(k){h=c.extend(true, +{},h,k)}},_fn:{},plugin:function(b,e){e=c.extend({},{__init:c.noop,__destroy:c.noop,_fn:{},defaults:false},e);g[b]=e;c.jstree.defaults[b]=e.defaults;c.each(e._fn,function(h,k){k.plugin=b;k.old=c.jstree._fn[h];c.jstree._fn[h]=function(){var j,i=k,m=Array.prototype.slice.call(arguments);j=this.get_settings();var l=new c.Event("before.jstree"),o=false;do{if(i&&i.plugin&&c.inArray(i.plugin,j.plugins)!==-1)break;i=i.old}while(i);if(i){j=this.get_container().triggerHandler(l,{func:h,inst:this,args:m}); +if(j!==false){if(typeof j!=="undefined")m=j;return j=i.apply(c.extend({},this,{__callback:function(n){this.get_container().triggerHandler(h+".jstree",{inst:this,args:m,rslt:n,rlbk:o})},__rollback:function(){return o=this.get_rollback()},__call_old:function(n){return i.old.apply(this,n?Array.prototype.slice.call(arguments,1):m)}}),m)}}};c.jstree._fn[h].old=k.old;c.jstree._fn[h].plugin=b})},rollback:function(b){if(b){c.isArray(b)||(b=[b]);c.each(b,function(e,h){a[h.i].set_rollback(h.h,h.d)})}}};c.jstree._fn= +c.jstree._instance.prototype={};c(function(){var b=navigator.userAgent.toLowerCase(),e=(b.match(/.+?(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[0,"0"])[1],h=".jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } .jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; } .jstree > ul > li { margin-left:0px; } .jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } .jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; } .jstree a:focus { outline: none; } .jstree a > ins { height:16px; width:16px; } .jstree a > .jstree-icon { margin-right:3px; } li.jstree-open > ul { display:block; } li.jstree-closed > ul { display:none; } "; +if(/msie/.test(b)&&parseInt(e,10)==6)h+=".jstree li { height:18px; margin-left:0; } .jstree li li { margin-left:18px; } li.jstree-open ul { display:block; } li.jstree-closed ul { display:none !important; } .jstree li a { display:inline; } .jstree li a ins { height:16px; width:16px; margin-right:3px; } ";c.vakata.css.add_sheet({str:h})});c.jstree.plugin("core",{__init:function(){this.data.core.to_open=c.map(c.makeArray(this.get_settings().core.initially_open),function(b){return"#"+b.toString().replace(/^#/, +"").replace("\\/","/").replace("/","\\/")})},defaults:{html_titles:false,animation:500,initially_open:[]},_fn:{init:function(){this.set_focus();this.get_container().html("");this.data.core.li_height=this.get_container().find("ul li.jstree-closed, ul li.jstree-leaf").eq(0).height()||18;this.get_container().delegate("li > ins","click.jstree",c.proxy(function(b){var e= +c(b.target);e.is("ins")&&b.pageY-e.offset().top ul > li:first-child");if(!b.length)return false;if(e)return b.nextAll("li").size()>0?b.nextAll("li:eq(0)"):false;return b.hasClass("jstree-open")?b.find("li:eq(0)"):b.nextAll("li").size()> +0?b.nextAll("li:eq(0)"):b.parentsUntil(this.get_container(),"li").next("li").eq(0)},_get_prev:function(b,e){b=this._get_node(b);if(b===-1)return this.get_container().find("> ul > li:last-child");if(!b.length)return false;if(e)return b.prevAll("li").length>0?b.prevAll("li:eq(0)"):false;if(b.prev("li").length){for(b=b.prev("li").eq(0);b.hasClass("jstree-open");)b=b.children("ul:eq(0)").children("li:last");return b}else{var h=b.parentsUntil(this.get_container(),"li:eq(0)");return h.length?h:false}}, +_get_parent:function(b){b=this._get_node(b);if(b==-1||!b.length)return false;b=b.parentsUntil(this.get_container(),"li:eq(0)");return b.length?b:-1},_get_children:function(b){b=this._get_node(b);if(b===-1)return this.get_container().children("ul:eq(0)").children("li");if(!b.length)return false;return b.children("ul:eq(0)").children("li")},get_path:function(b,e){var h=[],k=this;b=this._get_node(b);if(b===-1||!b||!b.length)return false;b.parentsUntil(this.get_container(),"li").each(function(){h.push(e? +this.id:k.get_text(this))});h.reverse();h.push(e?b.attr("id"):this.get_text(b));return h},open_node:function(b,e,h){b=this._get_node(b);if(!b.length)return false;var k=h?0:this.get_settings().core.animation,j=this;if(this._is_loaded(b)){k&&b.children("ul").css("display","none");b.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading");k&&b.children("ul").slideDown(k,function(){this.style.display=""});this.__callback({obj:b});e&&e.call()}else{b.children("a").addClass("jstree-loading"); +this.load_node(b,function(){j.open_node(b,e,h)},e)}},close_node:function(b,e){b=this._get_node(b);var h=e?0:this.get_settings().core.animation;if(!b.length)return false;h&&b.children("ul").attr("style","display:block !important");b.removeClass("jstree-open").addClass("jstree-closed");h&&b.children("ul").slideUp(h,function(){this.style.display=""});this.__callback({obj:b})},toggle_node:function(b){b=this._get_node(b);if(b.hasClass("jstree-closed"))return this.open_node(b);if(b.hasClass("jstree-open"))return this.close_node(b)}, +open_all:function(b,e){b=b?this._get_node(b):this.get_container();if(e)b=b.find("li.jstree-closed");else{e=b;b=b.is(".jstree-closed")?b.find("li.jstree-closed").andSelf():b.find("li.jstree-closed")}var h=this;b.each(function(){var k=this;h._is_loaded(this)?h.open_node(this,false,true):h.open_node(this,function(){h.open_all(k,e)},true)});e.find("li.jstree-closed").length===0&&this.__callback({obj:e})},close_all:function(b){var e=this;b=b?this._get_node(b):this.get_container();b.find("li.jstree-open").andSelf().each(function(){e.close_node(this)}); +this.__callback({obj:b})},clean_node:function(b){b=b&&b!=-1?c(b):this.get_container();b=b.is("li")?b.find("li").andSelf():b.find("li");b.removeClass("jstree-last").filter("li:last-child").addClass("jstree-last").end().filter(":has(ul)").not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed");b.not(".jstree-open, .jstree-closed").addClass("jstree-leaf");this.__callback({obj:b})},get_rollback:function(){this.__callback();return{i:this.get_index(),h:this.get_container().children("ul").clone(true), +d:this.data}},set_rollback:function(b,e){this.get_container().empty().append(b);this.data=e;this.__callback()},load_node:function(b){this.__callback({obj:b})},_is_loaded:function(){return true},create_node:function(b,e,h,k,j){b=this._get_node(b);e=typeof e==="undefined"?"last":e;var i=c("
  • "),m=this.get_settings().core.html_titles,l;if(b!==-1&&!b.length)return false;if(!j&&!this._is_loaded(b)){this.load_node(b,function(){this.create_node(b,e,h,k,true)});return false}this.__rollback();if(typeof h=== +"string")h={data:h};h||(h={});h.attr&&i.attr(h.attr);h.state&&i.addClass("jstree-"+h.state);if(!h.data)h.data="New node";if(!c.isArray(h.data)){l=h.data;h.data=[];h.data.push(l)}c.each(h.data,function(o,n){l=c("");if(c.isFunction(n))n=n.call(this,h);if(typeof n=="string")l.attr("href","#")[m?"html":"text"](n);else{if(!n.attr)n.attr={};if(!n.attr.href)n.attr.href="#";l.attr(n.attr)[m?"html":"text"](n.title);n.language&&l.addClass(n.language)}l.prepend(" ");if(n.icon)n.icon.indexOf("/")=== +-1?l.children("ins").addClass(n.icon):l.children("ins").css("background","url('"+n.icon+"') center center no-repeat;");i.append(l)});i.prepend(" ");if(b===-1){b=this.get_container();if(e==="before")e="first";if(e==="after")e="last"}switch(e){case "before":b.before(i);l=this._get_parent(b);break;case "after":b.after(i);l=this._get_parent(b);break;case "inside":case "first":b.children("ul").length||b.append("
      ");b.children("ul").prepend(i);l=b;break;case "last":b.children("ul").length|| +b.append("
        ");b.children("ul").append(i);l=b;break;default:b.children("ul").length||b.append("
          ");e||(e=0);l=b.children("ul").children("li").eq(e);l.length?l.before(i):b.children("ul").append(i);l=b;break}if(l===-1||l.get(0)===this.get_container().get(0))l=-1;this.clean_node(l);this.__callback({obj:i,parent:l});k&&k.call(this,i);return i},get_text:function(b){b=this._get_node(b);if(!b.length)return false;var e=this.get_settings().core.html_titles;b=b.children("a:eq(0)");if(e){b=b.clone();b.children("INS").remove(); +return b.html()}else{b=b.contents().filter(function(){return this.nodeType==3})[0];return b.nodeValue}},set_text:function(b,e){b=this._get_node(b);if(!b.length)return false;b=b.children("a:eq(0)");if(this.get_settings().core.html_titles){var h=b.children("INS").clone();b.html(e).prepend(h);this.__callback({obj:b,name:e});return true}else{b=b.contents().filter(function(){return this.nodeType==3})[0];this.__callback({obj:b,name:e});return b.nodeValue=e}},rename_node:function(b,e){b=this._get_node(b); +this.__rollback();b&&b.length&&this.set_text.apply(this,Array.prototype.slice.call(arguments))&&this.__callback({obj:b,name:e})},delete_node:function(b){b=this._get_node(b);if(!b.length)return false;this.__rollback();var e=this._get_parent(b);this.deselect_node(b);b=b.remove();e!==-1&&e.find("> ul > li").length===0&&e.removeClass("jstree-open, jstree-closed").addClass("jstree-leaf");this.clean_node(e);this.__callback({obj:b});return b},prepare_move:function(b,e,h,k,j){var i={};i.ot=c.jstree._reference(i.o)|| +this;i.o=i.ot._get_node(b);i.r=e===-1?-1:this._get_node(e);i.p=typeof i==="undefined"?"last":h;if(!(!j&&f.o&&f.o[0]===i.o[0]&&f.r[0]===i.r[0]&&f.p===i.p)){i.ot=c.jstree._reference(i.o)||this;i.rt=e===-1?i.ot:c.jstree._reference(i.r)||this;if(i.r===-1){i.cr=-1;switch(i.p){case "first":case "before":case "inside":i.cp=0;break;case "after":case "last":i.cp=i.rt.get_container().find(" > ul > li").length;break;default:i.cp=i.p;break}}else{if(!/^(before|after)$/.test(i.p)&&!this._is_loaded(i.r))return this.load_node(i.r, +function(){this.prepare_move(b,e,i,k,true)});switch(i.p){case "before":i.cp=i.r.index();i.cr=i.rt._get_parent(i.r);break;case "after":i.cp=i.r.index()+1;i.cr=i.rt._get_parent(i.r);break;case "inside":case "first":i.cp=0;i.cr=i.r;break;case "last":i.cp=i.r.find(" > ul > li").length;i.cr=i.r;break;default:i.cp=i.p;i.cr=i.r;break}}i.np=i.cr==-1?i.rt.get_container():i.cr;i.op=i.ot._get_parent(i.o);i.or=i.np.find(" > ul > li:nth-child("+(i.cp+1)+")");f=i}this.__callback(f);k&&k.call(this,f)},check_move:function(){var b= +f;if(b.or[0]===b.o[0]||b.r.parentsUntil(".jstree").andSelf().filter("li").index(b.o)!==-1)return false;return true},move_node:function(b,e,h,k,j,i){if(!j)return this.prepare_move(b,e,h,function(l){this.move_node(l,false,false,k,true,i)});if(!i&&!this.check_move())return false;this.__rollback();e=false;if(k){e=b.o.clone();e.find("*[id]").andSelf().each(function(){if(this.id)this.id="copy_"+this.id})}else e=b.o;if(b.or.length)b.or.before(e);else{b.np.children("ul").length||c("
            ").appendTo(b.np); +b.np.children("ul:eq(0)").append(e)}try{b.ot.clean_node(b.op);b.rt.clean_node(b.np);b.op.find("> ul > li").length||b.op.removeClass("jstree-open jstree-closed").addClass("jstree-leaf").children("ul").remove()}catch(m){}if(k){f.cy=true;f.oc=e}this.__callback(f);return f},_get_move:function(){return f}}})})(jQuery); +(function(c){c.jstree.plugin("ui",{__init:function(){this.data.ui.selected=c();this.data.ui.last_selected=false;this.data.ui.hovered=null;this.data.ui.to_select=this.get_settings().ui.initially_select;this.get_container().delegate("a","click.jstree",c.proxy(function(a){a.preventDefault();this.select_node(a.currentTarget,true,a)},this)).delegate("a","mouseenter.jstree",c.proxy(function(a){this.hover_node(a.target)},this)).delegate("a","mouseleave.jstree",c.proxy(function(a){this.dehover_node(a.target)}, +this)).bind("reopen.jstree",c.proxy(function(){this.reselect()},this)).bind("get_rollback.jstree",c.proxy(function(){this.dehover_node();this.save_selected()},this)).bind("set_rollback.jstree",c.proxy(function(){this.reselect()},this)).bind("close_node.jstree",c.proxy(function(a,d){var g=this.get_settings().ui,f=this._get_node(d.args[0]),b=f&&f.length?f.find(".jstree-clicked"):[],e=this;g.selected_parent_close===false||!b.length||b.each(function(){e.deselect_node(this);g.selected_parent_close==="select_parent"&& +e.select_node(f)})},this)).bind("delete_node.jstree",c.proxy(function(a,d){var g=this._get_node(d.rslt.obj),f=this;(g&&g.length?g.find(".jstree-clicked"):[]).each(function(){f.deselect_node(this)})},this)).bind("move_node.jstree",c.proxy(function(a,d){d.rslt.cy&&d.rslt.oc.find(".jstree-clicked").removeClass("jstree-clicked")},this))},defaults:{select_limit:-1,select_multiple_modifier:"ctrl",selected_parent_close:"select_parent",initially_select:[]},_fn:{_get_node:function(a,d){if(typeof a==="undefined"|| +a===null)return d?this.data.ui.selected:this.data.ui.last_selected;return this.__call_old()},save_selected:function(){var a=this;this.data.ui.to_select=[];this.data.ui.selected.each(function(){a.data.ui.to_select.push("#"+this.id.toString().replace(/^#/,"").replace("\\/","/").replace("/","\\/"))});this.__callback(this.data.ui.to_select)},reselect:function(){var a=this,d=this.data.ui.to_select;d=c.map(c.makeArray(d),function(g){return"#"+g.toString().replace(/^#/,"").replace("\\/","/").replace("/", +"\\/")});this.deselect_all();c.each(d,function(g,f){f&&f!=="#"&&a.select_node(f)});this.__callback()},refresh:function(){this.save_selected();return this.__call_old()},hover_node:function(a){a=this._get_node(a);if(!a.length)return false;a.hasClass("jstree-hovered")||this.dehover_node();this.data.ui.hovered=a.children("a").addClass("jstree-hovered").parent();this.__callback({obj:a})},dehover_node:function(){var a=this.data.ui.hovered;if(!a||!a.length)return false;if(this.data.ui.hovered[0]===a.children("a").removeClass("jstree-hovered").parent()[0])this.data.ui.hovered= +null;this.__callback({obj:a})},select_node:function(a,d,g){a=this._get_node(a);if(!a.length)return false;var f=this.get_settings().ui;g=f.select_multiple_modifier=="on"||f.select_multiple_modifier!==false&&g&&g[f.select_multiple_modifier+"Key"];var b=this.is_selected(a),e=true;if(d){e=false;switch(true){case b&&!g:break;case !b&&!g:if(f.select_limit==-1||f.select_limit>0){this.deselect_all();e=true}break;case b&&g:this.deselect_node(a);break;case !b&&g:if(f.select_limit==-1||this.data.ui.selected.length+ +1<=f.select_limit)e=true;break}}if(e&&!b){a.children("a").addClass("jstree-clicked");this.data.ui.selected=this.data.ui.selected.add(a);this.data.ui.last_selected=a;this.__callback({obj:a})}},deselect_node:function(a){a=this._get_node(a);if(!a.length)return false;if(this.is_selected(a)){a.children("a").removeClass("jstree-clicked");this.data.ui.selected=this.data.ui.selected.not(a);if(this.data.ui.last_selected.get(0)===a.get(0))this.data.ui.last_selected=this.data.ui.selected.eq(0);this.__callback({obj:a})}}, +toggle_select:function(a){a=this._get_node(a);if(!a.length)return false;this.is_selected(a)?this.deselect_node(a):this.select_node(a)},is_selected:function(a){return this.data.ui.selected.index(this._get_node(a))>=0},get_selected:function(a){return a?c(a).find(".jstree-clicked").parent():this.data.ui.selected},deselect_all:function(a){a?c(a).find(".jstree-clicked").removeClass("jstree-clicked"):this.get_container().find(".jstree-clicked").removeClass("jstree-clicked");this.data.ui.selected=c([]); +this.data.ui.last_selected=false;this.__callback()}}});c.jstree.defaults.plugins.push("ui")})(jQuery); +(function(c){c.jstree.plugin("crrm",{__init:function(){this.get_container().bind("move_node.jstree",c.proxy(function(a,d){if(this.get_settings().crrm.move.open_onmove){var g=this;d.rslt.np.parentsUntil(".jstree").andSelf().filter(".jstree-closed").each(function(){g.open_node(this,false,true)})}},this))},defaults:{input_width_limit:200,move:{always_copy:false,open_onmove:true,default_position:"last",check_move:function(){return true}}},_fn:{_show_input:function(a,d){a=this._get_node(a);var g=this.get_settings().crrm.input_width_limit, +f=a.children("ins").width(),b=a.find("> a:visible > ins").width()*a.find("> a:visible > ins").length,e=this.get_text(a),h=c("
            ",{css:{position:"absolute",top:"-200px",left:"-1000px",visibility:"hidden"}}).appendTo("body"),k=a.css("position","relative").append(c("",{value:e,css:{padding:"0",border:"1px solid silver",position:"absolute",left:f+b+4+"px",top:"0px",height:this.data.core.li_height-2+"px",lineHeight:this.data.core.li_height-2+"px",width:"150px"},blur:c.proxy(function(){var j= +a.children("input"),i=j.val();if(i==="")i=e;this.rename_node(a,i);d.call(this,a,i,e);j.remove();a.css("position","")},this),keyup:function(j){j=j.keyCode||j.which;if(j==27){this.value=e;this.blur()}else j==13?this.blur():k.width(Math.min(h.text("pW"+this.value).width(),g))}})).children("input");this.set_text(a,"");h.css({fontFamily:k.css("fontFamily")||"",fontSize:k.css("fontSize")||"",fontWeight:k.css("fontWeight")||"",fontStyle:k.css("fontStyle")||"",fontStretch:k.css("fontStretch")||"",fontVariant:k.css("fontVariant")|| +"",letterSpacing:k.css("letterSpacing")||"",wordSpacing:k.css("wordSpacing")||""});k.width(Math.min(h.text("pW"+k[0].value).width(),g))[0].select()},rename:function(a){a=this._get_node(a);this.__rollback();var d=this.__callback;this._show_input(a,function(g,f,b){d.call(this,{obj:g,new_name:f,old_name:b})})},create:function(a,d,g,f,b){var e=this;(a=this._get_node(a))||(a=-1);this.__rollback();return this.create_node(a,d,g,function(h){var k=this._get_parent(h),j=c(h).index();f&&f.call(this,h);k.length&& +k.hasClass("jstree-closed")&&this.open_node(k,false,true);b?e.__callback({obj:h,name:this.get_text(h),parent:k,position:j}):this._show_input(h,function(i,m){e.__callback({obj:i,name:m,parent:k,position:j})})})},remove:function(a){a=this._get_node(a,true);this.__rollback();this.delete_node(a);this.__callback({obj:a})},check_move:function(){if(!this.__call_old())return false;if(!this.get_settings().crrm.move.check_move.call(this,this._get_move()))return false;return true},move_node:function(a,d,g,f, +b,e){var h=this.get_settings().crrm.move;if(!b){if(!g)g=h.default_position;return this.__call_old(true,a,d,g,f,false,e)}if(h.always_copy===true||h.always_copy==="multitree"&&a.rt.get_index()===a.ot.get_index())f=true;this.__call_old(true,a,d,g,f,true,e)},cut:function(a){a=this._get_node(a);this.data.crrm.cp_nodes=false;this.data.crrm.ct_nodes=false;if(!a||!a.length)return false;this.data.crrm.ct_nodes=a},copy:function(a){a=this._get_node(a);this.data.crrm.cp_nodes=false;this.data.crrm.ct_nodes=false; +if(!a||!a.length)return false;this.data.crrm.cp_nodes=a},paste:function(a){a=this._get_node(a);if(!a||!a.length)return false;if(!this.data.crrm.ct_nodes&&!this.data.crrm.cp_nodes)return false;this.data.crrm.ct_nodes&&this.move_node(this.data.crrm.ct_nodes,a);this.data.crrm.cp_nodes&&this.move_node(this.data.crrm.cp_nodes,a,false,true)}}});c.jstree.defaults.plugins.push("crrm")})(jQuery); +(function(c){var a=[];c.jstree._themes=false;c.jstree.plugin("themes",{__init:function(){this.get_container().bind("init.jstree",c.proxy(function(){var d=this.get_settings().themes;this.data.themes.dots=d.dots;this.data.themes.icons=d.icons;this.set_theme(d.theme,d.url)},this)).bind("loaded.jstree",c.proxy(function(){this.data.themes.dots?this.show_dots():this.hide_dots();this.data.themes.icons?this.show_icons():this.hide_icons()},this))},defaults:{theme:"default",url:false,dots:true,icons:true}, +_fn:{set_theme:function(d,g){if(!d)return false;g||(g=c.jstree._themes+d+"/style.css");if(c.inArray(g,a)==-1){c.vakata.css.add_sheet({url:g,rel:"jstree"});a.push(g)}if(this.data.theme!=d){this.get_container().removeClass("jstree-"+this.data.theme);this.data.themes.theme=d}this.get_container().addClass("jstree-"+d);this.data.themes.dots?this.show_dots():this.hide_dots();this.data.themes.icons?this.show_icons():this.hide_icons();this.__callback()},get_theme:function(){return this.data.themes.theme}, +show_dots:function(){this.data.themes.dots=true;this.get_container().children("ul").removeClass("jstree-no-dots")},hide_dots:function(){this.data.themes.dots=false;this.get_container().children("ul").addClass("jstree-no-dots")},toggle_dots:function(){this.data.themes.dots?this.hide_dots():this.show_dots()},show_icons:function(){this.data.themes.icons=true;this.get_container().children("ul").removeClass("jstree-no-icons")},hide_icons:function(){this.data.themes.icons=false;this.get_container().children("ul").addClass("jstree-no-icons")}, +toggle_icons:function(){this.data.themes.icons?this.hide_icons():this.show_icons()}}});c(function(){c.jstree._themes===false&&c("script").each(function(){if(this.src.toString().match(/jquery\.jstree[^\/]*?\.js(\?.*)?$/)){c.jstree._themes=this.src.toString().replace(/jquery\.jstree[^\/]*?\.js(\?.*)?$/,"")+"themes/";return false}});if(c.jstree._themes===false)c.jstree._themes="themes/"});c.jstree.defaults.plugins.push("themes")})(jQuery); +(function(c){c.jstree.plugin("html_data",{__init:function(){this.data.html_data.original_container_html=this.get_container().html().replace(/<\/([^>]+)>\s+<").replace(/>\s+<([a-z]{1})/ig,"><$1")},defaults:{data:false,ajax:false,correct_state:false},_fn:{load_node:function(a,d,g){var f=this;this.load_node_html(a,function(){f.__callback({obj:a});d.call(this)},g)},_is_loaded:function(a){a=this._get_node(a);return a==-1||!a||!this.get_settings().html_data.ajax||a.is(".jstree-open, .jstree-leaf")|| +a.children("ul").children("li").size()>0},load_node_html:function(a,d,g){var f,b=this.get_settings().html_data,e=function(){};switch(true){case !b.data&&!b.ajax:if(!a||a==-1){this.get_container().html(this.data.html_data.original_container_html).find("li, a").filter(function(){return this.firstChild.tagName!=="INS"}).prepend(" ");this.clean_node()}d&&d.call(this);break;case !!b.data&&!b.ajax||!!b.data&&!!b.ajax&&(!a||a===-1):if(!a||a==-1){f=c(b.data);f.is("ul")|| +(f=c("
              ").append(f));this.get_container().children("ul").empty().append(f.children()).find("li, a").filter(function(){return this.firstChild.tagName!=="INS"}).prepend(" ");this.clean_node()}d&&d.call(this);break;case !b.data&&!!b.ajax||!!b.data&&!!b.ajax&&a&&a!==-1:a=this._get_node(a);e=function(h,k,j){var i=this.get_settings().html_data.ajax.error;i&&i.call(this,h,k,j);if(a!=-1&&a.length){a.children(".jstree-loading").removeClass("jstree-loading");b.correct_state&& +a.removeClass("jstree-open jstree-closed").addClass("jstree-leaf")}g&&g.call(this)};b.ajax.context=this;b.ajax.error=e;b.ajax.success=function(h,k,j){if(j.responseText=="")return e.call(this,j,k,"");var i=this.get_settings().html_data.ajax.success;if(i)h=i.call(this,h,k,j)||h;if(h){h=c(h);h.is("ul")||(h=c("
                ").append(h));if(a==-1||!a)this.get_container().children("ul").empty().append(h.children()).find("li, a").filter(function(){return this.firstChild.tagName!=="INS"}).prepend(" "); +else{a.children(".jstree-loading").removeClass("jstree-loading");a.append(h).find("li, a").filter(function(){return this.firstChild.tagName!=="INS"}).prepend(" ")}this.clean_node(a);d&&d.call(this)}else{a.children(".jstree-loading").removeClass("jstree-loading");b.correct_state&&a.removeClass("jstree-open jstree-closed").addClass("jstree-leaf")}};if(c.isFunction(b.ajax.data))b.ajax.data=b.ajax.data.call(this,a);c.ajax(b.ajax);break}}}});c.jstree.defaults.plugins.push("html_data")})(jQuery); +(function(c){var a=[];c.jstree.plugin("hotkeys",{__init:function(){if(typeof c.hotkeys==="undefined")throw"jsTree hotkeys: jQuery hotkeys plugin not included.";if(!this.data.ui)throw"jsTree hotkeys: jsTree UI plugin not included.";c.each(this.get_settings().hotkeys,function(d){if(c.inArray(d,a)==-1){c(document).bind("keydown",d,function(g){var f;var b=c.jstree._focused();if(b&&b.data&&b.data.hotkeys&&b.data.hotkeys.enabled)if(b.get_settings().hotkeys[d])f=b.get_settings().hotkeys[d].call(b,g);return f}); +a.push(d)}});this.enable_hotkeys()},defaults:{up:function(){this.hover_node(this._get_prev(this.data.ui.hovered||this.data.ui.last_selected||-1));return false},down:function(){this.hover_node(this._get_next(this.data.ui.hovered||this.data.ui.last_selected||-1));return false},left:function(){var d=this.data.ui.hovered||this.data.ui.last_selected;if(d)d.hasClass("jstree-open")?this.close_node(d):this.hover_node(this._get_prev(d));return false},right:function(){var d=this.data.ui.hovered||this.data.ui.last_selected; +if(d&&d.length)d.hasClass("jstree-closed")?this.open_node(d):this.hover_node(this._get_next(d));return false},space:function(){this.data.ui.hovered&&this.data.ui.hovered.children("a:eq(0)").click();return false},"ctrl+space":function(d){d.type="click";this.data.ui.hovered&&this.data.ui.hovered.children("a:eq(0)").trigger(d);return false},f2:function(){this.rename(this.data.ui.hovered||this.data.ui.last_selected)},del:function(){this.remove(this.data.ui.hovered||this._get_node(null))}},_fn:{enable_hotkeys:function(){this.data.hotkeys.enabled= +true},disable_hotkeys:function(){this.data.hotkeys.enabled=false}}})})(jQuery); +(function(c){c.jstree.plugin("json_data",{defaults:{data:false,ajax:false,correct_state:false,progressive_render:false},_fn:{load_node:function(a,d,g){var f=this;this.load_node_json(a,function(){f.__callback({obj:a});d.call(this)},g)},_is_loaded:function(a){var d=this.get_settings().json_data;if((a=this._get_node(a))&&a!==-1&&d.progressive_render){a.append(this.parse_json(a.data("jstree-children")));c.removeData(a,"jstree-children");this.clean_node(a)}return a==-1||!a||!d.ajax||a.is(".jstree-open, .jstree-leaf")|| +a.children("ul").children("li").size()>0},load_node_json:function(a,d,g){var f=this.get_settings().json_data,b=function(){};switch(true){case !f.data&&!f.ajax:throw"Neither data nor ajax settings supplied.";case !!f.data&&!f.ajax||!!f.data&&!!f.ajax&&(!a||a===-1):if(!a||a==-1){this.get_container().children("ul").empty().append(this.parse_json(f.data).children());this.clean_node()}d&&d.call(this);break;case !f.data&&!!f.ajax||!!f.data&&!!f.ajax&&a&&a!==-1:a=this._get_node(a);b=function(e,h,k){var j= +this.get_settings().json_data.ajax.error;j&&j.call(this,e,h,k);if(a!=-1&&a.length){a.children(".jstree-loading").removeClass("jstree-loading");f.correct_state&&a.removeClass("jstree-open jstree-closed").addClass("jstree-leaf")}g&&g.call(this)};f.ajax.context=this;f.ajax.error=b;f.ajax.success=function(e,h,k){if(k.responseText==""||!c.isArray(e)&&!c.isPlainObject(e))return b.call(this,k,h,"");var j=this.get_settings().json_data.ajax.success;if(j)e=j.call(this,e,h,k)||e;if(e=this.parse_json(e)){a== +-1||!a?this.get_container().children("ul").empty().append(e.children()):a.append(e).children(".jstree-loading").removeClass("jstree-loading");this.clean_node(a);d&&d.call(this)}else{a.children(".jstree-loading").removeClass("jstree-loading");f.correct_state&&a.removeClass("jstree-open jstree-closed").addClass("jstree-leaf")}};if(c.isFunction(f.ajax.data))f.ajax.data=f.ajax.data.call(this,a);c.ajax(f.ajax);break}},parse_json:function(a,d){var g=c(),f,b,e;b=this.get_settings().json_data;var h=this.get_settings().core.html_titles; +if(!a)return g;if(c.isFunction(a))a=a.call(this);if(c.isArray(a)){if(!a.length)return false;b=0;for(e=a.length;b");a.attr&&g.attr(a.attr);a.metadata&&g.data("jstree",a.metadata);a.state&&g.addClass("jstree-"+a.state);if(!c.isArray(a.data)){f=a.data;a.data=[];a.data.push(f)}c.each(a.data,function(k,j){f=c("");if(c.isFunction(j))j=j.call(this,a);if(typeof j== +"string")f.attr("href","#")[h?"html":"text"](j);else{if(!j.attr)j.attr={};if(!j.attr.href)j.attr.href="#";f.attr(j.attr)[h?"html":"text"](j.title);j.language&&f.addClass(j.language)}f.prepend(" ");if(j.icon)j.icon.indexOf("/")===-1?f.children("ins").addClass(j.icon):f.children("ins").css("background","url('"+j.icon+"') center center no-repeat;");g.append(f)});g.prepend(" ");if(a.children)if(b.progressive_render&&a.state!=="open")g.addClass("jstree-closed").data("jstree-children", +a.children);else{if(c.isFunction(a.children))a.children=a.children.call(this,a);if(c.isArray(a.children)&&a.children.length){f=this.parse_json(a.children,true);if(f.length){b=c("
                  ");b.append(f);g.append(b)}}}}if(!d){b=c("
                    ");b.append(g);g=b}return g},get_json:function(a,d,g){var f=[],b=this.get_settings(),e=this,h,k,j,i,m,l;a=this._get_node(a);if(!a||a===-1)a=this.get_container().find("> ul > li");d=c.isArray(d)?d:["id","class"];this.data.types&&d.push(b.types.type_attr);g=c.isArray(g)?g:[]; +a.each(function(){j=c(this);h={data:[]};if(d.length)h.attr={};c.each(d,function(o,n){if((k=j.attr(n))&&k.length&&k.replace(/jstree[^ ]*|$/ig,"").length)h.attr[n]=k.replace(/jstree[^ ]*|$/ig,"")});if(j.hasClass("jstree-open"))h.state="open";if(j.hasClass("jstree-closed"))h.state="closed";i=j.children("a");i.each(function(){m=c(this);if(g.length||c.inArray("languages",b.plugins)!==-1||m.children("ins").get(0).style.backgroundImage.length||m.children("ins").get(0).className&&m.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig, +"").length){l=false;c.inArray("languages",b.plugins)!==-1&&c.isArray(b.languages)&&b.languages.length&&c.each(b.languages,function(o,n){if(m.hasClass(n)){l=n;return false}});k={attr:{},title:e.get_text(m,l)};c.each(g,function(o,n){h.attr[n]=j.attr(n).replace(/jstree[^ ]*|$/ig,"")});c.each(b.languages,function(o,n){if(m.hasClass(n)){k.language=n;return true}});if(m.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,"").replace(/^\s+$/ig,"").length)k.icon=m.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig, +"").replace(/^\s+$/ig,"");if(m.children("ins").get(0).style.backgroundImage.length)k.icon=m.children("ins").get(0).style.backgroundImage.replace("url(","").replace(")","")}else k=e.get_text(m);if(i.length>1)h.data.push(k);else h.data=k});j=j.find("> ul > li");if(j.length)h.children=e.get_json(j,d,g);f.push(h)});return f}}})})(jQuery); +(function(c){c.jstree.plugin("languages",{__init:function(){this._load_css()},defaults:[],_fn:{set_lang:function(a){var d=this.get_settings().languages,g=false,f=".jstree-"+this.get_index()+" a";if(!c.isArray(d)||d.length===0)return false;if(c.inArray(a,d)==-1)if(d[a])a=d[a];else return false;if(a==this.data.languages.current_language)return true;g=c.vakata.css.get_css(f+"."+this.data.languages.current_language,false,this.data.languages.language_css);if(g!==false)g.style.display="none";g=c.vakata.css.get_css(f+ +"."+a,false,this.data.languages.language_css);if(g!==false)g.style.display="";this.data.languages.current_language=a;this.__callback(a);return true},get_lang:function(){return this.data.languages.current_language},get_text:function(a,d){a=this._get_node(a)||this.data.ui.last_selected;if(!a.size())return false;var g=this.get_settings().languages,f=this.get_settings().core.html_titles;if(c.isArray(g)&&g.length){d=d&&c.inArray(d,g)!=-1?d:this.data.languages.current_language;a=a.children("a."+d)}else a= +a.children("a:eq(0)");if(f){a=a.clone();a.children("INS").remove();return a.html()}else{a=a.contents().filter(function(){return this.nodeType==3})[0];return a.nodeValue}},set_text:function(a,d,g){a=this._get_node(a)||this.data.ui.last_selected;if(!a.size())return false;var f=this.get_settings().languages,b=this.get_settings().core.html_titles;if(c.isArray(f)&&f.length){g=g&&c.inArray(g,f)!=-1?g:this.data.languages.current_language;a=a.children("a."+g)}else a=a.children("a:eq(0)");if(b){f=a.children("INS").clone(); +a.html(d).prepend(f);this.__callback({obj:a,name:d,lang:g});return true}else{a=a.contents().filter(function(){return this.nodeType==3})[0];this.__callback({obj:a,name:d,lang:g});return a.nodeValue=d}},_load_css:function(){var a=this.get_settings().languages,d="/* languages css */",g=".jstree-"+this.get_index()+" a",f;if(c.isArray(a)&&a.length){this.data.languages.current_language=a[0];for(f=0;fthis.get_text(d)? +1:-1},_fn:{sort:function(a){var d=this.get_settings().sort,g=this;a.append(c.makeArray(a.children("li")).sort(c.proxy(d,g)));a.find("> li > ul").each(function(){g.sort(c(this))});this.clean_node(a)}}})})(jQuery); +(function(c){var a=false,d=false,g=false;c.vakata.dnd={is_down:false,is_drag:false,helper:false,init_x:0,init_y:0,threshold:5,user_data:{},drag_start:function(f,b,e){c.vakata.dnd.is_drag&&c.vakata.drag_stop({});try{f.currentTarget.unselectable="on";f.currentTarget.onselectstart=function(){return false};if(f.currentTarget.style)f.currentTarget.style.MozUserSelect="none"}catch(h){}c.vakata.dnd.init_x=f.pageX;c.vakata.dnd.init_y=f.pageY;c.vakata.dnd.user_data=b;c.vakata.dnd.is_down=true;c.vakata.dnd.helper= +c("
                    ").html(e).css("opacity","0.75");c(document).bind("mousemove",c.vakata.dnd.drag);c(document).bind("mouseup",c.vakata.dnd.drag_stop);return false},drag:function(f){if(c.vakata.dnd.is_down){if(!c.vakata.dnd.is_drag)if(Math.abs(f.pageX-c.vakata.dnd.init_x)>5||Math.abs(f.pageY-c.vakata.dnd.init_y)>5){c.vakata.dnd.helper.appendTo("body");c.vakata.dnd.is_drag=true;c(document).triggerHandler("vakata.drag_start",{event:f,data:c.vakata.dnd.user_data})}else return;c.vakata.dnd.helper.css({left:f.pageX+ +5+"px",top:f.pageY+10+"px"});c(document).triggerHandler("vakata.drag",{event:f,data:c.vakata.dnd.user_data})}},drag_stop:function(f){c(document).unbind("mousemove",c.vakata.dnd.drag);c(document).unbind("mouseup",c.vakata.dnd.drag_stop);c(document).triggerHandler("vakata.drag_stop",{event:f,data:c.vakata.dnd.user_data});c.vakata.dnd.helper.remove();c.vakata.dnd.init_x=0;c.vakata.dnd.init_y=0;c.vakata.dnd.user_data={};c.vakata.dnd.is_down=false;c.vakata.dnd.is_drag=false}};c(function(){c.vakata.css.add_sheet({str:"#vakata-dragged { display:block; margin:0 0 0 0; padding:4px 4px 4px 24px; position:absolute; left:-2000px; top:-2000px; line-height:16px; } "})}); +c.jstree.plugin("dnd",{__init:function(){this.data.dnd={active:false,after:false,inside:false,before:false,off:false,prepared:false,w:0,to1:false,to2:false,cof:false,cw:false,ch:false,i1:false,i2:false};this.get_container().bind("mouseenter.jstree",c.proxy(function(){if(c.vakata.dnd.is_drag&&c.vakata.dnd.user_data.jstree&&this.data.themes){g.attr("class","jstree-"+this.data.themes.theme);c.vakata.dnd.helper.attr("class","jstree-dnd-helper jstree-"+this.data.themes.theme)}},this)).bind("mouseleave.jstree", +c.proxy(function(){if(c.vakata.dnd.is_drag&&c.vakata.dnd.user_data.jstree){this.data.dnd.i1&&clearInterval(this.data.dnd.i1);this.data.dnd.i2&&clearInterval(this.data.dnd.i2)}},this)).bind("mousemove.jstree",c.proxy(function(b){if(c.vakata.dnd.is_drag&&c.vakata.dnd.user_data.jstree){var e=this.get_container()[0];if(b.pageX+20>this.data.dnd.cof.left+this.data.dnd.cw){this.data.dnd.i1&&clearInterval(this.data.dnd.i1);this.data.dnd.i1=setInterval(c.proxy(function(){this.scrollLeft+=5},e),100)}else if(b.pageX- +20this.data.dnd.cof.top+this.data.dnd.ch){this.data.dnd.i2&&clearInterval(this.data.dnd.i2);this.data.dnd.i2=setInterval(c.proxy(function(){this.scrollTop+=5},e),100)}else if(b.pageY-20"+c(b.target).text()); +if(this.data.themes){g.attr("class","jstree-"+this.data.themes.theme);c.vakata.dnd.helper.attr("class","jstree-dnd-helper jstree-"+this.data.themes.theme)}c.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");b=this.get_container();this.data.dnd.cof=b.children("ul").offset();this.data.dnd.cw=parseInt(b.width(),10);this.data.dnd.ch=parseInt(b.height(),10);this.data.dnd.foreign=true;return false},this));f.drop_target&&c(document).delegate(f.drop_target,"mouseenter.jstree",c.proxy(function(b){this.data.dnd.active&& +this.get_settings().dnd.drop_check.call(this,{o:a,r:c(b.target)})&&c.vakata.dnd.helper.children("ins").attr("class","jstree-ok")},this)).delegate(f.drop_target,"mouseleave.jstree",c.proxy(function(){this.data.dnd.active&&c.vakata.dnd.helper.children("ins").attr("class","jstree-invalid")},this)).delegate(f.drop_target,"mouseup.jstree",c.proxy(function(b){this.data.dnd.active&&c.vakata.dnd.helper.children("ins").hasClass("jstree-ok")&&this.get_settings().dnd.drop_finish.call(this,{o:a,r:c(b.target)})}, +this))},defaults:{copy_modifier:"ctrl",check_timeout:200,open_timeout:500,drop_target:".jstree-drop",drop_check:function(){return true},drop_finish:c.noop,drag_target:".jstree-draggable",drag_finish:c.noop,drag_check:function(){return{after:false,before:false,inside:true}}},_fn:{dnd_prepare:function(){this.data.dnd.off=d.offset();if(this.data.dnd.foreign){var f=this.get_settings().dnd.drag_check.call(this,{o:a,r:d});this.data.dnd.after=f.after;this.data.dnd.before=f.before;this.data.dnd.inside=f.inside; +this.data.dnd.prepared=true;return this.dnd_show()}this.prepare_move(a,d,"before");this.data.dnd.before=this.check_move();this.prepare_move(a,d,"after");this.data.dnd.after=this.check_move();if(this._is_loaded(d)){this.prepare_move(a,d,"inside");this.data.dnd.inside=this.check_move()}else this.data.dnd.inside=false;this.data.dnd.prepared=true;return this.dnd_show()},dnd_show:function(){if(this.data.dnd.prepared){var f=["before","inside","after"],b=false;f=this.data.dnd.w"+(a.length>1?"Multiple selection":this.get_text(a)));if(this.data.themes){g.attr("class","jstree-"+this.data.themes.theme);c.vakata.dnd.helper.attr("class", +"jstree-dnd-helper jstree-"+this.data.themes.theme)}var e=this.get_container();this.data.dnd.cof=e.children("ul").offset();this.data.dnd.cw=parseInt(e.width(),10);this.data.dnd.ch=parseInt(e.height(),10);this.data.dnd.active=true}}});c(function(){c.vakata.css.add_sheet({str:"#vakata-dragged ins { display:block; text-decoration:none; width:16px; height:16px; margin:0 0 0 0; padding:0; position:absolute; top:4px; left:4px; } #vakata-dragged .jstree-ok { background:green; } #vakata-dragged .jstree-invalid { background:red; } #jstree-marker { padding:0; margin:0; line-height:12px; font-size:1px; overflow:hidden; height:12px; width:8px; position:absolute; left:-45px; top:-30px; z-index:1000; background-repeat:no-repeat; display:none; background-color:silver; } "}); +g=c("
                    ").attr({id:"jstree-marker"}).hide().appendTo("body");c(document).bind("vakata.drag_start",function(f,b){b.data.jstree&&g.show()});c(document).bind("vakata.drag_stop",function(f,b){b.data.jstree&&g.hide()})})})(jQuery); +(function(c){c.jstree.plugin("checkbox",{__init:function(){if(!this.data.ui)throw"jsTree checkboxes: jsTree UI plugin not included";this.select_node=this.deselect_node=this.deselect_all=c.noop;this.get_selected=this.get_checked;this.get_container().bind("open_node.jstree create_node.jstree",c.proxy(function(a,d){this._prepare_checkboxes(d.rslt.obj)},this)).bind("loaded.jstree",c.proxy(function(){this._prepare_checkboxes()},this)).bind("clean_node.jstree",c.proxy(function(a,d){this._repair_state(d.args[0])}, +this)).delegate("a","click.jstree",c.proxy(function(a){this.change_state(a.target);this.save_selected();this.data.cookies&&this.save_cookie("select_node");a.preventDefault()},this))},_fn:{_prepare_checkboxes:function(a){a=!a||a==-1?this.get_container():this._get_node(a);var d=a.is("li")&&a.hasClass("jstree-checked")?"jstree-checked":"jstree-unchecked";a.find("a").not(":has(.checkbox)").prepend(" ").parent().addClass(d)},change_state:function(a,d){a=this._get_node(a); +if(d=d===false||d===true?d:a.hasClass("jstree-checked"))a.find("li").andSelf().removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked");else{a.find("li").andSelf().removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked");this.data.ui.last_selected=a}var g=this;a.parentsUntil(this.get_container(),"li").each(function(){var f=c(this);if(d)if(f.children("ul").children(".jstree-checked, .jstree-undetermined").length){f.parentsUntil(g.get_container(),"li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined"); +return false}else f.removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked");else if(f.children("ul").children(".jstree-unchecked, .jstree-undetermined").length){f.parentsUntil(g.get_container(),"li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");return false}else f.removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked")});this.data.ui.selected=this.get_checked();this.__callback(a)},check_node:function(a){this.change_state(a, +false)},uncheck_node:function(a){this.change_state(a,true)},check_all:function(){var a=this;this.get_container().children("ul").children("li").each(function(){a.check_node(this,false)})},uncheck_all:function(){var a=this;this.get_container().children("ul").children("li").each(function(){a.change_state(this,true)})},is_checked:function(a){a=this._get_node(a);return a.length?a.is(".jstree-checked"):false},get_checked:function(a){a=!a||a===-1?this.get_container():this._get_node(a);return a.find("> ul > .jstree-checked, .jstree-undetermined > ul > .jstree-checked")}, +get_unchecked:function(a){a=!a||a===-1?this.get_container():this._get_node(a);return a.find("> ul > .jstree-unchecked, .jstree-undetermined > ul > .jstree-unchecked")},show_checkboxes:function(){this.get_container().children("ul").removeClass("jstree-no-checkboxes")},hide_checkboxes:function(){this.get_container().children("ul").addClass("jstree-no-checkboxes")},_repair_state:function(a){a=this._get_node(a);if(a.length){var d=a.find("> ul > .jstree-checked").length,g=a.find("> ul > .jstree-undetermined").length, +f=a.find("> ul > li").length;if(f===0)a.hasClass("jstree-undetermined")&&this.check_node(a);else if(d===0&&g===0)this.uncheck_node(a);else d===f?this.check_node(a):a.parentsUntil(this.get_container(),"li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined")}},reselect:function(){var a=this,d=this.data.ui.to_select;d=c.map(c.makeArray(d),function(g){return"#"+g.toString().replace(/^#/,"").replace("\\/","/").replace("/","\\/")});this.deselect_all();c.each(d,function(g, +f){a.check_node(f)});this.__callback()}}})})(jQuery); +(function(c){c.vakata.xslt=function(d,g){var f="",b,e;if(document.recalc){b=document.createElement("xml");e=document.createElement("xml");b.innerHTML=d;e.innerHTML=g;c("body").append(b).append(e);f=b.transformNode(e.XMLDocument);c("body").remove(b).remove(e);return f}if(typeof window.DOMParser!=="undefined"&&typeof window.XMLHttpRequest!=="undefined"&&typeof window.XSLTProcessor!=="undefined"){b=new XSLTProcessor;f=c.isFunction(b.transformDocument)?typeof window.XMLSerializer!=="undefined":true;if(!f)return false; +d=(new DOMParser).parseFromString(d,"text/xml");g=(new DOMParser).parseFromString(g,"text/xml");if(c.isFunction(b.transformDocument)){f=document.implementation.createDocument("","",null);b.transformDocument(d,g,f,null);return(new XMLSerializer).serializeToString(f)}else{b.importStylesheet(g);f=b.transformToFragment(d,document);return c("
                    ").append(f).html()}}return false};var a={nest:'\t\t\t\t\t\t', +flat:'\t
                      \t\t\t\t\t\t\t\t\t\t\t\t\t
                    \t\t\t\t
                  • \t\t\tjstree-last \t\t\t\t\tjstree-open \t\t\tjstree-closed \t\t\tjstree-leaf \t\t\t\t\t\t\t\t\t\t\t\t\t\t \t\t\t\t\t\t\t\t\t\t\t\t\t\t#\t\t\t\t\t\t\t \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tjstree-icon \t\t\t\t\t\t\t\t\t\t\t\t\tbackground:url() center center no-repeat;\t\t\t\t \t\t\t\t\t\t\t\t\t\t\t\t
                      \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
                    \t
                    \t
                  • '}; +c.jstree.plugin("xml_data",{defaults:{data:false,ajax:false,xsl:"flat",clean_node:false},_fn:{load_node:function(d,g,f){var b=this;this.load_node_xml(d,function(){b.__callback({obj:d});g.call(this)},f)},_is_loaded:function(d){var g=this.get_settings().xml_data;return d==-1||!d||!g.ajax||d.is(".jstree-open, .jstree-leaf")||d.children("ul").children("li").size()>0},load_node_xml:function(d,g,f){var b=this.get_settings().xml_data,e=function(){};switch(true){case !b.data&&!b.ajax:throw"Neither data nor ajax settings supplied."; +case !!b.data&&!b.ajax||!!b.data&&!!b.ajax&&(!d||d===-1):if(!d||d==-1){this.get_container().children("ul").empty().append(this.parse_xml(b.data).children());b.clean_node&&this.clean_node(d)}g&&g.call(this);break;case !b.data&&!!b.ajax||!!b.data&&!!b.ajax&&d&&d!==-1:d=this._get_node(d);e=function(h,k,j){var i=this.get_settings().xml_data.ajax.error;i&&i.call(this,h,k,j);if(d!==-1&&d.length){d.children(".jstree-loading").removeClass("jstree-loading");b.correct_state&&d.removeClass("jstree-open jstree-closed").addClass("jstree-leaf")}f&& +f.call(this)};b.ajax.context=this;b.ajax.error=e;b.ajax.success=function(h,k,j){if(j.responseText=="")return e.call(this,j,k,"");h=j.responseText;var i=this.get_settings().xml_data.ajax.success;if(i)h=i.call(this,h,k,j)||h;if(h=this.parse_xml(h)){d===-1||!d?this.get_container().children("ul").empty().append(this.parse_xml(j.responseText).children()):d.append(this.parse_xml(j.responseText)).children(".jstree-loading").removeClass("jstree-loading");b.clean_node&&this.clean_node(d);g&&g.call(this)}else{d.children(".jstree-loading").removeClass("jstree-loading"); +b.correct_state&&d.removeClass("jstree-open jstree-closed").addClass("jstree-leaf")}};if(c.isFunction(b.ajax.data))b.ajax.data=b.ajax.data.call(null,d);c.ajax(b.ajax);break}},parse_xml:function(d){var g=this.get_settings().xml_data;d=c.vakata.xslt(d,a[g.xsl]);if(d!==false)d=c(d);return d},get_xml:function(d,g,f,b,e){var h="",k=this.get_settings(),j=this,i,m,l,o,n;d||(d="flat");e||(e=0);g=this._get_node(g);if(!g||g===-1)g=this.get_container().find("> ul > li");f=c.isArray(f)?f:["id","class"];this.data.types&& +f.push(k.types.type_attr);b=c.isArray(b)?b:[];e||(h+="");g.each(function(){h+="";h+=""});h+="";m=l[0].id;l=l.find("> ul > li");if(l.length)m=j.get_xml(d,l,f,b,m);if(d=="nest")h+=m;h+="";if(d=="flat")h+=m});e||(h+="");return h}}})})(jQuery); +(function(c){c.expr[":"].jstree_contains=function(a,d,g){return(a.textContent||a.innerText||"").toLowerCase().indexOf(g[3].toLowerCase())>=0};c.jstree.plugin("search",{__init:function(){this.data.search.str="";this.data.search.result=c()},defaults:{ajax:false,case_insensitive:false},_fn:{search:function(a,d){var g=this.get_settings().search,f=this;this.data.search.str=a;if(!d&&g.ajax!==false&&this.get_container().find(".jstree-closed:eq(0)").length>0){this.search.supress_callback=true;g.ajax.context= +this;g.ajax.error=function(){};g.ajax.success=function(b,e,h){var k=this.get_settings().search.ajax.success;if(k)b=k.call(this,b,e,h)||b;this.data.search.to_open=b;this._search_open()};if(c.isFunction(g.ajax.data))g.ajax.data=g.ajax.data.call(this,a);if(!g.ajax.data)g.ajax.data={search_string:a};if(!g.ajax.dataType||/^json/.exec(g.ajax.dataType))g.ajax.dataType="json";c.ajax(g.ajax)}else{this.data.search.result.length&&this.clear_search();this.data.search.result=this.get_container().find("a"+(this.data.languages? +"."+this.get_lang():"")+":"+(g.case_insensitive?"jstree_contains":"contains")+"("+this.data.search.str+")");this.data.search.result.addClass("jstree-search").parents(".jstree-closed").each(function(){f.open_node(this,false,true)});this.__callback({nodes:this.data.search.result,str:a})}},clear_search:function(){this.data.search.result.removeClass("jstree-search");this.__callback(this.data.search.result);this.data.search.result=c()},_search_open:function(){var a=this,d=true,g=[],f=[];if(this.data.search.to_open.length){c.each(this.data.search.to_open, +function(b,e){if(e=="#")return true;c(e).length&&c(e).is(".jstree-closed")?g.push(e):f.push(e)});if(g.length){this.data.search.to_open=f;c.each(g,function(b,e){a.open_node(e,function(){a._search_open(true)})});d=false}}d&&this.search(this.data.search.str,true)}}})})(jQuery); +(function(c){c.vakata.context={cnt:c("
                    "),vis:false,tgt:false,func:false,data:false,show:function(a,d,g,f,b){if(a=c.vakata.context.parse(a)){c.vakata.context.vis=true;c.vakata.context.tgt=d;c.vakata.context.data=b||null;c.vakata.context.cnt.html(a).css({visibility:"hidden",display:"block",left:0,top:0});b=c.vakata.context.cnt.height();a=c.vakata.context.cnt.width();if(g+a>c(document).width()){g=c(document).width()-(a+5);c.vakata.context.cnt.find("li > ul").addClass("right")}if(f+ +b>c(document).height()){f-=b+d[0].offsetHeight;c.vakata.context.cnt.find("li > ul").addClass("bottom")}c.vakata.context.cnt.css({left:g,top:f}).find("li:has(ul)").bind("mouseenter",function(){var e=c(document).width(),h=c(document).height(),k=c(this).children("ul").show();e!==c(document).width()&&k.toggleClass("right");h!==c(document).height()&&k.toggleClass("bottom")}).bind("mouseleave",function(){c(this).children("ul").hide()}).end().css({visibility:"visible"}).show();c(document).triggerHandler("vakata.context_show")}}, +hide:function(){c.vakata.context.vis=false;c.vakata.context.cnt.attr("class","").hide();c(document).triggerHandler("vakata.context_hide")},parse:function(a,d){var g="",f=false;if(!d)c.vakata.context.func={};g+="
                      ";c.each(a,function(b,e){if(!e)return true;c.vakata.context.func[b]=e.action;if(e.separator_before)g+="
                    • ";g+="
                    • "+e.label;if(e.submenu)g+="»";g+="";if(e.submenu)if(f=c.vakata.context.parse(e.submenu,true))g+=f;g+="
                    • ";if(e.separator_after)g+="
                    • "});g+="
                    ";return g.length>10?g:false},exec:function(a){if(c.isFunction(c.vakata.context.func[a])){c.vakata.context.func[a].call(c.vakata.context.data,c.vakata.context.tgt);return true}else return false}}; +c(function(){c.vakata.css.add_sheet({str:"#vakata-contextmenu { display:none; position:absolute; margin:0; padding:0; min-width:180px; background:#ebebeb; border:1px solid silver; } #vakata-contextmenu ul { min-width:180px; } #vakata-contextmenu ul, #vakata-contextmenu li { margin:0; padding:0; list-style-type:none; display:block; } #vakata-contextmenu li { line-height:20px; min-height:20px; position:relative; padding:0px; } #vakata-contextmenu li a { padding:1px 6px; line-height:17px; display:block; text-decoration:none; margin:1px 1px 0 1px; } #vakata-contextmenu li ins { float:left; width:16px; height:16px; text-decoration:none; margin-right:2px; } #vakata-contextmenu li a:hover, #vakata-contextmenu li.vakata-hover > a { background:gray; color:white; } #vakata-contextmenu li ul { display:none; position:absolute; top:-2px; left:100%; background:#ebebeb; border:1px solid gray; } #vakata-contextmenu .right { right:100%; left:auto; } #vakata-contextmenu .bottom { bottom:-1px; top:auto; } #vakata-contextmenu li.vakata-separator { min-height:0; height:1px; line-height:1px; font-size:1px; overflow:hidden; margin:0 2px; background:silver; /* border-top:1px solid #fefefe; */ padding:0; } "}); +c.vakata.context.cnt.delegate("a","click",function(a){a.preventDefault()}).delegate("a","mouseup",function(){c.vakata.context.exec(c(this).attr("rel"))&&c.vakata.context.hide()}).delegate("a","mouseover",function(){c.vakata.context.cnt.find(".vakata-hover").removeClass("vakata-hover")}).appendTo("body");c(document).bind("mousedown",function(a){c.vakata.context.vis&&!c.contains(c.vakata.context.cnt[0],a.target)&&c.vakata.context.hide()});typeof c.hotkeys!=="undefined"&&c(document).bind("keydown","up", +function(a){if(c.vakata.context.vis){var d=c.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").prevAll("li:not(.vakata-separator)").first();d.length||(d=c.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").last());d.addClass("vakata-hover");a.stopImmediatePropagation();a.preventDefault()}}).bind("keydown","down",function(a){if(c.vakata.context.vis){var d=c.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").nextAll("li:not(.vakata-separator)").first(); +d.length||(d=c.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").first());d.addClass("vakata-hover");a.stopImmediatePropagation();a.preventDefault()}}).bind("keydown","right",function(a){if(c.vakata.context.vis){c.vakata.context.cnt.find(".vakata-hover").children("ul").show().children("li:not(.vakata-separator)").removeClass("vakata-hover").first().addClass("vakata-hover");a.stopImmediatePropagation();a.preventDefault()}}).bind("keydown","left",function(a){if(c.vakata.context.vis){c.vakata.context.cnt.find(".vakata-hover").children("ul").hide().children(".vakata-separator").removeClass("vakata-hover"); +a.stopImmediatePropagation();a.preventDefault()}}).bind("keydown","esc",function(a){c.vakata.context.hide();a.preventDefault()}).bind("keydown","space",function(a){c.vakata.context.cnt.find(".vakata-hover").last().children("a").click();a.preventDefault()})});c.jstree.plugin("contextmenu",{__init:function(){this.get_container().delegate("a","contextmenu.jstree",c.proxy(function(a){a.preventDefault();this.show_contextmenu(a.currentTarget,a.pageX,a.pageY)},this))},defaults:{show_at_node:true,items:{create:{separator_before:false, +separator_after:true,label:"Create",action:function(a){this.create(a)}},rename:{separator_before:false,separator_after:false,label:"Rename",action:function(a){this.rename(a)}},remove:{separator_before:false,icon:false,separator_after:false,label:"Delete",action:function(a){this.remove(a)}},ccp:{separator_before:true,icon:false,separator_after:false,label:"Edit",action:function(a){this.remove(a)},submenu:{cut:{separator_before:false,separator_after:false,label:"Cut",action:function(a){this.cut(a)}}, +copy:{separator_before:false,icon:false,separator_after:false,label:"Copy",action:function(a){this.copy(a)}},paste:{separator_before:false,icon:false,separator_after:false,label:"Paste",action:function(a){this.paste(a)}}}}}},_fn:{show_contextmenu:function(a,d,g){a=this._get_node(a);var f=this.get_settings().contextmenu,b=a.children("a:visible:eq(0)"),e=false;if(f.show_at_node||typeof d==="undefined"||typeof g==="undefined"){e=b.offset();d=e.left;g=e.top+this.data.core.li_height}if(c.isFunction(f.items))f.items= +f.items.call(this,a);c.vakata.context.show(f.items,b,d,g,this);this.data.themes&&c.vakata.context.cnt.attr("class","jstree-"+this.data.themes.theme+"-context")}}})})(jQuery); +(function(c){c.jstree.plugin("types",{__init:function(){var a=this.get_settings().types;this.data.types.attach_to=[];this.get_container().bind("init.jstree",c.proxy(function(){var d=a.type_attr,g="",f=this;c.each(a.types,function(b,e){c.each(e,function(h){/^(max_depth|max_children|icon|valid_children)$/.test(h)||f.data.types.attach_to.push(h)});if(!e.icon)return true;if(e.icon.image||e.icon.position){g+=b=="default"?".jstree-"+f.get_index()+" a > .jstree-icon { ":".jstree-"+f.get_index()+" li["+d+ +"="+b+"] > a > .jstree-icon { ";if(e.icon.image)g+=" background-image:url("+e.icon.image+"); ";g+=e.icon.position?" background-position:"+e.icon.position+"; ":" background-position:0 0; ";g+="} "}});g!=""&&c.vakata.css.add_sheet({str:g})},this)).bind("before.jstree",c.proxy(function(d,g){if(c.inArray(g.func,this.data.types.attach_to)!==-1){var f=this.get_settings().types.types,b=this._get_type(g.args[0]);if(f[b]&&typeof f[b][g.func]!=="undefined"&&!this._check(g.func,g.args[0])){d.stopImmediatePropagation(); +return false}}},this))},defaults:{max_children:-1,max_depth:-1,valid_children:"all",type_attr:"rel",types:{"default":{max_children:-1,max_depth:-1,valid_children:"all"}}},_fn:{_get_type:function(a){a=this._get_node(a);return!a||!a.length?false:a.attr(this.get_settings().types.type_attr)||"default"},set_type:function(a,d){d=this._get_node(d);return!d.length||!a?false:d.attr(this.get_settings().types.type_attr,a)},_check:function(a,d,g){var f=false,b=this._get_type(d),e=0,h=this,k=this.get_settings().types; +if(d===-1)if(k[a])f=k[a];else return;else{if(b===false)return;if(k.types[b]&&k.types[b][a])f=k.types[b][a];else if(k.types["default"]&&k.types["default"][a])f=k.types["default"][a]}if(c.isFunction(f))f=f.call(this,d);a==="max_depth"&&d!==-1&&g!==false&&k.max_depth!==-2&&f!==0&&this._get_node(d).parentsUntil(this.get_container(),"li").each(function(j){e=h._check(a,this,false);if(e!==-1&&e-(j+1)<=0){f=0;return false}if(e>=0&&(e-(j+1) ul > li").not(a.o).length:a.cr.children("> ul > li").not(a.o).length;if(e+a.o.length>g)return false}if(d.max_depth!== +-2&&f!==-1){h=0;if(f===0)return false;if(typeof a.o.d==="undefined"){for(d=a.o;d.length>0;){d=d.find("> ul > li");h++}a.o.d=h}if(f-a.o.d<0)return false}return true},create_node:function(a,d,g,f,b,e){if(!e&&(b||this._is_loaded(a))){var h=d&&d.match(/^before|after$/i)?this._get_parent(a):this._get_node(a),k=this.get_settings().types,j=this._check("max_children",h),i=this._check("max_depth",h),m=this._check("valid_children",h);g||(g={});if(m==="none")return false;if(c.isArray(m))if(!g.attr||!g.attr[k.type_attr]){if(!g.attr)g.attr= +{};g.attr[k.type_attr]=m[0]}else if(c.inArray(g.attr[k.type_attr],m)===-1)return false;if(k.max_children!==-2&&j!==-1){h=h===-1?this.get_container().children("> ul > li").length:h.children("> ul > li").length;if(h+1>j)return false}if(k.max_depth!==-2&&i!==-1&&i-1<=0)return false}return this.__call_old(true,a,d,g,f,b,e)}}})})(jQuery); \ No newline at end of file diff --git a/application/media/js/jquery.stickyfloat-1.0.js b/application/media/js/jquery.stickyfloat-1.0.js new file mode 100644 index 0000000..9ef886e --- /dev/null +++ b/application/media/js/jquery.stickyfloat-1.0.js @@ -0,0 +1,48 @@ +/* + * stickyfloat - jQuery plugin for verticaly floating anything in a constrained area + * + * Example: jQuery('#menu').stickyfloat({duration: 400}); + * parameters: + * duration - the duration of the animation + * startOffset - the amount of scroll offset after it the animations kicks in + * offsetY - the offset from the top when the object is animated + * lockBottom - 'true' by default, set to false if you don't want your floating box to stop at parent's bottom + * $Version: 05.16.2009 r1 + * Copyright (c) 2009 Yair Even-Or + * vsync.design@gmail.com + */ + +$.fn.stickyfloat = function(options, lockBottom) { + var $obj = this; + var parentPaddingTop = parseInt($obj.parent().css('padding-top')); + var startOffset = $obj.parent().offset().top; + var opts = $.extend({ startOffset: startOffset, offsetY: parentPaddingTop, duration: 200, lockBottom:true }, options); + + $obj.css({ position: 'absolute' }); + + if(opts.lockBottom){ + var bottomPos = $obj.parent().height() - $obj.height() + parentPaddingTop; //get the maximum scrollTop value + if( bottomPos < 0 ) + bottomPos = 0; + } + + $(window).scroll(function () { + $obj.stop(); // stop all calculations on scroll event + + var pastStartOffset = $(document).scrollTop() > opts.startOffset; // check if the window was scrolled down more than the start offset declared. + var objFartherThanTopPos = $obj.offset().top > startOffset; // check if the object is at it's top position (starting point) + var objBiggerThanWindow = $obj.outerHeight() < $(window).height(); // if the window size is smaller than the Obj size, then do not animate. + + // if window scrolled down more than startOffset OR obj position is greater than + // the top position possible (+ offsetY) AND window size must be bigger than Obj size + if( (pastStartOffset || objFartherThanTopPos) && objBiggerThanWindow ){ + var newpos = ($(document).scrollTop() -startOffset + opts.offsetY ); + if ( newpos > bottomPos ) + newpos = bottomPos; + if ( $(document).scrollTop() < opts.startOffset ) // if window scrolled < starting offset, then reset Obj position (opts.offsetY); + newpos = parentPaddingTop; + + $obj.animate({ top: newpos }, opts.duration ); + } + }); +}; diff --git a/application/media/js/themes/default/d.png b/application/media/js/themes/default/d.png new file mode 100644 index 0000000000000000000000000000000000000000..8540175a04b0cd303e3966d1727f30ee9b8a7254 GIT binary patch literal 7635 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 literal 0 HcmV?d00001 diff --git a/application/media/js/themes/default/style.css b/application/media/js/themes/default/style.css new file mode 100644 index 0000000..2ca17ea --- /dev/null +++ b/application/media/js/themes/default/style.css @@ -0,0 +1,56 @@ +/* + * jsTree default theme 1.0 + * Supported features: dots/no-dots, icons/no-icons, focused, loading + * Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search + */ + +.jstree-default li, +.jstree-default ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; } +.jstree-default li { background-position:-90px 0; background-repeat:repeat-y; } +.jstree-default li.jstree-last { background:transparent; } +.jstree-default .jstree-open > ins { background-position:-72px 0; } +.jstree-default .jstree-closed > ins { background-position:-54px 0; } +.jstree-default .jstree-leaf > ins { background-position:-36px 0; } + +.jstree-default .jstree-hovered { background:#FFF0C0; border:1px solid #841212; padding:0 2px 0 1px; color: #841212;} +.jstree-default .jstree-clicked { background:#FCFCFC; border:1px solid #FCFCFC; padding:0 2px 0 1px; color: #841212;} +.jstree-default a .jstree-icon { background-position:-56px -19px; } +.jstree-default a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; } + +.jstree-default.jstree-focused { background:#FCFCFC; } + +.jstree-default .jstree-no-dots li, +.jstree-default .jstree-no-dots .jstree-leaf > ins { background:transparent; } +.jstree-default .jstree-no-dots .jstree-open > ins { background-position:-18px 0; } +.jstree-default .jstree-no-dots .jstree-closed > ins { background-position:0 0; } + +.jstree-default .jstree-no-icons a .jstree-icon { display:none; } + +.jstree-default .jstree-search { font-style:italic; } + +.jstree-default .jstree-no-icons .checkbox { display:inline-block; } +.jstree-default .jstree-no-checkboxes .checkbox { display:none !important; } +.jstree-default .jstree-checked > a > .checkbox { background-position:-38px -19px; } +.jstree-default .jstree-unchecked > a > .checkbox { background-position:-2px -19px; } +.jstree-default .jstree-undetermined > a > .checkbox { background-position:-20px -19px; } +.jstree-default .jstree-checked > a > .checkbox:hover { background-position:-38px -37px; } +.jstree-default .jstree-unchecked > a > .checkbox:hover { background-position:-2px -37px; } +.jstree-default .jstree-undetermined > a > .checkbox:hover { background-position:-20px -37px; } + +#vakata-dragged.jstree-default ins { background:transparent !important; } +#vakata-dragged.jstree-default .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; } +#vakata-dragged.jstree-default .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; } +#jstree-marker.jstree-default { background:url("d.png") -41px -57px no-repeat !important; } + +.jstree-default a.jstree-search { color:aqua; } + +#vakata-contextmenu.jstree-default-context, +#vakata-contextmenu.jstree-default-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; } +#vakata-contextmenu.jstree-default-context li { } +#vakata-contextmenu.jstree-default-context a { color:black; } +#vakata-contextmenu.jstree-default-context a:hover, +#vakata-contextmenu.jstree-default-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; } +#vakata-contextmenu.jstree-default-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; } +#vakata-contextmenu.jstree-default-context li ul { margin-left:-4px; } + +/* TODO: IE6 support - the `>` selectors */ diff --git a/application/media/js/themes/default/throbber.gif b/application/media/js/themes/default/throbber.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b33f7e54f4e55b6b8774d86d96895db9af044b4 GIT binary patch literal 1849 zcma*odr(tX9tZI2z31lM+(&YVk%mZ}5P~KlG2s=WSbGzm0!x7^P##Mnh7t-jP!X0Q zk_SQ}Po-L1tlDK;6l?(>v)e5ZBQx4|Y-Q?nr@Px3?9h(3ZWr3^tj=`TP57gKr87N$ zp2wWee1GRRCwo_xahnw)5cxNPJbCg2L6DV|6`#+yw6v6!mDS$f9-JvFD^n;GQ&UrZ zzh5jCkByB101O60U0q#p_1BM>Cv-vP?&s4@g_((4_1L=L$(a91)0=J91Gas#R{McE znYG^9*0A5YZ>#;~+Wkn(W5B0^yELIYLP!K}mB~<)AM@1&nqekynuaEGqPrzoH|KodRXJy)%+w_fu3nE5>@Bd_b zqC$EQ;{c`T&?EsNO|igL9gC7Ygxv?aQUEXMq?~>wg{EyW;VcJ37CUF#HjrT=KQO_* zS>M9yydXk18D(+QDJ1>r);Lav_uYKp$T?4vr{Q$lTo&pKv^?(>L-)G2*lwH!Ah7k? z7oH<8h-(KTKt5V6$8gF)C7Io&P5=SjTh)=zV=E2EUhQZP##L8S{d%UK>>+y82>+FV+#^BzW7u3F)Bb>=lYQ%%j`F>ASe zo*cw@V#u6T`A2He;70mR(V&iV&-7{qP~=SRf&jm9-T{*ZeZ}$rd0#6c&fLG^xJcf5 z+p<`wJYgW+_s*V{uI$nMB;%8`S_3>PfGOj3Rq}@Cx^+j?rk92fANSFDBYnOqQ>Vdj z)(|$AhP4t&Lb=Gvo2#3Gl%9<=Gv`Mz?Po@P4iLF!x}GUWJICDlFk-hS^Whyh7x~VH z@0vD1>HYD4&e+~yzS*-sFR{9`{QEEZO1zg7>R&7cHts-6j!xHVdA8eI+ZlVzd%`es zJT@$#GX(gvCJ1oJN%yLBK}{V=V;seo;!w|Yte!W1%5qLNFWqvZW>h&IiH+oPT=b@E zPhGzv5=(Un*X>v`>%8h_nj^NdYcE6NHS_ifkCV$*D)Tqrbu`s;<=t<4 zAHNqNV?6(g<1PY-w@#I-WYFViz?9TrkMr)u0g`O`u|>T;k|2sV*YF^punvT;$SuTy{j3Gv)yqD!R_CF>yR)MzmmYS5v+~R zXAdD%ng9?df;wd8GxR#%3O+gz};Vo;)sK%Bj-q>Oq%R7JU-KD?vYu>#2UjaDo z&8$>5xW~?KPD_#XFToU1hIb*VOMidUr6iYiO0N|i-7s`T8!cFT`rN!^1Pt78J93i6 z5HI1wIM$94m{3SLDvISDe6$ZG1;eq_D9RTaaC>=cO{@Bs>$IlPCPJJ$h$)-3vzNUQ6OsN#_zWxey!_9%hxwH2_dEJi=yY|1c7nDm2_Lm!Cof8-R_+9UkS zcBE(o47yE)oMR(Q=dp1a2wTX5KvvGyLqlWTa7V&!A*|w|)ax~1_~aJ0=_Lilg*0iQk7#ZD EAHN$8j{pDw literal 0 HcmV?d00001 diff --git a/application/messages/.htaccess b/application/messages/.htaccess new file mode 100644 index 0000000..281d5c3 --- /dev/null +++ b/application/messages/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/application/views/.htaccess b/application/views/.htaccess new file mode 100644 index 0000000..281d5c3 --- /dev/null +++ b/application/views/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/application/views/lnapp/default.php b/application/views/lnapp/default.php new file mode 100644 index 0000000..57d9566 --- /dev/null +++ b/application/views/lnapp/default.php @@ -0,0 +1,93 @@ + + + + + <?php echo $meta->title; ?> + + + + + + + styles; ?> + scripts; ?> + + + + + + + + + + + + + + + + + + + + +
                    +
                    + + + + + + +
                    +
                    +
                    +
                    + + + + +
                    +
                    +
                    > +
                    + + + + +
                    +
                    +
                    +
                    + + + + + +
                    + + + + + +
                    +
                    +
                    > +
                    + + + + +
                    +
                    +
                    +
                    + + + + +
                    + + diff --git a/application/views/login.php b/application/views/login.php new file mode 100644 index 0000000..fd491b1 --- /dev/null +++ b/application/views/login.php @@ -0,0 +1,16 @@ +
                    + + + + + + + + + + + + + + + diff --git a/application/views/login_reset.php b/application/views/login_reset.php new file mode 100644 index 0000000..5d4294d --- /dev/null +++ b/application/views/login_reset.php @@ -0,0 +1,14 @@ +
                    +

                    If you have forgotten your password, we can issue you a temporary access code via email that will allow you to change your password.

                    +

                    To start this process, please enter your email address.

                    +
                    + + + + + + + + + + diff --git a/application/views/login_reset_sent.php b/application/views/login_reset_sent.php new file mode 100644 index 0000000..dd2554b --- /dev/null +++ b/application/views/login_reset_sent.php @@ -0,0 +1,13 @@ +
                    +

                    You should have received an email with a pass code. Please enter that pass code here.

                    +
                    + + + + + + + + + + diff --git a/application/views/template.php b/application/views/template.php new file mode 100644 index 0000000..6f72764 --- /dev/null +++ b/application/views/template.php @@ -0,0 +1 @@ + diff --git a/application/views/userguide/template.php b/application/views/userguide/template.php new file mode 100644 index 0000000..cfd4a19 --- /dev/null +++ b/application/views/userguide/template.php @@ -0,0 +1,108 @@ + + + + + +<?php echo $title ?> | Kohana <?php echo __('User Guide'); ?> + + $media) echo HTML::style($style, array('media' => $media), NULL, TRUE), "\n" ?> + + + + + + + + + +
                    +
                    +
                    +
                    + +
                    +
                    +
                    + +
                    +
                    +
                    + + + +
                    + + + Documentation comments powered by Disqus + +
                    +
                    +
                    +
                    + + + + + + + + diff --git a/includes/kohana/LICENSE.md b/includes/kohana/LICENSE.md new file mode 100644 index 0000000..87af9ad --- /dev/null +++ b/includes/kohana/LICENSE.md @@ -0,0 +1,14 @@ +# Kohana License Agreement + +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 +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of the Kohana nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/includes/kohana/README.md b/includes/kohana/README.md new file mode 100644 index 0000000..e19aba8 --- /dev/null +++ b/includes/kohana/README.md @@ -0,0 +1,3 @@ +# Kohana PHP Framework, version 3.1 (release) + +This is the current release version of [Kohana](http://kohanaframework.org/). diff --git a/includes/kohana/application/bootstrap.php b/includes/kohana/application/bootstrap.php new file mode 100644 index 0000000..d6081cd --- /dev/null +++ b/includes/kohana/application/bootstrap.php @@ -0,0 +1,118 @@ +" + */ +if (isset($_SERVER['KOHANA_ENV'])) +{ + Kohana::$environment = constant('Kohana::'.strtoupper($_SERVER['KOHANA_ENV'])); +} + +/** + * Initialize Kohana, setting the default options. + * + * The following options are available: + * + * - string base_url path, and optionally domain, of your application NULL + * - 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 + * - boolean errors enable or disable error handling TRUE + * - boolean profile enable or disable internal profiling TRUE + * - boolean caching enable or disable internal caching FALSE + */ +Kohana::init(array( + 'base_url' => '/', +)); + +/** + * Attach the file write to logging. Multiple writers are supported. + */ +Kohana::$log->attach(new Log_File(APPPATH.'logs')); + +/** + * Attach a file reader to config. Multiple readers are supported. + */ +Kohana::$config->attach(new Config_File); + +/** + * Enable modules. Modules are referenced by a relative or absolute path. + */ +Kohana::modules(array( + // 'auth' => MODPATH.'auth', // Basic authentication + // 'cache' => MODPATH.'cache', // Caching with multiple backends + // 'codebench' => MODPATH.'codebench', // Benchmarking tool + // 'database' => MODPATH.'database', // Database access + // 'image' => MODPATH.'image', // Image manipulation + // 'orm' => MODPATH.'orm', // Object Relationship Mapping + // 'unittest' => MODPATH.'unittest', // Unit testing + // 'userguide' => MODPATH.'userguide', // User guide and API documentation + )); + +/** + * Set the routes. Each route must have a minimum of a name, a URI and a set of + * defaults for the URI. + */ +Route::set('default', '((/(/)))') + ->defaults(array( + 'controller' => 'welcome', + 'action' => 'index', + )); diff --git a/includes/kohana/application/classes/controller/welcome.php b/includes/kohana/application/classes/controller/welcome.php new file mode 100644 index 0000000..0984eff --- /dev/null +++ b/includes/kohana/application/classes/controller/welcome.php @@ -0,0 +1,10 @@ +response->body('hello, world!'); + } + +} // End Welcome diff --git a/includes/kohana/install.php b/includes/kohana/install.php new file mode 100644 index 0000000..cbb0a4c --- /dev/null +++ b/includes/kohana/install.php @@ -0,0 +1,217 @@ + + + + + + + + Kohana Installation + + + + + + +

                    Environment Tests

                    + +

                    + The following tests have been run to determine if Kohana will work in your environment. + If any of the tests have failed, consult the documentation + for more information on how to correct the problem. +

                    + + + + + + + =')): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                    PHP VersionKohana requires PHP 5.2.3 or newer, this version is .
                    System DirectoryThe configured system directory does not exist or does not contain required files.
                    Application DirectoryThe configured application directory does not exist or does not contain required files.
                    Cache DirectoryThe directory is not writable.
                    Logs DirectoryThe directory is not writable.
                    PCRE UTF-8PCRE has not been compiled with UTF-8 support.PCRE has not been compiled with Unicode property support.Pass
                    SPL EnabledPassPHP SPL is either not loaded or not compiled in.
                    Reflection EnabledPassPHP reflection is either not loaded or not compiled in.
                    Filters EnabledPassThe filter extension is either not loaded or not compiled in.
                    Iconv Extension LoadedPassThe iconv extension is not loaded.
                    Mbstring Not OverloadedThe mbstring extension is overloading PHP's native string functions.Pass
                    Character Type (CTYPE) ExtensionThe ctype extension is not enabled.Pass
                    URI DeterminationPassNeither $_SERVER['REQUEST_URI'], $_SERVER['PHP_SELF'], or $_SERVER['PATH_INFO'] is available.
                    + + +

                    ✘ Kohana may not work correctly with your environment.

                    + +

                    ✔ Your environment passed all requirements.
                    + Remove or rename the install file now.

                    + + +

                    Optional Tests

                    + +

                    + The following extensions are not required to run the Kohana core, but if enabled can provide access to additional classes. +

                    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                    PECL HTTP EnabledPassKohana can use the http extension for the Request_Client_External class.
                    cURL EnabledPassKohana can use the cURL extension for the Request_Client_External class.
                    mcrypt EnabledPassKohana requires mcrypt for the Encrypt class.
                    GD EnabledPassKohana requires GD v2 for the Image class.
                    MySQL EnabledPassKohana can use the MySQL extension to support MySQL databases.
                    PDO EnabledPassKohana can use PDO to support additional databases.
                    + + + diff --git a/includes/kohana/modules/auth/README.md b/includes/kohana/modules/auth/README.md new file mode 100644 index 0000000..27ac9cd --- /dev/null +++ b/includes/kohana/modules/auth/README.md @@ -0,0 +1,13 @@ +New Age Auth +--- + +I've forked the main Auth module because there were some fundamental flaws with it: + + 1. It's trivial to [bruteforce](http://dev.kohanaframework.org/issues/3163) publicly hidden salt hashes. + - I've fixed this by switching the password hashing algorithm to the more secure secret-key based hash_hmac method. + 2. ORM drivers were included. + - I've fixed this by simply removing them. They cause confusion with new users because they think that Auth requires ORM. The only driver currently provided by default is the file driver. + 3. Auth::get_user()'s api is inconsistent because it returns different data types. + - I've fixed this by returning an empty user model by default. You can override what gets returned (if you've changed your user model class name for instance) by overloading the get_user() method in your application. + +These changes should be merged into the mainline branch eventually, but they completely break the API, so likely won't be done until 3.1. \ No newline at end of file diff --git a/includes/kohana/modules/auth/classes/auth.php b/includes/kohana/modules/auth/classes/auth.php new file mode 100644 index 0000000..a02b1e5 --- /dev/null +++ b/includes/kohana/modules/auth/classes/auth.php @@ -0,0 +1,3 @@ +get('driver')) + { + $type = 'file'; + } + + // Set the session class name + $class = 'Auth_'.ucfirst($type); + + // Create a new session instance + Auth::$_instance = new $class($config); + } + + return Auth::$_instance; + } + + protected $_session; + + protected $_config; + + /** + * Loads Session and configuration options. + * + * @return void + */ + public function __construct($config = array()) + { + // Save the config in the object + $this->_config = $config; + + $this->_session = Session::instance(); + } + + abstract protected function _login($username, $password, $remember); + + abstract public function password($username); + + abstract public function check_password($password); + + /** + * Gets the currently logged in user from the session. + * Returns NULL if no user is currently logged in. + * + * @return mixed + */ + public function get_user($default = NULL) + { + return $this->_session->get($this->_config['session_key'], $default); + } + + /** + * 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 + * @return boolean + */ + public function login($username, $password, $remember = FALSE) + { + 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 + * @return boolean + */ + public function logout($destroy = FALSE, $logout_all = FALSE) + { + if ($destroy === TRUE) + { + // Destroy the session completely + $this->_session->destroy(); + } + else + { + // Remove the user from the session + $this->_session->delete($this->_config['session_key']); + + // Regenerate session_id + $this->_session->regenerate(); + } + + // Double check + return ! $this->logged_in(); + } + + /** + * Check if there is an active session. Optionally allows checking for a + * specific role. + * + * @param string role name + * @return mixed + */ + public function logged_in($role = NULL) + { + return ($this->get_user() !== NULL); + } + + /** + * Creates a hashed hmac password from a plaintext password. This + * method is deprecated, [Auth::hash] should be used instead. + * + * @deprecated + * @param string plaintext password + */ + public function hash_password($password) + { + return $this->hash($password); + } + + /** + * Perform a hmac hash, using the configured method. + * + * @param string string to hash + * @return string + */ + public function hash($str) + { + if ( ! $this->_config['hash_key']) + throw new Kohana_Exception('A valid hash key must be set in your auth config.'); + + return hash_hmac($this->_config['hash_method'], $str, $this->_config['hash_key']); + } + + protected function complete_login($user) + { + // Regenerate session_id + $this->_session->regenerate(); + + // Store username in session + $this->_session->set($this->_config['session_key'], $user); + + return TRUE; + } + +} // End Auth diff --git a/includes/kohana/modules/auth/classes/kohana/auth/file.php b/includes/kohana/modules/auth/classes/kohana/auth/file.php new file mode 100644 index 0000000..31ca0c3 --- /dev/null +++ b/includes/kohana/modules/auth/classes/kohana/auth/file.php @@ -0,0 +1,88 @@ +_users = Arr::get($config, 'users', array()); + } + + /** + * Logs a user in. + * + * @param string username + * @param string password + * @param boolean enable autologin (not supported) + * @return boolean + */ + protected function _login($username, $password, $remember) + { + if (isset($this->_users[$username]) AND $this->_users[$username] === $password) + { + // Complete the login + return $this->complete_login($username); + } + + // Login failed + return FALSE; + } + + /** + * Forces a user to be logged in, without specifying a password. + * + * @param mixed username + * @return boolean + */ + public function force_login($username) + { + // Complete the login + return $this->complete_login($username); + } + + /** + * Get the stored password for a username. + * + * @param mixed username + * @return string + */ + public function password($username) + { + return Arr::get($this->_users, $username, FALSE); + } + + /** + * Compare password with original (plain text). Works for current (logged in) user + * + * @param string $password + * @return boolean + */ + public function check_password($password) + { + $username = $this->get_user(); + + if ($username === FALSE) + { + return FALSE; + } + + return ($password === $this->password($username)); + } + +} // End Auth File \ No newline at end of file diff --git a/includes/kohana/modules/auth/config/auth.php b/includes/kohana/modules/auth/config/auth.php new file mode 100644 index 0000000..0d80208 --- /dev/null +++ b/includes/kohana/modules/auth/config/auth.php @@ -0,0 +1,16 @@ + 'file', + 'hash_method' => 'sha256', + 'hash_key' => NULL, + 'lifetime' => 1209600, + 'session_key' => 'auth_user', + + // Username/password combinations for the Auth File driver + 'users' => array( + // 'admin' => 'b3154acf3a344170077d11bdb5fff31532f679a1919e716a02', + ), + +); diff --git a/includes/kohana/modules/auth/guide/auth/config.md b/includes/kohana/modules/auth/guide/auth/config.md new file mode 100644 index 0000000..e69de29 diff --git a/includes/kohana/modules/auth/guide/auth/edit.md b/includes/kohana/modules/auth/guide/auth/edit.md new file mode 100644 index 0000000..e69de29 diff --git a/includes/kohana/modules/auth/guide/auth/index.md b/includes/kohana/modules/auth/guide/auth/index.md new file mode 100644 index 0000000..e69de29 diff --git a/includes/kohana/modules/auth/guide/auth/login.md b/includes/kohana/modules/auth/guide/auth/login.md new file mode 100644 index 0000000..e69de29 diff --git a/includes/kohana/modules/auth/guide/auth/menu.md b/includes/kohana/modules/auth/guide/auth/menu.md new file mode 100644 index 0000000..1708caa --- /dev/null +++ b/includes/kohana/modules/auth/guide/auth/menu.md @@ -0,0 +1,7 @@ +## [Auth]() +- [Config](config) +- [User Model](user) +- [Register Users](register) +- [Log in and out](login) +- [Edit User](edit) +- [Using Roles](roles) diff --git a/includes/kohana/modules/auth/guide/auth/register.md b/includes/kohana/modules/auth/guide/auth/register.md new file mode 100644 index 0000000..e69de29 diff --git a/includes/kohana/modules/auth/guide/auth/roles.md b/includes/kohana/modules/auth/guide/auth/roles.md new file mode 100644 index 0000000..e69de29 diff --git a/includes/kohana/modules/auth/guide/auth/user.md b/includes/kohana/modules/auth/guide/auth/user.md new file mode 100644 index 0000000..e69de29 diff --git a/includes/kohana/modules/cache/README.md b/includes/kohana/modules/cache/README.md new file mode 100644 index 0000000..cd04fbd --- /dev/null +++ b/includes/kohana/modules/cache/README.md @@ -0,0 +1,61 @@ +Kohana Cache library +==================== + +The cache library for Kohana 3 provides a simple interface to the most common cache solutions. Developers are free to add their own caching solutions that follow the cache design pattern defined within this module. + +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 + +Planned support +--------------- + +In the near future, additional support for the following methods will be included. + +1. Memcached + +Introduction to caching +----------------------- + +To use caching to the maximum potential, your application should be designed with caching in mind from the outset. In general, the most effective caches contain lots of small collections of data that are the result of expensive computational operations, such as searching through a large data set. + +There are many different caching methods available for PHP, from the very basic file based caching to opcode caching in eAccelerator and APC. Caching engines that use physical memory over disk based storage are always faster, however many do not support more advanced features such as tagging. + +Using Cache +----------- + +To use Kohana Cache, download and extract the latest stable release of Kohana Cache from [Github](http://github.com/samsoir/kohana-cache). Place the module into your Kohana instances modules folder. Finally enable the module within the application bootstrap within the section entitled _modules_. + +Quick example +------------- + +The following is a quick example of how to use Kohana Cache. The example is using the SQLite driver. + + 'bar', 'apples' => 'pear', 'BDFL' => 'Shadowhand'); + + // Save the data to cache, with an id of test_id and a lifetime of 10 minutes + $mycache->set('test_id', $data, 600); + + // Retrieve the data from cache + $retrieved_data = $mycache->get('test_id'); + + // Remove the cache item + $mycache->delete('test_id'); + + // Clear the cache of all stored items + $mycache->delete_all(); diff --git a/includes/kohana/modules/cache/classes/cache.php b/includes/kohana/modules/cache/classes/cache.php new file mode 100644 index 0000000..2b43c93 --- /dev/null +++ b/includes/kohana/modules/cache/classes/cache.php @@ -0,0 +1,3 @@ + array( // Default group + * 'driver' => 'memcache', // using Memcache driver + * 'servers' => array( // Available server definitions + * array( + * 'host' => 'localhost', + * 'port' => 11211, + * 'persistent' => FALSE + * ) + * ), + * '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 + * @license http://kohanaphp.com/license + */ +abstract class Kohana_Cache { + + const DEFAULT_EXPIRE = 3600; + + /** + * @var string default driver to use + */ + public static $default = 'file'; + + /** + * @var Kohana_Cache instances + */ + public static $instances = array(); + + /** + * 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 + */ + public static function instance($group = NULL) + { + // If there is no group supplied + if ($group === NULL) + { + // Use the default setting + $group = Cache::$default; + } + + if (isset(Cache::$instances[$group])) + { + // Return the current group if initiated already + return Cache::$instances[$group]; + } + + $config = Kohana::config('cache'); + + if ( ! $config->offsetExists($group)) + { + throw new Kohana_Cache_Exception('Failed to load Kohana Cache group: :group', array(':group' => $group)); + } + + $config = $config->get($group); + + // Create a new cache type instance + $cache_class = 'Cache_'.ucfirst($config['driver']); + Cache::$instances[$group] = new $cache_class($config); + + // Return the instance + return Cache::$instances[$group]; + } + + /** + * @var Config + */ + protected $_config; + + /** + * Ensures singleton pattern is observed, loads the default expiry + * + * @param array configuration + */ + protected function __construct(array $config) + { + $this->_config = $config; + } + + /** + * Overload the __clone() method to prevent cloning + * + * @return void + * @throws Kohana_Cache_Exception + */ + public function __clone() + { + throw new Kohana_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 + * @return mixed + * @throws Kohana_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)) + * { + * // Cache was set successfully + * return + * } + * + * @param string id of cache entry + * @param string data to set to cache + * @param integer 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 + * @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(); + * + * @return boolean + */ + abstract public function delete_all(); + + /** + * Replaces troublesome characters with underscores. + * + * // Sanitize a cache id + * $id = $this->_sanitize_id($id); + * + * @param string id of cache to sanitize + * @return string + */ + protected function _sanitize_id($id) + { + // Change slashes and spaces to underscores + return str_replace(array('/', '\\', ' '), '_', $id); + } +} +// End Kohana_Cache diff --git a/includes/kohana/modules/cache/classes/kohana/cache/apc.php b/includes/kohana/modules/cache/classes/kohana/cache/apc.php new file mode 100644 index 0000000..78765fe --- /dev/null +++ b/includes/kohana/modules/cache/classes/kohana/cache/apc.php @@ -0,0 +1,135 @@ + 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 + * @license http://kohanaphp.com/license + */ +class Kohana_Cache_Apc extends Cache { + + /** + * 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 + */ + protected function __construct(array $config) + { + if ( ! extension_loaded('apc')) + { + throw new Kohana_Cache_Exception('PHP APC extension is not available.'); + } + + parent::__construct($config); + } + + /** + * 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 + * @return mixed + * @throws Kohana_Cache_Exception + */ + public function get($id, $default = NULL) + { + $data = apc_fetch($this->_sanitize_id($id), $success); + + return $success ? $data : $default; + } + + /** + * 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 + * @return boolean + */ + public function set($id, $data, $lifetime = NULL) + { + if ($lifetime === NULL) + { + $lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE); + } + + return apc_store($this->_sanitize_id($id), $data, $lifetime); + } + + /** + * 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 + * @return boolean + */ + public function delete($id) + { + return apc_delete($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 apc group + * Cache::instance('apc')->delete_all(); + * + * @return boolean + */ + public function delete_all() + { + return apc_clear_cache('user'); + } +} diff --git a/includes/kohana/modules/cache/classes/kohana/cache/eaccelerator.php b/includes/kohana/modules/cache/classes/kohana/cache/eaccelerator.php new file mode 100644 index 0000000..96c97f5 --- /dev/null +++ b/includes/kohana/modules/cache/classes/kohana/cache/eaccelerator.php @@ -0,0 +1,133 @@ + 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/exception.php b/includes/kohana/modules/cache/classes/kohana/cache/exception.php new file mode 100644 index 0000000..d89d25e --- /dev/null +++ b/includes/kohana/modules/cache/classes/kohana/cache/exception.php @@ -0,0 +1,11 @@ + 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 + * @license http://kohanaphp.com/license + */ +class Kohana_Cache_File extends Cache implements Kohana_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 + * @return string + */ + protected static function filename($string) + { + return sha1($string).'.json'; + } + + /** + * @var string the caching directory + */ + protected $_cache_dir; + + /** + * 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 + */ + protected function __construct(array $config) + { + // Setup parent + parent::__construct($config); + + try + { + $directory = Arr::get($this->_config, 'cache_dir', Kohana::$cache_dir); + $this->_cache_dir = new SplFileInfo($directory); + } + // PHP < 5.3 exception handle + catch (ErrorException $e) + { + $this->_cache_dir = $this->_make_directory($directory, 0777, TRUE); + } + // PHP >= 5.3 exception handle + catch (UnexpectedValueException $e) + { + $this->_cache_dir = $this->_make_directory($directory, 0777, TRUE); + } + + // 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())); + } + + // 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())); + } + + // 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())); + } + } + + /** + * 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 + * @return mixed + * @throws Kohana_Cache_Exception + */ + public function get($id, $default = NULL) + { + $filename = Cache_File::filename($this->_sanitize_id($id)); + $directory = $this->_resolve_directory($filename); + + // Wrap operations in try/catch to handle notices + try + { + // Open file + $file = new SplFileInfo($directory.$filename); + + // If file does not exist + if ( ! $file->isFile()) + { + // Return default value + return $default; + } + else + { + // Open the file and extract the json + $json = $file->openFile()->current(); + + // Decode the json into PHP object + $data = json_decode($json); + + // Test the expiry + if ($data->expiry < 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); + } + } + + } + 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()); + } + + // Otherwise throw the exception + throw $e; + } + } + + /** + * 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 + * @return boolean + */ + public function set($id, $data, $lifetime = NULL) + { + $filename = Cache_File::filename($this->_sanitize_id($id)); + $directory = $this->_resolve_directory($filename); + + // If lifetime is NULL + if ($lifetime === NULL) + { + // Set to the default expiry + $lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE); + } + + // Open directory + $dir = new SplFileInfo($directory); + + // If the directory path is not a directory + if ( ! $dir->isDir()) + { + // Create the directory + if ( ! mkdir($directory, 0777, TRUE)) + { + throw new Kohana_Cache_Exception(__METHOD__.' unable to create directory : :directory', array(':directory' => $directory)); + } + + // chmod to solve potential umask issues + chmod($directory, 0777); + } + + // Open file to inspect + $resouce = new SplFileInfo($directory.$filename); + $file = $resouce->openFile('w'); + + 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); + } + catch (ErrorException $e) + { + // If serialize through an error exception + 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()); + } + + // 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 + * @return boolean + */ + public function delete($id) + { + $filename = Cache_File::filename($this->_sanitize_id($id)); + $directory = $this->_resolve_directory($filename); + + return $this->_delete_file(new SplFileInfo($directory.$filename), NULL, TRUE); + } + + /** + * 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(); + * + * @return boolean + */ + public function delete_all() + { + return $this->_delete_file($this->_cache_dir, TRUE); + } + + /** + * Garbage collection method that cleans any expired + * cache entries from the cache. + * + * @return void + */ + public function garbage_collect() + { + $this->_delete_file($this->_cache_dir, TRUE, FALSE, TRUE); + return; + } + + /** + * 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 + * @return boolean + * @throws Kohana_Cache_Exception + */ + protected function _delete_file(SplFileInfo $file, $retain_parent_directory = FALSE, $ignore_errors = FALSE, $only_expired = FALSE) + { + // Allow graceful error handling + try + { + // If is file + if ($file->isFile()) + { + try + { + // If only expired is not set + if ($only_expired === FALSE) + { + // We want to delete the file + $delete = TRUE; + } + // Otherwise... + else + { + // Assess the file expiry to flag it for deletion + $json = $file->openFile('r')->current(); + $data = json_decode($json); + $delete = $data->expiry < time(); + } + + // If the delete flag is set + if ($delete === TRUE) + { + // Try to delete + unlink($file->getRealPath()); + } + } + 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())); + } + } + } + // Else, is directory + elseif ($file->isDir()) + { + // Create new DirectoryIterator + $files = new DirectoryIterator($file->getPathname()); + + // Iterate over each entry + while ($files->valid()) + { + // Extract the entry name + $name = $files->getFilename(); + + // If the name is not a dot + if ($name != '.' AND $name != '..' AND substr($file->getFilename(), 0, 1) == '.') + { + // Create new file resource + $fp = new SplFileInfo($files->getRealPath()); + // Delete the file + $this->_delete_file($fp); + } + + // Move the file pointer on + $files->next(); + } + + // If set to retain parent directory, return now + if ($retain_parent_directory) + { + return TRUE; + } + + try + { + // Remove the files iterator + // (fixes Windows PHP which has permission issues with open iterators) + unset($files); + + // Try to remove the parent directory + return rmdir($file->getRealPath()); + } + catch (ErrorException $e) + { + // 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())); + } + } + } + } + // Catch all exceptions + catch (Exception $e) + { + // If ignore_errors is on + if ($ignore_errors === TRUE) + { + // Return + return FALSE; + } + // Throw exception + throw $e; + } + } + + /** + * 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 + * @return string + */ + protected function _resolve_directory($filename) + { + return $this->_cache_dir->getRealPath().DIRECTORY_SEPARATOR.$filename[0].$filename[1].DIRECTORY_SEPARATOR; + } + + /** + * 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 + * @return SplFileInfo + * @throws Kohana_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)); + } + chmod($directory, $mode); + + 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 new file mode 100644 index 0000000..62c3148 --- /dev/null +++ b/includes/kohana/modules/cache/classes/kohana/cache/garbagecollect.php @@ -0,0 +1,23 @@ + array( // Default group + * 'driver' => 'memcache', // using Memcache driver + * 'servers' => array( // Available server definitions + * // First memcache server server + * array( + * 'host' => 'localhost', + * 'port' => 11211, + * 'persistent' => FALSE + * 'weight' => 1, + * 'timeout' => 1, + * 'retry_interval' => 15, + * 'status' => TRUE, + * 'instant_death' => TRUE, + * 'failure_callback' => array('className', 'classMethod') + * ), + * // Second memcache server + * array( + * 'host' => '192.168.1.5', + * 'port' => 22122, + * 'persistent' => TRUE + * ) + * ), + * '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__ + * port | __NO__ | (_integer_) Point to the port where memcached is listening for connections. Set this parameter to 0 when using UNIX domain sockets. Default __11211__ + * persistent | __NO__ | (_boolean_) Controls the use of a persistent connection. Default __TRUE__ + * weight | __NO__ | (_integer_) Number of buckets to create for this server which in turn control its probability of it being selected. The probability is relative to the total weight of all servers. Default __1__ + * timeout | __NO__ | (_integer_) Value in seconds which will be used for connecting to the daemon. Think twice before changing the default value of 1 second - you can lose all the advantages of caching if your connection is too slow. Default __1__ + * 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 + * @license http://kohanaphp.com/license + */ +class Kohana_Cache_Memcache extends Cache { + + // Memcache has a maximum cache lifetime of 30 days + const CACHE_CEILING = 2592000; + + /** + * Memcache resource + * + * @var Memcache + */ + protected $_memcache; + + /** + * Flags to use when storing values + * + * @var string + */ + protected $_flags; + + /** + * The default configuration for the memcached server + * + * @var array + */ + protected $_default_config = array(); + + /** + * Constructs the memcache Kohana_Cache object + * + * @param array configuration + * @throws Kohana_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'); + } + + parent::__construct($config); + + // Setup Memcache + $this->_memcache = new Memcache; + + // Load servers from configuration + $servers = Arr::get($this->_config, 'servers', NULL); + + if ( ! $servers) + { + // Throw an exception if no server found + throw new Kohana_Cache_Exception('No Memcache servers defined in configuration'); + } + + // Setup default server configuration + $this->_default_config = array( + 'host' => 'localhost', + 'port' => 11211, + 'persistent' => FALSE, + 'weight' => 1, + 'timeout' => 1, + 'retry_interval' => 15, + 'status' => TRUE, + 'instant_death' => TRUE, + 'failure_callback' => array($this, '_failed_request'), + ); + + // Add the memcache servers to the pool + foreach ($servers as $server) + { + // Merge the defined config with defaults + $server += $this->_default_config; + + 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'])); + } + } + + // Setup the flags + $this->_flags = Arr::get($this->_config, 'compression', FALSE) ? MEMCACHE_COMPRESSED : FALSE; + } + + /** + * 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 + * @return mixed + * @throws Kohana_Cache_Exception + */ + public function get($id, $default = NULL) + { + // Get the value from Memcache + $value = $this->_memcache->get($this->_sanitize_id($id)); + + // If the value wasn't found, normalise it + if ($value === FALSE) + { + $value = (NULL === $default) ? NULL : $default; + } + + // Return the value + return $value; + } + + /** + * 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)) + * { + * // Cache was set successfully + * return + * } + * + * @param string id of cache entry + * @param mixed data to set to cache + * @param integer lifetime in seconds, maximum value 2592000 + * @return boolean + */ + public function set($id, $data, $lifetime = 3600) + { + // If the lifetime is greater than the ceiling + if ($lifetime > Cache_Memcache::CACHE_CEILING) + { + // Set the lifetime to maximum cache time + $lifetime = Cache_Memcache::CACHE_CEILING + time(); + } + // Else if the lifetime is greater than zero + elseif ($lifetime > 0) + { + $lifetime += time(); + } + // Else + else + { + // Normalise the lifetime + $lifetime = 0; + } + + // Set the data to memcache + return $this->_memcache->set($this->_sanitize_id($id), $data, $this->_flags, $lifetime); + } + + /** + * 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 + * @return boolean + */ + public function delete($id, $timeout = 0) + { + // Delete the id + return $this->_memcache->delete($this->_sanitize_id($id), $timeout); + } + + /** + * 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(); + * + * @return boolean + */ + public function delete_all() + { + $result = $this->_memcache->flush(); + + // We must sleep after flushing, or overwriting will not work! + // @see http://php.net/manual/en/function.memcache-flush.php#81420 + sleep(1); + + return $result; + } + + /** + * Callback method for Memcache::failure_callback to use if any Memcache call + * 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 + * @return void|boolean + * @since 3.0.8 + */ + public function _failed_request($hostname, $port) + { + if ( ! $this->_config['instant_death']) + return; + + // Setup non-existent host + $host = FALSE; + + // Get host settings from configuration + foreach ($this->_config['servers'] as $server) + { + // Merge the defaults, since they won't always be set + $server += $this->_default_config; + // We're looking at the failed server + if ($hostname == $server['host'] and $port == $server['port']) + { + // Server to disable, since it failed + $host = $server; + continue; + } + } + + if ( ! $host) + return; + else + { + return $this->_memcache->setServerParams( + $host['host'], + $host['port'], + $host['timeout'], + $host['retry_interval'], + FALSE, // Server is offline + array($this, '_failed_request' + )); + } + } +} \ 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 new file mode 100644 index 0000000..866ab9b --- /dev/null +++ b/includes/kohana/modules/cache/classes/kohana/cache/memcachetag.php @@ -0,0 +1,76 @@ +_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'); + } + } + + /** + * Set a value based on an id with tags + * + * @param string id + * @param mixed data + * @param integer lifetime [Optional] + * @param array tags [Optional] + * @return boolean + */ + public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL) + { + $result = $this->set($id, $data, $lifetime); + + if ($result and $tags) + { + foreach ($tags as $tag) + { + $this->_memcache->tag_add($tag, $id); + } + } + + return $result; + } + + /** + * Delete cache entries based on a tag + * + * @param string tag + * @return boolean + */ + public function delete_tag($tag) + { + return $this->_memcache->tag_delete($tag); + } + + /** + * Find cache entries based on a tag + * + * @param string tag + * @return void + * @throws Kohana_Cache_Exception + */ + public function find($tag) + { + throw new Kohana_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 new file mode 100644 index 0000000..70045e5 --- /dev/null +++ b/includes/kohana/modules/cache/classes/kohana/cache/sqlite.php @@ -0,0 +1,336 @@ +_config, 'database', NULL); + + if ($database === NULL) + { + throw new Kohana_Cache_Exception('Database path not available in Kohana Cache configuration'); + } + + // Load new Sqlite DB + $this->_db = new PDO('sqlite:'.$database); + + // Test for existing DB + $result = $this->_db->query("SELECT * FROM sqlite_master WHERE name = 'caches' AND type = 'table'")->fetchAll(); + + // If there is no table, create a new one + if (0 == count($result)) + { + $database_schema = Arr::get($this->_config, 'schema', NULL); + + if ($database_schema === NULL) + { + throw new Kohana_Cache_Exception('Database schema not found in Kohana Cache configuration'); + } + + try + { + // Create the caches table + $this->_db->query(Arr::get($this->_config, 'schema', NULL)); + } + catch (PDOException $e) + { + throw new Kohana_Cache_Exception('Failed to create new SQLite caches table with the following error : :error', array(':error' => $e->getMessage())); + } + } + } + + /** + * Retrieve a value based on an id + * + * @param string id + * @param string default [Optional] Default value to return if id not found + * @return mixed + * @throws Kohana_Cache_Exception + */ + public function get($id, $default = NULL) + { + // Prepare statement + $statement = $this->_db->prepare('SELECT id, expiration, cache FROM caches WHERE id = :id LIMIT 0, 1'); + + // Try and load the cache based on id + try + { + $statement->execute(array(':id' => $this->_sanitize_id($id))); + } + catch (PDOException $e) + { + throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + } + + if ( ! $result = $statement->fetch(PDO::FETCH_OBJ)) + { + return $default; + } + + // If the cache has expired + if ($result->expiration != 0 and $result->expiration <= time()) + { + // Delete it and return default value + $this->delete($id); + return $default; + } + // Otherwise return cached object + else + { + // Disable notices for unserializing + $ER = error_reporting(~E_NOTICE); + + // Return the valid cache data + $data = unserialize($result->cache); + + // Turn notices back on + error_reporting($ER); + + // Return the resulting data + return $data; + } + } + + /** + * Set a value based on an id. Optionally add tags. + * + * @param string id + * @param mixed data + * @param integer lifetime [Optional] + * @return boolean + */ + public function set($id, $data, $lifetime = NULL) + { + return (bool) $this->set_with_tags($id, $data, $lifetime); + } + + /** + * Delete a cache entry based on id + * + * @param string id + * @param integer timeout [Optional] + * @return boolean + * @throws Kohana_Cache_Exception + */ + public function delete($id) + { + // Prepare statement + $statement = $this->_db->prepare('DELETE FROM caches WHERE id = :id'); + + // Remove the entry + try + { + $statement->execute(array(':id' => $this->_sanitize_id($id))); + } + catch (PDOException $e) + { + throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + } + + return (bool) $statement->rowCount(); + } + + /** + * Delete all cache entries + * + * @return boolean + */ + public function delete_all() + { + // Prepare statement + $statement = $this->_db->prepare('DELETE FROM caches'); + + // Remove the entry + try + { + $statement->execute(); + } + catch (PDOException $e) + { + throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + } + + return (bool) $statement->rowCount(); + } + + /** + * Set a value based on an id. Optionally add tags. + * + * @param string id + * @param mixed data + * @param integer lifetime [Optional] + * @param array tags [Optional] + * @return boolean + * @throws Kohana_Cache_Exception + */ + public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL) + { + // Serialize the data + $data = serialize($data); + + // Normalise tags + $tags = (NULL === $tags) ? NULL : ('<'.implode('>,<', $tags).'>'); + + // Setup lifetime + if ($lifetime === NULL) + { + $lifetime = (0 === Arr::get('default_expire', NULL)) ? 0 : (Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE) + time()); + } + else + { + $lifetime = (0 === $lifetime) ? 0 : ($lifetime + time()); + } + + // Prepare statement + // $this->exists() may throw Kohana_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 + try + { + $statement->execute(array(':id' => $this->_sanitize_id($id), ':cache' => $data, ':expiration' => $lifetime, ':tags' => $tags)); + } + catch (PDOException $e) + { + throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + } + + return (bool) $statement->rowCount(); + } + + /** + * Delete cache entries based on a tag + * + * @param string tag + * @param integer timeout [Optional] + * @return boolean + * @throws Kohana_Cache_Exception + */ + public function delete_tag($tag) + { + // Prepare the statement + $statement = $this->_db->prepare('DELETE FROM caches WHERE tags LIKE :tag'); + + // Try to delete + try + { + $statement->execute(array(':tag' => "%<{$tag}>%")); + } + catch (PDOException $e) + { + throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + } + + return (bool) $statement->rowCount(); + } + + /** + * Find cache entries based on a tag + * + * @param string tag + * @return array + * @throws Kohana_Cache_Exception + */ + public function find($tag) + { + // Prepare the statement + $statement = $this->_db->prepare('SELECT id, cache FROM caches WHERE tags LIKE :tag'); + + // Try to find + try + { + if ( ! $statement->execute(array(':tag' => "%<{$tag}>%"))) + { + return array(); + } + } + catch (PDOException $e) + { + throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage())); + } + + $result = array(); + + while ($row = $statement->fetchObject()) + { + // Disable notices for unserializing + $ER = error_reporting(~E_NOTICE); + + $result[$row->id] = unserialize($row->cache); + + // Turn notices back on + error_reporting($ER); + } + + return $result; + } + + /** + * Garbage collection method that cleans any expired + * cache entries from the cache. + * + * @return void + */ + public function garbage_collect() + { + // Create the sequel statement + $statement = $this->_db->prepare('DELETE FROM caches WHERE expiration < :expiration'); + + try + { + $statement->execute(array(':expiration' => time())); + } + catch (PDOException $e) + { + throw new Kohana_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 + * @return boolean + * @throws Kohana_Cache_Exception + */ + protected function exists($id) + { + $statement = $this->_db->prepare('SELECT id FROM caches WHERE id = :id'); + try + { + $statement->execute(array(':id' => $this->_sanitize_id($id))); + } + catch (PDOExeption $e) + { + throw new Kohana_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 new file mode 100644 index 0000000..001a224 --- /dev/null +++ b/includes/kohana/modules/cache/classes/kohana/cache/tagging.php @@ -0,0 +1,42 @@ + 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 + * * Windows Server 2008 with IIS 7.0 and FastCGI Module + * * Windows 7 with IIS 7.5 and FastCGI Module + * * 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 + * @license http://kohanaphp.com/license + */ +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 + */ + protected function __construct(array $config) + { + if ( ! extension_loaded('wincache')) + { + throw new Kohana_Cache_Exception('PHP wincache extension is not available.'); + } + + parent::__construct($config); + } + + /** + * 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 + * @return mixed + * @throws Kohana_Cache_Exception + */ + public function get($id, $default = NULL) + { + $data = wincache_ucache_get($this->_sanitize_id($id), $success); + + return $success ? $data : $default; + } + + /** + * 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 + * @return boolean + */ + public function set($id, $data, $lifetime = NULL) + { + if ($lifetime === NULL) + { + $lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE); + } + + return wincache_ucache_set($this->_sanitize_id($id), $data, $lifetime); + } + + /** + * 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 + * @return boolean + */ + public function delete($id) + { + return wincache_ucache_delete($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 wincache group + * Cache::instance('wincache')->delete_all(); + * + * @return boolean + */ + public function delete_all() + { + return wincache_ucache_clear(); + } +} diff --git a/includes/kohana/modules/cache/classes/kohana/cache/xcache.php b/includes/kohana/modules/cache/classes/kohana/cache/xcache.php new file mode 100644 index 0000000..133f18b --- /dev/null +++ b/includes/kohana/modules/cache/classes/kohana/cache/xcache.php @@ -0,0 +1,84 @@ +_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 new file mode 100644 index 0000000..9ec311c --- /dev/null +++ b/includes/kohana/modules/cache/config/cache.php @@ -0,0 +1,76 @@ + array + ( + 'driver' => 'memcache', + 'default_expire' => 3600, + 'compression' => FALSE, // Use Zlib compression (can cause issues with integers) + 'servers' => array + ( + array + ( + 'host' => 'localhost', // Memcache Server + 'port' => 11211, // Memcache port number + 'persistent' => FALSE, // Persistent connection + 'weight' => 1, + 'timeout' => 1, + 'retry_interval' => 15, + 'status' => TRUE, + ), + ), + 'instant_death' => TRUE, // Take server offline immediately on first fail (no retry) + ), + 'memcachetag' => array + ( + 'driver' => 'memcachetag', + 'default_expire' => 3600, + 'compression' => FALSE, // Use Zlib compression (can cause issues with integers) + 'servers' => array + ( + array + ( + 'host' => 'localhost', // Memcache Server + 'port' => 11211, // Memcache port number + 'persistent' => FALSE, // Persistent connection + 'weight' => 1, + 'timeout' => 1, + 'retry_interval' => 15, + 'status' => TRUE, + ), + ), + 'instant_death' => TRUE, + ), + 'apc' => array + ( + 'driver' => 'apc', + 'default_expire' => 3600, + ), + 'wincache' => array + ( + 'driver' => 'wincache', + 'default_expire' => 3600, + ), + '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 + ( + 'driver' => 'eaccelerator', + ), + 'xcache' => array + ( + 'driver' => 'xcache', + 'default_expire' => 3600, + ), + 'file' => array + ( + 'driver' => 'file', + 'cache_dir' => APPPATH.'cache', + 'default_expire' => 3600, + ) +); \ No newline at end of file diff --git a/includes/kohana/modules/cache/config/userguide.php b/includes/kohana/modules/cache/config/userguide.php new file mode 100644 index 0000000..524afc4 --- /dev/null +++ b/includes/kohana/modules/cache/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' + 'cache' => array( + + // Whether this modules userguide pages should be shown + 'enabled' => TRUE, + + // The name that should show up on the userguide index page + 'name' => 'Cache', + + // A short description of this module, shown on the index page + 'description' => 'Common interface for caching engines.', + + // Copyright message, shown in the footer for this module + 'copyright' => '© 2008–2010 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 new file mode 100644 index 0000000..8a8239b --- /dev/null +++ b/includes/kohana/modules/cache/guide/cache.usage.md @@ -0,0 +1,219 @@ +# Kohana Cache usage + +[Kohana_Cache] provides a simple interface allowing getting, setting and deleting of cached values. Two interfaces included in _Kohana Cache_ additionally provide _tagging_ and _garbage collection_ where they are supported by the respective drivers. + +## Getting a new cache instance + +Creating a new _Kohana Cache_ instance is simple, however it must be done using the [Cache::instance] method, rather than the traditional `new` constructor. + + // Create a new instance of cache using the default group + $cache = Cache::instance(); + +The default group will use whatever is set to [Cache::$default] and must have a corresponding [configuration](cache.config) definition for that group. + +To create a cache instance using a group other than the _default_, simply provide the group name as an argument. + + // Create a new instance of the memcache group + $memcache = Cache::instance('memcache'); + +If there is a cache instance already instantiated then you can get it directly from the class member. + + [!!] Beware that this can cause issues if you do not test for the instance before trying to access it. + + // Check for the existance of the cache driver + if (isset(Cache::$instances['memcache'])) + { + // Get the existing cache instance directly (faster) + $memcache = Cache::$instances['memcache']; + } + else + { + // Get the cache driver instance (slower) + $memcache = Cache::instance('memcache'); + } + +## Setting and getting variables to and from cache + +The cache library supports scalar and object values, utilising object serialization where required (or not supported by the caching engine). This means that the majority or objects can be cached without any modification. + + [!!] Serialisation does not work with resource handles, such as filesystem, curl or socket resources. + +### Setting a value to cache + +Setting a value to cache using the [Cache::set] method can be done in one of two ways; either using the Cache instance interface, which is good for atomic operations; or getting an instance and using that for multiple operations. + +The first example demonstrates how to quickly load and set a value to the default cache instance. + + // Create a cachable object + $object = new stdClass; + + // Set a property + $object->foo = 'bar'; + + // Cache the object using default group (quick interface) with default time (3600 seconds) + Cache::instance()->set('foo', $object); + +If multiple cache operations are required, it is best to assign an instance of Cache to a variable and use that as below. + + // Set the object using a defined group for a defined time period (30 seconds) + $memcache = Cache::instance('memcache'); + $memcache->set('foo', $object, 30); + +#### Setting a value with tags + +Certain cache drivers support setting values with tags. To set a value to cache with tags using the following interface. + + // Get a cache instance that supports tags + $memcache = Cache::instance('memcachetag'); + + // Test for tagging interface + if ($memcache instanceof Kohana_Cache_Tagging) + { + // Set a value with some tags for 30 seconds + $memcache->set('foo', $object, 30, array('snafu', 'stfu', 'fubar')); + } + // Otherwise set without tags + else + { + // Set a value for 30 seconds + $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. + +### Getting a value from cache + +Getting variables back from cache is achieved using the [Cache::get] method using a single key to identify the cache entry. + + // Retrieve a value from cache (quickly) + $object = Cache::instance()->get('foo'); + +In cases where the requested key is not available or the entry has expired, a default value will be returned (__NULL__ by default). It is possible to define the default value as the key is requested. + + // If the cache key is available (with default value set to FALSE) + if ($object = Cache::instance()->get('foo', FALSE)) + { + // Do something + } + else + { + // Do something else + } + +#### Getting values from cache using tags + +It is possible to retrieve values from cache grouped by tag, using the [Cache::find] method with drivers that support tagging. + + [!!] The __Memcachetag__ driver does not support the `Cache::find($tag)` interface and will throw an exception. + + // Get an instance of cache + $cache = Cache::instance('memcachetag'); + + // Wrap in a try/catch statement to gracefully handle memcachetag + try + { + // Find values based on tag + return $cache->find('snafu'); + } + catch (Kohana_Cache_Exception $e) + { + // Handle gracefully + return FALSE; + } + +### Deleting values from cache + +Deleting variables is very similar to the getting and setting methods already described. Deleting operations are split into three categories: + + - __Delete value by key__. Deletes a cached value by the associated key. + - __Delete all values__. Deletes all caches values stored in the cache instance. + - __Delete values by tag__. Deletes all values that have the supplied tag. This is only supported by Memcached-Tag and Sqlite. + +#### Delete value by key + +To delete a specific value by its associated key: + + // If the cache entry for 'foo' is deleted + if (Cache::instance()->delete('foo')) + { + // Cache entry successfully deleted, do something + } + +By default a `TRUE` value will be returned. However a `FALSE` value will be returned in instances where the key did not exist in the cache. + +#### Delete all values + +To delete all values in a specific instance: + + // If all cache items where deleted successfully + if (Cache::instance()->delete_all()) + { + // Do something + } + +It is also possible to delete all cache items in every instance: + + // For each cache instance + foreach (Cache::$instances as $group => $instance) + { + if ($instance->delete_all()) + { + var_dump('instance : '.$group.' has been flushed!'); + } + } + +#### Delete values by tag + +Some of the caching drivers support deleting by tag. This will remove all the cached values that are associated with a specific tag. Below is an example of how to robustly handle deletion by tag. + + // Get cache instance + $cache = Cache::instance(); + + // Check for tagging interface + if ($cache instanceof Kohana_Cache_Tagging) + { + // Delete all entries by the tag 'snafu' + $cache->delete_tag('snafu'); + } + +#### Garbage Collection + +Garbage Collection (GC) is the cleaning of expired cache entries. For the most part, caching engines will take care of garbage collection internally. However a few of the file based systems do not handle this task and in these circumstances it would be prudent to garbage collect at a predetermined frequency. If no garbage collection is executed, the resource storing the cache entries will eventually fill and become unusable. + +When not automated, garbage collection is the responsibility of the developer. It is prudent to have a GC probability value that dictates how likely the garbage collection routing will be run. An example of such a system is demonstrated below. + + // Get a cache instance + $cache_file = Cache::instance('file'); + + // Set a GC probability of 10% + $gc = 10; + + // If the GC probability is a hit + if (rand(0,99) <= $gc and $cache_file instanceof Kohana_Cache_GarbageCollect) + { + // Garbage Collect + $cache_file->garbage_collect(); + } + +# Interfaces + +Kohana Cache comes with two interfaces that are implemented where the drivers support them: + + - __[Kohana_Cache_Tagging] for tagging support on cache entries__ + - [Cache_MemcacheTag] + - [Cache_Sqlite] + - __[Kohana_Cache_GarbageCollect] for garbage collection with drivers without native support__ + - [Cache_File] + - [Cache_Sqlite] + +When using interface specific caching features, ensure that code checks for the required interface before using the methods supplied. The following example checks whether the garbage collection interface is available before calling the `garbage_collect` method. + + // Create a cache instance + $cache = Cache::instance(); + + // Test for Garbage Collection + if ($cache instanceof Kohana_Cache_GarbageCollect) + { + // Collect garbage + $cache->garbage_collect(); + } \ No newline at end of file diff --git a/includes/kohana/modules/cache/guide/cache/config.md b/includes/kohana/modules/cache/guide/cache/config.md new file mode 100644 index 0000000..a6d428f --- /dev/null +++ b/includes/kohana/modules/cache/guide/cache/config.md @@ -0,0 +1,168 @@ +# Kohana Cache configuration + +Kohana Cache uses configuration groups to create cache instances. A configuration group can +use any supported driver, with successive groups using multiple instances of the same driver type. + +The default cache group is loaded based on the `Cache::$default` setting. It is set to the `file` driver as standard, however this can be changed within the `/application/boostrap.php` file. + + // Change the default cache driver to memcache + Cache::$default = 'memcache'; + + // Load the memcache cache driver using default setting + $memcache = Cache::instance(); + +## Group settings + +Below are the default cache configuration groups for each supported driver. Add to- or override these settings +within the `application/config/cache.php` file. + +Name | Required | Description +-------------- | -------- | --------------------------------------------------------------- +driver | __YES__ | (_string_) The driver type to use +default_expire | __NO__ | (_string_) The driver type to use + + + 'file' => array + ( + 'driver' => 'file', + 'cache_dir' => APPPATH.'cache/.kohana_cache', + 'default_expire' => 3600, + ), + +## Memcache & Memcached-tag settings + +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 + +Name | Required | Description +---------------- | -------- | --------------------------------------------------------------- +host | __YES__ | (_string_) The host of the memcache server, i.e. __localhost__; or __127.0.0.1__; or __memcache.domain.tld__ +port | __NO__ | (_integer_) Point to the port where memcached is listening for connections. Set this parameter to 0 when using UNIX domain sockets. Default __11211__ +persistent | __NO__ | (_boolean_) Controls the use of a persistent connection. Default __TRUE__ +weight | __NO__ | (_integer_) Number of buckets to create for this server which in turn control its probability of it being selected. The probability is relative to the total weight of all servers. Default __1__ +timeout | __NO__ | (_integer_) Value in seconds which will be used for connecting to the daemon. Think twice before changing the default value of 1 second - you can lose all the advantages of caching if your connection is too slow. Default __1__ +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__ + + 'memcache' => array + ( + 'driver' => 'memcache', + 'default_expire' => 3600, + 'compression' => FALSE, // Use Zlib compression + (can cause issues with integers) + 'servers' => array + ( + array + ( + 'host' => 'localhost', // Memcache Server + 'port' => 11211, // Memcache port number + 'persistent' => FALSE, // Persistent connection + ), + ), + ), + 'memcachetag' => array + ( + 'driver' => 'memcachetag', + 'default_expire' => 3600, + 'compression' => FALSE, // Use Zlib compression + (can cause issues with integers) + 'servers' => array + ( + array + ( + 'host' => 'localhost', // Memcache Server + 'port' => 11211, // Memcache port number + 'persistent' => FALSE, // Persistent connection + ), + ), + ), + +## APC settings + + 'apc' => array + ( + 'driver' => 'apc', + 'default_expire' => 3600, + ), + +## SQLite settings + + '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 settings + + 'eaccelerator' array + ( + 'driver' => 'eaccelerator', + ), + +## Xcache settings + + 'xcache' => array + ( + 'driver' => 'xcache', + 'default_expire' => 3600, + ), + +## File settings + + 'file' => array + ( + 'driver' => 'file', + 'cache_dir' => 'cache/.kohana_cache', + '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`. + + array + ( + 'driver' => 'memcache', // Use Memcached as the default driver + 'default_expire' => 8000, // Overide default expiry + 'servers' => array + ( + // Add a new server + array + ( + 'host' => 'cache.domain.tld', + 'port' => 11211, + 'persistent' => FALSE + ) + ), + 'compression' => FALSE + ) + ); + +## Add new configuration group + +The following example demonstrates how to add a new configuration setting, using the config file in `/application/config/cache.php`. + + array + ( + '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/examples.md b/includes/kohana/modules/cache/guide/cache/examples.md new file mode 100644 index 0000000..e69de29 diff --git a/includes/kohana/modules/cache/guide/cache/index.md b/includes/kohana/modules/cache/guide/cache/index.md new file mode 100644 index 0000000..b93b11d --- /dev/null +++ b/includes/kohana/modules/cache/guide/cache/index.md @@ -0,0 +1,59 @@ +# About Kohana Cache + +[Kohana_Cache] provides a common interface to a variety of caching engines. [Kohana_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]) + +## Introduction to caching + +Caching should be implemented with consideration. Generally, caching the result of resources +is faster than reprocessing them. Choosing what, how and when to cache is vital. [PHP APC](http://php.net/manual/en/book.apc.php) is one of the fastest caching systems available, closely followed by [Memcached](http://memcached.org/). [SQLite](http://www.sqlite.org/) and File caching are two of the slowest cache methods, however usually faster than reprocessing +a complex set of instructions. + +Caching engines that use memory are considerably faster than file based alternatives. But +memory is limited whereas disk space is plentiful. If caching large datasets, such as large database result sets, it is best to use file caching. + + [!!] Cache drivers require the relevant PHP extensions to be installed. APC, eAccelerator, Memecached and Xcache all require non-standard PHP extensions. + +## What the Kohana Cache module does (and does not do) + +This module provides a simple abstracted interface to a wide selection of popular PHP cache engines. The caching API provides the basic caching methods implemented across all solutions, memory, network or disk based. Basic key / value storing is supported by all drivers, with additional tagging and garbage collection support where implemented or required. + +_Kohana Cache_ does not provide HTTP style caching for clients (web browsers) and/or proxies (_Varnish_, _Squid_). There are other Kohana modules that provide this functionality. + +## Choosing a cache provider + +Getting and setting values to cache is very simple when using the _Kohana Cache_ interface. The hardest choice is choosing which cache engine to use. When choosing a caching engine, the following criteria must be considered: + + 1. __Does the cache need to be distributed?__ + This is an important consideration as it will severely limit the options available to solutions such as Memcache when a distributed solution is required. + 2. __Does the cache need to be fast?__ + In almost all cases retrieving data from a cache is faster than execution. However generally memory based caching is considerably faster than disk based caching (see table below). + 3. __How much cache is required?__ + Cache is not endless, and memory based caches are subject to a considerably more limited storage resource. + +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 +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 +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. + +## Minimum requirements + + * Kohana 3.0.4 + * PHP 5.2.4 or greater \ No newline at end of file diff --git a/includes/kohana/modules/cache/guide/cache/menu.md b/includes/kohana/modules/cache/guide/cache/menu.md new file mode 100644 index 0000000..5218558 --- /dev/null +++ b/includes/kohana/modules/cache/guide/cache/menu.md @@ -0,0 +1,3 @@ +## [Cache]() +- [Configuration](config) +- [Usage](usage) \ 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 new file mode 100644 index 0000000..8a8239b --- /dev/null +++ b/includes/kohana/modules/cache/guide/cache/usage.md @@ -0,0 +1,219 @@ +# Kohana Cache usage + +[Kohana_Cache] provides a simple interface allowing getting, setting and deleting of cached values. Two interfaces included in _Kohana Cache_ additionally provide _tagging_ and _garbage collection_ where they are supported by the respective drivers. + +## Getting a new cache instance + +Creating a new _Kohana Cache_ instance is simple, however it must be done using the [Cache::instance] method, rather than the traditional `new` constructor. + + // Create a new instance of cache using the default group + $cache = Cache::instance(); + +The default group will use whatever is set to [Cache::$default] and must have a corresponding [configuration](cache.config) definition for that group. + +To create a cache instance using a group other than the _default_, simply provide the group name as an argument. + + // Create a new instance of the memcache group + $memcache = Cache::instance('memcache'); + +If there is a cache instance already instantiated then you can get it directly from the class member. + + [!!] Beware that this can cause issues if you do not test for the instance before trying to access it. + + // Check for the existance of the cache driver + if (isset(Cache::$instances['memcache'])) + { + // Get the existing cache instance directly (faster) + $memcache = Cache::$instances['memcache']; + } + else + { + // Get the cache driver instance (slower) + $memcache = Cache::instance('memcache'); + } + +## Setting and getting variables to and from cache + +The cache library supports scalar and object values, utilising object serialization where required (or not supported by the caching engine). This means that the majority or objects can be cached without any modification. + + [!!] Serialisation does not work with resource handles, such as filesystem, curl or socket resources. + +### Setting a value to cache + +Setting a value to cache using the [Cache::set] method can be done in one of two ways; either using the Cache instance interface, which is good for atomic operations; or getting an instance and using that for multiple operations. + +The first example demonstrates how to quickly load and set a value to the default cache instance. + + // Create a cachable object + $object = new stdClass; + + // Set a property + $object->foo = 'bar'; + + // Cache the object using default group (quick interface) with default time (3600 seconds) + Cache::instance()->set('foo', $object); + +If multiple cache operations are required, it is best to assign an instance of Cache to a variable and use that as below. + + // Set the object using a defined group for a defined time period (30 seconds) + $memcache = Cache::instance('memcache'); + $memcache->set('foo', $object, 30); + +#### Setting a value with tags + +Certain cache drivers support setting values with tags. To set a value to cache with tags using the following interface. + + // Get a cache instance that supports tags + $memcache = Cache::instance('memcachetag'); + + // Test for tagging interface + if ($memcache instanceof Kohana_Cache_Tagging) + { + // Set a value with some tags for 30 seconds + $memcache->set('foo', $object, 30, array('snafu', 'stfu', 'fubar')); + } + // Otherwise set without tags + else + { + // Set a value for 30 seconds + $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. + +### Getting a value from cache + +Getting variables back from cache is achieved using the [Cache::get] method using a single key to identify the cache entry. + + // Retrieve a value from cache (quickly) + $object = Cache::instance()->get('foo'); + +In cases where the requested key is not available or the entry has expired, a default value will be returned (__NULL__ by default). It is possible to define the default value as the key is requested. + + // If the cache key is available (with default value set to FALSE) + if ($object = Cache::instance()->get('foo', FALSE)) + { + // Do something + } + else + { + // Do something else + } + +#### Getting values from cache using tags + +It is possible to retrieve values from cache grouped by tag, using the [Cache::find] method with drivers that support tagging. + + [!!] The __Memcachetag__ driver does not support the `Cache::find($tag)` interface and will throw an exception. + + // Get an instance of cache + $cache = Cache::instance('memcachetag'); + + // Wrap in a try/catch statement to gracefully handle memcachetag + try + { + // Find values based on tag + return $cache->find('snafu'); + } + catch (Kohana_Cache_Exception $e) + { + // Handle gracefully + return FALSE; + } + +### Deleting values from cache + +Deleting variables is very similar to the getting and setting methods already described. Deleting operations are split into three categories: + + - __Delete value by key__. Deletes a cached value by the associated key. + - __Delete all values__. Deletes all caches values stored in the cache instance. + - __Delete values by tag__. Deletes all values that have the supplied tag. This is only supported by Memcached-Tag and Sqlite. + +#### Delete value by key + +To delete a specific value by its associated key: + + // If the cache entry for 'foo' is deleted + if (Cache::instance()->delete('foo')) + { + // Cache entry successfully deleted, do something + } + +By default a `TRUE` value will be returned. However a `FALSE` value will be returned in instances where the key did not exist in the cache. + +#### Delete all values + +To delete all values in a specific instance: + + // If all cache items where deleted successfully + if (Cache::instance()->delete_all()) + { + // Do something + } + +It is also possible to delete all cache items in every instance: + + // For each cache instance + foreach (Cache::$instances as $group => $instance) + { + if ($instance->delete_all()) + { + var_dump('instance : '.$group.' has been flushed!'); + } + } + +#### Delete values by tag + +Some of the caching drivers support deleting by tag. This will remove all the cached values that are associated with a specific tag. Below is an example of how to robustly handle deletion by tag. + + // Get cache instance + $cache = Cache::instance(); + + // Check for tagging interface + if ($cache instanceof Kohana_Cache_Tagging) + { + // Delete all entries by the tag 'snafu' + $cache->delete_tag('snafu'); + } + +#### Garbage Collection + +Garbage Collection (GC) is the cleaning of expired cache entries. For the most part, caching engines will take care of garbage collection internally. However a few of the file based systems do not handle this task and in these circumstances it would be prudent to garbage collect at a predetermined frequency. If no garbage collection is executed, the resource storing the cache entries will eventually fill and become unusable. + +When not automated, garbage collection is the responsibility of the developer. It is prudent to have a GC probability value that dictates how likely the garbage collection routing will be run. An example of such a system is demonstrated below. + + // Get a cache instance + $cache_file = Cache::instance('file'); + + // Set a GC probability of 10% + $gc = 10; + + // If the GC probability is a hit + if (rand(0,99) <= $gc and $cache_file instanceof Kohana_Cache_GarbageCollect) + { + // Garbage Collect + $cache_file->garbage_collect(); + } + +# Interfaces + +Kohana Cache comes with two interfaces that are implemented where the drivers support them: + + - __[Kohana_Cache_Tagging] for tagging support on cache entries__ + - [Cache_MemcacheTag] + - [Cache_Sqlite] + - __[Kohana_Cache_GarbageCollect] for garbage collection with drivers without native support__ + - [Cache_File] + - [Cache_Sqlite] + +When using interface specific caching features, ensure that code checks for the required interface before using the methods supplied. The following example checks whether the garbage collection interface is available before calling the `garbage_collect` method. + + // Create a cache instance + $cache = Cache::instance(); + + // Test for Garbage Collection + if ($cache instanceof Kohana_Cache_GarbageCollect) + { + // Collect garbage + $cache->garbage_collect(); + } \ No newline at end of file diff --git a/includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php b/includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php new file mode 100644 index 0000000..229e7d3 --- /dev/null +++ b/includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php @@ -0,0 +1,91 @@ +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/phpunit.xml b/includes/kohana/modules/cache/tests/phpunit.xml new file mode 100644 index 0000000..5e3b9c7 --- /dev/null +++ b/includes/kohana/modules/cache/tests/phpunit.xml @@ -0,0 +1,16 @@ + + + + + cache/ + + + diff --git a/includes/kohana/modules/codebench/classes/bench/arrcallback.php b/includes/kohana/modules/codebench/classes/bench/arrcallback.php new file mode 100644 index 0000000..698a6b8 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/arrcallback.php @@ -0,0 +1,57 @@ + + */ +class Bench_ArrCallback extends Codebench { + + public $description = + 'Parsing command[param,param] strings in Arr::callback(): + http://github.com/shadowhand/kohana/commit/c3aaae849164bf92a486e29e736a265b350cb4da#L0R127'; + + public $loops = 10000; + + public $subjects = array + ( + // Valid callback strings + 'foo', + 'foo::bar', + 'foo[apple,orange]', + 'foo::bar[apple,orange]', + '[apple,orange]', // no command, only params + 'foo[[apple],[orange]]', // params with brackets inside + + // Invalid callback strings + 'foo[apple,orange', // no closing bracket + ); + + public function bench_shadowhand($subject) + { + // The original regex we're trying to optimize + if (preg_match('/([^\[]*+)\[(.*)\]/', $subject, $match)) + return $match; + } + + public function bench_geert_regex_1($subject) + { + // Added ^ and $ around the whole pattern + if (preg_match('/^([^\[]*+)\[(.*)\]$/', $subject, $matches)) + return $matches; + } + + public function bench_geert_regex_2($subject) + { + // A rather experimental approach using \K which requires PCRE 7.2 ~ PHP 5.2.4 + // Note: $matches[0] = params, $matches[1] = command + if (preg_match('/^([^\[]*+)\[\K.*(?=\]$)/', $subject, $matches)) + return $matches; + } + + public function bench_geert_str($subject) + { + // A native string function approach which beats all the regexes + if (strpos($subject, '[') !== FALSE AND substr($subject, -1) === ']') + return explode('[', substr($subject, 0, -1), 2); + } +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/autolinkemails.php b/includes/kohana/modules/codebench/classes/bench/autolinkemails.php new file mode 100644 index 0000000..46e7a15 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/autolinkemails.php @@ -0,0 +1,70 @@ + + */ +class Bench_AutoLinkEmails extends Codebench { + + public $description = + 'Fixing #2772, and comparing some possibilities.'; + + public $loops = 1000; + + public $subjects = array + ( + '
                      +
                    • voorzitter@xxxx.com
                    • +
                    • vicevoorzitter@xxxx.com
                    • +
                    ', + ); + + // The original function, with str_replace replaced by preg_replace. Looks clean. + public function bench_match_all_loop($subject) + { + if (preg_match_all('~\b(?|58;)(?!\.)[-+_a-z0-9.]++(?|58;)(?!\.)[-+_a-z0-9.]++(?|58;)(?!\.)[-+_a-z0-9.]++(?|58;)(?!\.)[-+_a-z0-9.]++(? + */ +class Bench_DateSpan extends Codebench { + + public $description = + 'Optimization for Date::span().'; + + public $loops = 1000; + + public $subjects = array(); + + public function __construct() + { + parent::__construct(); + + $this->subjects = array( + time(), + time() - Date::MONTH, + time() - Date::YEAR, + time() - Date::YEAR * 10, + ); + } + + // Original method + public static function bench_span_original($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds') + { + // Array with the output formats + $output = preg_split('/[^a-z]+/', strtolower( (string) $output)); + + // Invalid output + if (empty($output)) + return FALSE; + + // Make the output values into keys + extract(array_flip($output), EXTR_SKIP); + + if ($local === NULL) + { + // Calculate the span from the current time + $local = time(); + } + + // Calculate timespan (seconds) + $timespan = abs($remote - $local); + + if (isset($years)) + { + $timespan -= Date::YEAR * ($years = (int) floor($timespan / Date::YEAR)); + } + + if (isset($months)) + { + $timespan -= Date::MONTH * ($months = (int) floor($timespan / Date::MONTH)); + } + + if (isset($weeks)) + { + $timespan -= Date::WEEK * ($weeks = (int) floor($timespan / Date::WEEK)); + } + + if (isset($days)) + { + $timespan -= Date::DAY * ($days = (int) floor($timespan / Date::DAY)); + } + + if (isset($hours)) + { + $timespan -= Date::HOUR * ($hours = (int) floor($timespan / Date::HOUR)); + } + + if (isset($minutes)) + { + $timespan -= Date::MINUTE * ($minutes = (int) floor($timespan / Date::MINUTE)); + } + + // Seconds ago, 1 + if (isset($seconds)) + { + $seconds = $timespan; + } + + // Remove the variables that cannot be accessed + unset($timespan, $remote, $local); + + // Deny access to these variables + $deny = array_flip(array('deny', 'key', 'difference', 'output')); + + // Return the difference + $difference = array(); + foreach ($output as $key) + { + if (isset($$key) AND ! isset($deny[$key])) + { + // Add requested key to the output + $difference[$key] = $$key; + } + } + + // Invalid output formats string + if (empty($difference)) + return FALSE; + + // If only one output format was asked, don't put it in an array + if (count($difference) === 1) + return current($difference); + + // Return array + return $difference; + } + + // Using an array for the output + public static function bench_span_use_array($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds') + { + // Array with the output formats + $output = preg_split('/[^a-z]+/', strtolower( (string) $output)); + + // Invalid output + if (empty($output)) + return FALSE; + + // Convert the list of outputs to an associative array + $output = array_combine($output, array_fill(0, count($output), 0)); + + // Make the output values into keys + extract(array_flip($output), EXTR_SKIP); + + if ($local === NULL) + { + // Calculate the span from the current time + $local = time(); + } + + // Calculate timespan (seconds) + $timespan = abs($remote - $local); + + if (isset($output['years'])) + { + $timespan -= Date::YEAR * ($output['years'] = (int) floor($timespan / Date::YEAR)); + } + + if (isset($output['months'])) + { + $timespan -= Date::MONTH * ($output['months'] = (int) floor($timespan / Date::MONTH)); + } + + if (isset($output['weeks'])) + { + $timespan -= Date::WEEK * ($output['weeks'] = (int) floor($timespan / Date::WEEK)); + } + + if (isset($output['days'])) + { + $timespan -= Date::DAY * ($output['days'] = (int) floor($timespan / Date::DAY)); + } + + if (isset($output['hours'])) + { + $timespan -= Date::HOUR * ($output['hours'] = (int) floor($timespan / Date::HOUR)); + } + + if (isset($output['minutes'])) + { + $timespan -= Date::MINUTE * ($output['minutes'] = (int) floor($timespan / Date::MINUTE)); + } + + // Seconds ago, 1 + if (isset($output['seconds'])) + { + $output['seconds'] = $timespan; + } + + if (count($output) === 1) + { + // Only a single output was requested, return it + return array_pop($output); + } + + // Return array + return $output; + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/explodelimit.php b/includes/kohana/modules/codebench/classes/bench/explodelimit.php new file mode 100644 index 0000000..4bf2acc --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/explodelimit.php @@ -0,0 +1,34 @@ + + */ +class Bench_ExplodeLimit extends Codebench { + + public $description = + 'Having a look at the effect of adding a limit to the explode function.
                    + http://stackoverflow.com/questions/1308149/how-to-get-a-part-of-url-between-4th-and-5th-slashes'; + + public $loops = 10000; + + public $subjects = array + ( + 'http://example.com/articles/123a/view', + 'http://example.com/articles/123a/view/x/x/x/x/x', + 'http://example.com/articles/123a/view/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x', + ); + + public function bench_explode_without_limit($subject) + { + $parts = explode('/', $subject); + return $parts[4]; + } + + public function bench_explode_with_limit($subject) + { + $parts = explode('/', $subject, 6); + return $parts[4]; + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/gruberurl.php b/includes/kohana/modules/codebench/classes/bench/gruberurl.php new file mode 100644 index 0000000..af23975 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/gruberurl.php @@ -0,0 +1,61 @@ + + */ +class Bench_GruberURL extends Codebench { + + public $description = + 'Optimization for http://daringfireball.net/2009/11/liberal_regex_for_matching_urls'; + + public $loops = 10000; + + public $subjects = array + ( + 'http://foo.com/blah_blah', + 'http://foo.com/blah_blah/', + '(Something like http://foo.com/blah_blah)', + 'http://foo.com/blah_blah_(wikipedia)', + '(Something like http://foo.com/blah_blah_(wikipedia))', + 'http://foo.com/blah_blah.', + 'http://foo.com/blah_blah/.', + '', + '', + 'http://foo.com/blah_blah,', + 'http://www.example.com/wpstyle/?p=364.', + 'http://✪df.ws/e7l', + 'rdar://1234', + 'rdar:/1234', + 'x-yojimbo-item://6303E4C1-xxxx-45A6-AB9D-3A908F59AE0E', + 'message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e', + 'http://➡.ws/䨹', + 'www.➡.ws/䨹', + 'http://example.com', + 'Just a www.example.com link.', + // To test the use of possessive quatifiers: + 'httpppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp', + ); + + public function bench_daringfireball($subject) + { + // Original regex by John Gruber + preg_match('~\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))~', $subject, $matches); + return (empty($matches)) ? FALSE : $matches[0]; + } + + public function bench_daringfireball_v2($subject) + { + // Removed outer capturing parentheses, made another pair non-capturing + preg_match('~\b(?:[\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|(?:[^[:punct:]\s]|/))~', $subject, $matches); + return (empty($matches)) ? FALSE : $matches[0]; + } + + public function bench_daringfireball_v3($subject) + { + // Made quantifiers possessive where possible + preg_match('~\b(?:[\w-]++://?+|www[.])[^\s()<>]+(?:\([\w\d]++\)|(?:[^[:punct:]\s]|/))~', $subject, $matches); + return (empty($matches)) ? FALSE : $matches[0]; + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/ltrimdigits.php b/includes/kohana/modules/codebench/classes/bench/ltrimdigits.php new file mode 100644 index 0000000..71ead49 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/ltrimdigits.php @@ -0,0 +1,28 @@ + + */ +class Bench_LtrimDigits extends Codebench { + + public $description = 'Chopping off leading digits: regex vs ltrim.'; + + public $loops = 100000; + + public $subjects = array + ( + '123digits', + 'no-digits', + ); + + public function bench_regex($subject) + { + return preg_replace('/^\d+/', '', $subject); + } + + public function bench_ltrim($subject) + { + return ltrim($subject, '0..9'); + } +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/mddobaseurl.php b/includes/kohana/modules/codebench/classes/bench/mddobaseurl.php new file mode 100644 index 0000000..1ad2a1b --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/mddobaseurl.php @@ -0,0 +1,66 @@ + + */ +class Bench_MDDoBaseURL extends Codebench { + + public $description = + 'Optimization for the doBaseURL() method of Kohana_Kodoc_Markdown + for the Kohana Userguide.'; + + public $loops = 10000; + + public $subjects = array + ( + // Valid matches + '[filesystem](about.filesystem)', + '[filesystem](about.filesystem "Optional title")', + '[same page link](#id)', + '[object oriented](http://wikipedia.org/wiki/Object-Oriented_Programming)', + + // Invalid matches + '![this is image syntax](about.filesystem)', + '[filesystem](about.filesystem', + ); + + public function bench_original($subject) + { + // The original regex contained a bug, which is fixed here for benchmarking purposes. + // At the very start of the regex, (?!!) has been replace by (? + */ +class Bench_MDDoImageURL extends Codebench { + + public $description = + 'Optimization for the doImageURL() method of Kohana_Kodoc_Markdown + for the Kohana Userguide.'; + + public $loops = 10000; + + public $subjects = array + ( + // Valid matches + '![Alt text](http://img.skitch.com/20091019-rud5mmqbf776jwua6hx9nm1n.png)', + '![Alt text](https://img.skitch.com/20091019-rud5mmqbf776jwua6hx9nm1n.png)', + '![Alt text](otherprotocol://image.png "Optional title")', + '![Alt text](img/install.png "Optional title")', + '![Alt text containing [square] brackets](img/install.png)', + '![Empty src]()', + + // Invalid matches + '![Alt text](img/install.png "No closing parenthesis"', + ); + + public function bench_original($subject) + { + return preg_replace_callback('~!\[(.+?)\]\((\S*(?:\s*".+?")?)\)~', array($this, '_add_image_url_original'), $subject); + } + protected function _add_image_url_original($matches) + { + if ($matches[2] AND strpos($matches[2], '://') === FALSE) + { + // Add the base url to the link URL + $matches[2] = 'http://BASE/'.$matches[2]; + } + + // Recreate the link + return "![{$matches[1]}]({$matches[2]})"; + } + + public function bench_optimized_callback($subject) + { + // Moved the check for "://" to the regex, simplifying the callback function + return preg_replace_callback('~!\[(.+?)\]\((?!\w++://)(\S*(?:\s*+".+?")?)\)~', array($this, '_add_image_url_optimized'), $subject); + } + protected function _add_image_url_optimized($matches) + { + // Add the base url to the link URL + $matches[2] = 'http://BASE/'.$matches[2]; + + // Recreate the link + return "![{$matches[1]}]({$matches[2]})"; + } + + public function bench_callback_gone($subject) + { + // All the optimized callback was doing now, is prepend some text to the URL. + // We don't need a callback for that, and that should be clearly faster. + return preg_replace('~(!\[.+?\]\()(?!\w++://)(\S*(?:\s*+".+?")?\))~', '$1http://BASE/$2', $subject); + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/mddoincludeviews.php b/includes/kohana/modules/codebench/classes/bench/mddoincludeviews.php new file mode 100644 index 0000000..9cac3d6 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/mddoincludeviews.php @@ -0,0 +1,50 @@ + + */ +class Bench_MDDoIncludeViews extends Codebench { + + public $description = + 'Optimization for the doIncludeViews() method of Kohana_Kodoc_Markdown + for the Kohana Userguide.'; + + public $loops = 10000; + + public $subjects = array + ( + // Valid matches + '{{one}} two {{three}}', + '{{userguide/examples/hello_world_error}}', + + // Invalid matches + '{}', + '{{}}', + '{{userguide/examples/hello_world_error}', + '{{userguide/examples/hello_world_error }}', + '{{userguide/examples/{{hello_world_error }}', + ); + + public function bench_original($subject) + { + preg_match_all('/{{(\S+?)}}/m', $subject, $matches, PREG_SET_ORDER); + return $matches; + } + + public function bench_possessive($subject) + { + // Using a possessive character class + // Removed useless /m modifier + preg_match_all('/{{([^\s{}]++)}}/', $subject, $matches, PREG_SET_ORDER); + return $matches; + } + + public function bench_lookaround($subject) + { + // Using lookaround to move $mathes[1] into $matches[0] + preg_match_all('/(?<={{)[^\s{}]++(?=}})/', $subject, $matches, PREG_SET_ORDER); + return $matches; + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/stripnullbytes.php b/includes/kohana/modules/codebench/classes/bench/stripnullbytes.php new file mode 100644 index 0000000..4d28853 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/stripnullbytes.php @@ -0,0 +1,37 @@ + + */ +class Bench_StripNullBytes extends Codebench { + + public $description = + 'String replacement comparisons related to #2676.'; + + public $loops = 1000; + + public $subjects = array + ( + "\0", + "\0\0\0\0\0\0\0\0\0\0", + "bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla", + "blablablablablablablablablablablablablablablabla", + ); + + public function bench_str_replace($subject) + { + return str_replace("\0", '', $subject); + } + + public function bench_strtr($subject) + { + return strtr($subject, array("\0" => '')); + } + + public function bench_preg_replace($subject) + { + return preg_replace('~\0+~', '', $subject); + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/transliterate.php b/includes/kohana/modules/codebench/classes/bench/transliterate.php new file mode 100644 index 0000000..aff8693 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/transliterate.php @@ -0,0 +1,65 @@ + + */ +class Bench_Transliterate extends Codebench { + + public $description = + 'Inspired by: + http://forum.kohanaframework.org/comments.php?DiscussionID=6113'; + + public $loops = 10; + + public $subjects = array + ( + // ASCII + 'a', 'b', 'c', 'd', '1', '2', '3', + + // Non-ASCII + 'à', 'ô', 'ď', 'ḟ', 'ë', 'š', 'ơ', + 'ß', 'ă', 'ř', 'ț', 'ň', 'ā', 'ķ', + 'ŝ', 'ỳ', 'ņ', 'ĺ', 'ħ', 'ṗ', 'ó', + 'ú', 'ě', 'é', 'ç', 'ẁ', 'ċ', 'õ', + 'ṡ', 'ø', 'ģ', 'ŧ', 'ș', 'ė', 'ĉ', + 'ś', 'î', 'ű', 'ć', 'ę', 'ŵ', 'ṫ', + 'ū', 'č', 'ö', 'è', 'ŷ', 'ą', 'ł', + 'ų', 'ů', 'ş', 'ğ', 'ļ', 'ƒ', 'ž', + 'ẃ', 'ḃ', 'å', 'ì', 'ï', 'ḋ', 'ť', + 'ŗ', 'ä', 'í', 'ŕ', 'ê', 'ü', 'ò', + 'ē', 'ñ', 'ń', 'ĥ', 'ĝ', 'đ', 'ĵ', + 'ÿ', 'ũ', 'ŭ', 'ư', 'ţ', 'ý', 'ő', + 'â', 'ľ', 'ẅ', 'ż', 'ī', 'ã', 'ġ', + 'ṁ', 'ō', 'ĩ', 'ù', 'į', 'ź', 'á', + 'û', 'þ', 'ð', 'æ', 'µ', 'ĕ', 'ı', + 'À', 'Ô', 'Ď', 'Ḟ', 'Ë', 'Š', 'Ơ', + 'Ă', 'Ř', 'Ț', 'Ň', 'Ā', 'Ķ', 'Ĕ', + 'Ŝ', 'Ỳ', 'Ņ', 'Ĺ', 'Ħ', 'Ṗ', 'Ó', + 'Ú', 'Ě', 'É', 'Ç', 'Ẁ', 'Ċ', 'Õ', + 'Ṡ', 'Ø', 'Ģ', 'Ŧ', 'Ș', 'Ė', 'Ĉ', + 'Ś', 'Î', 'Ű', 'Ć', 'Ę', 'Ŵ', 'Ṫ', + 'Ū', 'Č', 'Ö', 'È', 'Ŷ', 'Ą', 'Ł', + 'Ų', 'Ů', 'Ş', 'Ğ', 'Ļ', 'Ƒ', 'Ž', + 'Ẃ', 'Ḃ', 'Å', 'Ì', 'Ï', 'Ḋ', 'Ť', + 'Ŗ', 'Ä', 'Í', 'Ŕ', 'Ê', 'Ü', 'Ò', + 'Ē', 'Ñ', 'Ń', 'Ĥ', 'Ĝ', 'Đ', 'Ĵ', + 'Ÿ', 'Ũ', 'Ŭ', 'Ư', 'Ţ', 'Ý', 'Ő', + 'Â', 'Ľ', 'Ẅ', 'Ż', 'Ī', 'Ã', 'Ġ', + 'Ṁ', 'Ō', 'Ĩ', 'Ù', 'Į', 'Ź', 'Á', + 'Û', 'Þ', 'Ð', 'Æ', 'İ', + ); + + public function bench_utf8($subject) + { + return UTF8::transliterate_to_ascii($subject); + } + + public function bench_iconv($subject) + { + // Note: need to suppress errors on iconv because some chars trigger the following notice: + // "Detected an illegal character in input string" + return preg_replace('~[^-a-z0-9]+~i', '', @iconv('UTF-8', 'ASCII//TRANSLIT', $subject)); + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/urlsite.php b/includes/kohana/modules/codebench/classes/bench/urlsite.php new file mode 100644 index 0000000..0db347d --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/urlsite.php @@ -0,0 +1,123 @@ + + */ +class Bench_URLSite extends Codebench { + + public $description = 'http://dev.kohanaframework.org/issues/3110'; + + public $loops = 1000; + + public $subjects = array + ( + '', + 'news', + 'news/', + '/news/', + 'news/page/5', + 'news/page:5', + 'http://example.com/', + 'http://example.com/hello', + 'http://example.com:80/', + 'http://user:pass@example.com/', + ); + + public function __construct() + { + foreach ($this->subjects as $subject) + { + // Automatically create URIs with query string and/or fragment part appended + $this->subjects[] = $subject.'?query=string'; + $this->subjects[] = $subject.'#fragment'; + $this->subjects[] = $subject.'?query=string#fragment'; + } + + parent::__construct(); + } + + public function bench_original($uri) + { + // Get the path from the URI + $path = trim(parse_url($uri, PHP_URL_PATH), '/'); + + if ($query = parse_url($uri, PHP_URL_QUERY)) + { + $query = '?'.$query; + } + + if ($fragment = parse_url($uri, PHP_URL_FRAGMENT)) + { + $fragment = '#'.$fragment; + } + + return $path.$query.$fragment; + } + + public function bench_explode($uri) + { + // Chop off possible scheme, host, port, user and pass parts + $path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/')); + + $fragment = ''; + $explode = explode('#', $path, 2); + if (isset($explode[1])) + { + $path = $explode[0]; + $fragment = '#'.$explode[1]; + } + + $query = ''; + $explode = explode('?', $path, 2); + if (isset($explode[1])) + { + $path = $explode[0]; + $query = '?'.$explode[1]; + } + + return $path.$query.$fragment; + } + + public function bench_regex($uri) + { + preg_match('~^(?:[-a-z0-9+.]++://[^/]++/?)?([^?#]++)?(\?[^#]*+)?(#.*)?~', trim($uri, '/'), $matches); + $path = Arr::get($matches, 1, ''); + $query = Arr::get($matches, 2, ''); + $fragment = Arr::get($matches, 3, ''); + + return $path.$query.$fragment; + } + + public function bench_regex_without_arrget($uri) + { + preg_match('~^(?:[-a-z0-9+.]++://[^/]++/?)?([^?#]++)?(\?[^#]*+)?(#.*)?~', trim($uri, '/'), $matches); + $path = isset($matches[1]) ? $matches[1] : ''; + $query = isset($matches[2]) ? $matches[2] : ''; + $fragment = isset($matches[3]) ? $matches[3] : ''; + + return $path.$query.$fragment; + } + + // And then I thought, why do all the work of extracting the query and fragment parts and then reappending them? + // Just leaving them alone should be fine, right? As a bonus we get a very nice speed boost. + public function bench_less_is_more($uri) + { + // Chop off possible scheme, host, port, user and pass parts + $path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/')); + + return $path; + } + + public function bench_less_is_more_with_strpos_optimization($uri) + { + if (strpos($uri, '://') !== FALSE) + { + // Chop off possible scheme, host, port, user and pass parts + $uri = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/')); + } + + return $uri; + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/userfuncarray.php b/includes/kohana/modules/codebench/classes/bench/userfuncarray.php new file mode 100644 index 0000000..f53d0c6 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/userfuncarray.php @@ -0,0 +1,58 @@ + + */ +class Bench_UserFuncArray extends Codebench { + + public $description = + 'Testing the speed difference of using call_user_func_array + compared to counting args and doing manual calls.'; + + public $loops = 100000; + + public $subjects = array + ( + // Argument sets + array(), + array('one'), + array('one', 'two'), + array('one', 'two', 'three'), + ); + + public function bench_count_args($args) + { + $name = 'callme'; + switch (count($args)) + { + case 1: + $this->$name($args[0]); + break; + case 2: + $this->$name($args[0], $args[1]); + break; + case 3: + $this->$name($args[0], $args[1], $args[2]); + break; + case 4: + $this->$name($args[0], $args[1], $args[2], $args[3]); + break; + default: + call_user_func_array(array($this, $name), $args); + break; + } + } + + public function bench_direct_call($args) + { + $name = 'callme'; + call_user_func_array(array($this, $name), $args); + } + + protected function callme() + { + return count(func_get_args()); + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/validcolor.php b/includes/kohana/modules/codebench/classes/bench/validcolor.php new file mode 100644 index 0000000..8d04608 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/validcolor.php @@ -0,0 +1,116 @@ + + */ +class Bench_ValidColor extends Codebench { + + public $description = + 'Optimization for Validate::color(). + See: http://forum.kohanaphp.com/comments.php?DiscussionID=2192. + + Note that the methods with an _invalid suffix contain flawed regexes and should be + completely discarded. I left them in here for educational purposes, and to remind myself + to think harder and test more thoroughly. It can\'t be that I only found out so late in + the game. For the regex explanation have a look at the forum topic mentioned earlier.'; + + public $loops = 10000; + + public $subjects = array + ( + // Valid colors + 'aaA', + '123', + '000000', + '#123456', + '#abcdef', + + // Invalid colors + 'ggg', + '1234', + '#1234567', + "#000\n", + '}§è!çà%$z', + ); + + // Note that I added the D modifier to corey's regexes. We need to match exactly + // the same if we want the benchmarks to be of any value. + public function bench_corey_regex_1_invalid($subject) + { + return (bool) preg_match('/^#?([0-9a-f]{1,2}){3}$/iD', $subject); + } + + public function bench_corey_regex_2($subject) + { + return (bool) preg_match('/^#?([0-9a-f]){3}(([0-9a-f]){3})?$/iD', $subject); + } + + // Optimized corey_regex_1 + // Using non-capturing parentheses and a possessive interval + public function bench_geert_regex_1a_invalid($subject) + { + return (bool) preg_match('/^#?(?:[0-9a-f]{1,2}+){3}$/iD', $subject); + } + + // Optimized corey_regex_2 + // Removed useless parentheses, made the remaining ones non-capturing + public function bench_geert_regex_2a($subject) + { + return (bool) preg_match('/^#?[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject); + } + + // Optimized geert_regex_1a + // Possessive "#" + public function bench_geert_regex_1b_invalid($subject) + { + return (bool) preg_match('/^#?+(?:[0-9a-f]{1,2}+){3}$/iD', $subject); + } + + // Optimized geert_regex_2a + // Possessive "#" + public function bench_geert_regex_2b($subject) + { + return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject); + } + + // Using \z instead of $ + public function bench_salathe_regex_1($subject) + { + return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject); + } + + // Using \A instead of ^ + public function bench_salathe_regex_2($subject) + { + return (bool) preg_match('/\A#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject); + } + + // A solution without regex + public function bench_geert_str($subject) + { + if ($subject[0] === '#') + { + $subject = substr($subject, 1); + } + + $strlen = strlen($subject); + return (($strlen === 3 OR $strlen === 6) AND ctype_xdigit($subject)); + } + + // An ugly, but fast, solution without regex + public function bench_salathe_str($subject) + { + if ($subject[0] === '#') + { + $subject = substr($subject, 1); + } + + // TRUE if: + // 1. $subject is 6 or 3 chars long + // 2. $subject contains only hexadecimal digits + return (((isset($subject[5]) AND ! isset($subject[6])) OR + (isset($subject[2]) AND ! isset($subject[3]))) + AND ctype_xdigit($subject)); + } +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/bench/validurl.php b/includes/kohana/modules/codebench/classes/bench/validurl.php new file mode 100644 index 0000000..3c88675 --- /dev/null +++ b/includes/kohana/modules/codebench/classes/bench/validurl.php @@ -0,0 +1,105 @@ + + */ +class Bench_ValidURL extends Codebench { + + public $description = + 'filter_var vs regex: + http://dev.kohanaframework.org/issues/2847'; + + public $loops = 1000; + + public $subjects = array + ( + // Valid + 'http://google.com', + 'http://google.com/', + 'http://google.com/?q=abc', + 'http://google.com/#hash', + 'http://localhost', + 'http://hello-world.pl', + 'http://hello--world.pl', + 'http://h.e.l.l.0.pl', + 'http://server.tld/get/info', + 'http://127.0.0.1', + 'http://127.0.0.1:80', + 'http://user@127.0.0.1', + 'http://user:pass@127.0.0.1', + 'ftp://my.server.com', + 'rss+xml://rss.example.com', + + // Invalid + 'http://google.2com', + 'http://google.com?q=abc', + 'http://google.com#hash', + 'http://hello-.pl', + 'http://hel.-lo.world.pl', + 'http://ww£.google.com', + 'http://127.0.0.1234', + 'http://127.0.0.1.1', + 'http://user:@127.0.0.1', + "http://finalnewline.com\n", + ); + + public function bench_filter_var($url) + { + return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED); + } + + public function bench_regex($url) + { + // Based on http://www.apps.ietf.org/rfc/rfc1738.html#sec-5 + if ( ! preg_match( + '~^ + + # scheme + [-a-z0-9+.]++:// + + # username:password (optional) + (?: + [-a-z0-9$_.+!*\'(),;?&=%]++ # username + (?::[-a-z0-9$_.+!*\'(),;?&=%]++)? # password (optional) + @ + )? + + (?: + # ip address + \d{1,3}+(?:\.\d{1,3}+){3}+ + + | # or + + # hostname (captured) + ( + (?!-)[-a-z0-9]{1,63}+(? 253) + return FALSE; + + // An extra check for the top level domain + // It must start with a letter + $tld = ltrim(substr($matches[1], (int) strrpos($matches[1], '.')), '.'); + return ctype_alpha($tld[0]); + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/codebench/classes/codebench.php b/includes/kohana/modules/codebench/classes/codebench.php new file mode 100644 index 0000000..0f4c55c --- /dev/null +++ b/includes/kohana/modules/codebench/classes/codebench.php @@ -0,0 +1,3 @@ +request->redirect('codebench/'.trim($_POST['class'])); + } + + // Pass the class name on to the view + $this->template->class = (string) $class; + + // Try to load the class, then run it + if (Kohana::auto_load($class) === TRUE) + { + $codebench = new $class; + $this->template->codebench = $codebench->run(); + } + } +} diff --git a/includes/kohana/modules/codebench/classes/kohana/codebench.php b/includes/kohana/modules/codebench/classes/kohana/codebench.php new file mode 100644 index 0000000..68601bd --- /dev/null +++ b/includes/kohana/modules/codebench/classes/kohana/codebench.php @@ -0,0 +1,217 @@ + 'A', + 150 => 'B', + 200 => 'C', + 300 => 'D', + 500 => 'E', + 'default' => 'F', + ); + + /** + * Constructor. + * + * @return void + */ + public function __construct() + { + // Set the maximum execution time + set_time_limit(Kohana::config('codebench')->max_execution_time); + } + + /** + * Runs Codebench on the extending class. + * + * @return array benchmark output + */ + public function run() + { + // Array of all methods to loop over + $methods = array_filter(get_class_methods($this), array($this, '_method_filter')); + + // Make sure the benchmark runs at least once, + // also if no subject data has been provided. + if (empty($this->subjects)) + { + $this->subjects = array('NULL' => NULL); + } + + // Initialize benchmark output + $codebench = array + ( + 'class' => get_class($this), + 'description' => $this->description, + 'loops' => array + ( + 'base' => (int) $this->loops, + 'total' => (int) $this->loops * count($this->subjects) * count($methods), + ), + 'subjects' => $this->subjects, + 'benchmarks' => array(), + ); + + // Benchmark each method + foreach ($methods as $method) + { + // Initialize benchmark output for this method + $codebench['benchmarks'][$method] = array('time' => 0, 'memory' => 0); + + // Using Reflection because simply calling $this->$method($subject) in the loop below + // results in buggy benchmark times correlating to the length of the method name. + $reflection = new ReflectionMethod(get_class($this), $method); + + // Benchmark each subject on each method + foreach ($this->subjects as $subject_key => $subject) + { + // Prerun each method/subject combo before the actual benchmark loop. + // This way relatively expensive initial processes won't be benchmarked, e.g. autoloading. + // At the same time we capture the return here so we don't have to do that in the loop anymore. + $return = $reflection->invoke($this, $subject); + + // Start the timer for one subject + $token = Profiler::start('codebench', $method.$subject_key); + + // The heavy work + for ($i = 0; $i < $this->loops; ++$i) + { + $reflection->invoke($this, $subject); + } + + // Stop and read the timer + $benchmark = Profiler::total($token); + + // Benchmark output specific to the current method and subject + $codebench['benchmarks'][$method]['subjects'][$subject_key] = array + ( + 'return' => $return, + 'time' => $benchmark[0], + 'memory' => $benchmark[1], + ); + + // Update method totals + $codebench['benchmarks'][$method]['time'] += $benchmark[0]; + $codebench['benchmarks'][$method]['memory'] += $benchmark[1]; + } + } + + // Initialize the fastest and slowest benchmarks for both methods and subjects, time and memory, + // these values will be overwritten using min() and max() later on. + // The 999999999 values look like a hack, I know, but they work, + // unless your method runs for more than 31 years or consumes over 1GB of memory. + $fastest_method = $fastest_subject = array('time' => 999999999, 'memory' => 999999999); + $slowest_method = $slowest_subject = array('time' => 0, 'memory' => 0); + + // Find the fastest and slowest benchmarks, needed for the percentage calculations + foreach ($methods as $method) + { + // Update the fastest and slowest method benchmarks + $fastest_method['time'] = min($fastest_method['time'], $codebench['benchmarks'][$method]['time']); + $fastest_method['memory'] = min($fastest_method['memory'], $codebench['benchmarks'][$method]['memory']); + $slowest_method['time'] = max($slowest_method['time'], $codebench['benchmarks'][$method]['time']); + $slowest_method['memory'] = max($slowest_method['memory'], $codebench['benchmarks'][$method]['memory']); + + foreach ($this->subjects as $subject_key => $subject) + { + // Update the fastest and slowest subject benchmarks + $fastest_subject['time'] = min($fastest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']); + $fastest_subject['memory'] = min($fastest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']); + $slowest_subject['time'] = max($slowest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']); + $slowest_subject['memory'] = max($slowest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']); + } + } + + // Percentage calculations for methods + foreach ($codebench['benchmarks'] as & $method) + { + // Calculate percentage difference relative to fastest and slowest methods + $method['percent']['fastest']['time'] = (empty($fastest_method['time'])) ? 0 : ($method['time'] / $fastest_method['time'] * 100); + $method['percent']['fastest']['memory'] = (empty($fastest_method['memory'])) ? 0 : ($method['memory'] / $fastest_method['memory'] * 100); + $method['percent']['slowest']['time'] = (empty($slowest_method['time'])) ? 0 : ($method['time'] / $slowest_method['time'] * 100); + $method['percent']['slowest']['memory'] = (empty($slowest_method['memory'])) ? 0 : ($method['memory'] / $slowest_method['memory'] * 100); + + // Assign a grade for time and memory to each method + $method['grade']['time'] = $this->_grade($method['percent']['fastest']['time']); + $method['grade']['memory'] = $this->_grade($method['percent']['fastest']['memory']); + + // Percentage calculations for subjects + foreach ($method['subjects'] as & $subject) + { + // Calculate percentage difference relative to fastest and slowest subjects for this method + $subject['percent']['fastest']['time'] = (empty($fastest_subject['time'])) ? 0 : ($subject['time'] / $fastest_subject['time'] * 100); + $subject['percent']['fastest']['memory'] = (empty($fastest_subject['memory'])) ? 0 : ($subject['memory'] / $fastest_subject['memory'] * 100); + $subject['percent']['slowest']['time'] = (empty($slowest_subject['time'])) ? 0 : ($subject['time'] / $slowest_subject['time'] * 100); + $subject['percent']['slowest']['memory'] = (empty($slowest_subject['memory'])) ? 0 : ($subject['memory'] / $slowest_subject['memory'] * 100); + + // Assign a grade letter for time and memory to each subject + $subject['grade']['time'] = $this->_grade($subject['percent']['fastest']['time']); + $subject['grade']['memory'] = $this->_grade($subject['percent']['fastest']['memory']); + } + } + + return $codebench; + } + + /** + * Callback for array_filter(). + * Filters out all methods not to benchmark. + * + * @param string method name + * @return boolean + */ + protected function _method_filter($method) + { + // Only benchmark methods with the "bench" prefix + return (substr($method, 0, 5) === 'bench'); + } + + /** + * Returns the applicable grade letter for a score. + * + * @param integer|double score + * @return string grade letter + */ + protected function _grade($score) + { + foreach ($this->grades as $max => $grade) + { + if ($max === 'default') + continue; + + if ($score <= $max) + return $grade; + } + + return $this->grades['default']; + } +} diff --git a/includes/kohana/modules/codebench/config/codebench.php b/includes/kohana/modules/codebench/config/codebench.php new file mode 100644 index 0000000..590186d --- /dev/null +++ b/includes/kohana/modules/codebench/config/codebench.php @@ -0,0 +1,16 @@ + 0, + + /** + * Expand all benchmark details by default. + */ + 'expand_all' => FALSE, + +); diff --git a/includes/kohana/modules/codebench/config/userguide.php b/includes/kohana/modules/codebench/config/userguide.php new file mode 100644 index 0000000..f9e6a14 --- /dev/null +++ b/includes/kohana/modules/codebench/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' + 'codebench' => array( + + // Whether this modules userguide pages should be shown + 'enabled' => TRUE, + + // The name that should show up on the userguide index page + 'name' => 'Codebench', + + // A short description of this module, shown on the index page + 'description' => 'Code benchmarking tool.', + + // Copyright message, shown in the footer for this module + 'copyright' => '© 2008–2010 Kohana Team', + ) + ) +); \ No newline at end of file diff --git a/includes/kohana/modules/codebench/guide/codebench/index.md b/includes/kohana/modules/codebench/guide/codebench/index.md new file mode 100644 index 0000000..78b6f21 --- /dev/null +++ b/includes/kohana/modules/codebench/guide/codebench/index.md @@ -0,0 +1,76 @@ +# Using Codebench + +[!!] The contents of this page are taken (with some minor changes) from and are copyright Geert De Deckere. + +For a long time I have been using a quick-and-dirty `benchmark.php` file to optimize bits of PHP code, many times regex-related stuff. The file contained not much more than a [gettimeofday](http://php.net/gettimeofday) function wrapped around a `for` loop. It worked, albeit not very efficiently. Something more solid was needed. I set out to create a far more usable piece of software to aid in the everlasting quest to squeeze every millisecond out of those regular expressions. + +## Codebench Goals + +### Benchmark multiple regular expressions at once + +Being able to compare the speed of an arbitrary amount of regular expressions would be tremendously useful. In case you are wondering—yes, I had been writing down benchmark times for each regex, uncommenting them one by one. You get the idea. Those days should be gone forever now. + +### Benchmark multiple subjects at once + +What gets overlooked too often when testing and optimizing regular expressions is the fact that speed can vastly differ depending on the subjects, also known as input or target strings. Just because your regular expression matches, say, a valid email address quickly, does not necessarily mean it will quickly realize when an invalid email is provided. I plan to write a follow-up article with hands-on regex examples to demonstrate this point. Anyway, Codebench allows you to create an array of subjects which will be passed to each benchmark. + +### Make it flexible enough to work for all PCRE functions + +Initially I named the module “Regexbench”. I quickly realized, though, it would be flexible enough to benchmark all kinds of PHP code, hence the change to “Codebench”. While tools specifically built to help profiling PCRE functions, like [preg_match](http://php.net/preg_match) or [preg_replace](http://php.net/preg_replace), definitely have their use, more flexibility was needed here. You should be able to compare all kinds of constructions like combinations of PCRE functions and native PHP string functions. + +### Create clean and portable benchmark cases + +Throwing valuable benchmark data away every time I needed to optimize another regular expression had to stop. A clean file containing the complete set of all regex variations to compare, together with the set of subjects to test them against, would be more than welcome. Moreover, it would be easy to exchange benchmark cases with others. + +### Visualize the benchmarks + +Obviously providing a visual representation of the benchmark results, via simple graphs, would make interpreting them easier. Having not to think about Internet Explorer for once, made writing CSS a whole lot more easy and fun. It resulted in some fine graphs which are fully resizable. + +Below are two screenshots of Codebench in action. `Valid_Color` is a class made for benchmarking different ways to validate hexadecimal HTML color values, e.g. `#FFF`. If you are interested in the story behind the actual regular expressions, take a look at [this topic in the Kohana forums](http://forum.kohanaphp.com/comments.php?DiscussionID=2192). + +![Benchmarking several ways to validate HTML color values](codebench_screenshot1.png) +**Benchmarking seven ways to validate HTML color values** + +![Collapsable results per subject for each method](codebench_screenshot2.png) +**Collapsable results per subject for each method** + +## Working with Codebench + +Codebench is included in Kohana 3, but if you need you [can download it](http://github.com/kohana/codebench/) from GitHub. Be sure Codebench is activated in your `application/bootstrap.php`. + +Creating your own benchmarks is just a matter of creating a class that extends the Codebench class. The class should go in `classes/bench` and the class name should have the `Bench_` prefix. Put the code parts you want to compare into separate methods. Be sure to prefix those methods with `bench_`, other methods will not be benchmarked. Glance at the files in `modules/codebench/classes/bench/` for more examples. + +Here is another short example with some extra explanations. + + // classes/bench/ltrimdigits.php + class Bench_LtrimDigits extends Codebench { + + // Some optional explanatory comments about the benchmark file. + // HTML allowed. URLs will be converted to links automatically. + public $description = 'Chopping off leading digits: regex vs ltrim.'; + + // How many times to execute each method per subject. + // Total loops = loops * number of methods * number of subjects + public $loops = 100000; + + // The subjects to supply iteratively to your benchmark methods. + public $subjects = array + ( + '123digits', + 'no-digits', + ); + + public function bench_regex($subject) + { + return preg_replace('/^\d+/', '', $subject); + } + + public function bench_ltrim($subject) + { + return ltrim($subject, '0..9'); + } + } + + + +And the winner is… [ltrim](http://php.net/ltrim). Happy benchmarking! \ No newline at end of file diff --git a/includes/kohana/modules/codebench/guide/codebench/menu.md b/includes/kohana/modules/codebench/guide/codebench/menu.md new file mode 100644 index 0000000..c73a19a --- /dev/null +++ b/includes/kohana/modules/codebench/guide/codebench/menu.md @@ -0,0 +1 @@ +## [Codebench]() \ No newline at end of file diff --git a/includes/kohana/modules/codebench/init.php b/includes/kohana/modules/codebench/init.php new file mode 100644 index 0000000..866cc34 --- /dev/null +++ b/includes/kohana/modules/codebench/init.php @@ -0,0 +1,8 @@ +)') + ->defaults(array( + 'controller' => 'codebench', + 'action' => 'index', + 'class' => NULL)); diff --git a/includes/kohana/modules/codebench/media/guide/codebench/codebench_screenshot1.png b/includes/kohana/modules/codebench/media/guide/codebench/codebench_screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..d1cfcfb2cc4db8afe79465408be1c0587244b780 GIT binary patch literal 15827 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 X%k+Esjeze-6WmwTQ7Kn?68`@HDeN9w literal 0 HcmV?d00001 diff --git a/includes/kohana/modules/codebench/media/guide/codebench/codebench_screenshot2.png b/includes/kohana/modules/codebench/media/guide/codebench/codebench_screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..85bf560bead5dd6042892a5a90f2d1212a7c8ee0 GIT binary patch literal 13575 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 literal 0 HcmV?d00001 diff --git a/includes/kohana/modules/codebench/views/codebench.php b/includes/kohana/modules/codebench/views/codebench.php new file mode 100644 index 0000000..7b6ef2a --- /dev/null +++ b/includes/kohana/modules/codebench/views/codebench.php @@ -0,0 +1,260 @@ + + + + + + + + <?php if ($class !== ''): ?> + <?php echo $class, ' · ' ?> + <?php endif; ?>Codebench + + + + + + + + + + + +
                    +

                    + + + + + Library not found + + No methods found to benchmark + + +

                    +
                    + + + + + +

                    + + Remember to prefix the methods you want to benchmark with “bench”.
                    + You might also want to overwrite Codebench->method_filter(). +
                    +

                    + + + +
                      + $benchmark) { ?> +
                    • + +

                      + + + +% + +

                      + +
                      + + + + + + + + + + + + $subject) { ?> + + + + + + + + +
                      Benchmarks per subject for
                      subject → returns
                      + + [] → + + () + + + + + + + + + + + s + + +
                      +
                      + +
                    • + +
                    + + + + + + + + Raw output:', Kohana::debug($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 0000000..776a801 --- /dev/null +++ b/includes/kohana/modules/database/classes/config/database.php @@ -0,0 +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.php b/includes/kohana/modules/database/classes/kohana/database.php new file mode 100644 index 0000000..d84420a --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database.php @@ -0,0 +1,699 @@ +$name; + } + + if ( ! isset($config['type'])) + { + throw new Kohana_Exception('Database type not defined in :name configuration', + array(':name' => $name)); + } + + // Set the driver class name + $driver = 'Database_'.ucfirst($config['type']); + + // Create the database connection instance + new $driver($name, $config); + } + + return Database::$instances[$name]; + } + + /** + * @var string the last query executed + */ + public $last_query; + + // Character that is used to quote identifiers + protected $_identifier = '"'; + + // Instance name + protected $_instance; + + // Raw server connection + protected $_connection; + + // Configuration array + protected $_config; + + /** + * Stores the database configuration locally and name the instance. + * + * [!!] This method cannot be accessed directly, you must use [Database::instance]. + * + * @return void + */ + protected function __construct($name, array $config) + { + // Set the instance name + $this->_instance = $name; + + // Store the config locally + $this->_config = $config; + + // Store the database instance + Database::$instances[$name] = $this; + } + + /** + * Disconnect from the database when the object is destroyed. + * + * // Destroy the database instance + * unset(Database::instances[(string) $db], $db); + * + * [!!] Calling `unset($db)` is not enough to destroy the database, as it + * will still be stored in `Database::$instances`. + * + * @return void + */ + final public function __destruct() + { + $this->disconnect(); + } + + /** + * Returns the database instance name. + * + * echo (string) $db; + * + * @return string + */ + final public function __toString() + { + return $this->_instance; + } + + /** + * Connect to the database. This is called automatically when the first + * query is executed. + * + * $db->connect(); + * + * @throws Database_Exception + * @return void + */ + abstract public function connect(); + + /** + * Disconnect from the database. This is called automatically by [Database::__destruct]. + * Clears the database instance from [Database::$instances]. + * + * $db->disconnect(); + * + * @return boolean + */ + public function disconnect() + { + unset(Database::$instances[$this->_instance]); + + return TRUE; + } + + /** + * Set the connection character set. This is called automatically by [Database::connect]. + * + * $db->set_charset('utf8'); + * + * @throws Database_Exception + * @param string character set name + * @return void + */ + abstract public function set_charset($charset); + + /** + * Perform an SQL query of the given type. + * + * // Make a SELECT query and use objects for results + * $db->query(Database::SELECT, 'SELECT * FROM groups', TRUE); + * + * // 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 + * @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 + */ + abstract public function query($type, $sql, $as_object = FALSE, array $params = NULL); + + /** + * Start a SQL transaction + * + * // Start the transactions + * $db->begin(); + * + * try { + * DB::insert('users')->values($user1)... + * DB::insert('users')->values($user2)... + * // Insert successful commit the changes + * $db->commit(); + * } + * catch (Database_Exception $e) + * { + * // Insert failed. Rolling back changes... + * $db->rollback(); + * } + * + * @param string transaction mode + * @return boolean + */ + abstract public function begin($mode = NULL); + + /** + * Commit the current transaction + * + * // Commit the database changes + * $db->commit(); + * + * @return boolean + */ + abstract public function commit(); + + /** + * Abort the current transaction + * + * // Undo the changes + * $db->rollback(); + * + * @return boolean + */ + abstract public function rollback(); + + /** + * Count the number of records in a table. + * + * // Get the total number of records in the "users" table + * $count = $db->count_records('users'); + * + * @param mixed table name string or array(query, alias) + * @return integer + */ + public function count_records($table) + { + // Quote the table name + $table = $this->quote_table($table); + + return $this->query(Database::SELECT, 'SELECT COUNT(*) AS total_row_count FROM '.$table, FALSE) + ->get('total_row_count'); + } + + /** + * Returns a normalized array describing the SQL data type + * + * $db->datatype('char'); + * + * @param string SQL data type + * @return array + */ + public function datatype($type) + { + static $types = array + ( + // SQL-92 + 'bit' => array('type' => 'string', 'exact' => TRUE), + 'bit varying' => array('type' => 'string'), + 'char' => array('type' => 'string', 'exact' => TRUE), + 'char varying' => array('type' => 'string'), + 'character' => array('type' => 'string', 'exact' => TRUE), + 'character varying' => array('type' => 'string'), + 'date' => array('type' => 'string'), + 'dec' => array('type' => 'float', 'exact' => TRUE), + 'decimal' => array('type' => 'float', 'exact' => TRUE), + 'double precision' => array('type' => 'float'), + 'float' => array('type' => 'float'), + 'int' => array('type' => 'int', 'min' => '-2147483648', 'max' => '2147483647'), + 'integer' => array('type' => 'int', 'min' => '-2147483648', 'max' => '2147483647'), + 'interval' => array('type' => 'string'), + 'national char' => array('type' => 'string', 'exact' => TRUE), + 'national char varying' => array('type' => 'string'), + 'national character' => array('type' => 'string', 'exact' => TRUE), + 'national character varying' => array('type' => 'string'), + 'nchar' => array('type' => 'string', 'exact' => TRUE), + 'nchar varying' => array('type' => 'string'), + 'numeric' => array('type' => 'float', 'exact' => TRUE), + 'real' => array('type' => 'float'), + 'smallint' => array('type' => 'int', 'min' => '-32768', 'max' => '32767'), + 'time' => array('type' => 'string'), + 'time with time zone' => array('type' => 'string'), + 'timestamp' => array('type' => 'string'), + 'timestamp with time zone' => array('type' => 'string'), + 'varchar' => array('type' => 'string'), + + // SQL:1999 + 'binary large object' => array('type' => 'string', 'binary' => TRUE), + 'blob' => array('type' => 'string', 'binary' => TRUE), + 'boolean' => array('type' => 'bool'), + 'char large object' => array('type' => 'string'), + 'character large object' => array('type' => 'string'), + 'clob' => array('type' => 'string'), + 'national character large object' => array('type' => 'string'), + 'nchar large object' => array('type' => 'string'), + 'nclob' => array('type' => 'string'), + 'time without time zone' => array('type' => 'string'), + 'timestamp without time zone' => array('type' => 'string'), + + // SQL:2003 + 'bigint' => array('type' => 'int', 'min' => '-9223372036854775808', 'max' => '9223372036854775807'), + + // SQL:2008 + 'binary' => array('type' => 'string', 'binary' => TRUE, 'exact' => TRUE), + 'binary varying' => array('type' => 'string', 'binary' => TRUE), + 'varbinary' => array('type' => 'string', 'binary' => TRUE), + ); + + if (isset($types[$type])) + return $types[$type]; + + return array(); + } + + /** + * List all of the tables in the database. Optionally, a LIKE string can + * be used to search for specific tables. + * + * // Get all tables in the current database + * $tables = $db->list_tables(); + * + * // Get all user-related tables + * $tables = $db->list_tables('user%'); + * + * @param string table to search for + * @return array + */ + abstract public function list_tables($like = NULL); + + /** + * Lists all of the columns in a table. Optionally, a LIKE string can be + * used to search for specific fields. + * + * // Get all columns from the "users" table + * $columns = $db->list_columns('users'); + * + * // Get all name-related columns + * $columns = $db->list_columns('users', '%name%'); + * + * // 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 + * @return array + */ + abstract public function list_columns($table, $like = NULL, $add_prefix = TRUE); + + /** + * Extracts the text between parentheses, if any. + * + * // Returns: array('CHAR', '6') + * list($type, $length) = $db->_parse_type('CHAR(6)'); + * + * @param string + * @return array list containing the type and length, if any + */ + protected function _parse_type($type) + { + if (($open = strpos($type, '(')) === FALSE) + { + // No length specified + return array($type, NULL); + } + + // Closing parenthesis + $close = strpos($type, ')', $open); + + // Length without parentheses + $length = substr($type, $open + 1, $close - 1 - $open); + + // Type without the length + $type = substr($type, 0, $open).substr($type, $close + 1); + + return array($type, $length); + } + + /** + * Return the table prefix defined in the current configuration. + * + * $prefix = $db->table_prefix(); + * + * @return string + */ + public function table_prefix() + { + return $this->_config['table_prefix']; + } + + /** + * Quote a value for an SQL query. + * + * $db->quote(NULL); // 'NULL' + * $db->quote(10); // 10 + * $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_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 + * @return string + * @uses Database::escape + */ + public function quote($value) + { + if ($value === NULL) + { + return 'NULL'; + } + elseif ($value === TRUE) + { + return "'1'"; + } + elseif ($value === FALSE) + { + return "'0'"; + } + elseif (is_object($value)) + { + if ($value instanceof Database_Query) + { + // Create a sub-query + return '('.$value->compile($this).')'; + } + elseif ($value instanceof Database_Expression) + { + // Use a raw expression + return $value->value(); + } + else + { + // Convert the object to a string + return $this->quote( (string) $value); + } + } + elseif (is_array($value)) + { + return '('.implode(', ', array_map(array($this, __FUNCTION__), $value)).')'; + } + elseif (is_int($value)) + { + return (int) $value; + } + elseif (is_float($value)) + { + // Convert to non-locale aware float to prevent possible commas + return sprintf('%F', $value); + } + + return $this->escape($value); + } + + /** + * Quote a database column name and add the table prefix if needed. + * + * $column = $db->quote_column($column); + * + * You can also use SQL methods within identifiers. + * + * // The value of "column" will be quoted + * $column = $db->quote_column('COUNT("column")'); + * + * @param mixed column name or array(column, alias) + * @return string + * @uses Database::quote_identifier + * @uses Database::table_prefix + */ + public function quote_column($column) + { + if (is_array($column)) + { + list($column, $alias) = $column; + } + + if ($column instanceof Database_Query) + { + // Create a sub-query + $column = '('.$column->compile($this).')'; + } + elseif ($column instanceof Database_Expression) + { + // Use a raw expression + $column = $column->value(); + } + else + { + // Convert to a string + $column = (string) $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); + + if ($prefix = $this->table_prefix()) + { + // Get the offset of the table name, 2nd-to-last part + $offset = count($parts) - 2; + + // Add the table prefix to the table name + $parts[$offset] = $prefix.$parts[$offset]; + } + + foreach ($parts as & $part) + { + if ($part !== '*') + { + // Quote each of the parts + $part = $this->_identifier.$part.$this->_identifier; + } + } + + $column = implode('.', $parts); + } + else + { + $column = $this->_identifier.$column.$this->_identifier; + } + } + + if (isset($alias)) + { + $column .= ' AS '.$this->_identifier.$alias.$this->_identifier; + } + + return $column; + } + + /** + * Quote a database table name and adds the table prefix if needed. + * + * $table = $db->quote_table($table); + * + * @param mixed table name or array(table, alias) + * @return string + * @uses Database::quote_identifier + * @uses Database::table_prefix + */ + public function quote_table($table) + { + if (is_array($table)) + { + list($table, $alias) = $table; + } + + if ($table instanceof Database_Query) + { + // Create a sub-query + $table = '('.$table->compile($this).')'; + } + elseif ($table instanceof Database_Expression) + { + // Use a raw expression + $table = $table->value(); + } + else + { + // Convert to a string + $table = (string) $table; + + if (strpos($table, '.') !== FALSE) + { + $parts = explode('.', $table); + + if ($prefix = $this->table_prefix()) + { + // Get the offset of the table name, last part + $offset = count($parts) - 1; + + // Add the table prefix to the table name + $parts[$offset] = $prefix.$parts[$offset]; + } + + foreach ($parts as & $part) + { + // Quote each of the parts + $part = $this->_identifier.$part.$this->_identifier; + } + + $table = implode('.', $parts); + } + else + { + // Add the table prefix + $table = $this->_identifier.$this->table_prefix().$table.$this->_identifier; + } + } + + if (isset($alias)) + { + // Attach table prefix to alias + $table .= ' AS '.$this->_identifier.$this->table_prefix().$alias.$this->_identifier; + } + + return $table; + } + + /** + * 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_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 + * @return string + */ + public function quote_identifier($value) + { + if (is_array($value)) + { + list($value, $alias) = $value; + } + + if ($value instanceof Database_Query) + { + // Create a sub-query + $value = '('.$value->compile($this).')'; + } + elseif ($value instanceof Database_Expression) + { + // Use a raw expression + $value = $value->value(); + } + else + { + // Convert to a string + $value = (string) $value; + + if (strpos($value, '.') !== FALSE) + { + $parts = explode('.', $value); + + foreach ($parts as & $part) + { + // Quote each of the parts + $part = $this->_identifier.$part.$this->_identifier; + } + + $value = implode('.', $parts); + } + else + { + $value = $this->_identifier.$value.$this->_identifier; + } + } + + if (isset($alias)) + { + $value .= ' AS '.$this->_identifier.$alias.$this->_identifier; + } + + return $value; + } + + /** + * Sanitize a string by escaping characters that could cause an SQL + * injection attack. + * + * $value = $db->escape('any string'); + * + * @param string value to quote + * @return string + */ + abstract public function escape($value); + +} // End Database_Connection diff --git a/includes/kohana/modules/database/classes/kohana/database/exception.php b/includes/kohana/modules/database/classes/kohana/database/exception.php new file mode 100644 index 0000000..ea2630e --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/exception.php @@ -0,0 +1,11 @@ +_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/kohana/database/mysql.php b/includes/kohana/modules/database/classes/kohana/database/mysql.php new file mode 100644 index 0000000..4fdbf01 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/mysql.php @@ -0,0 +1,432 @@ +_connection) + return; + + if (Database_MySQL::$_set_names === NULL) + { + // Determine if we can use mysql_set_charset(), which is only + // available on PHP 5.2.3+ when compiled against MySQL 5.0+ + Database_MySQL::$_set_names = ! function_exists('mysql_set_charset'); + } + + // Extract the connection parameters, adding required variabels + extract($this->_config['connection'] + array( + 'database' => '', + 'hostname' => '', + 'username' => '', + 'password' => '', + 'persistent' => FALSE, + )); + + // Prevent this information from showing up in traces + unset($this->_config['connection']['username'], $this->_config['connection']['password']); + + try + { + if ($persistent) + { + // Create a persistent connection + $this->_connection = mysql_pconnect($hostname, $username, $password); + } + else + { + // Create a connection and force it to be a new link + $this->_connection = mysql_connect($hostname, $username, $password, TRUE); + } + } + catch (ErrorException $e) + { + // No connection exists + $this->_connection = NULL; + + throw new Database_Exception(':error', + array(':error' => mysql_error()), + mysql_errno()); + } + + // \xFF is a better delimiter, but the PHP driver uses underscore + $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password); + + $this->_select_db($database); + + if ( ! empty($this->_config['charset'])) + { + // Set the character set + $this->set_charset($this->_config['charset']); + } + } + + /** + * Select the database + * + * @param string Database + * @return void + */ + protected function _select_db($database) + { + if ( ! mysql_select_db($database, $this->_connection)) + { + // Unable to select database + throw new Database_Exception(':error', + array(':error' => mysql_error($this->_connection)), + mysql_errno($this->_connection)); + } + + Database_MySQL::$_current_databases[$this->_connection_id] = $database; + } + + public function disconnect() + { + try + { + // Database is assumed disconnected + $status = TRUE; + + if (is_resource($this->_connection)) + { + if ($status = mysql_close($this->_connection)) + { + // Clear the connection + $this->_connection = NULL; + + // Clear the instance + parent::disconnect(); + } + } + } + catch (Exception $e) + { + // Database is probably not disconnected + $status = ! is_resource($this->_connection); + } + + return $status; + } + + public function set_charset($charset) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if (Database_MySQL::$_set_names === TRUE) + { + // PHP is compiled against MySQL 4.x + $status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->_connection); + } + else + { + // PHP is compiled against MySQL 5.x + $status = mysql_set_charset($charset, $this->_connection); + } + + if ($status === FALSE) + { + throw new Database_Exception(':error', + array(':error' => mysql_error($this->_connection)), + mysql_errno($this->_connection)); + } + } + + public function query($type, $sql, $as_object = FALSE, array $params = NULL) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if ( ! empty($this->_config['profiling'])) + { + // Benchmark this query for the current instance + $benchmark = Profiler::start("Database ({$this->_instance})", $sql); + } + + if ( ! empty($this->_config['connection']['persistent']) AND $this->_config['connection']['database'] !== Database_MySQL::$_current_databases[$this->_connection_id]) + { + // Select database on persistent connections + $this->_select_db($this->_config['connection']['database']); + } + + // Execute the query + if (($result = mysql_query($sql, $this->_connection)) === FALSE) + { + if (isset($benchmark)) + { + // This benchmark is worthless + Profiler::delete($benchmark); + } + + throw new Database_Exception(':error [ :query ]', + array(':error' => mysql_error($this->_connection), ':query' => $sql), + mysql_errno($this->_connection)); + } + + if (isset($benchmark)) + { + Profiler::stop($benchmark); + } + + // Set the last query + $this->last_query = $sql; + + if ($type === Database::SELECT) + { + // Return an iterator of results + return new Database_MySQL_Result($result, $sql, $as_object, $params); + } + elseif ($type === Database::INSERT) + { + // Return a list of insert id and rows created + return array( + mysql_insert_id($this->_connection), + mysql_affected_rows($this->_connection), + ); + } + else + { + // Return the number of rows affected + return mysql_affected_rows($this->_connection); + } + } + + public function datatype($type) + { + static $types = array + ( + 'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'), + 'bool' => array('type' => 'bool'), + 'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'), + 'datetime' => array('type' => 'string'), + 'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), + 'double' => array('type' => 'float'), + 'double precision unsigned' => array('type' => 'float', 'min' => '0'), + 'double unsigned' => array('type' => 'float', 'min' => '0'), + 'enum' => array('type' => 'string'), + 'fixed' => array('type' => 'float', 'exact' => TRUE), + 'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), + 'float unsigned' => array('type' => 'float', 'min' => '0'), + 'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), + 'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), + 'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'), + 'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'), + 'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'), + 'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'), + 'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'), + 'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'), + 'national varchar' => array('type' => 'string'), + 'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), + 'nvarchar' => array('type' => 'string'), + 'point' => array('type' => 'string', 'binary' => TRUE), + 'real unsigned' => array('type' => 'float', 'min' => '0'), + 'set' => array('type' => 'string'), + 'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'), + 'text' => array('type' => 'string', 'character_maximum_length' => '65535'), + 'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'), + 'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'), + 'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'), + 'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'), + 'year' => array('type' => 'string'), + ); + + $type = str_replace(' zerofill', '', $type); + + if (isset($types[$type])) + return $types[$type]; + + return parent::datatype($type); + } + + /** + * Start a SQL transaction + * + * @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html + * + * @param string Isolation level + * @return boolean + */ + public function begin($mode = NULL) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if ($mode AND ! mysql_query("SET TRANSACTION ISOLATION LEVEL $mode", $this->_connection)) + { + throw new Database_Exception(':error', + array(':error' => mysql_error($this->_connection)), + mysql_errno($this->_connection)); + } + + return (bool) mysql_query('START TRANSACTION', $this->_connection); + } + + /** + * Commit a SQL transaction + * + * @param string Isolation level + * @return boolean + */ + public function commit() + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + return (bool) mysql_query('COMMIT', $this->_connection); + } + + /** + * Rollback a SQL transaction + * + * @param string Isolation level + * @return boolean + */ + public function rollback() + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + return (bool) mysql_query('ROLLBACK', $this->_connection); + } + + public function list_tables($like = NULL) + { + if (is_string($like)) + { + // Search for table names + $result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE); + } + else + { + // Find all table names + $result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE); + } + + $tables = array(); + foreach ($result as $row) + { + $tables[] = reset($row); + } + + return $tables; + } + + public function list_columns($table, $like = NULL, $add_prefix = TRUE) + { + // Quote the table name + $table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table; + + if (is_string($like)) + { + // Search for column names + $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE); + } + else + { + // Find all column names + $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE); + } + + $count = 0; + $columns = array(); + foreach ($result as $row) + { + list($type, $length) = $this->_parse_type($row['Type']); + + $column = $this->datatype($type); + + $column['column_name'] = $row['Field']; + $column['column_default'] = $row['Default']; + $column['data_type'] = $type; + $column['is_nullable'] = ($row['Null'] == 'YES'); + $column['ordinal_position'] = ++$count; + + switch ($column['type']) + { + case 'float': + if (isset($length)) + { + list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length); + } + break; + case 'int': + if (isset($length)) + { + // MySQL attribute + $column['display'] = $length; + } + break; + case 'string': + switch ($column['data_type']) + { + case 'binary': + case 'varbinary': + $column['character_maximum_length'] = $length; + break; + case 'char': + case 'varchar': + $column['character_maximum_length'] = $length; + case 'text': + case 'tinytext': + case 'mediumtext': + case 'longtext': + $column['collation_name'] = $row['Collation']; + break; + case 'enum': + case 'set': + $column['collation_name'] = $row['Collation']; + $column['options'] = explode('\',\'', substr($length, 1, -1)); + break; + } + break; + } + + // MySQL attributes + $column['comment'] = $row['Comment']; + $column['extra'] = $row['Extra']; + $column['key'] = $row['Key']; + $column['privileges'] = $row['Privileges']; + + $columns[$row['Field']] = $column; + } + + return $columns; + } + + public function escape($value) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if (($value = mysql_real_escape_string( (string) $value, $this->_connection)) === FALSE) + { + throw new Database_Exception(':error', + array(':error' => mysql_error($this->_connection)), + mysql_errno($this->_connection)); + } + + // SQL standard is to use single-quotes for all values + return "'$value'"; + } + +} // End Database_MySQL diff --git a/includes/kohana/modules/database/classes/kohana/database/mysql/result.php b/includes/kohana/modules/database/classes/kohana/database/mysql/result.php new file mode 100644 index 0000000..bbfa66e --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/mysql/result.php @@ -0,0 +1,71 @@ +_total_rows = mysql_num_rows($result); + } + + public function __destruct() + { + if (is_resource($this->_result)) + { + mysql_free_result($this->_result); + } + } + + public function seek($offset) + { + if ($this->offsetExists($offset) AND mysql_data_seek($this->_result, $offset)) + { + // Set the current row to the offset + $this->_current_row = $this->_internal_row = $offset; + + return TRUE; + } + else + { + return FALSE; + } + } + + public function current() + { + if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row)) + return NULL; + + // Increment internal row for optimization assuming rows are fetched in order + $this->_internal_row++; + + if ($this->_as_object === TRUE) + { + // Return an stdClass + return mysql_fetch_object($this->_result); + } + elseif (is_string($this->_as_object)) + { + // Return an object of given class name + return mysql_fetch_object($this->_result, $this->_as_object, $this->_object_params); + } + else + { + // Return an array of the row + return mysql_fetch_assoc($this->_result); + } + } + +} // End Database_MySQL_Result_Select diff --git a/includes/kohana/modules/database/classes/kohana/database/pdo.php b/includes/kohana/modules/database/classes/kohana/database/pdo.php new file mode 100644 index 0000000..4f631b6 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/pdo.php @@ -0,0 +1,208 @@ +_config['identifier'])) + { + // Allow the identifier to be overloaded per-connection + $this->_identifier = (string) $this->_config['identifier']; + } + } + + public function connect() + { + if ($this->_connection) + return; + + // Extract the connection parameters, adding required variabels + extract($this->_config['connection'] + array( + 'dsn' => '', + 'username' => NULL, + 'password' => NULL, + 'persistent' => FALSE, + )); + + // Clear the connection parameters for security + unset($this->_config['connection']); + + // Force PDO to use exceptions for all errors + $attrs = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + + if ( ! empty($persistent)) + { + // Make the connection persistent + $attrs[PDO::ATTR_PERSISTENT] = TRUE; + } + + try + { + // Create a new PDO connection + $this->_connection = new PDO($dsn, $username, $password, $attrs); + } + catch (PDOException $e) + { + throw new Database_Exception(':error', + array(':error' => $e->getMessage()), + $e->getCode()); + } + + if ( ! empty($this->_config['charset'])) + { + // Set the character set + $this->set_charset($this->_config['charset']); + } + } + + public function disconnect() + { + // Destroy the PDO object + $this->_connection = NULL; + + return parent::disconnect(); + } + + public function set_charset($charset) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + // Execute a raw SET NAMES query + $this->_connection->exec('SET NAMES '.$this->quote($charset)); + } + + public function query($type, $sql, $as_object = FALSE, array $params = NULL) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if ( ! empty($this->_config['profiling'])) + { + // Benchmark this query for the current instance + $benchmark = Profiler::start("Database ({$this->_instance})", $sql); + } + + try + { + $result = $this->_connection->query($sql); + } + catch (Exception $e) + { + if (isset($benchmark)) + { + // This benchmark is worthless + Profiler::delete($benchmark); + } + + // Convert the exception in a database exception + throw new Database_Exception(':error [ :query ]', + array( + ':error' => $e->getMessage(), + ':query' => $sql + ), + $e->getCode()); + } + + if (isset($benchmark)) + { + Profiler::stop($benchmark); + } + + // Set the last query + $this->last_query = $sql; + + if ($type === Database::SELECT) + { + // Convert the result into an array, as PDOStatement::rowCount is not reliable + if ($as_object === FALSE) + { + $result->setFetchMode(PDO::FETCH_ASSOC); + } + elseif (is_string($as_object)) + { + $result->setFetchMode(PDO::FETCH_CLASS, $as_object, $params); + } + else + { + $result->setFetchMode(PDO::FETCH_CLASS, 'stdClass'); + } + + $result = $result->fetchAll(); + + // Return an iterator of results + return new Database_Result_Cached($result, $sql, $as_object, $params); + } + elseif ($type === Database::INSERT) + { + // Return a list of insert id and rows created + return array( + $this->_connection->lastInsertId(), + $result->rowCount(), + ); + } + else + { + // Return the number of rows affected + return $result->rowCount(); + } + } + + public function begin($mode = NULL) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + return $this->_connection->beginTransaction(); + } + + public function commit() + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + return $this->_connection->commit(); + } + + public function rollback() + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + return $this->_connection->rollBack(); + } + + public function list_tables($like = NULL) + { + throw new Kohana_Exception('Database method :method is not supported by :class', + array(':method' => __FUNCTION__, ':class' => __CLASS__)); + } + + public function list_columns($table, $like = NULL, $add_prefix = TRUE) + { + throw new Kohana_Exception('Database method :method is not supported by :class', + array(':method' => __FUNCTION__, ':class' => __CLASS__)); + } + + public function escape($value) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + return $this->_connection->quote($value); + } + +} // End Database_PDO diff --git a/includes/kohana/modules/database/classes/kohana/database/query.php b/includes/kohana/modules/database/classes/kohana/database/query.php new file mode 100644 index 0000000..f08f3c1 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/query.php @@ -0,0 +1,236 @@ +_type = $type; + $this->_sql = $sql; + } + + /** + * Return the SQL query string. + * + * @return string + */ + final public function __toString() + { + try + { + // Return the SQL string + return $this->compile(Database::instance()); + } + catch (Exception $e) + { + return Kohana_Exception::text($e); + } + } + + /** + * Get the type of the query. + * + * @return integer + */ + public function type() + { + return $this->_type; + } + + /** + * Enables the query to be cached for a specified amount of time. + * + * @param integer number of seconds to cache + * @return $this + * @uses Kohana::$cache_life + */ + public function cached($lifetime = NULL) + { + if ($lifetime === NULL) + { + // Use the global setting + $lifetime = Kohana::$cache_life; + } + + $this->_lifetime = $lifetime; + + return $this; + } + + /** + * Returns results as associative arrays + * + * @return $this + */ + public function as_assoc() + { + $this->_as_object = FALSE; + + $this->_object_params = array(); + + return $this; + } + + /** + * Returns results as objects + * + * @param string classname or TRUE for stdClass + * @return $this + */ + public function as_object($class = TRUE, array $params = NULL) + { + $this->_as_object = $class; + + if ($params) + { + // Add object parameters + $this->_object_params = $params; + } + + return $this; + } + + /** + * Set the value of a parameter in the query. + * + * @param string parameter key to replace + * @param mixed value to use + * @return $this + */ + public function param($param, $value) + { + // Add or overload a new parameter + $this->_parameters[$param] = $value; + + return $this; + } + + /** + * Bind a variable to a parameter in the query. + * + * @param string parameter key to replace + * @param mixed variable to use + * @return $this + */ + public function bind($param, & $var) + { + // Bind a value to a variable + $this->_parameters[$param] =& $var; + + return $this; + } + + /** + * Add multiple parameters to the query. + * + * @param array list of parameters + * @return $this + */ + public function parameters(array $params) + { + // Merge the new parameters in + $this->_parameters = $params + $this->_parameters; + + return $this; + } + + /** + * Compile the SQL query and return it. Replaces any parameters with their + * given values. + * + * @param object Database instance + * @return string + */ + public function compile(Database $db) + { + // Import the SQL locally + $sql = $this->_sql; + + if ( ! empty($this->_parameters)) + { + // Quote all of the values + $values = array_map(array($db, 'quote'), $this->_parameters); + + // Replace the values in the SQL + $sql = strtr($sql, $values); + } + + return $sql; + } + + /** + * Execute the current query on the given database. + * + * @param mixed Database instance or name of instance + * @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) + { + if ( ! is_object($db)) + { + // Get the database instance + $db = Database::instance($db); + } + + // Compile the SQL query + $sql = $this->compile($db); + + if ($this->_lifetime !== NULL AND $this->_type === Database::SELECT) + { + // 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))) + { + // Return a cached result + return new Database_Result_Cached($result, $sql, $this->_as_object, $this->_object_params); + } + } + + // Execute the query + $result = $db->query($this->_type, $sql, $this->_as_object, $this->_object_params); + + if (isset($cache_key)) + { + // Cache the result array + Kohana::cache($cache_key, $result->as_array(), $this->_lifetime); + } + + return $result; + } + +} // End Database_Query diff --git a/includes/kohana/modules/database/classes/kohana/database/query/builder.php b/includes/kohana/modules/database/classes/kohana/database/query/builder.php new file mode 100644 index 0000000..8d36e79 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/query/builder.php @@ -0,0 +1,209 @@ +compile($db); + } + + return implode(' ', $statements); + } + + /** + * Compiles an array of conditions into an SQL partial. Used for WHERE + * and HAVING. + * + * @param object Database instance + * @param array condition statements + * @return string + */ + protected function _compile_conditions(Database $db, array $conditions) + { + $last_condition = NULL; + + $sql = ''; + foreach ($conditions as $group) + { + // Process groups of conditions + foreach ($group as $logic => $condition) + { + if ($condition === '(') + { + if ( ! empty($sql) AND $last_condition !== '(') + { + // Include logic operator + $sql .= ' '.$logic.' '; + } + + $sql .= '('; + } + elseif ($condition === ')') + { + $sql .= ')'; + } + else + { + if ( ! empty($sql) AND $last_condition !== '(') + { + // Add the logic operator + $sql .= ' '.$logic.' '; + } + + // Split the condition + list($column, $op, $value) = $condition; + + if ($value === NULL) + { + if ($op === '=') + { + // Convert "val = NULL" to "val IS NULL" + $op = 'IS'; + } + elseif ($op === '!=') + { + // Convert "val != NULL" to "valu IS NOT NULL" + $op = 'IS NOT'; + } + } + + // Database operators are always uppercase + $op = strtoupper($op); + + if ($op === 'BETWEEN' AND is_array($value)) + { + // BETWEEN always has exactly two arguments + list($min, $max) = $value; + + if ((is_string($min) AND array_key_exists($min, $this->_parameters)) === FALSE) + { + // Quote the value, it is not a parameter + $min = $db->quote($min); + } + + if ((is_string($max) AND array_key_exists($max, $this->_parameters)) === FALSE) + { + // Quote the value, it is not a parameter + $max = $db->quote($max); + } + + // Quote the min and max value + $value = $min.' AND '.$max; + } + elseif ($op === 'IN' AND is_array($value)) + { + $value = $db->quote($value); + } + elseif ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE) + { + // Quote the value, it is not a parameter + $value = $db->quote($value); + } + + if ($column) + { + // Apply proper quoting to the column + $column = $db->quote_column($column); + } + + // Append the statement to the query + $sql .= trim($column.' '.$op.' '.$value); + } + + $last_condition = $condition; + } + } + + return $sql; + } + + /** + * Compiles an array of set values into an SQL partial. Used for UPDATE. + * + * @param object Database instance + * @param array updated values + * @return string + */ + protected function _compile_set(Database $db, array $values) + { + $set = array(); + foreach ($values as $group) + { + // Split the set + list ($column, $value) = $group; + + // Quote the column name + $column = $db->quote_column($column); + + if ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE) + { + // Quote the value, it is not a parameter + $value = $db->quote($value); + } + + $set[$column] = $column.' = '.$value; + } + + return implode(', ', $set); + } + + /** + * Compiles an array of ORDER BY statements into an SQL partial. + * + * @param object Database instance + * @param array sorting columns + * @return string + */ + protected function _compile_order_by(Database $db, array $columns) + { + $sort = array(); + foreach ($columns as $group) + { + list ($column, $direction) = $group; + + if ($direction) + { + // Make the direction uppercase + $direction = strtoupper($direction); + } + + if ($column) + { + // Quote the column, if it has a value + $column = $db->quote_column($column); + } + + $sort[] = trim($column.' '.$direction); + } + + return 'ORDER BY '.implode(', ', $sort); + } + + /** + * Reset the current builder status. + * + * @return $this + */ + abstract public function reset(); + +} // End Database_Query_Builder 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 new file mode 100644 index 0000000..b7dec29 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/query/builder/delete.php @@ -0,0 +1,93 @@ +_table = $table; + } + + // Start the query with no SQL + return parent::__construct(Database::DELETE, ''); + } + + /** + * Sets the table to delete from. + * + * @param mixed table name or array($table, $alias) or object + * @return $this + */ + public function table($table) + { + $this->_table = $table; + + return $this; + } + + /** + * Compile the SQL query and return it. + * + * @param object Database instance + * @return string + */ + public function compile(Database $db) + { + // Start a deletion query + $query = 'DELETE FROM '.$db->quote_table($this->_table); + + if ( ! empty($this->_where)) + { + // Add deletion conditions + $query .= ' WHERE '.$this->_compile_conditions($db, $this->_where); + } + + if ( ! empty($this->_order_by)) + { + // Add sorting + $query .= ' '.$this->_compile_order_by($db, $this->_order_by); + } + + if ($this->_limit !== NULL) + { + // Add limiting + $query .= ' LIMIT '.$this->_limit; + } + + $this->_sql = $query; + + return parent::compile($db); + } + + public function reset() + { + $this->_table = NULL; + $this->_where = array(); + + $this->_parameters = array(); + + $this->_sql = NULL; + + return $this; + } + +} // End Database_Query_Builder_Delete 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 new file mode 100644 index 0000000..525fbfe --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/query/builder/insert.php @@ -0,0 +1,175 @@ +_table = $table; + } + + if ($columns) + { + // Set the column names + $this->_columns = $columns; + } + + // Start the query with no SQL + return parent::__construct(Database::INSERT, ''); + } + + /** + * Sets the table to insert into. + * + * @param mixed table name or array($table, $alias) or object + * @return $this + */ + public function table($table) + { + $this->_table = $table; + + return $this; + } + + /** + * Set the columns that will be inserted. + * + * @param array column names + * @return $this + */ + public function columns(array $columns) + { + $this->_columns = $columns; + + return $this; + } + + /** + * Adds or overwrites values. Multiple value sets can be added. + * + * @param array values list + * @param ... + * @return $this + */ + public function values(array $values) + { + if ( ! is_array($this->_values)) + { + throw new Kohana_Exception('INSERT INTO ... SELECT statements cannot be combined with INSERT INTO ... VALUES'); + } + + // Get all of the passed values + $values = func_get_args(); + + $this->_values = array_merge($this->_values, $values); + + return $this; + } + + /** + * Use a sub-query to for the inserted values. + * + * @param object Database_Query of SELECT type + * @return $this + */ + public function select(Database_Query $query) + { + if ($query->type() !== Database::SELECT) + { + throw new Kohana_Exception('Only SELECT queries can be combined with INSERT queries'); + } + + $this->_values = $query; + + return $this; + } + + /** + * Compile the SQL query and return it. + * + * @param object Database instance + * @return string + */ + public function compile(Database $db) + { + // Start an insertion query + $query = 'INSERT INTO '.$db->quote_table($this->_table); + + // Add the column names + $query .= ' ('.implode(', ', array_map(array($db, 'quote_column'), $this->_columns)).') '; + + if (is_array($this->_values)) + { + // Callback for quoting values + $quote = array($db, 'quote'); + + $groups = array(); + foreach ($this->_values as $group) + { + foreach ($group as $offset => $value) + { + if ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE) + { + // Quote the value, it is not a parameter + $group[$offset] = $db->quote($value); + } + } + + $groups[] = '('.implode(', ', $group).')'; + } + + // Add the values + $query .= 'VALUES '.implode(', ', $groups); + } + else + { + // Add the sub-query + $query .= (string) $this->_values; + } + + $this->_sql = $query; + + return parent::compile($db);; + } + + public function reset() + { + $this->_table = NULL; + + $this->_columns = + $this->_values = array(); + + $this->_parameters = array(); + + $this->_sql = NULL; + + return $this; + } + +} // End Database_Query_Builder_Insert 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 new file mode 100644 index 0000000..03448c7 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/query/builder/join.php @@ -0,0 +1,144 @@ +_table = $table; + + if ($type !== NULL) + { + // Set the JOIN type + $this->_type = (string) $type; + } + } + + /** + * Adds a new condition for joining. + * + * @param mixed column name or array($column, $alias) or object + * @param string logic operator + * @param mixed column name or array($column, $alias) or object + * @return $this + */ + public function on($c1, $op, $c2) + { + if ( ! empty($this->_using)) + { + throw new Kohana_Exception('JOIN ... ON ... cannot be combined with JOIN ... USING ...'); + } + + $this->_on[] = array($c1, $op, $c2); + + return $this; + } + + /** + * Adds a new condition for joining. + * + * @param string column name + * @param ... + * @return $this + */ + public function using($columns) + { + if ( ! empty($this->_on)) + { + throw new Kohana_Exception('JOIN ... ON ... cannot be combined with JOIN ... USING ...'); + } + + $columns = func_get_args(); + + $this->_using = array_merge($this->_using, $columns); + + return $this; + } + + /** + * Compile the SQL partial for a JOIN statement and return it. + * + * @param object Database instance + * @return string + */ + public function compile(Database $db) + { + if ($this->_type) + { + $sql = strtoupper($this->_type).' JOIN'; + } + else + { + $sql = 'JOIN'; + } + + // Quote the table name that is being joined + $sql .= ' '.$db->quote_table($this->_table); + + if ( ! empty($this->_using)) + { + // Quote and concat the columns + $sql .= ' USING ('.implode(', ', array_map(array($db, 'quote_column'), $this->_using)).')'; + } + else + { + $conditions = array(); + foreach ($this->_on as $condition) + { + // Split the condition + list($c1, $op, $c2) = $condition; + + if ($op) + { + // Make the operator uppercase and spaced + $op = ' '.strtoupper($op); + } + + // Quote each of the columns used for the condition + $conditions[] = $db->quote_column($c1).$op.' '.$db->quote_column($c2); + } + + // Concat the conditions "... AND ..." + $sql .= ' ON ('.implode(' AND ', $conditions).')'; + } + + return $sql; + } + + public function reset() + { + $this->_type = + $this->_table = NULL; + + $this->_on = array(); + } + +} // End Database_Query_Builder_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 new file mode 100644 index 0000000..4c45624 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/query/builder/select.php @@ -0,0 +1,445 @@ +_select = $columns; + } + + // Start the query with no actual SQL statement + parent::__construct(Database::SELECT, ''); + } + + /** + * Enables or disables selecting only unique columns using "SELECT DISTINCT" + * + * @param boolean enable or disable distinct columns + * @return $this + */ + public function distinct($value) + { + $this->_distinct = (bool) $value; + + return $this; + } + + /** + * Choose the columns to select from. + * + * @param mixed column name or array($column, $alias) or object + * @param ... + * @return $this + */ + public function select($columns = NULL) + { + $columns = func_get_args(); + + $this->_select = array_merge($this->_select, $columns); + + return $this; + } + + /** + * Choose the columns to select from, using an array. + * + * @param array list of column names or aliases + * @return $this + */ + public function select_array(array $columns) + { + $this->_select = array_merge($this->_select, $columns); + + return $this; + } + + /** + * Choose the tables to select "FROM ..." + * + * @param mixed table name or array($table, $alias) or object + * @param ... + * @return $this + */ + public function from($tables) + { + $tables = func_get_args(); + + $this->_from = array_merge($this->_from, $tables); + + return $this; + } + + /** + * Adds addition tables to "JOIN ...". + * + * @param mixed column name or array($column, $alias) or object + * @param string join type (LEFT, RIGHT, INNER, etc) + * @return $this + */ + public function join($table, $type = NULL) + { + $this->_join[] = $this->_last_join = new Database_Query_Builder_Join($table, $type); + + return $this; + } + + /** + * Adds "ON ..." conditions for the last created JOIN statement. + * + * @param mixed column name or array($column, $alias) or object + * @param string logic operator + * @param mixed column name or array($column, $alias) or object + * @return $this + */ + public function on($c1, $op, $c2) + { + $this->_last_join->on($c1, $op, $c2); + + return $this; + } + + /** + * Adds "USING ..." conditions for the last created JOIN statement. + * + * @param string column name + * @param ... + * @return $this + */ + public function using($columns) + { + $columns = func_get_args(); + + call_user_func_array(array($this->_last_join, 'using'), $columns); + + return $this; + } + + /** + * Creates a "GROUP BY ..." filter. + * + * @param mixed column name or array($column, $alias) or object + * @param ... + * @return $this + */ + public function group_by($columns) + { + $columns = func_get_args(); + + $this->_group_by = array_merge($this->_group_by, $columns); + + return $this; + } + + /** + * Alias of and_having() + * + * @param mixed column name or array($column, $alias) or object + * @param string logic operator + * @param mixed 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 name or array($column, $alias) or object + * @param string logic operator + * @param mixed column value + * @return $this + */ + public function and_having($column, $op, $value = NULL) + { + $this->_having[] = array('AND' => array($column, $op, $value)); + + return $this; + } + + /** + * Creates a new "OR HAVING" condition for the query. + * + * @param mixed column name or array($column, $alias) or object + * @param string logic operator + * @param mixed column value + * @return $this + */ + public function or_having($column, $op, $value = NULL) + { + $this->_having[] = array('OR' => 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() + { + $this->_having[] = array('AND' => '('); + + return $this; + } + + /** + * Opens a new "OR HAVING (...)" grouping. + * + * @return $this + */ + public function or_having_open() + { + $this->_having[] = array('OR' => '('); + + 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() + { + $this->_having[] = array('AND' => ')'); + + return $this; + } + + /** + * Closes an open "OR HAVING (...)" grouping. + * + * @return $this + */ + public function or_having_close() + { + $this->_having[] = array('OR' => ')'); + + return $this; + } + + /** + * Adds an other UNION clause. + * + * @param mixed $select if string, it must be the name of a table. Else + * must be an instance of Database_Query_Builder_Select + * @param boolean $all decides if it's an UNION or UNION ALL clause + * @return $this + */ + public function union($select, $all = TRUE) + { + if (is_string($select)) + { + $select = DB::select()->from($select); + } + if ( ! $select instanceof Database_Query_Builder_Select) + throw new Kohana_Exception('first parameter must be a string or an instance of Database_Query_Builder_Select'); + $this->_union []= array('select' => $select, 'all' => $all); + return $this; + } + + /** + * Start returning results after "OFFSET ..." + * + * @param integer starting result number + * @return $this + */ + public function offset($number) + { + $this->_offset = (int) $number; + + return $this; + } + + /** + * Compile the SQL query and return it. + * + * @param object Database instance + * @return string + */ + public function compile(Database $db) + { + // Callback to quote columns + $quote_column = array($db, 'quote_column'); + + // Callback to quote tables + $quote_table = array($db, 'quote_table'); + + // Start a selection query + $query = 'SELECT '; + + if ($this->_distinct === TRUE) + { + // Select only unique results + $query .= 'DISTINCT '; + } + + if (empty($this->_select)) + { + // Select all columns + $query .= '*'; + } + else + { + // Select all columns + $query .= implode(', ', array_unique(array_map($quote_column, $this->_select))); + } + + if ( ! empty($this->_from)) + { + // Set tables to select from + $query .= ' FROM '.implode(', ', array_unique(array_map($quote_table, $this->_from))); + } + + if ( ! empty($this->_join)) + { + // Add tables to join + $query .= ' '.$this->_compile_join($db, $this->_join); + } + + if ( ! empty($this->_where)) + { + // Add selection conditions + $query .= ' WHERE '.$this->_compile_conditions($db, $this->_where); + } + + if ( ! empty($this->_group_by)) + { + // Add sorting + $query .= ' GROUP BY '.implode(', ', array_map($quote_column, $this->_group_by)); + } + + if ( ! empty($this->_having)) + { + // Add filtering conditions + $query .= ' HAVING '.$this->_compile_conditions($db, $this->_having); + } + + if ( ! empty($this->_order_by)) + { + // Add sorting + $query .= ' '.$this->_compile_order_by($db, $this->_order_by); + } + + if ($this->_limit !== NULL) + { + // Add limiting + $query .= ' LIMIT '.$this->_limit; + } + + if ($this->_offset !== NULL) + { + // Add offsets + $query .= ' OFFSET '.$this->_offset; + } + + if ( ! empty($this->_union)) + { + foreach ($this->_union as $u) { + $query .= ' UNION '; + if ($u['all'] === TRUE) + { + $query .= 'ALL '; + } + $query .= $u['select']->compile($db); + } + } + + $this->_sql = $query; + + return parent::compile($db); + } + + public function reset() + { + $this->_select = + $this->_from = + $this->_join = + $this->_where = + $this->_group_by = + $this->_having = + $this->_order_by = + $this->_union = array(); + + $this->_distinct = FALSE; + + $this->_limit = + $this->_offset = + $this->_last_join = NULL; + + $this->_parameters = array(); + + $this->_sql = NULL; + + return $this; + } + +} // 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 new file mode 100644 index 0000000..4658e6a --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/query/builder/update.php @@ -0,0 +1,134 @@ +_table = $table; + } + + // Start the query with no SQL + return parent::__construct(Database::UPDATE, ''); + } + + /** + * Sets the table to update. + * + * @param mixed table name or array($table, $alias) or object + * @return $this + */ + public function table($table) + { + $this->_table = $table; + + return $this; + } + + /** + * Set the values to update with an associative array. + * + * @param array associative (column => value) list + * @return $this + */ + public function set(array $pairs) + { + foreach ($pairs as $column => $value) + { + $this->_set[] = array($column, $value); + } + + return $this; + } + + /** + * Set the value of a single column. + * + * @param mixed table name or array($table, $alias) or object + * @param mixed column value + * @return $this + */ + public function value($column, $value) + { + $this->_set[] = array($column, $value); + + return $this; + } + + /** + * Compile the SQL query and return it. + * + * @param object Database instance + * @return string + */ + public function compile(Database $db) + { + // Start an update query + $query = 'UPDATE '.$db->quote_table($this->_table); + + // Add the columns to update + $query .= ' SET '.$this->_compile_set($db, $this->_set); + + if ( ! empty($this->_where)) + { + // Add selection conditions + $query .= ' WHERE '.$this->_compile_conditions($db, $this->_where); + } + + if ( ! empty($this->_order_by)) + { + // Add sorting + $query .= ' '.$this->_compile_order_by($db, $this->_order_by); + } + + if ($this->_limit !== NULL) + { + // Add limiting + $query .= ' LIMIT '.$this->_limit; + } + + $this->_sql = $query; + + return parent::compile($db); + } + + public function reset() + { + $this->_table = NULL; + + $this->_set = + $this->_where = array(); + + $this->_limit = NULL; + + $this->_parameters = array(); + + $this->_sql = NULL; + + return $this; + } + + +} // End Database_Query_Builder_Update 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 new file mode 100644 index 0000000..aeece75 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/query/builder/where.php @@ -0,0 +1,160 @@ +and_where($column, $op, $value); + } + + /** + * Creates a new "AND WHERE" condition for the query. + * + * @param mixed column name or array($column, $alias) or object + * @param string logic operator + * @param mixed column value + * @return $this + */ + public function and_where($column, $op, $value) + { + $this->_where[] = array('AND' => array($column, $op, $value)); + + return $this; + } + + /** + * Creates a new "OR WHERE" condition for the query. + * + * @param mixed column name or array($column, $alias) or object + * @param string logic operator + * @param mixed column value + * @return $this + */ + public function or_where($column, $op, $value) + { + $this->_where[] = array('OR' => 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() + { + $this->_where[] = array('AND' => '('); + + return $this; + } + + /** + * Opens a new "OR WHERE (...)" grouping. + * + * @return $this + */ + public function or_where_open() + { + $this->_where[] = array('OR' => '('); + + 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() + { + $this->_where[] = array('AND' => ')'); + + return $this; + } + + /** + * Closes an open "OR WHERE (...)" grouping. + * + * @return $this + */ + public function or_where_close() + { + $this->_where[] = array('OR' => ')'); + + return $this; + } + + /** + * Applies sorting with "ORDER BY ..." + * + * @param mixed column name or array($column, $alias) or object + * @param string direction of sorting + * @return $this + */ + public function order_by($column, $direction = NULL) + { + $this->_order_by[] = array($column, $direction); + + return $this; + } + + /** + * Return up to "LIMIT ..." results + * + * @param integer maximum results to return + * @return $this + */ + public function limit($number) + { + $this->_limit = (int) $number; + + return $this; + } + +} // End Database_Query_Builder_Where diff --git a/includes/kohana/modules/database/classes/kohana/database/result.php b/includes/kohana/modules/database/classes/kohana/database/result.php new file mode 100644 index 0000000..6ddd458 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/result.php @@ -0,0 +1,331 @@ +_result = $result; + + // Store the SQL locally + $this->_query = $sql; + + if (is_object($as_object)) + { + // Get the object class name + $as_object = get_class($as_object); + } + + // Results as objects or associative arrays + $this->_as_object = $as_object; + + if ($params) + { + // Object constructor params + $this->_object_params = $params; + } + } + + /** + * Result destruction cleans up all open result sets. + * + * @return void + */ + abstract public function __destruct(); + + /** + * Get a cached database result from the current result iterator. + * + * $cachable = serialize($result->cached()); + * + * @return Database_Result_Cached + * @since 3.0.5 + */ + public function cached() + { + return new Database_Result_Cached($this->as_array(), $this->_query, $this->_as_object); + } + + /** + * Return all of the rows in the result as an array. + * + * // Indexed array of all rows + * $rows = $result->as_array(); + * + * // Associative array of rows by "id" + * $rows = $result->as_array('id'); + * + * // Associative array of rows, "id" => "name" + * $rows = $result->as_array('id', 'name'); + * + * @param string column for associative keys + * @param string column for values + * @return array + */ + public function as_array($key = NULL, $value = NULL) + { + $results = array(); + + if ($key === NULL AND $value === NULL) + { + // Indexed rows + + foreach ($this as $row) + { + $results[] = $row; + } + } + elseif ($key === NULL) + { + // Indexed columns + + if ($this->_as_object) + { + foreach ($this as $row) + { + $results[] = $row->$value; + } + } + else + { + foreach ($this as $row) + { + $results[] = $row[$value]; + } + } + } + elseif ($value === NULL) + { + // Associative rows + + if ($this->_as_object) + { + foreach ($this as $row) + { + $results[$row->$key] = $row; + } + } + else + { + foreach ($this as $row) + { + $results[$row[$key]] = $row; + } + } + } + else + { + // Associative columns + + if ($this->_as_object) + { + foreach ($this as $row) + { + $results[$row->$key] = $row->$value; + } + } + else + { + foreach ($this as $row) + { + $results[$row[$key]] = $row[$value]; + } + } + } + + $this->rewind(); + + return $results; + } + + /** + * Return the named column from the current row. + * + * // Get the "id" value + * $id = $result->get('id'); + * + * @param string column to get + * @param mixed default value if the column does not exist + * @return mixed + */ + public function get($name, $default = NULL) + { + $row = $this->current(); + + if ($this->_as_object) + { + if (isset($row->$name)) + return $row->$name; + } + else + { + if (isset($row[$name])) + return $row[$name]; + } + + return $default; + } + + /** + * Implements [Countable::count], returns the total number of rows. + * + * echo count($result); + * + * @return integer + */ + public function count() + { + return $this->_total_rows; + } + + /** + * Implements [ArrayAccess::offsetExists], determines if row exists. + * + * if (isset($result[10])) + * { + * // Row 10 exists + * } + * + * @return boolean + */ + public function offsetExists($offset) + { + return ($offset >= 0 AND $offset < $this->_total_rows); + } + + /** + * Implements [ArrayAccess::offsetGet], gets a given row. + * + * $row = $result[10]; + * + * @return mixed + */ + public function offsetGet($offset) + { + if ( ! $this->seek($offset)) + return NULL; + + return $this->current(); + } + + /** + * Implements [ArrayAccess::offsetSet], throws an error. + * + * [!!] You cannot modify a database result. + * + * @return void + * @throws Kohana_Exception + */ + final public function offsetSet($offset, $value) + { + throw new Kohana_Exception('Database results are read-only'); + } + + /** + * Implements [ArrayAccess::offsetUnset], throws an error. + * + * [!!] You cannot modify a database result. + * + * @return void + * @throws Kohana_Exception + */ + final public function offsetUnset($offset) + { + throw new Kohana_Exception('Database results are read-only'); + } + + /** + * Implements [Iterator::key], returns the current row number. + * + * echo key($result); + * + * @return integer + */ + public function key() + { + return $this->_current_row; + } + + /** + * Implements [Iterator::next], moves to the next row. + * + * next($result); + * + * @return $this + */ + public function next() + { + ++$this->_current_row; + return $this; + } + + /** + * Implements [Iterator::prev], moves to the previous row. + * + * prev($result); + * + * @return $this + */ + public function prev() + { + --$this->_current_row; + return $this; + } + + /** + * Implements [Iterator::rewind], sets the current row to zero. + * + * rewind($result); + * + * @return $this + */ + public function rewind() + { + $this->_current_row = 0; + return $this; + } + + /** + * Implements [Iterator::valid], checks if the current row exists. + * + * [!!] This method is only used internally. + * + * @return boolean + */ + public function valid() + { + return $this->offsetExists($this->_current_row); + } + +} // End Database_Result diff --git a/includes/kohana/modules/database/classes/kohana/database/result/cached.php b/includes/kohana/modules/database/classes/kohana/database/result/cached.php new file mode 100644 index 0000000..ee8659a --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/database/result/cached.php @@ -0,0 +1,51 @@ +_total_rows = count($result); + } + + public function __destruct() + { + // Cached results do not use resources + } + + public function cached() + { + return $this; + } + + public function seek($offset) + { + if ($this->offsetExists($offset)) + { + $this->_current_row = $offset; + + return TRUE; + } + else + { + return FALSE; + } + } + + public function current() + { + // Return an array of the row + return $this->valid() ? $this->_result[$this->_current_row] : NULL; + } + +} // End Database_Result_Cached diff --git a/includes/kohana/modules/database/classes/kohana/db.php b/includes/kohana/modules/database/classes/kohana/db.php new file mode 100644 index 0000000..b6e513f --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/db.php @@ -0,0 +1,139 @@ +[`DB::select_array()`](#select_array) | [Database_Query_Builder_Select] + * [`DB::update()`](#update) | [Database_Query_Builder_Update] + * [`DB::delete()`](#delete) | [Database_Query_Builder_Delete] + * [`DB::expr()`](#expr) | [Database_Expression] + * + * You pass the same parameters to these functions as you pass to the objects they return. + * + * @package Kohana/Database + * @category Base + * @author Kohana Team + * @copyright (c) 2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Kohana_DB { + + /** + * Create a new [Database_Query] of the given type. + * + * // Create a new SELECT query + * $query = DB::query(Database::SELECT, 'SELECT * FROM users'); + * + * // Create a new DELETE query + * $query = DB::query(Database::DELETE, 'DELETE FROM users WHERE id = 5'); + * + * Specifying the type changes the returned result. When using + * `Database::SELECT`, a [Database_Query_Result] will be returned. + * `Database::INSERT` queries will return the insert id and number of rows. + * For all other queries, the number of affected rows is returned. + * + * @param integer type: Database::SELECT, Database::UPDATE, etc + * @param string SQL statement + * @return Database_Query + */ + public static function query($type, $sql) + { + return new Database_Query($type, $sql); + } + + /** + * Create a new [Database_Query_Builder_Select]. Each argument will be + * treated as a column. To generate a `foo AS bar` alias, use an array. + * + * // SELECT id, username + * $query = DB::select('id', 'username'); + * + * // SELECT id AS user_id + * $query = DB::select(array('id', 'user_id')); + * + * @param mixed column name or array($column, $alias) or object + * @param ... + * @return Database_Query_Builder_Select + */ + public static function select($columns = NULL) + { + return new Database_Query_Builder_Select(func_get_args()); + } + + /** + * Create a new [Database_Query_Builder_Select] from an array of columns. + * + * // SELECT id, username + * $query = DB::select_array(array('id', 'username')); + * + * @param array columns to select + * @return Database_Query_Builder_Select + */ + public static function select_array(array $columns = NULL) + { + return new Database_Query_Builder_Select($columns); + } + + /** + * Create a new [Database_Query_Builder_Insert]. + * + * // INSERT INTO users (id, username) + * $query = DB::insert('users', array('id', 'username')); + * + * @param string table to insert into + * @param array list of column names or array($column, $alias) or object + * @return Database_Query_Builder_Insert + */ + public static function insert($table = NULL, array $columns = NULL) + { + return new Database_Query_Builder_Insert($table, $columns); + } + + /** + * Create a new [Database_Query_Builder_Update]. + * + * // UPDATE users + * $query = DB::update('users'); + * + * @param string table to update + * @return Database_Query_Builder_Update + */ + public static function update($table = NULL) + { + return new Database_Query_Builder_Update($table); + } + + /** + * Create a new [Database_Query_Builder_Delete]. + * + * // DELETE FROM users + * $query = DB::delete('users'); + * + * @param string table to delete from + * @return Database_Query_Builder_Delete + */ + public static function delete($table = NULL) + { + return new Database_Query_Builder_Delete($table); + } + + /** + * Create a new [Database_Expression] which is not escaped. An expression + * is the only way to use SQL functions within query builders. + * + * $expression = DB::expr('COUNT(users.id)'); + * $query = DB::update('users')->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 + * @return Database_Expression + */ + public static function expr($string) + { + return new Database_Expression($string); + } + +} // End DB diff --git a/includes/kohana/modules/database/classes/kohana/model/database.php b/includes/kohana/modules/database/classes/kohana/model/database.php new file mode 100644 index 0000000..d5bc0e3 --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/model/database.php @@ -0,0 +1,58 @@ +_db = $db; + } + + if (is_string($this->_db)) + { + // Load the database + $this->_db = Database::instance($this->_db); + } + } + +} // End Model diff --git a/includes/kohana/modules/database/classes/kohana/session/database.php b/includes/kohana/modules/database/classes/kohana/session/database.php new file mode 100644 index 0000000..1a381cd --- /dev/null +++ b/includes/kohana/modules/database/classes/kohana/session/database.php @@ -0,0 +1,229 @@ + 'session_id', + 'last_active' => 'last_active', + 'contents' => 'contents' + ); + + // Garbage collection requests + protected $_gc = 500; + + // The current session id + protected $_session_id; + + // The old session id + protected $_update_id; + + public function __construct(array $config = NULL, $id = NULL) + { + if ( ! isset($config['group'])) + { + // Use the default group + $config['group'] = 'default'; + } + + // Load the database + $this->_db = Database::instance($config['group']); + + if (isset($config['table'])) + { + // Set the table name + $this->_table = (string) $config['table']; + } + + if (isset($config['gc'])) + { + // Set the gc chance + $this->_gc = (int) $config['gc']; + } + + if (isset($config['columns'])) + { + // Overload column names + $this->_columns = $config['columns']; + } + + parent::__construct($config, $id); + + if (mt_rand(0, $this->_gc) === $this->_gc) + { + // Run garbage collection + // This will average out to run once every X requests + $this->_gc(); + } + } + + public function id() + { + return $this->_session_id; + } + + protected function _read($id = NULL) + { + if ($id OR $id = Cookie::get($this->_name)) + { + $result = DB::select(array($this->_columns['contents'], 'contents')) + ->from($this->_table) + ->where($this->_columns['session_id'], '=', ':id') + ->limit(1) + ->param(':id', $id) + ->execute($this->_db); + + if ($result->count()) + { + // Set the current session id + $this->_session_id = $this->_update_id = $id; + + // Return the contents + return $result->get('contents'); + } + } + + // Create a new session id + $this->_regenerate(); + + return NULL; + } + + protected function _regenerate() + { + // Create the query to find an ID + $query = DB::select($this->_columns['session_id']) + ->from($this->_table) + ->where($this->_columns['session_id'], '=', ':id') + ->limit(1) + ->bind(':id', $id); + + do + { + // Create a new session id + $id = str_replace('.', '-', uniqid(NULL, TRUE)); + + // Get the the id from the database + $result = $query->execute($this->_db); + } + while ($result->count()); + + return $this->_session_id = $id; + } + + protected function _write() + { + if ($this->_update_id === NULL) + { + // Insert a new row + $query = DB::insert($this->_table, $this->_columns) + ->values(array(':new_id', ':active', ':contents')); + } + else + { + // Update the row + $query = DB::update($this->_table) + ->value($this->_columns['last_active'], ':active') + ->value($this->_columns['contents'], ':contents') + ->where($this->_columns['session_id'], '=', ':old_id'); + + if ($this->_update_id !== $this->_session_id) + { + // Also update the session id + $query->value($this->_columns['session_id'], ':new_id'); + } + } + + $query + ->param(':new_id', $this->_session_id) + ->param(':old_id', $this->_update_id) + ->param(':active', $this->_data['last_active']) + ->param(':contents', $this->__toString()); + + // Execute the query + $query->execute($this->_db); + + // The update and the session id are now the same + $this->_update_id = $this->_session_id; + + // Update the cookie with the new session id + Cookie::set($this->_name, $this->_session_id, $this->_lifetime); + + return TRUE; + } + + protected function _destroy() + { + if ($this->_update_id === NULL) + { + // Session has not been created yet + return TRUE; + } + + // Delete the current session + $query = DB::delete($this->_table) + ->where($this->_columns['session_id'], '=', ':id') + ->param(':id', $this->_update_id); + + try + { + // Execute the query + $query->execute($this->_db); + + // Delete the cookie + Cookie::delete($this->_name); + } + catch (Exception $e) + { + // An error occurred, the session has not been deleted + return FALSE; + } + + return TRUE; + } + + protected function _gc() + { + if ($this->_lifetime) + { + // Expire sessions when their lifetime is up + $expires = $this->_lifetime; + } + else + { + // Expire sessions after one month + $expires = Date::MONTH; + } + + // Delete all sessions that have expired + DB::delete($this->_table) + ->where($this->_columns['last_active'], '<', ':time') + ->param(':time', time() - $expires) + ->execute($this->_db); + } + +} // End Session_Database diff --git a/includes/kohana/modules/database/classes/model/database.php b/includes/kohana/modules/database/classes/model/database.php new file mode 100644 index 0000000..e26ea12 --- /dev/null +++ b/includes/kohana/modules/database/classes/model/database.php @@ -0,0 +1,3 @@ + array + ( + 'type' => 'mysql', + 'connection' => array( + /** + * The following options are available for MySQL: + * + * string hostname server hostname, or socket + * string database database name + * string username database username + * string password database password + * boolean persistent use persistent connections? + * + * Ports and sockets may be appended to the hostname. + */ + 'hostname' => 'localhost', + 'database' => 'kohana', + 'username' => FALSE, + 'password' => FALSE, + 'persistent' => FALSE, + ), + 'table_prefix' => '', + 'charset' => 'utf8', + 'caching' => FALSE, + 'profiling' => TRUE, + ), + 'alternate' => array( + 'type' => 'pdo', + 'connection' => array( + /** + * The following options are available for PDO: + * + * string dsn Data Source Name + * string username database username + * string password database password + * boolean persistent use persistent connections? + */ + 'dsn' => 'mysql:host=localhost;dbname=kohana', + 'username' => 'root', + 'password' => 'r00tdb', + 'persistent' => FALSE, + ), + /** + * The following extra options are available for PDO: + * + * string identifier set the escaping identifier + */ + '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 new file mode 100644 index 0000000..a4229c7 --- /dev/null +++ b/includes/kohana/modules/database/config/session.php @@ -0,0 +1,27 @@ + array( + /** + * Database settings for session storage. + * + * string group configuation group name + * string table session table name + * integer gc number of requests before gc is invoked + * columns array custom column names + */ + 'group' => 'default', + 'table' => 'sessions', + 'gc' => 500, + 'columns' => array( + /** + * session_id: session identifier + * last_active: timestamp of the last activity + * contents: serialized session data + */ + 'session_id' => 'session_id', + 'last_active' => 'last_active', + 'contents' => 'contents' + ), + ), +); diff --git a/includes/kohana/modules/database/config/userguide.php b/includes/kohana/modules/database/config/userguide.php new file mode 100644 index 0000000..36b79a0 --- /dev/null +++ b/includes/kohana/modules/database/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' + 'database' => array( + + // Whether this modules userguide pages should be shown + 'enabled' => TRUE, + + // The name that should show up on the userguide index page + 'name' => 'Database', + + // A short description of this module, shown on the index page + 'description' => 'Database agnostic querying and result management.', + + // Copyright message, shown in the footer for this module + 'copyright' => '© 2008–2010 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 new file mode 100644 index 0000000..6879c0a --- /dev/null +++ b/includes/kohana/modules/database/guide/database/config.md @@ -0,0 +1,118 @@ +# Configuration + +The default config file is located in `MODPATH/database/config/database.php`. You should copy this file to `APPPATH/config/database.php` and make changes there, in keeping with the [cascading filesystem](../kohana/files). + +The database configuration file contains an array of configuration groups. The structure of each database configuration group, called an "instance", looks like this: + + string INSTANCE_NAME => array( + 'type' => string DATABASE_TYPE, + 'connection' => array CONNECTION_ARRAY, + 'table_prefix' => string TABLE_PREFIX, + 'charset' => string CHARACTER_SET, + 'profiling' => boolean QUERY_PROFILING, + ), + +Understanding each of these settings is important. + +INSTANCE_NAME +: Connections can be named anything you want, but you should always have at least one connection called "default". + +DATABASE_TYPE +: One of the installed database drivers. Kohana comes with "mysql" and "pdo" drivers. Drivers must extend the Database class. + +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. + +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 + +The example file below shows 2 MySQL connections, one local and one remote. + + return array + ( + 'default' => array + ( + 'type' => 'mysql', + 'connection' => array( + 'hostname' => 'localhost', + 'username' => 'dbuser', + 'password' => 'mypassword', + 'persistent' => FALSE, + 'database' => 'my_db_name', + ), + 'table_prefix' => '', + 'charset' => 'utf8', + 'profiling' => TRUE, + ), + 'remote' => array( + 'type' => 'mysql', + 'connection' => array( + 'hostname' => '55.55.55.55', + 'username' => 'remote_user', + 'password' => 'mypassword', + 'persistent' => FALSE, + 'database' => 'my_remote_db_name', + ), + 'table_prefix' => '', + 'charset' => 'utf8', + 'profiling' => TRUE, + ), + ); + +## Connections and Instances + +Each configuration group is referred to as a database instance. Each instance can be accessed by calling [Database::instance]. If you don't provide a parameter, the default instance is used. + + // This would connect to the database defined as 'default' + $default = Database::instance(); + + // This would connect to the database defined as 'remote' + $remote = Database::instance('remote'); + +To disconnect the database, simply destroy the object: + + unset($default) + + // Or + + unset(Database::$instances['default']); + +If you want to disconnect all of the database instances at once: + + Database::$instances = array(); + +## Connection Settings + +Every database driver has different connection settings. + +### MySQL + +A [MySQL database](http://www.php.net/manual/en/book.mysql.php) can accept the following options in the `connection` array: + +Type | Option | Description | Default value +----------|------------|----------------------------| ------------------------- +`string` | hostname | Hostname of the database | `localhost` +`integer` | port | Port number | `NULL` +`string` | socket | UNIX socket | `NULL` +`string` | username | Database username | `NULL` +`string` | password | Database password | `NULL` +`boolean` | persistent | Persistent connections | `FALSE` +`string` | database | Database name | `kohana` + +### PDO + +A [PDO database](http://php.net/manual/en/book.pdo.php) can accept these options in the `connection` array: + +Type | Option | Description | Default value +----------|------------|----------------------------| ------------------------- +`string` | dsn | PDO data source identifier | `localhost` +`string` | username | Database username | `NULL` +`string` | password | Database password | `NULL` +`boolean` | persistent | Persistent connections | `FALSE` + +[!!] 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 new file mode 100644 index 0000000..4849823 --- /dev/null +++ b/includes/kohana/modules/database/guide/database/examples.md @@ -0,0 +1,52 @@ +# Examples + +Here are some "real world" examples of using the database library to construct your queries and use the results. + +## Examples of Prepared Statements + +TODO: 4-6 examples of prepared statements of varying complexity, including a good bind() example. + +## Pagination and search/filter + +In this example, we loop through an array of whitelisted input fields and for each allowed non-empty value we add it to the search query. We make a clone of the query and then execute that query to count the total number of results. The count is then passed to the [Pagination](../pagination) class to determine the search offset. The last few lines search with Pagination's items_per_page and offset values to return a page of results based on the current page the user is on. + + $query = DB::select()->from('users'); + + //only search for these fields + $form_inputs = array('first_name', 'last_name', 'email'); + foreach ($form_inputs as $name) + { + $value = Arr::get($_GET, $name, FALSE); + if ($value !== FALSE AND $value != '') + { + $query->where($name, 'like', '%'.$value.'%'); + } + } + + //copy the query & execute it + $pagination_query = clone $query; + $count = $pagination_query->select('COUNT("*") AS mycount')->execute()->get('mycount'); + + //pass the total item count to Pagination + $config = Kohana::config('pagination'); + $pagination = Pagination::factory(array( + 'total_items' => $count, + 'current_page' => array('source' => 'route', 'key' => 'page'), + 'items_per_page' => 20, + 'view' => 'pagination/pretty', + 'auto_hide' => TRUE, + )); + $page_links = $pagination->render(); + + //search for results starting at the offset calculated by the Pagination class + $query->order_by('last_name', 'asc') + ->order_by('first_name', 'asc') + ->limit($pagination->items_per_page) + ->offset($pagination->offset); + $results = $query->execute()->as_array(); + +## Having + +TODO: example goes here + +[!!] We could use more examples on this page. \ No newline at end of file diff --git a/includes/kohana/modules/database/guide/database/index.md b/includes/kohana/modules/database/guide/database/index.md new file mode 100644 index 0000000..162651d --- /dev/null +++ b/includes/kohana/modules/database/guide/database/index.md @@ -0,0 +1,17 @@ +# Database + +Kohana 3.0 comes with a robust module for working with databases. By default, the database module supports drivers for [MySQL](http://php.net/mysql) and [PDO](http://php.net/pdo), but new drivers can be made for other database servers. + +The database module is included with the Kohana 3.0 install, 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 database module like so: + + Kohana::modules(array( + ... + 'database' => MODPATH.'database', + ... + )); + +Next, you will then need to [configure](config) the database module to connect to your database. + +Once that is done then you can make [queries](query) and use the [results](results). + +The database module also provides a [config driver](../api/Kohana_Config_Database) (for storing [configuration](../kohana/files/config) in the database) and a [session driver](Session_Database). diff --git a/includes/kohana/modules/database/guide/database/menu.md b/includes/kohana/modules/database/guide/database/menu.md new file mode 100644 index 0000000..e3f2c7a --- /dev/null +++ b/includes/kohana/modules/database/guide/database/menu.md @@ -0,0 +1,7 @@ +## [Database]() +- [Configuration](config) +- [Querying](query) + - [Prepared Statements](query/prepared) + - [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 new file mode 100644 index 0000000..2e9bd41 --- /dev/null +++ b/includes/kohana/modules/database/guide/database/query.md @@ -0,0 +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). + +[!!] 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 new file mode 100644 index 0000000..ec11b1a --- /dev/null +++ b/includes/kohana/modules/database/guide/database/query/builder.md @@ -0,0 +1,253 @@ +# Query Builder + +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: + + $query = DB::select(); + +Query Builder methods return a reference to itself so that method chaining may be used. Select queries ussually require a table and they are referenced using the `from()` method. The `from()` method takes one parameter which can be the table name (string), an array of two strings (table name and alias), or an object (See Subqueries in the Advanced Queries section below). + + $query = DB::select()->from('users'); + +Limiting the results of queries is done using the `where()`, `and_where()` and `or_where()` methods. These methods take three parameters: a column, an operator, and a value. + + $query = DB::select()->from('users')->where('username', '=', 'john'); + +Multiple `where()` methods may be used to string together multiple clauses connected by the boolean operator in the method's prefix. The `where()` method is a wrapper that just calls `and_where()`. + + $query = DB::select()->from('users')->where('username', '=', 'john')->or_where('username', '=', 'jane'); + +You can use any operator you want. Examples include `IN`, `BETWEEN`, `>`, `=<`, `!=`, etc. Use an array for operators that require more than one value. + + $query = DB::select()->from('users')->where('logins', '<=', 1); + + $query = DB::select()->from('users')->where('logins', '>', 50); + + $query = DB::select()->from('users')->where('username', 'IN', array('john','mark','matt')); + + $query = DB::select()->from('users')->where('joindate', 'BETWEEN', array($then, $now)); + +By default, [DB::select] will select all columns (`SELECT * ...`), but you can also specify which columns you want returned by passing parameters to [DB::select]: + + $query = DB::select('username', 'password')->from('users')->where('username', '=', 'john'); + +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); + // Should display: + // SELECT `username`, `password` FROM `users` WHERE `username` = 'john' + +Notice how the column and table names are automatically escaped, as well as the values? This is one of the key benefits of using the query builder. + +### Select - AS (column aliases) + +It is also possible to create `AS` aliases when selecting, by passing an array as each parameter to [DB::select]: + + $query = DB::select(array('username', 'u'), array('password', 'p'))->from('users'); + +This query would generate the following SQL: + + SELECT `username` AS `u`, `password` AS `p` FROM `users` + +### Select - DISTINCT + +Unique column values may be turned on or off (default) by passing TRUE or FALSE, respectively, to the `distinct()` method. + + $query = DB::select('username')->distinct(TRUE)->from('posts'); + +This query would generate the following SQL: + + SELECT DISTINCT `username` FROM `posts` + +### Select - LIMIT & OFFSET + +When querying large sets of data, it is often better to limit the results and page through the data one chunk at a time. This is done using the `limit()` and `offset()` methods. + + $query = DB::select()->from(`posts`)->limit(10)->offset(30); + +This query would generate the following SQL: + + SELECT * FROM `posts` LIMIT 10 OFFSET 30 + +### Select - ORDER BY + +Often you will want the results in a particular order and rather than sorting the results, it's better to have the results returned to you in the correct order. You can do this by using the order_by() method. It takes the column name and an optional direction string as the parameters. Multiple `order_by()` methods can be used to add additional sorting capability. + + $query = DB::select()->from(`posts`)->order_by(`published`, `DESC`); + +This query would generate the following SQL: + + SELECT * FROM `posts` ORDER BY `published` DESC + +[!!] For a complete list of methods available while building a select query see [Database_Query_Builder_Select]. + +## Insert + +To create records into the database, use [DB::insert] to create an INSERT query, using `values()` to pass in the data: + + $query = DB::insert('users', array('username', 'password'))->values(array('fred', 'p@5sW0Rd')); + +This query would generate the following SQL: + + INSERT INTO `users` (`username`, `password`) VALUES ('fred', 'p@5sW0Rd') + +[!!] For a complete list of methods available while building an insert query see [Database_Query_Builder_Insert]. + +## Update + +To modify an existing record, use [DB::update] to create an UPDATE query: + + $query = DB::update('users')->set(array('username' => 'jane'))->where('username', '=', 'john'); + +This query would generate the following SQL: + + UPDATE `users` SET `username` = 'jane' WHERE `username` = 'john' + +[!!] For a complete list of methods available while building an update query see [Database_Query_Builder_Update]. + +## Delete + +To remove an existing record, use [DB::delete] to create a DELETE query: + + $query = DB::delete('users')->where('username', 'IN', array('john', 'jane')); + +This query would generate the following SQL: + + DELETE FROM `users` WHERE `username` IN ('john', 'jane') + +[!!] For a complete list of methods available while building a delete query see [Database_Query_Builder_Delete]. + +## Advanced Queries + +### Joins + +Multiple tables can be joined using the `join()` and `on()` methods. The `join()` method takes two parameters. The first is either a table name, an array containing the table and alias, or an object (subquery or expression). The second parameter is the join type: LEFT, RIGHT, INNER, etc. + +The `on()` method sets the conditions for the previous `join()` method and is very similar to the `where()` method in that it takes three parameters; left column (name or object), an operator, and the right column (name or object). Multiple `on()` methods may be used to supply multiple conditions and they will be appended with an 'AND' operator. + + // This query will find all the posts related to "smith" with JOIN + $query = DB::select('authors.name', 'posts.content')->from('authors')->join('posts')->on('authors.id', '=', 'posts.author_id')->where('authors.name', '=', 'smith'); + +This query would generate the following SQL: + + SELECT `authors`.`name`, `posts`.`content` FROM `authors` JOIN `posts` ON (`authors`.`id` = `posts`.`author_id`) WHERE `authors`.`name` = 'smith' + +If you want to do a LEFT, RIGHT or INNER JOIN you would do it like this `join('colum_name', 'type_of_join')`: + + // This query will find all the posts related to "smith" with LEFT JOIN + $query = DB::select()->from('authors')->join('posts', 'LEFT')->on('authors.id', '=', 'posts.author_id')->where('authors.name', '=', 'smith'); + +This query would generate the following SQL: + + SELECT `authors`.`name`, `posts`.`content` FROM `authors` LEFT JOIN `posts` ON (`authors`.`id` = `posts`.`author_id`) WHERE `authors`.`name` = 'smith' + +[!!] When joining multiple tables with similar column names, it's best to prefix the columns with the table name or table alias to avoid errors. Ambiguous column names should also be aliased so that they can be referenced easier. + +### 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: + + $query = DB::select(array('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: + + SELECT COUNT(`username`) AS `total_users` FROM `users` + +[!!] When building complex queries and you need to get a count of the total rows that will be returned, build the expression with an empty column list first. Then clone the query and add the COUNT function to one copy and the columns list to the other. This will cut down on the total lines of code and make updating the query easier. + + $query = DB::select()->from('users') + ->join('posts')->on('posts.username', '=', 'users.username') + ->where('users.active', '=', TRUE) + ->where('posts.created', '>=', $yesterday); + + $total = clone $query; + $total->select(array('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') + ->from('posts')->group_by('username')->having('total_posts', '>=', 10); + +This will generate the following query: + + SELECT `username`, COUNT(`id`) AS `total_posts` FROM `posts` GROUP BY `username` HAVING `total_posts` >= 10 + +### Subqueries + +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') + ->from('posts')->group_by('username')->having('total_posts', '>=', 10); + + $query = DB::select('profiles.*', 'posts.total_posts')->from('profiles') + ->join(array($sub, 'posts'), 'INNER')->on('profiles.username', '=', 'posts.username'); + +This will generate the following query: + + SELECT `profiles`.*, `posts`.`total_posts` FROM `profiles` INNER JOIN + ( SELECT `username`, COUNT(`id`) AS `total_posts` FROM `posts` GROUP BY `username` HAVING `total_posts` >= 10 ) AS posts + ON `profiles`.`username` = `posts`.`username` + +Insert queries can also use a select query for the input values + + $sub = DB::select('username', array('COUNT("id")', 'total_posts') + ->from('posts')->group_by('username')->having('total_posts', '>=', 10); + + $query = DB::insert('post_totals', array('username', 'posts'))->select($sub); + +This will generate the following query: + + INSERT INTO `post_totals` (`username`, `posts`) + SELECT `username`, COUNT(`id`) AS `total_posts` FROM `posts` GROUP BY `username` HAVING `total_posts` >= 10 + +### Boolean Operators and Nested Clauses + +Multiple Where and Having clauses are added to the query with Boolean operators connecting each expression. The default operator for both methods is AND which is the same as the and_ prefixed method. The OR operator can be specified by prefixing the methods with or_. Where and Having clauses can be nested or grouped by post fixing either method with _open and then followed by a method with a _close. + + $query = DB::select()->from('users') + ->where_open() + ->or_where('id', 'IN', $expired) + ->and_where_open() + ->where('last_login', '<=', $last_month) + ->or_where('last_login', 'IS', NULL) + ->and_where_close() + ->where_close() + ->and_where('removed','IS', NULL); + +This will generate the following query: + + SELECT * FROM `users` WHERE ( `id` IN (1, 2, 3, 5) OR ( `last_login` <= 1276020805 OR `last_login` IS NULL ) ) AND `removed` IS NULL + +### Database Expressions + +There are cases were you need a complex expression or other database functions, which you don't want the Query Builder to try and escape. In these cases, you will need to use a database expression created with [DB::expr]. **A database expression is taken as direct input and no escaping is performed.** + + $query = DB::update('users')->set(array('login_count' => DB::expr('login_count + 1')))->where('id', '=', $id); + +This will generate the following query, assuming `$id = 45`: + + UPDATE `users` SET `login_count` = `login_count` + 1 WHERE `id` = 45 + +Another example to calculate the distance of two geographical points: + + $query = DB::select(array(DB::expr('degrees(acos(sin(radians('.$lat.')) * sin(radians(`latitude`)) + cos(radians('.$lat.')) * cos(radians(`latitude`)) * cos(radians(abs('.$lng.' - `longitude`))))) * 69.172'), 'distance'))->from('locations'); + +[!!] You must validate or escape any user input inside of DB::expr as it will obviously not be escaped it for you. + +## Executing + +Once you are done building, you can execute the query using `execute()` and use [the results](results). + + $result = $query->execute(); + +To use a different database [config group](config) pass either the name or the config object to `execute()`. + + $result = $query->execute('config_name') \ No newline at end of file diff --git a/includes/kohana/modules/database/guide/database/query/prepared.md b/includes/kohana/modules/database/guide/database/query/prepared.md new file mode 100644 index 0000000..53611b6 --- /dev/null +++ b/includes/kohana/modules/database/guide/database/query/prepared.md @@ -0,0 +1,67 @@ +# Prepared 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: + + $query = DB::query(Database::SELECT, 'SELECT * FROM users WHERE username = :user'); + +The [DB::query] method is just a shortcut that creates a new [Database_Query] class for us, to allow method chaining. The query contains a `:user` parameter, which we will get to in a second. + +The first parameter of [DB::query] is the type of query. It should be `Database::SELECT`, `Database::INSERT`, `Database::UPDATE`, or `Database::DELETE`. This is done for compatibility reasons for drivers, and to easily determine what `execute()` should return. + +The second parameter is the query itself. Rather than trying to concatenate your query and variables together, you should make use of [Database_Query::param]. This will make your queries much easier to mantain, and will escape the values to prevent [SQL injection](http://wikipedia.org/wiki/SQL_Injection). + +## Parameters + +Our example query earlier contains a `:user` parameter, which we can assign to a value using [Database_Query::param] like so: + + $query->param(':user', 'john'); + +[!!] Parameter names can be any unique string, as they are replaced using [strtr](http://php.net/strtr). It is highly recommended to **not** use dollars signs as parameter names to prevent confusion. Colons are commonly used. + +You can also update the `:user` parameter by calling [Database_Query::param] again: + + $query->param(':user', $_GET['search']); + +If you want to set multiple parameters at once, you can use [Database_Query::parameters]. + + $query = DB::query(Database::SELECT, 'SELECT * FROM users WHERE username = :user AND status = :status'); + + $query->parameters(array( + ':user' => 'john', + ':status' => 'active', + )); + +It is also possible to bind a parameter to a variable, using a [variable reference]((http://php.net/language.references.whatdo)). This can be extremely useful when running the same query many times: + + $query = DB::query(Database::INSERT, 'INSERT INTO users (username, password) VALUES (:user, :pass)') + ->bind(':user', $username) + ->bind(':pass', $password); + + foreach ($new_users as $username => $password) + { + $query->execute(); + } + +In the above example, the variables `$username` and `$password` are changed for every loop of the `foreach` statement. When the parameter changes, it effectively changes the `:user` and `:pass` query parameters. Careful parameter binding can save a lot of code when it is used properly. + +The only difference between `param()` and `bind()` is that `bind()` passes the variable by reference rather than by assignment (copied), so future changes to the variable can be "seen" by the query. + +[!!] Although all parameters are escaped to prevent SQL injection, it is still a good idea to validate/sanitize your input. + +## Display the raw query + +If you want to display the SQL that will be executed, simply cast the object to a string: + + echo Kohana::debug((string) $query); + // Should display: + // SELECT * FROM users WHERE username = 'john' + +## Executing + +Once you have assigned something to each of the parameters, you can execute the query using `execute()` and use [the results](results). + + $result = $query->execute(); + +To use a different database [config group](config) pass either the name or the config object to `execute()`. + + $result = $query->execute('config_name') \ No newline at end of file diff --git a/includes/kohana/modules/database/guide/database/results.md b/includes/kohana/modules/database/guide/database/results.md new file mode 100644 index 0000000..653aacc --- /dev/null +++ b/includes/kohana/modules/database/guide/database/results.md @@ -0,0 +1,105 @@ +# Results + +## 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. + +## Select + +[DB::select] will return a [Database_Result] object which you can then iterate over. This example shows how you can iterate through the [Database_Result] using a foreach. + + $results = DB::select()->from('users')->where('verified', '=', 0)->execute(); + foreach($results as $user) + { + // Send reminder email to $user['email'] + echo $user['email']." needs to verify his/her account\n"; + } + +### Select - `as_object()` and `as_assoc()` + +When iterating over a result set, the default type will be an associative array with the column names or aliases as the keys. As an option, before calling `execute()`, you can specify to return the result rows as an object by using the `as_object()` method. The `as_object()` method takes one parameter, the name of the class of your choice, but will default to TRUE which uses the `stdClass`. Here is the example again using `stdClass`. + + $results = DB::select()->from('users')->where('verified', '=', 0)->as_object()->execute(); + foreach($results as $user) + { + // Send reminder email to $user->email + echo $user->email." needs to verify his/her account\n"; + } + +[!!] The method `as_assoc()` will remove the object name and return the results set back to an associative array. Since this is the default, this method is seldom required. + +### Select - `as_array()` + +Sometimes you will require the results as a pure array rather than as an object. The `Database_Result` method `as_array()` will return an array of all rows. + + $results = DB::select('id', 'email')->from('users')->execute(); + $users = $results->as_array(); + foreach($users as $user) + { + echo 'User ID: '.$user['id']; + echo 'User Email: '.$user['email']; + } + +It also accepts two parameters that can be very helpful: `$key` and `$value`. When passing a value to `$key` you will index the resulting array by the column specified. + + $results = DB::select('id', 'email')->from('users')->execute(); + $users = $results->as_array('id'); + foreach($users as $id => $user) + { + echo 'User ID: '.$id; + echo 'User Email: '.$user['email']; + } + +The second parameter, `$value`, will reference the column specified and return that value rather than the whole row. This is particularly useful when making ` +
                    +

                    Environment

                    + +
                    diff --git a/includes/kohana/modules/userguide/views/userguide/index.php b/includes/kohana/modules/userguide/views/userguide/index.php new file mode 100644 index 0000000..5764fcb --- /dev/null +++ b/includes/kohana/modules/userguide/views/userguide/index.php @@ -0,0 +1,20 @@ +

                    User Guide

                    + +

                    The following modules have userguide pages:

                    + + + + $options): ?> + +

                    + uri(array('module' => $url)), $options['name'], NULL, NULL, TRUE) ?> - + +

                    + + + + + +

                    I couldn't find any modules with userguide pages.

                    + + \ No newline at end of file diff --git a/includes/kohana/modules/userguide/views/userguide/menu.php b/includes/kohana/modules/userguide/views/userguide/menu.php new file mode 100644 index 0000000..e6537c9 --- /dev/null +++ b/includes/kohana/modules/userguide/views/userguide/menu.php @@ -0,0 +1,17 @@ +

                    Modules

                    + + + +
                      + $options): ?> + +
                    • uri(array('module' => $url)), $options['name'], NULL, NULL, TRUE) ?>
                    • + + +
                    + + + +

                    No modules.

                    + + \ No newline at end of file diff --git a/includes/kohana/modules/userguide/views/userguide/page-toc.php b/includes/kohana/modules/userguide/views/userguide/page-toc.php new file mode 100644 index 0000000..4eefcc2 --- /dev/null +++ b/includes/kohana/modules/userguide/views/userguide/page-toc.php @@ -0,0 +1,10 @@ + +
                    + + 1): ?> + + +
                    + +
                    + diff --git a/includes/kohana/modules/userguide/views/userguide/template.php b/includes/kohana/modules/userguide/views/userguide/template.php new file mode 100644 index 0000000..8868d80 --- /dev/null +++ b/includes/kohana/modules/userguide/views/userguide/template.php @@ -0,0 +1,108 @@ + + + + + +<?php echo $title ?> | Kohana <?php echo __('User Guide'); ?> + + $media) echo HTML::style($style, array('media' => $media), NULL, TRUE), "\n" ?> + + + + + + + + + +
                    +
                    +
                    +
                    + +
                    +
                    +
                    + +
                    +
                    +
                    + + + +
                    + + + Documentation comments powered by Disqus + +
                    +
                    +
                    +
                    + + + + + + + + diff --git a/includes/kohana/system/classes/arr.php b/includes/kohana/system/classes/arr.php new file mode 100644 index 0000000..a279831 --- /dev/null +++ b/includes/kohana/system/classes/arr.php @@ -0,0 +1,3 @@ + 'john.doe')); + * + * // Returns FALSE + * Arr::is_assoc('foo', 'bar'); + * + * @param array array to check + * @return boolean + */ + public static function is_assoc(array $array) + { + // Keys of the array + $keys = array_keys($array); + + // If the array keys of the keys match the keys, then the array must + // not be associative (e.g. the keys array looked like {0:0, 1:1...}). + return array_keys($keys) !== $keys; + } + + /** + * Test if a value is an array with an additional check for array-like objects. + * + * // Returns TRUE + * Arr::is_array(array()); + * Arr::is_array(new ArrayObject); + * + * // Returns FALSE + * Arr::is_array(FALSE); + * Arr::is_array('not an array!'); + * Arr::is_array(Database::instance()); + * + * @param mixed value to check + * @return boolean + */ + public static function is_array($value) + { + if (is_array($value)) + { + // Definitely an array + return TRUE; + } + else + { + // Possibly a Traversable object, functionally the same as an array + return (is_object($value) AND $value instanceof Traversable); + } + } + + /** + * Gets a value from an array using a dot separated path. + * + * // Get the value of $array['foo']['bar'] + * $value = Arr::path($array, 'foo.bar'); + * + * Using a wildcard "*" will search intermediate arrays and return an array. + * + * // Get the values of "color" in theme + * $colors = Arr::path($array, 'theme.*.color'); + * + * // Using an array of keys + * $colors = Arr::path($array, array('theme', '*', 'color')); + * + * @param array array to search + * @param mixed key path string (delimiter separated) or array of keys + * @param mixed default value if the path is not set + * @param string key path delimiter + * @return mixed + */ + public static function path($array, $path, $default = NULL, $delimiter = NULL) + { + if ( ! Arr::is_array($array)) + { + // This is not an array! + return $default; + } + + if (is_array($path)) + { + // The path has already been separated into keys + $keys = $path; + } + else + { + if (array_key_exists($path, $array)) + { + // No need to do extra processing + return $array[$path]; + } + + if ($delimiter === NULL) + { + // Use the default delimiter + $delimiter = Arr::$delimiter; + } + + // Remove starting delimiters and spaces + $path = ltrim($path, "{$delimiter} "); + + // Remove ending delimiters, spaces, and wildcards + $path = rtrim($path, "{$delimiter} *"); + + // Split the keys by delimiter + $keys = explode($delimiter, $path); + } + + do + { + $key = array_shift($keys); + + if (ctype_digit($key)) + { + // Make the key an integer + $key = (int) $key; + } + + if (isset($array[$key])) + { + if ($keys) + { + if (Arr::is_array($array[$key])) + { + // Dig down into the next part of the path + $array = $array[$key]; + } + else + { + // Unable to dig deeper + break; + } + } + else + { + // Found the path requested + return $array[$key]; + } + } + elseif ($key === '*') + { + // Handle wildcards + + $values = array(); + foreach ($array as $arr) + { + if ($value = Arr::path($arr, implode('.', $keys))) + { + $values[] = $value; + } + } + + if ($values) + { + // Found the values requested + return $values; + } + else + { + // Unable to dig deeper + break; + } + } + else + { + // Unable to dig deeper + break; + } + } + while ($keys); + + // Unable to find the value requested + return $default; + } + + /** + * Set a value on an array by path. + * + * @see Arr::path() + * @param array $array Array to update + * @param string $path Path + * @param mixed $value Value to set + * @param string $delimiter Path delimiter + */ + public static function set_path( & $array, $path, $value, $delimiter = NULL) + { + if ( ! $delimiter) + { + // Use the default delimiter + $delimiter = Arr::$delimiter; + } + + // Split the keys by delimiter + $keys = explode($delimiter, $path); + + // Set current $array to inner-most array path + while (count($keys) > 1) + { + $key = array_shift($keys); + + if (ctype_digit($key)) + { + // Make the key an integer + $key = (int) $key; + } + + if ( ! isset($array[$key])) + { + $array[$key] = array(); + } + + $array = & $array[$key]; + } + + // Set key on inner-most array + $array[array_shift($keys)] = $value; + } + + /** + * Fill an array with a range of numbers. + * + * // Fill an array with values 5, 10, 15, 20 + * $values = Arr::range(5, 20); + * + * @param integer stepping + * @param integer ending number + * @return array + */ + public static function range($step = 10, $max = 100) + { + if ($step < 1) + return array(); + + $array = array(); + for ($i = $step; $i <= $max; $i += $step) + { + $array[$i] = $i; + } + + return $array; + } + + /** + * Retrieve a single key from an array. If the key does not exist in the + * array, the default value will be returned instead. + * + * // Get the value "username" from $_POST, if it exists + * $username = Arr::get($_POST, 'username'); + * + * // Get the value "sorting" from $_GET, if it exists + * $sorting = Arr::get($_GET, 'sorting'); + * + * @param array array to extract from + * @param string key name + * @param mixed default value + * @return mixed + */ + public static function get($array, $key, $default = NULL) + { + return isset($array[$key]) ? $array[$key] : $default; + } + + /** + * Retrieves multiple keys from an array. If the key does not exist in the + * array, the default value will be added instead. + * + * // Get the values "username", "password" from $_POST + * $auth = Arr::extract($_POST, array('username', 'password')); + * + * @param array array to extract keys from + * @param array list of key names + * @param mixed default value + * @return array + */ + public static function extract($array, array $keys, $default = NULL) + { + $found = array(); + foreach ($keys as $key) + { + $found[$key] = isset($array[$key]) ? $array[$key] : $default; + } + + return $found; + } + + /** + * Retrieves muliple single-key values from a list of arrays. + * + * // Get all of the "id" values from a result + * $ids = Arr::pluck($result, 'id'); + * + * [!!] A list of arrays is an array that contains arrays, eg: array(array $a, array $b, array $c, ...) + * + * @param array list of arrays to check + * @param string key to pluck + * @return array + */ + public static function pluck($array, $key) + { + $values = array(); + + foreach ($array as $row) + { + if (isset($row[$key])) + { + // Found a value in this row + $values[] = $row[$key]; + } + } + + return $values; + } + + /** + * Adds a value to the beginning of an associative array. + * + * // Add an empty value to the start of a select list + * Arr::unshift($array, 'none', 'Select a value'); + * + * @param array array to modify + * @param string array key name + * @param mixed array value + * @return array + */ + public static function unshift( array & $array, $key, $val) + { + $array = array_reverse($array, TRUE); + $array[$key] = $val; + $array = array_reverse($array, TRUE); + + return $array; + } + + /** + * Recursive version of [array_map](http://php.net/array_map), applies the + * same callback to all elements in an array, including sub-arrays. + * + * // Apply "strip_tags" to every element in the array + * $array = Arr::map('strip_tags', $array); + * + * [!!] Unlike `array_map`, this method requires a callback and will only map + * a single array. + * + * @param mixed callback applied to every element in the array + * @param array array to map + * @return array + */ + public static function map($callback, $array) + { + foreach ($array as $key => $val) + { + if (is_array($val)) + { + $array[$key] = Arr::map($callback, $val); + } + else + { + $array[$key] = call_user_func($callback, $val); + } + } + + return $array; + } + + /** + * Merges one or more arrays recursively and preserves all keys. + * Note that this does not work the same as [array_merge_recursive](http://php.net/array_merge_recursive)! + * + * $john = array('name' => 'john', 'children' => array('fred', 'paul', 'sally', 'jane')); + * $mary = array('name' => 'mary', 'children' => array('jane')); + * + * // John and Mary are married, merge them together + * $john = Arr::merge($john, $mary); + * + * // The output of $john will now be: + * array('name' => 'mary', 'children' => array('fred', 'paul', 'sally', 'jane')) + * + * @param array initial array + * @param array array to merge + * @param array ... + * @return array + */ + public static function merge(array $a1, array $a2) + { + $result = array(); + for ($i = 0, $total = func_num_args(); $i < $total; $i++) + { + // Get the next array + $arr = func_get_arg($i); + + // Is the array associative? + $assoc = Arr::is_assoc($arr); + + foreach ($arr as $key => $val) + { + if (isset($result[$key])) + { + if (is_array($val) AND is_array($result[$key])) + { + if (Arr::is_assoc($val)) + { + // Associative arrays are merged recursively + $result[$key] = Arr::merge($result[$key], $val); + } + else + { + // Find the values that are not already present + $diff = array_diff($val, $result[$key]); + + // Indexed arrays are merged to prevent duplicates + $result[$key] = array_merge($result[$key], $diff); + } + } + else + { + if ($assoc) + { + // Associative values are replaced + $result[$key] = $val; + } + elseif ( ! in_array($val, $result, TRUE)) + { + // Indexed values are added only if they do not yet exist + $result[] = $val; + } + } + } + else + { + // New values are added + $result[$key] = $val; + } + } + } + + return $result; + } + + /** + * Overwrites an array with values from input arrays. + * Keys that do not exist in the first array will not be added! + * + * $a1 = array('name' => 'john', 'mood' => 'happy', 'food' => 'bacon'); + * $a2 = array('name' => 'jack', 'food' => 'tacos', 'drink' => 'beer'); + * + * // Overwrite the values of $a1 with $a2 + * $array = Arr::overwrite($a1, $a2); + * + * // The output of $array will now be: + * array('name' => 'jack', 'mood' => 'happy', 'food' => 'tacos') + * + * @param array master array + * @param array input arrays that will overwrite existing values + * @return array + */ + public static function overwrite($array1, $array2) + { + foreach (array_intersect_key($array2, $array1) as $key => $value) + { + $array1[$key] = $value; + } + + if (func_num_args() > 2) + { + foreach (array_slice(func_get_args(), 2) as $array2) + { + foreach (array_intersect_key($array2, $array1) as $key => $value) + { + $array1[$key] = $value; + } + } + } + + return $array1; + } + + /** + * Creates a callable function and parameter list from a string representation. + * Note that this function does not validate the callback string. + * + * // Get the callback function and parameters + * list($func, $params) = Arr::callback('Foo::bar(apple,orange)'); + * + * // Get the result of the callback + * $result = call_user_func_array($func, $params); + * + * @param string callback string + * @return array function, params + */ + public static function callback($str) + { + // Overloaded as parts are found + $command = $params = NULL; + + // command[param,param] + if (preg_match('/^([^\(]*+)\((.*)\)$/', $str, $match)) + { + // command + $command = $match[1]; + + if ($match[2] !== '') + { + // param,param + $params = preg_split('/(? array('one' => 'something'), 'two' => 'other'); + * + * // Flatten the array + * $array = Arr::flatten($array); + * + * // The array will now be + * array('one' => 'something', 'two' => 'other'); + * + * [!!] The keys of array values will be discarded. + * + * @param array array to flatten + * @return array + * @since 3.0.6 + */ + public static function flatten($array) + { + $flat = array(); + foreach ($array as $key => $value) + { + if (is_array($value)) + { + $flat += Arr::flatten($value); + } + else + { + $flat[$key] = $value; + } + } + return $flat; + } + +} // End arr diff --git a/includes/kohana/system/classes/kohana/cli.php b/includes/kohana/system/classes/kohana/cli.php new file mode 100644 index 0000000..f8a3aee --- /dev/null +++ b/includes/kohana/system/classes/kohana/cli.php @@ -0,0 +1,75 @@ +attach($reader); // Try first + * $config->attach($reader, FALSE); // Try last + * + * @param object Config_Reader instance + * @param boolean add the reader as the first used object + * @return $this + */ + public function attach(Config_Reader $reader, $first = TRUE) + { + if ($first === TRUE) + { + // Place the log reader at the top of the stack + array_unshift($this->_readers, $reader); + } + else + { + // Place the reader at the bottom of the stack + $this->_readers[] = $reader; + } + + return $this; + } + + /** + * Detach a configuration reader. + * + * $config->detach($reader); + * + * @param object Config_Reader instance + * @return $this + */ + public function detach(Config_Reader $reader) + { + if (($key = array_search($reader, $this->_readers)) !== FALSE) + { + // Remove the writer + unset($this->_readers[$key]); + } + + return $this; + } + + /** + * Load a configuration group. Searches the readers in order until the + * group is found. If the group does not exist, an empty configuration + * array will be loaded using the first reader. + * + * $array = $config->load($name); + * + * @param string configuration group name + * @return Config_Reader + * @throws Kohana_Exception + */ + public function load($group) + { + foreach ($this->_readers as $reader) + { + if ($config = $reader->load($group)) + { + // Found a reader for this configuration group + return $config; + } + } + + // Reset the iterator + reset($this->_readers); + + if ( ! is_object($config = current($this->_readers))) + { + throw new Kohana_Exception('No configuration readers attached'); + } + + // Load the reader as an empty array + return $config->load($group, array()); + } + + /** + * Copy one configuration group to all of the other readers. + * + * $config->copy($name); + * + * @param string configuration group name + * @return $this + */ + public function copy($group) + { + // Load the configuration group + $config = $this->load($group); + + foreach ($this->_readers as $reader) + { + if ($config instanceof $reader) + { + // Do not copy the config to the same group + continue; + } + + // Load the configuration object + $object = $reader->load($group, array()); + + foreach ($config as $key => $value) + { + // Copy each value in the config + $object->offsetSet($key, $value); + } + } + + return $this; + } + +} // End Kohana_Config diff --git a/includes/kohana/system/classes/kohana/config/file.php b/includes/kohana/system/classes/kohana/config/file.php new file mode 100644 index 0000000..8b792f8 --- /dev/null +++ b/includes/kohana/system/classes/kohana/config/file.php @@ -0,0 +1,60 @@ +_directory = trim($directory, '/'); + + // Load the empty array + parent::__construct(); + } + + /** + * Load and merge all of the configuration files in this group. + * + * $config->load($name); + * + * @param string configuration group name + * @param array configuration array + * @return $this clone of the current object + * @uses Kohana::load + */ + public function load($group, array $config = NULL) + { + if ($files = Kohana::find_file($this->_directory, $group, NULL, TRUE)) + { + // Initialize the config array + $config = array(); + + foreach ($files as $file) + { + // Merge each file to the configuration array + $config = Arr::merge($config, Kohana::load($file)); + } + } + + return parent::load($group, $config); + } + +} // End Kohana_Config_File diff --git a/includes/kohana/system/classes/kohana/config/reader.php b/includes/kohana/system/classes/kohana/config/reader.php new file mode 100644 index 0000000..eb4b126 --- /dev/null +++ b/includes/kohana/system/classes/kohana/config/reader.php @@ -0,0 +1,115 @@ +getArrayCopy()); + } + + /** + * Loads a configuration group. + * + * $config->load($name, $array); + * + * This method must be extended by all readers. After the group has been + * loaded, call `parent::load($group, $config)` for final preparation. + * + * @param string configuration group name + * @param array configuration array + * @return $this a clone of this object + */ + public function load($group, array $config = NULL) + { + if ($config === NULL) + { + return FALSE; + } + + // Clone the current object + $object = clone $this; + + // Set the group name + $object->_configuration_group = $group; + + // Swap the array with the actual configuration + $object->exchangeArray($config); + + return $object; + } + + /** + * Return the raw array that is being used for this object. + * + * $array = $config->as_array(); + * + * @return array + */ + public function as_array() + { + return $this->getArrayCopy(); + } + + /** + * Get a variable from the configuration or return the default value. + * + * $value = $config->get($key); + * + * @param string array key + * @param mixed default value + * @return mixed + */ + public function get($key, $default = NULL) + { + return $this->offsetExists($key) ? $this->offsetGet($key) : $default; + } + + /** + * Sets a value in the configuration array. + * + * $config->set($key, $new_value); + * + * @param string array key + * @param mixed array value + * @return $this + */ + public function set($key, $value) + { + $this->offsetSet($key, $value); + + return $this; + } + +} // End Kohana_Config_Reader diff --git a/includes/kohana/system/classes/kohana/controller.php b/includes/kohana/system/classes/kohana/controller.php new file mode 100644 index 0000000..c0b9f59 --- /dev/null +++ b/includes/kohana/system/classes/kohana/controller.php @@ -0,0 +1,75 @@ +before(); + * $controller->action_bar(); + * $controller->after(); + * + * The controller action should add the output it creates to + * `$this->response->body($output)`, typically in the form of a [View], during the + * "action" part of execution. + * + * @package Kohana + * @category Controller + * @author Kohana Team + * @copyright (c) 2008-2011 Kohana Team + * @license http://kohanaframework.org/license + */ +abstract class Kohana_Controller { + + /** + * @var Request Request that created the controller + */ + public $request; + + /** + * @var Response The response that will be returned from controller + */ + public $response; + + /** + * Creates a new controller instance. Each controller must be constructed + * with the request object that created it. + * + * @param Request $request Request that created the controller + * @param Response $response The request's response + * @return void + */ + public function __construct(Request $request, Response $response) + { + // Assign the request to the controller + $this->request = $request; + + // Assign a response to the controller + $this->response = $response; + } + + /** + * Automatically executed before the controller action. Can be used to set + * class properties, do authorization checks, and execute other custom code. + * + * @return void + */ + public function before() + { + // Nothing by default + } + + /** + * Automatically executed after the controller action. Can be used to apply + * transformation to the request response, add extra output, and execute + * other custom code. + * + * @return void + */ + public function after() + { + // Nothing by default + } + +} // End Controller diff --git a/includes/kohana/system/classes/kohana/controller/rest.php b/includes/kohana/system/classes/kohana/controller/rest.php new file mode 100644 index 0000000..2caaec6 --- /dev/null +++ b/includes/kohana/system/classes/kohana/controller/rest.php @@ -0,0 +1,96 @@ + 'index', + HTTP_Request::PUT => 'update', + HTTP_Request::POST => 'create', + HTTP_Request::DELETE => 'delete', + ); + + /** + * @var string requested action + */ + protected $_action_requested = ''; + + /** + * Checks the requested method against the available methods. If the method + * is supported, sets the request action from the map. If not supported, + * the "invalid" action will be called. + */ + public function before() + { + $this->_action_requested = $this->request->action(); + + $method = Arr::get($_SERVER, 'HTTP_X_HTTP_METHOD_OVERRIDE', $this->request->method()); + + if ( ! isset($this->_action_map[$method])) + { + $this->request->action('invalid'); + } + else + { + $this->request->action($this->_action_map[$method]); + } + + return parent::before(); + } + + /** + * undocumented function + */ + public function after() + { + if (in_array(Arr::get($_SERVER, 'HTTP_X_HTTP_METHOD_OVERRIDE', $this->request->method()), array( + HTTP_Request::PUT, + HTTP_Request::POST, + HTTP_Request::DELETE))) + { + $this->response->headers('cache-control', 'no-cache, no-store, max-age=0, must-revalidate'); + } + } + + /** + * Sends a 405 "Method Not Allowed" response and a list of allowed actions. + */ + public function action_invalid() + { + // Send the "Method Not Allowed" response + $this->response->status(405) + ->headers('Allow', implode(', ', array_keys($this->_action_map))); + } + +} // End REST diff --git a/includes/kohana/system/classes/kohana/controller/template.php b/includes/kohana/system/classes/kohana/controller/template.php new file mode 100644 index 0000000..ca19fdf --- /dev/null +++ b/includes/kohana/system/classes/kohana/controller/template.php @@ -0,0 +1,50 @@ +auto_render === TRUE) + { + // Load the template + $this->template = View::factory($this->template); + } + + return parent::before(); + } + + /** + * Assigns the template [View] as the request response. + */ + public function after() + { + if ($this->auto_render === TRUE) + { + $this->response->body($this->template->render()); + } + + return parent::after(); + } + +} // End Controller_Template diff --git a/includes/kohana/system/classes/kohana/cookie.php b/includes/kohana/system/classes/kohana/cookie.php new file mode 100644 index 0000000..e3772ab --- /dev/null +++ b/includes/kohana/system/classes/kohana/cookie.php @@ -0,0 +1,161 @@ +
                    Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE` + * `boolean` | profile | Whether to enable the [Profiler](kohana/profiling).

                    Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE` * `boolean` | caching | Cache file locations to speed up [Kohana::find_file]. This has nothing to do with [Kohana::cache], [Fragments](kohana/fragments) or the [Cache module](cache).

                    Recommended setting: `FALSE` while developing, `TRUE` on production servers. | `FALSE` + * + * @throws Kohana_Exception + * @param array Array of settings. See above. + * @return void + * @uses Kohana::globals + * @uses Kohana::sanitize + * @uses Kohana::cache + * @uses Profiler + */ + public static function init(array $settings = NULL) + { + if (Kohana::$_init) + { + // Do not allow execution twice + return; + } + + // Kohana is now initialized + Kohana::$_init = TRUE; + + if (isset($settings['profile'])) + { + // Enable profiling + Kohana::$profiling = (bool) $settings['profile']; + } + + // Start an output buffer + ob_start(); + + if (isset($settings['errors'])) + { + // Enable error handling + Kohana::$errors = (bool) $settings['errors']; + } + + if (Kohana::$errors === TRUE) + { + // Enable Kohana exception handling, adds stack traces and error source. + set_exception_handler(array('Kohana_Exception', 'handler')); + + // Enable Kohana error handling, converts all PHP errors to exceptions. + set_error_handler(array('Kohana', 'error_handler')); + } + + // Enable the Kohana shutdown handler, which catches E_FATAL errors. + register_shutdown_function(array('Kohana', 'shutdown_handler')); + + if (ini_get('register_globals')) + { + // Reverse the effects of register_globals + Kohana::globals(); + } + + if (isset($settings['expose'])) + { + Kohana::$expose = (bool) $settings['expose']; + } + + // Determine if we are running in a command line environment + Kohana::$is_cli = (PHP_SAPI === 'cli'); + + // Determine if we are running in a Windows environment + Kohana::$is_windows = (DIRECTORY_SEPARATOR === '\\'); + + // Determine if we are running in safe mode + Kohana::$safe_mode = (bool) ini_get('safe_mode'); + + if (isset($settings['cache_dir'])) + { + if ( ! is_dir($settings['cache_dir'])) + { + try + { + // Create the cache directory + mkdir($settings['cache_dir'], 0755, TRUE); + + // Set permissions (must be manually set to fix umask issues) + chmod($settings['cache_dir'], 0755); + } + catch (Exception $e) + { + throw new Kohana_Exception('Could not create cache directory :dir', + array(':dir' => Debug::path($settings['cache_dir']))); + } + } + + // Set the cache directory path + Kohana::$cache_dir = realpath($settings['cache_dir']); + } + else + { + // Use the default cache directory + Kohana::$cache_dir = APPPATH.'cache'; + } + + if ( ! is_writable(Kohana::$cache_dir)) + { + throw new Kohana_Exception('Directory :dir must be writable', + array(':dir' => Debug::path(Kohana::$cache_dir))); + } + + if (isset($settings['cache_life'])) + { + // Set the default cache lifetime + Kohana::$cache_life = (int) $settings['cache_life']; + } + + if (isset($settings['caching'])) + { + // Enable or disable internal caching + Kohana::$caching = (bool) $settings['caching']; + } + + if (Kohana::$caching === TRUE) + { + // Load the file path cache + Kohana::$_files = Kohana::cache('Kohana::find_file()'); + } + + if (isset($settings['charset'])) + { + // Set the system character set + Kohana::$charset = strtolower($settings['charset']); + } + + if (function_exists('mb_internal_encoding')) + { + // Set the MB extension encoding to the same character set + mb_internal_encoding(Kohana::$charset); + } + + if (isset($settings['base_url'])) + { + // Set the base URL + Kohana::$base_url = rtrim($settings['base_url'], '/').'/'; + } + + if (isset($settings['index_file'])) + { + // Set the index file + Kohana::$index_file = trim($settings['index_file'], '/'); + } + + // Determine if the extremely evil magic quotes are enabled + Kohana::$magic_quotes = (bool) get_magic_quotes_gpc(); + + // Sanitize all request variables + $_GET = Kohana::sanitize($_GET); + $_POST = Kohana::sanitize($_POST); + $_COOKIE = Kohana::sanitize($_COOKIE); + + // Load the logger + Kohana::$log = Log::instance(); + + // Load the config + Kohana::$config = Config::instance(); + } + + /** + * Cleans up the environment: + * + * - Restore the previous error and exception handlers + * - Destroy the Kohana::$log and Kohana::$config objects + * + * @return void + */ + public static function deinit() + { + if (Kohana::$_init) + { + // Removed the autoloader + spl_autoload_unregister(array('Kohana', 'auto_load')); + + if (Kohana::$errors) + { + // Go back to the previous error handler + restore_error_handler(); + + // Go back to the previous exception handler + restore_exception_handler(); + } + + // Destroy objects created by init + Kohana::$log = Kohana::$config = NULL; + + // Reset internal storage + Kohana::$_modules = Kohana::$_files = array(); + Kohana::$_paths = array(APPPATH, SYSPATH); + + // Reset file cache status + Kohana::$_files_changed = FALSE; + + // Kohana is no longer initialized + Kohana::$_init = FALSE; + } + } + + /** + * Reverts the effects of the `register_globals` PHP setting by unsetting + * all global varibles except for the default super globals (GPCS, etc), + * which is a [potential security hole.][ref-wikibooks] + * + * This is called automatically by [Kohana::init] if `register_globals` is + * on. + * + * + * [ref-wikibooks]: http://en.wikibooks.org/wiki/PHP_Programming/Register_Globals + * + * @return void + */ + public static function globals() + { + if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS'])) + { + // Prevent malicious GLOBALS overload attack + echo "Global variable overload attack detected! Request aborted.\n"; + + // Exit with an error status + exit(1); + } + + // Get the variable names of all globals + $global_variables = array_keys($GLOBALS); + + // Remove the standard global variables from the list + $global_variables = array_diff($global_variables, array( + '_COOKIE', + '_ENV', + '_GET', + '_FILES', + '_POST', + '_REQUEST', + '_SERVER', + '_SESSION', + 'GLOBALS', + )); + + foreach ($global_variables as $name) + { + // Unset the global variable, effectively disabling register_globals + unset($GLOBALS[$name]); + } + } + + /** + * Recursively sanitizes an input variable: + * + * - Strips slashes if magic quotes are enabled + * - Normalizes all newlines to LF + * + * @param mixed any variable + * @return mixed sanitized variable + */ + public static function sanitize($value) + { + if (is_array($value) OR is_object($value)) + { + foreach ($value as $key => $val) + { + // Recursively clean each value + $value[$key] = Kohana::sanitize($val); + } + } + elseif (is_string($value)) + { + if (Kohana::$magic_quotes === TRUE) + { + // Remove slashes added by magic quotes + $value = stripslashes($value); + } + + if (strpos($value, "\r") !== FALSE) + { + // Standardize newlines + $value = str_replace(array("\r\n", "\r"), "\n", $value); + } + } + + return $value; + } + + /** + * Provides auto-loading support of classes that follow Kohana's [class + * naming conventions](kohana/conventions#class-names-and-file-location). + * See [Loading Classes](kohana/autoloading) for more information. + * + * Class names are converted to file names by making the class name + * lowercase and converting underscores to slashes: + * + * // Loads classes/my/class/name.php + * Kohana::auto_load('My_Class_Name'); + * + * You should never have to call this function, as simply calling a class + * will cause it to be called. + * + * This function must be enabled as an autoloader in the bootstrap: + * + * spl_autoload_register(array('Kohana', 'auto_load')); + * + * @param string class name + * @return boolean + */ + public static function auto_load($class) + { + try + { + // Transform the class name into a path + $file = str_replace('_', '/', strtolower($class)); + + if ($path = Kohana::find_file('classes', $file)) + { + // Load the class file + require $path; + + // Class has been found + return TRUE; + } + + // Class is not in the filesystem + return FALSE; + } + catch (Exception $e) + { + Kohana_Exception::handler($e); + die; + } + } + + /** + * Changes the currently enabled modules. Module paths may be relative + * or absolute, but must point to a directory: + * + * Kohana::modules(array('modules/foo', MODPATH.'bar')); + * + * @param array list of module paths + * @return array enabled modules + */ + public static function modules(array $modules = NULL) + { + if ($modules === NULL) + { + // Not changing modules, just return the current set + return Kohana::$_modules; + } + + // Start a new list of include paths, APPPATH first + $paths = array(APPPATH); + + foreach ($modules as $name => $path) + { + if (is_dir($path)) + { + // Add the module to include paths + $paths[] = $modules[$name] = realpath($path).DIRECTORY_SEPARATOR; + } + else + { + // This module is invalid, remove it + unset($modules[$name]); + } + } + + // Finish the include paths by adding SYSPATH + $paths[] = SYSPATH; + + // Set the new include paths + Kohana::$_paths = $paths; + + // Set the current module list + Kohana::$_modules = $modules; + + foreach (Kohana::$_modules as $path) + { + $init = $path.'init'.EXT; + + if (is_file($init)) + { + // Include the module initialization file once + require_once $init; + } + } + + return Kohana::$_modules; + } + + /** + * Returns the the currently active include paths, including the + * application, system, and each module's path. + * + * @return array + */ + public static function include_paths() + { + return Kohana::$_paths; + } + + /** + * Searches for a file in the [Cascading Filesystem](kohana/files), and + * returns the path to the file that has the highest precedence, so that it + * can be included. + * + * When searching the "config", "messages", or "i18n" directories, or when + * the `$array` flag is set to true, an array of all the files that match + * that path in the [Cascading Filesystem](kohana/files) will be returned. + * These files will return arrays which must be merged together. + * + * If no extension is given, the default extension (`EXT` set in + * `index.php`) will be used. + * + * // Returns an absolute path to views/template.php + * Kohana::find_file('views', 'template'); + * + * // Returns an absolute path to media/css/style.css + * Kohana::find_file('media', 'css/style', 'css'); + * + * // Returns an array of all the "mimes" configuration files + * Kohana::find_file('config', 'mimes'); + * + * @param string directory name (views, i18n, classes, extensions, etc.) + * @param string filename with subdirectory + * @param string extension to search for + * @param boolean return an array of files? + * @return array a list of files when $array is TRUE + * @return string single file path + */ + public static function find_file($dir, $file, $ext = NULL, $array = FALSE) + { + if ($ext === NULL) + { + // Use the default extension + $ext = EXT; + } + elseif ($ext) + { + // Prefix the extension with a period + $ext = ".{$ext}"; + } + else + { + // Use no extension + $ext = ''; + } + + // Create a partial path of the filename + $path = $dir.DIRECTORY_SEPARATOR.$file.$ext; + + if (Kohana::$caching === TRUE AND isset(Kohana::$_files[$path.($array ? '_array' : '_path')])) + { + // This path has been cached + return Kohana::$_files[$path.($array ? '_array' : '_path')]; + } + + if (Kohana::$profiling === TRUE AND class_exists('Profiler', FALSE)) + { + // Start a new benchmark + $benchmark = Profiler::start('Kohana', __FUNCTION__); + } + + if ($array OR $dir === 'config' OR $dir === 'i18n' OR $dir === 'messages') + { + // Include paths must be searched in reverse + $paths = array_reverse(Kohana::$_paths); + + // Array of files that have been found + $found = array(); + + foreach ($paths as $dir) + { + if (is_file($dir.$path)) + { + // This path has a file, add it to the list + $found[] = $dir.$path; + } + } + } + else + { + // The file has not been found yet + $found = FALSE; + + foreach (Kohana::$_paths as $dir) + { + if (is_file($dir.$path)) + { + // A path has been found + $found = $dir.$path; + + // Stop searching + break; + } + } + } + + if (Kohana::$caching === TRUE) + { + // Add the path to the cache + Kohana::$_files[$path.($array ? '_array' : '_path')] = $found; + + // Files have been changed + Kohana::$_files_changed = TRUE; + } + + if (isset($benchmark)) + { + // Stop the benchmark + Profiler::stop($benchmark); + } + + return $found; + } + + /** + * Recursively finds all of the files in the specified directory at any + * location in the [Cascading Filesystem](kohana/files), and returns an + * array of all the files found, sorted alphabetically. + * + * // Find all view files. + * $views = Kohana::list_files('views'); + * + * @param string directory name + * @param array list of paths to search + * @return array + */ + public static function list_files($directory = NULL, array $paths = NULL) + { + if ($directory !== NULL) + { + // Add the directory separator + $directory .= DIRECTORY_SEPARATOR; + } + + if ($paths === NULL) + { + // Use the default paths + $paths = Kohana::$_paths; + } + + // Create an array for the files + $found = array(); + + foreach ($paths as $path) + { + if (is_dir($path.$directory)) + { + // Create a new directory iterator + $dir = new DirectoryIterator($path.$directory); + + foreach ($dir as $file) + { + // Get the file name + $filename = $file->getFilename(); + + if ($filename[0] === '.' OR $filename[strlen($filename)-1] === '~') + { + // Skip all hidden files and UNIX backup files + continue; + } + + // Relative filename is the array key + $key = $directory.$filename; + + if ($file->isDir()) + { + if ($sub_dir = Kohana::list_files($key, $paths)) + { + if (isset($found[$key])) + { + // Append the sub-directory list + $found[$key] += $sub_dir; + } + else + { + // Create a new sub-directory list + $found[$key] = $sub_dir; + } + } + } + else + { + if ( ! isset($found[$key])) + { + // Add new files to the list + $found[$key] = realpath($file->getPathName()); + } + } + } + } + } + + // Sort the results alphabetically + ksort($found); + + return $found; + } + + /** + * Loads a file within a totally empty scope and returns the output: + * + * $foo = Kohana::load('foo.php'); + * + * @param string + * @return mixed + */ + public static function load($file) + { + return include $file; + } + + /** + * Returns the configuration array for the requested group. See + * [configuration files](kohana/files/config) for more information. + * + * // Get all the configuration in config/database.php + * $config = Kohana::config('database'); + * + * // Get only the default connection configuration + * $default = Kohana::config('database.default') + * + * // Get only the hostname of the default connection + * $host = Kohana::config('database.default.connection.hostname') + * + * @param string group name + * @return Config + */ + public static function config($group) + { + static $config; + + if (strpos($group, '.') !== FALSE) + { + // Split the config group and path + list ($group, $path) = explode('.', $group, 2); + } + + if ( ! isset($config[$group])) + { + // Load the config group into the cache + $config[$group] = Kohana::$config->load($group); + } + + if (isset($path)) + { + return Arr::path($config[$group], $path, NULL, '.'); + } + else + { + return $config[$group]; + } + } + + /** + * Provides simple file-based caching for strings and arrays: + * + * // Set the "foo" cache + * Kohana::cache('foo', 'hello, world'); + * + * // Get the "foo" cache + * $foo = Kohana::cache('foo'); + * + * All caches are stored as PHP code, generated with [var_export][ref-var]. + * Caching objects may not work as expected. Storing references or an + * object or array that has recursion will cause an E_FATAL. + * + * The cache directory and default cache lifetime is set by [Kohana::init] + * + * [ref-var]: http://php.net/var_export + * + * @throws Kohana_Exception + * @param string name of the cache + * @param mixed data to cache + * @param integer number of seconds the cache is valid for + * @return mixed for getting + * @return boolean for setting + */ + public static function cache($name, $data = NULL, $lifetime = NULL) + { + // Cache file is a hash of the name + $file = sha1($name).'.txt'; + + // Cache directories are split by keys to prevent filesystem overload + $dir = Kohana::$cache_dir.DIRECTORY_SEPARATOR.$file[0].$file[1].DIRECTORY_SEPARATOR; + + if ($lifetime === NULL) + { + // Use the default lifetime + $lifetime = Kohana::$cache_life; + } + + if ($data === NULL) + { + if (is_file($dir.$file)) + { + if ((time() - filemtime($dir.$file)) < $lifetime) + { + // Return the cache + try + { + return unserialize(file_get_contents($dir.$file)); + } + catch (Exception $e) + { + // Cache is corrupt, let return happen normally. + } + } + else + { + try + { + // Cache has expired + unlink($dir.$file); + } + catch (Exception $e) + { + // Cache has mostly likely already been deleted, + // let return happen normally. + } + } + } + + // Cache not found + return NULL; + } + + if ( ! is_dir($dir)) + { + // Create the cache directory + mkdir($dir, 0777, TRUE); + + // Set permissions (must be manually set to fix umask issues) + chmod($dir, 0777); + } + + // Force the data to be a string + $data = serialize($data); + + try + { + // Write the cache + return (bool) file_put_contents($dir.$file, $data, LOCK_EX); + } + catch (Exception $e) + { + // Failed to write cache + return FALSE; + } + } + + /** + * Get a message from a file. Messages are arbitary strings that are stored + * in the `messages/` directory and reference by a key. Translation is not + * performed on the returned values. See [message files](kohana/files/messages) + * for more information. + * + * // Get "username" from messages/text.php + * $username = Kohana::message('text', 'username'); + * + * @param string file name + * @param string key path to get + * @param mixed default value if the path does not exist + * @return string message string for the given path + * @return array complete message list, when no path is specified + * @uses Arr::merge + * @uses Arr::path + */ + public static function message($file, $path = NULL, $default = NULL) + { + static $messages; + + if ( ! isset($messages[$file])) + { + // Create a new message list + $messages[$file] = array(); + + if ($files = Kohana::find_file('messages', $file)) + { + foreach ($files as $f) + { + // Combine all the messages recursively + $messages[$file] = Arr::merge($messages[$file], Kohana::load($f)); + } + } + } + + if ($path === NULL) + { + // Return all of the messages + return $messages[$file]; + } + else + { + // Get a message using the path + return Arr::path($messages[$file], $path, $default); + } + } + + /** + * PHP error handler, converts all errors into ErrorExceptions. This handler + * respects error_reporting settings. + * + * @throws ErrorException + * @return TRUE + */ + public static function error_handler($code, $error, $file = NULL, $line = NULL) + { + if (error_reporting() & $code) + { + // This error is not suppressed by current error reporting settings + // Convert the error into an ErrorException + throw new ErrorException($error, $code, 0, $file, $line); + } + + // Do not execute the PHP error handler + return TRUE; + } + + /** + * Catches errors that are not caught by the error handler, such as E_PARSE. + * + * @uses Kohana_Exception::handler + * @return void + */ + public static function shutdown_handler() + { + if ( ! Kohana::$_init) + { + // Do not execute when not active + return; + } + + try + { + if (Kohana::$caching === TRUE AND Kohana::$_files_changed === TRUE) + { + // Write the file path cache + Kohana::cache('Kohana::find_file()', Kohana::$_files); + } + } + catch (Exception $e) + { + // Pass the exception to the handler + Kohana_Exception::handler($e); + } + + if (Kohana::$errors AND $error = error_get_last() AND in_array($error['type'], Kohana::$shutdown_errors)) + { + // Clean the output buffer + ob_get_level() and ob_clean(); + + // Fake an exception for nice debugging + Kohana_Exception::handler(new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line'])); + + // Shutdown now to avoid a "death loop" + exit(1); + } + } + +} // End Kohana diff --git a/includes/kohana/system/classes/kohana/date.php b/includes/kohana/system/classes/kohana/date.php new file mode 100644 index 0000000..870d3d1 --- /dev/null +++ b/includes/kohana/system/classes/kohana/date.php @@ -0,0 +1,598 @@ +. + * + * @param string timezone that to find the offset of + * @param string timezone used as the baseline + * @param mixed UNIX timestamp or date string + * @return integer + */ + public static function offset($remote, $local = NULL, $now = NULL) + { + if ($local === NULL) + { + // Use the default timezone + $local = date_default_timezone_get(); + } + + if (is_int($now)) + { + // Convert the timestamp into a string + $now = date(DateTime::RFC2822, $now); + } + + // Create timezone objects + $zone_remote = new DateTimeZone($remote); + $zone_local = new DateTimeZone($local); + + // Create date objects from timezones + $time_remote = new DateTime($now, $zone_remote); + $time_local = new DateTime($now, $zone_local); + + // Find the offset + $offset = $zone_remote->getOffset($time_remote) - $zone_local->getOffset($time_local); + + return $offset; + } + + /** + * Number of seconds in a minute, incrementing by a step. Typically used as + * a shortcut for generating a list that can used in a form. + * + * $seconds = Date::seconds(); // 01, 02, 03, ..., 58, 59, 60 + * + * @param integer amount to increment each step by, 1 to 30 + * @param integer start value + * @param integer end value + * @return array A mirrored (foo => foo) array from 1-60. + */ + public static function seconds($step = 1, $start = 0, $end = 60) + { + // Always integer + $step = (int) $step; + + $seconds = array(); + + for ($i = $start; $i < $end; $i += $step) + { + $seconds[$i] = sprintf('%02d', $i); + } + + return $seconds; + } + + /** + * Number of minutes in an hour, incrementing by a step. Typically used as + * a shortcut for generating a list that can be used in a form. + * + * $minutes = Date::minutes(); // 05, 10, 15, ..., 50, 55, 60 + * + * @uses Date::seconds + * @param integer amount to increment each step by, 1 to 30 + * @return array A mirrored (foo => foo) array from 1-60. + */ + public static function minutes($step = 5) + { + // Because there are the same number of minutes as seconds in this set, + // we choose to re-use seconds(), rather than creating an entirely new + // function. Shhhh, it's cheating! ;) There are several more of these + // in the following methods. + return Date::seconds($step); + } + + /** + * Number of hours in a day. Typically used as a shortcut for generating a + * list that can be used in a form. + * + * $hours = Date::hours(); // 01, 02, 03, ..., 10, 11, 12 + * + * @param integer amount to increment each step by + * @param boolean use 24-hour time + * @param integer the hour to start at + * @return array A mirrored (foo => foo) array from start-12 or start-23. + */ + public static function hours($step = 1, $long = FALSE, $start = NULL) + { + // Default values + $step = (int) $step; + $long = (bool) $long; + $hours = array(); + + // Set the default start if none was specified. + if ($start === NULL) + { + $start = ($long === FALSE) ? 1 : 0; + } + + $hours = array(); + + // 24-hour time has 24 hours, instead of 12 + $size = ($long === TRUE) ? 23 : 12; + + for ($i = $start; $i <= $size; $i += $step) + { + $hours[$i] = (string) $i; + } + + return $hours; + } + + /** + * Returns AM or PM, based on a given hour (in 24 hour format). + * + * $type = Date::ampm(12); // PM + * $type = Date::ampm(1); // AM + * + * @param integer number of the hour + * @return string + */ + public static function ampm($hour) + { + // Always integer + $hour = (int) $hour; + + return ($hour > 11) ? 'PM' : 'AM'; + } + + /** + * Adjusts a non-24-hour number into a 24-hour number. + * + * $hour = Date::adjust(3, 'pm'); // 15 + * + * @param integer hour to adjust + * @param string AM or PM + * @return string + */ + public static function adjust($hour, $ampm) + { + $hour = (int) $hour; + $ampm = strtolower($ampm); + + switch ($ampm) + { + case 'am': + if ($hour == 12) + { + $hour = 0; + } + break; + case 'pm': + if ($hour < 12) + { + $hour += 12; + } + break; + } + + return sprintf('%02d', $hour); + } + + /** + * Number of days in a given month and year. Typically used as a shortcut + * for generating a list that can be used in a form. + * + * Date::days(4, 2010); // 1, 2, 3, ..., 28, 29, 30 + * + * @param integer number of month + * @param integer number of year to check month, defaults to the current year + * @return array A mirrored (foo => foo) array of the days. + */ + public static function days($month, $year = FALSE) + { + static $months; + + if ($year === FALSE) + { + // Use the current year by default + $year = date('Y'); + } + + // Always integers + $month = (int) $month; + $year = (int) $year; + + // We use caching for months, because time functions are used + if (empty($months[$year][$month])) + { + $months[$year][$month] = array(); + + // Use date to find the number of days in the given month + $total = date('t', mktime(1, 0, 0, $month, 1, $year)) + 1; + + for ($i = 1; $i < $total; $i++) + { + $months[$year][$month][$i] = (string) $i; + } + } + + return $months[$year][$month]; + } + + /** + * Number of months in a year. Typically used as a shortcut for generating + * a list that can be used in a form. + * + * By default a mirrored array of $month_number => $month_number is returned + * + * Date::months(); + * // aray(1 => 1, 2 => 2, 3 => 3, ..., 12 => 12) + * + * But you can customise this by passing in either Date::MONTHS_LONG + * + * Date::months(Date::MONTHS_LONG); + * // array(1 => 'January', 2 => 'February', ..., 12 => 'December') + * + * Or Date::MONTHS_SHORT + * + * Date::months(Date::MONTHS_SHORT); + * // array(1 => 'Jan', 2 => 'Feb', ..., 12 => 'Dec') + * + * @uses Date::hours + * @param string The format to use for months + * @return array An array of months based on the specified format + */ + public static function months($format = NULL) + { + $months = array(); + + if ($format === DATE::MONTHS_LONG OR $format === DATE::MONTHS_SHORT) + { + for ($i = 1; $i <= 12; ++$i) + { + $months[$i] = strftime($format, mktime(0, 0, 0, $i, 1)); + } + } + else + { + $months = Date::hours(); + } + + return $months; + } + + /** + * Returns an array of years between a starting and ending year. By default, + * the the current year - 5 and current year + 5 will be used. Typically used + * as a shortcut for generating a list that can be used in a form. + * + * $years = Date::years(2000, 2010); // 2000, 2001, ..., 2009, 2010 + * + * @param integer starting year (default is current year - 5) + * @param integer ending year (default is current year + 5) + * @return array + */ + public static function years($start = FALSE, $end = FALSE) + { + // Default values + $start = ($start === FALSE) ? (date('Y') - 5) : (int) $start; + $end = ($end === FALSE) ? (date('Y') + 5) : (int) $end; + + $years = array(); + + for ($i = $start; $i <= $end; $i++) + { + $years[$i] = (string) $i; + } + + return $years; + } + + /** + * Returns time difference between two timestamps, in human readable format. + * If the second timestamp is not given, the current time will be used. + * Also consider using [Date::fuzzy_span] when displaying a span. + * + * $span = Date::span(60, 182, 'minutes,seconds'); // array('minutes' => 2, 'seconds' => 2) + * $span = Date::span(60, 182, 'minutes'); // 2 + * + * @param integer timestamp to find the span of + * @param integer timestamp to use as the baseline + * @param string formatting string + * @return string when only a single output is requested + * @return array associative list of all outputs requested + */ + public static function span($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds') + { + // Normalize output + $output = trim(strtolower( (string) $output)); + + if ( ! $output) + { + // Invalid output + return FALSE; + } + + // Array with the output formats + $output = preg_split('/[^a-z]+/', $output); + + // Convert the list of outputs to an associative array + $output = array_combine($output, array_fill(0, count($output), 0)); + + // Make the output values into keys + extract(array_flip($output), EXTR_SKIP); + + if ($local === NULL) + { + // Calculate the span from the current time + $local = time(); + } + + // Calculate timespan (seconds) + $timespan = abs($remote - $local); + + if (isset($output['years'])) + { + $timespan -= Date::YEAR * ($output['years'] = (int) floor($timespan / Date::YEAR)); + } + + if (isset($output['months'])) + { + $timespan -= Date::MONTH * ($output['months'] = (int) floor($timespan / Date::MONTH)); + } + + if (isset($output['weeks'])) + { + $timespan -= Date::WEEK * ($output['weeks'] = (int) floor($timespan / Date::WEEK)); + } + + if (isset($output['days'])) + { + $timespan -= Date::DAY * ($output['days'] = (int) floor($timespan / Date::DAY)); + } + + if (isset($output['hours'])) + { + $timespan -= Date::HOUR * ($output['hours'] = (int) floor($timespan / Date::HOUR)); + } + + if (isset($output['minutes'])) + { + $timespan -= Date::MINUTE * ($output['minutes'] = (int) floor($timespan / Date::MINUTE)); + } + + // Seconds ago, 1 + if (isset($output['seconds'])) + { + $output['seconds'] = $timespan; + } + + if (count($output) === 1) + { + // Only a single output was requested, return it + return array_pop($output); + } + + // Return array + return $output; + } + + /** + * Returns the difference between a time and now in a "fuzzy" way. + * Displaying a fuzzy time instead of a date is usually faster to read and understand. + * + * $span = Date::fuzzy_span(time() - 10); // "moments ago" + * $span = Date::fuzzy_span(time() + 20); // "in moments" + * + * A second parameter is available to manually set the "local" timestamp, + * however this parameter shouldn't be needed in normal usage and is only + * included for unit tests + * + * @param integer "remote" timestamp + * @param integer "local" timestamp, defaults to time() + * @return string + */ + public static function fuzzy_span($timestamp, $local_timestamp = NULL) + { + $local_timestamp = ($local_timestamp === NULL) ? time() : (int) $local_timestamp; + + // Determine the difference in seconds + $offset = abs($local_timestamp - $timestamp); + + if ($offset <= Date::MINUTE) + { + $span = 'moments'; + } + elseif ($offset < (Date::MINUTE * 20)) + { + $span = 'a few minutes'; + } + elseif ($offset < Date::HOUR) + { + $span = 'less than an hour'; + } + elseif ($offset < (Date::HOUR * 4)) + { + $span = 'a couple of hours'; + } + elseif ($offset < Date::DAY) + { + $span = 'less than a day'; + } + elseif ($offset < (Date::DAY * 2)) + { + $span = 'about a day'; + } + elseif ($offset < (Date::DAY * 4)) + { + $span = 'a couple of days'; + } + elseif ($offset < Date::WEEK) + { + $span = 'less than a week'; + } + elseif ($offset < (Date::WEEK * 2)) + { + $span = 'about a week'; + } + elseif ($offset < Date::MONTH) + { + $span = 'less than a month'; + } + elseif ($offset < (Date::MONTH * 2)) + { + $span = 'about a month'; + } + elseif ($offset < (Date::MONTH * 4)) + { + $span = 'a couple of months'; + } + elseif ($offset < Date::YEAR) + { + $span = 'less than a year'; + } + elseif ($offset < (Date::YEAR * 2)) + { + $span = 'about a year'; + } + elseif ($offset < (Date::YEAR * 4)) + { + $span = 'a couple of years'; + } + elseif ($offset < (Date::YEAR * 8)) + { + $span = 'a few years'; + } + elseif ($offset < (Date::YEAR * 12)) + { + $span = 'about a decade'; + } + elseif ($offset < (Date::YEAR * 24)) + { + $span = 'a couple of decades'; + } + elseif ($offset < (Date::YEAR * 64)) + { + $span = 'several decades'; + } + else + { + $span = 'a long time'; + } + + if ($timestamp <= $local_timestamp) + { + // This is in the past + return $span.' ago'; + } + else + { + // This in the future + return 'in '.$span; + } + } + + /** + * Converts a UNIX timestamp to DOS format. There are very few cases where + * this is needed, but some binary formats use it (eg: zip files.) + * Converting the other direction is done using {@link Date::dos2unix}. + * + * $dos = Date::unix2dos($unix); + * + * @param integer UNIX timestamp + * @return integer + */ + public static function unix2dos($timestamp = FALSE) + { + $timestamp = ($timestamp === FALSE) ? getdate() : getdate($timestamp); + + if ($timestamp['year'] < 1980) + { + return (1 << 21 | 1 << 16); + } + + $timestamp['year'] -= 1980; + + // What voodoo is this? I have no idea... Geert can explain it though, + // and that's good enough for me. + return ($timestamp['year'] << 25 | $timestamp['mon'] << 21 | + $timestamp['mday'] << 16 | $timestamp['hours'] << 11 | + $timestamp['minutes'] << 5 | $timestamp['seconds'] >> 1); + } + + /** + * Converts a DOS timestamp to UNIX format.There are very few cases where + * this is needed, but some binary formats use it (eg: zip files.) + * Converting the other direction is done using {@link Date::unix2dos}. + * + * $unix = Date::dos2unix($dos); + * + * @param integer DOS timestamp + * @return integer + */ + public static function dos2unix($timestamp = FALSE) + { + $sec = 2 * ($timestamp & 0x1f); + $min = ($timestamp >> 5) & 0x3f; + $hrs = ($timestamp >> 11) & 0x1f; + $day = ($timestamp >> 16) & 0x1f; + $mon = ($timestamp >> 21) & 0x0f; + $year = ($timestamp >> 25) & 0x7f; + + return mktime($hrs, $min, $sec, $mon, $day, $year + 1980); + } + + /** + * Returns a date/time string with the specified timestamp format + * + * $time = Date::formatted_time('5 minutes ago'); + * + * @see http://php.net/manual/en/datetime.construct.php + * @param string datetime_str datetime string + * @param string timestamp_format timestamp format + * @return string + */ + public static function formatted_time($datetime_str = 'now', $timestamp_format = NULL, $timezone = NULL) + { + $timestamp_format = ($timestamp_format == NULL) ? Date::$timestamp_format : $timestamp_format; + $timezone = ($timezone === NULL) ? Date::$timezone : $timezone; + + $time = new DateTime($datetime_str, new DateTimeZone( + $timezone ? $timezone : date_default_timezone_get() + )); + + return $time->format($timestamp_format); + } + +} // End date diff --git a/includes/kohana/system/classes/kohana/debug.php b/includes/kohana/system/classes/kohana/debug.php new file mode 100644 index 0000000..6f1f8a2 --- /dev/null +++ b/includes/kohana/system/classes/kohana/debug.php @@ -0,0 +1,468 @@ +'.implode("\n", $output).''; + } + + /** + * Returns an HTML string of information about a single variable. + * + * Borrows heavily on concepts from the Debug class of [Nette](http://nettephp.com/). + * + * @param mixed variable to dump + * @param integer maximum length of strings + * @return string + */ + public static function dump($value, $length = 128) + { + return Debug::_dump($value, $length); + } + + /** + * Helper for Debug::dump(), handles recursion in arrays and objects. + * + * @param mixed variable to dump + * @param integer maximum length of strings + * @param integer recursion level (internal) + * @return string + */ + protected static function _dump( & $var, $length = 128, $level = 0) + { + if ($var === NULL) + { + return 'NULL'; + } + elseif (is_bool($var)) + { + return 'bool '.($var ? 'TRUE' : 'FALSE'); + } + elseif (is_float($var)) + { + return 'float '.$var; + } + elseif (is_resource($var)) + { + if (($type = get_resource_type($var)) === 'stream' AND $meta = stream_get_meta_data($var)) + { + $meta = stream_get_meta_data($var); + + if (isset($meta['uri'])) + { + $file = $meta['uri']; + + if (function_exists('stream_is_local')) + { + // Only exists on PHP >= 5.2.4 + if (stream_is_local($file)) + { + $file = Debug::path($file); + } + } + + return 'resource('.$type.') '.htmlspecialchars($file, ENT_NOQUOTES, Kohana::$charset); + } + } + else + { + return 'resource('.$type.')'; + } + } + elseif (is_string($var)) + { + // Clean invalid multibyte characters. iconv is only invoked + // if there are non ASCII characters in the string, so this + // isn't too much of a hit. + $var = UTF8::clean($var, Kohana::$charset); + + if (UTF8::strlen($var) > $length) + { + // Encode the truncated string + $str = htmlspecialchars(UTF8::substr($var, 0, $length), ENT_NOQUOTES, Kohana::$charset).' …'; + } + else + { + // Encode the string + $str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::$charset); + } + + return 'string('.strlen($var).') "'.$str.'"'; + } + elseif (is_array($var)) + { + $output = array(); + + // Indentation for this variable + $space = str_repeat($s = ' ', $level); + + static $marker; + + if ($marker === NULL) + { + // Make a unique marker + $marker = uniqid("\x00"); + } + + if (empty($var)) + { + // Do nothing + } + elseif (isset($var[$marker])) + { + $output[] = "(\n$space$s*RECURSION*\n$space)"; + } + elseif ($level < 5) + { + $output[] = "("; + + $var[$marker] = TRUE; + foreach ($var as $key => & $val) + { + if ($key === $marker) continue; + if ( ! is_int($key)) + { + $key = '"'.htmlspecialchars($key, ENT_NOQUOTES, Kohana::$charset).'"'; + } + + $output[] = "$space$s$key => ".Debug::_dump($val, $length, $level + 1); + } + unset($var[$marker]); + + $output[] = "$space)"; + } + else + { + // Depth too great + $output[] = "(\n$space$s...\n$space)"; + } + + return 'array('.count($var).') '.implode("\n", $output); + } + elseif (is_object($var)) + { + // Copy the object as an array + $array = (array) $var; + + $output = array(); + + // Indentation for this variable + $space = str_repeat($s = ' ', $level); + + $hash = spl_object_hash($var); + + // Objects that are being dumped + static $objects = array(); + + if (empty($var)) + { + // Do nothing + } + elseif (isset($objects[$hash])) + { + $output[] = "{\n$space$s*RECURSION*\n$space}"; + } + elseif ($level < 10) + { + $output[] = "{"; + + $objects[$hash] = TRUE; + foreach ($array as $key => & $val) + { + if ($key[0] === "\x00") + { + // Determine if the access is protected or protected + $access = ''.(($key[1] === '*') ? 'protected' : 'private').''; + + // Remove the access level from the variable name + $key = substr($key, strrpos($key, "\x00") + 1); + } + else + { + $access = 'public'; + } + + $output[] = "$space$s$access $key => ".Debug::_dump($val, $length, $level + 1); + } + unset($objects[$hash]); + + $output[] = "$space}"; + } + else + { + // Depth too great + $output[] = "{\n$space$s...\n$space}"; + } + + return 'object '.get_class($var).'('.count($array).') '.implode("\n", $output); + } + else + { + return ''.gettype($var).' '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::$charset); + } + } + + /** + * Removes application, system, modpath, or docroot from a filename, + * replacing them with the plain text equivalents. Useful for debugging + * when you want to display a shorter path. + * + * // Displays SYSPATH/classes/kohana.php + * echo Debug::path(Kohana::find_file('classes', 'kohana')); + * + * @param string path to debug + * @return string + */ + public static function path($file) + { + if (strpos($file, APPPATH) === 0) + { + $file = 'APPPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(APPPATH)); + } + elseif (strpos($file, MODPATH) === 0) + { + $file = 'MODPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(MODPATH)); + } + elseif (strpos($file, SYSPATH) === 0) + { + $file = 'SYSPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(SYSPATH)); + } + elseif (strpos($file, SMDPATH) === 0) + { + $file = 'SMDPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(SMDPATH)); + } + elseif (strpos($file, DOCROOT) === 0) + { + $file = 'DOCROOT'.DIRECTORY_SEPARATOR.substr($file, strlen(DOCROOT)); + } + + return $file; + } + + /** + * Returns an HTML string, highlighting a specific line of a file, with some + * number of lines padded above and below. + * + * // Highlights the current line of the current file + * echo Debug::source(__FILE__, __LINE__); + * + * @param string file to open + * @param integer line number to highlight + * @param integer number of padding lines + * @return string source of file + * @return FALSE file is unreadable + */ + public static function source($file, $line_number, $padding = 5) + { + if ( ! $file OR ! is_readable($file)) + { + // Continuing will cause errors + return FALSE; + } + + // Open the file and set the line position + $file = fopen($file, 'r'); + $line = 0; + + // Set the reading range + $range = array('start' => $line_number - $padding, 'end' => $line_number + $padding); + + // Set the zero-padding amount for line numbers + $format = '% '.strlen($range['end']).'d'; + + $source = ''; + while (($row = fgets($file)) !== FALSE) + { + // Increment the line number + if (++$line > $range['end']) + break; + + if ($line >= $range['start']) + { + // Make the row safe for output + $row = htmlspecialchars($row, ENT_NOQUOTES, Kohana::$charset); + + // Trim whitespace and sanitize the row + $row = ''.sprintf($format, $line).' '.$row; + + if ($line === $line_number) + { + // Apply highlighting to this row + $row = ''.$row.''; + } + else + { + $row = ''.$row.''; + } + + // Add to the captured source + $source .= $row; + } + } + + // Close the file + fclose($file); + + return '
                    '.$source.'
                    '; + } + + /** + * Returns an array of HTML strings that represent each step in the backtrace. + * + * // Displays the entire current backtrace + * echo implode('
                    ', Debug::trace()); + * + * @param string path to debug + * @return string + */ + public static function trace(array $trace = NULL) + { + if ($trace === NULL) + { + // Start a new trace + $trace = debug_backtrace(); + } + + // Non-standard function calls + $statements = array('include', 'include_once', 'require', 'require_once'); + + $output = array(); + foreach ($trace as $step) + { + if ( ! isset($step['function'])) + { + // Invalid trace step + continue; + } + + if (isset($step['file']) AND isset($step['line'])) + { + // Include the source of this step + $source = Debug::source($step['file'], $step['line']); + } + + if (isset($step['file'])) + { + $file = $step['file']; + + if (isset($step['line'])) + { + $line = $step['line']; + } + } + + // function() + $function = $step['function']; + + if (in_array($step['function'], $statements)) + { + if (empty($step['args'])) + { + // No arguments + $args = array(); + } + else + { + // Sanitize the file path + $args = array($step['args'][0]); + } + } + elseif (isset($step['args'])) + { + if ( ! function_exists($step['function']) OR strpos($step['function'], '{closure}') !== FALSE) + { + // Introspection on closures or language constructs in a stack trace is impossible + $params = NULL; + } + else + { + if (isset($step['class'])) + { + if (method_exists($step['class'], $step['function'])) + { + $reflection = new ReflectionMethod($step['class'], $step['function']); + } + else + { + $reflection = new ReflectionMethod($step['class'], '__call'); + } + } + else + { + $reflection = new ReflectionFunction($step['function']); + } + + // Get the function parameters + $params = $reflection->getParameters(); + } + + $args = array(); + + foreach ($step['args'] as $i => $arg) + { + if (isset($params[$i])) + { + // Assign the argument by the parameter name + $args[$params[$i]->name] = $arg; + } + else + { + // Assign the argument by number + $args[$i] = $arg; + } + } + } + + if (isset($step['class'])) + { + // Class->method() or Class::method() + $function = $step['class'].$step['type'].$step['function']; + } + + $output[] = array( + 'function' => $function, + 'args' => isset($args) ? $args : NULL, + 'file' => isset($file) ? $file : NULL, + 'line' => isset($line) ? $line : NULL, + 'source' => isset($source) ? $source : NULL, + ); + + unset($function, $args, $file, $line, $source); + } + + return $output; + } + +} diff --git a/includes/kohana/system/classes/kohana/encrypt.php b/includes/kohana/system/classes/kohana/encrypt.php new file mode 100644 index 0000000..6516f8c --- /dev/null +++ b/includes/kohana/system/classes/kohana/encrypt.php @@ -0,0 +1,213 @@ +$name; + + if ( ! isset($config['key'])) + { + // No default encryption key is provided! + throw new Kohana_Exception('No encryption key is defined in the encryption configuration group: :group', + array(':group' => $name)); + } + + if ( ! isset($config['mode'])) + { + // Add the default mode + $config['mode'] = MCRYPT_MODE_NOFB; + } + + if ( ! isset($config['cipher'])) + { + // Add the default cipher + $config['cipher'] = MCRYPT_RIJNDAEL_128; + } + + // Create a new instance + Encrypt::$instances[$name] = new Encrypt($config['key'], $config['mode'], $config['cipher']); + } + + return Encrypt::$instances[$name]; + } + + /** + * Creates a new mcrypt wrapper. + * + * @param string encryption key + * @param string mcrypt mode + * @param string mcrypt cipher + */ + public function __construct($key, $mode, $cipher) + { + // Find the max length of the key, based on cipher and mode + $size = mcrypt_get_key_size($cipher, $mode); + + if (isset($key[$size])) + { + // Shorten the key to the maximum size + $key = substr($key, 0, $size); + } + + // Store the key, mode, and cipher + $this->_key = $key; + $this->_mode = $mode; + $this->_cipher = $cipher; + + // Store the IV size + $this->_iv_size = mcrypt_get_iv_size($this->_cipher, $this->_mode); + } + + /** + * Encrypts a string and returns an encrypted string that can be decoded. + * + * $data = $encrypt->encode($data); + * + * The encrypted binary data is encoded using [base64](http://php.net/base64_encode) + * to convert it to a string. This string can be stored in a database, + * displayed, and passed using most other means without corruption. + * + * @param string data to be encrypted + * @return string + */ + public function encode($data) + { + // Set the rand type if it has not already been set + if (Encrypt::$_rand === NULL) + { + if (Kohana::$is_windows) + { + // Windows only supports the system random number generator + Encrypt::$_rand = MCRYPT_RAND; + } + else + { + if (defined('MCRYPT_DEV_URANDOM')) + { + // Use /dev/urandom + Encrypt::$_rand = MCRYPT_DEV_URANDOM; + } + elseif (defined('MCRYPT_DEV_RANDOM')) + { + // Use /dev/random + Encrypt::$_rand = MCRYPT_DEV_RANDOM; + } + else + { + // Use the system random number generator + Encrypt::$_rand = MCRYPT_RAND; + } + } + } + + if (Encrypt::$_rand === MCRYPT_RAND) + { + // The system random number generator must always be seeded each + // time it is used, or it will not produce true random results + mt_srand(); + } + + // Create a random initialization vector of the proper size for the current cipher + $iv = mcrypt_create_iv($this->_iv_size, Encrypt::$_rand); + + // Encrypt the data using the configured options and generated iv + $data = mcrypt_encrypt($this->_cipher, $this->_key, $data, $this->_mode, $iv); + + // Use base64 encoding to convert to a string + return base64_encode($iv.$data); + } + + /** + * Decrypts an encoded string back to its original value. + * + * $data = $encrypt->decode($data); + * + * @param string encoded string to be decrypted + * @return FALSE if decryption fails + * @return string + */ + public function decode($data) + { + // Convert the data back to binary + $data = base64_decode($data, TRUE); + + if ( ! $data) + { + // Invalid base64 data + return FALSE; + } + + // Extract the initialization vector from the data + $iv = substr($data, 0, $this->_iv_size); + + if ($this->_iv_size !== strlen($iv)) + { + // The iv is not the expected size + return FALSE; + } + + // Remove the iv from the data + $data = substr($data, $this->_iv_size); + + // Return the decrypted data, trimming the \0 padding bytes from the end of the data + return rtrim(mcrypt_decrypt($this->_cipher, $this->_key, $data, $this->_mode, $iv), "\0"); + } + +} // End Encrypt diff --git a/includes/kohana/system/classes/kohana/exception.php b/includes/kohana/system/classes/kohana/exception.php new file mode 100644 index 0000000..33d694b --- /dev/null +++ b/includes/kohana/system/classes/kohana/exception.php @@ -0,0 +1,3 @@ +getNamespaces(true); + + // Detect the feed type. RSS 1.0/2.0 and Atom 1.0 are supported. + $feed = isset($feed->channel) ? $feed->xpath('//item') : $feed->entry; + + $i = 0; + $items = array(); + + foreach ($feed as $item) + { + if ($limit > 0 AND $i++ === $limit) + break; + $item_fields = (array) $item; + + // get namespaced tags + foreach ($namespaces as $ns) + { + $item_fields += (array) $item->children($ns); + } + $items[] = $item_fields; + } + + return $items; + } + + /** + * Creates a feed from the given parameters. + * + * @param array feed information + * @param array items to add to the feed + * @param string define which format to use (only rss2 is supported) + * @param string define which encoding to use + * @return string + */ + public static function create($info, $items, $format = 'rss2', $encoding = 'UTF-8') + { + $info += array('title' => 'Generated Feed', 'link' => '', 'generator' => 'KohanaPHP'); + + $feed = ''; + $feed = simplexml_load_string($feed); + + foreach ($info as $name => $value) + { + if ($name === 'image') + { + // Create an image element + $image = $feed->channel->addChild('image'); + + if ( ! isset($value['link'], $value['url'], $value['title'])) + { + throw new Kohana_Exception('Feed images require a link, url, and title'); + } + + if (strpos($value['link'], '://') === FALSE) + { + // Convert URIs to URLs + $value['link'] = URL::site($value['link'], 'http'); + } + + if (strpos($value['url'], '://') === FALSE) + { + // Convert URIs to URLs + $value['url'] = URL::site($value['url'], 'http'); + } + + // Create the image elements + $image->addChild('link', $value['link']); + $image->addChild('url', $value['url']); + $image->addChild('title', $value['title']); + } + else + { + if (($name === 'pubDate' OR $name === 'lastBuildDate') AND (is_int($value) OR ctype_digit($value))) + { + // Convert timestamps to RFC 822 formatted dates + $value = date('r', $value); + } + elseif (($name === 'link' OR $name === 'docs') AND strpos($value, '://') === FALSE) + { + // Convert URIs to URLs + $value = URL::site($value, 'http'); + } + + // Add the info to the channel + $feed->channel->addChild($name, $value); + } + } + + foreach ($items as $item) + { + // Add the item to the channel + $row = $feed->channel->addChild('item'); + + foreach ($item as $name => $value) + { + if ($name === 'pubDate' AND (is_int($value) OR ctype_digit($value))) + { + // Convert timestamps to RFC 822 formatted dates + $value = date('r', $value); + } + elseif (($name === 'link' OR $name === 'guid') AND strpos($value, '://') === FALSE) + { + // Convert URIs to URLs + $value = URL::site($value, 'http'); + } + + // Add the info to the row + $row->addChild($name, $value); + } + } + + if (function_exists('dom_import_simplexml')) + { + // Convert the feed object to a DOM object + $feed = dom_import_simplexml($feed)->ownerDocument; + + // DOM generates more readable XML + $feed->formatOutput = TRUE; + + // Export the document as XML + $feed = $feed->saveXML(); + } + else + { + // Export the document as XML + $feed = $feed->asXML(); + } + + return $feed; + } + +} // End Feed diff --git a/includes/kohana/system/classes/kohana/file.php b/includes/kohana/system/classes/kohana/file.php new file mode 100644 index 0000000..4bfaeb3 --- /dev/null +++ b/includes/kohana/system/classes/kohana/file.php @@ -0,0 +1,243 @@ +file($filename); + } + } + + if (ini_get('mime_magic.magicfile') AND function_exists('mime_content_type')) + { + // The mime_content_type function is only useful with a magic file + return mime_content_type($filename); + } + + if ( ! empty($extension)) + { + return File::mime_by_ext($extension); + } + + // Unable to find the mime-type + return FALSE; + } + + /** + * Return the mime type of an extension. + * + * $mime = File::mime_by_ext('png'); // "image/png" + * + * @param string extension: php, pdf, txt, etc + * @return string mime type on success + * @return FALSE on failure + */ + public static function mime_by_ext($extension) + { + // Load all of the mime types + $mimes = Kohana::config('mimes'); + + return isset($mimes[$extension]) ? $mimes[$extension][0] : FALSE; + } + + /** + * Lookup MIME types for a file + * + * @see Kohana_File::mime_by_ext() + * @param string $extension Extension to lookup + * @return array Array of MIMEs associated with the specified extension + */ + public static function mimes_by_ext($extension) + { + // Load all of the mime types + $mimes = Kohana::config('mimes'); + + return isset($mimes[$extension]) ? ( (array) $mimes[$extension]) : array(); + } + + /** + * Lookup file extensions by MIME type + * + * @param string $type File MIME type + * @return array File extensions matching MIME type + */ + public static function exts_by_mime($type) + { + static $types = array(); + + // Fill the static array + if (empty($types)) + { + foreach (Kohana::config('mimes') as $ext => $mimes) + { + foreach ($mimes as $mime) + { + if ($mime == 'application/octet-stream') + { + // octet-stream is a generic binary + continue; + } + + if ( ! isset($types[$mime])) + { + $types[$mime] = array( (string) $ext); + } + elseif ( ! in_array($ext, $types[$mime])) + { + $types[$mime][] = (string) $ext; + } + } + } + } + + return isset($types[$type]) ? $types[$type] : FALSE; + } + + /** + * Lookup a single file extension by MIME type. + * + * @param string $type MIME type to lookup + * @return mixed First file extension matching or false + */ + public static function ext_by_mime($type) + { + return current(File::exts_by_mime($type)); + } + + /** + * Split a file into pieces matching a specific size. Used when you need to + * split large files into smaller pieces for easy transmission. + * + * $count = File::split($file); + * + * @param string file to be split + * @param string directory to output to, defaults to the same directory as the file + * @param integer size, in MB, for each piece to be + * @return integer The number of pieces that were created + */ + public static function split($filename, $piece_size = 10) + { + // Open the input file + $file = fopen($filename, 'rb'); + + // Change the piece size to bytes + $piece_size = floor($piece_size * 1024 * 1024); + + // Write files in 8k blocks + $block_size = 1024 * 8; + + // Total number of peices + $peices = 0; + + while ( ! feof($file)) + { + // Create another piece + $peices += 1; + + // Create a new file piece + $piece = str_pad($peices, 3, '0', STR_PAD_LEFT); + $piece = fopen($filename.'.'.$piece, 'wb+'); + + // Number of bytes read + $read = 0; + + do + { + // Transfer the data in blocks + fwrite($piece, fread($file, $block_size)); + + // Another block has been read + $read += $block_size; + } + while ($read < $piece_size); + + // Close the piece + fclose($piece); + } + + // Close the file + fclose($file); + + return $peices; + } + + /** + * Join a split file into a whole file. Does the reverse of [File::split]. + * + * $count = File::join($file); + * + * @param string split filename, without .000 extension + * @param string output filename, if different then an the filename + * @return integer The number of pieces that were joined. + */ + public static function join($filename) + { + // Open the file + $file = fopen($filename, 'wb+'); + + // Read files in 8k blocks + $block_size = 1024 * 8; + + // Total number of peices + $pieces = 0; + + while (is_file($piece = $filename.'.'.str_pad($pieces + 1, 3, '0', STR_PAD_LEFT))) + { + // Read another piece + $pieces += 1; + + // Open the piece for reading + $piece = fopen($piece, 'rb'); + + while ( ! feof($piece)) + { + // Transfer the data in blocks + fwrite($file, fread($piece, $block_size)); + } + + // Close the peice + fclose($piece); + } + + return $pieces; + } + +} // End file diff --git a/includes/kohana/system/classes/kohana/form.php b/includes/kohana/system/classes/kohana/form.php new file mode 100644 index 0000000..5979040 --- /dev/null +++ b/includes/kohana/system/classes/kohana/form.php @@ -0,0 +1,434 @@ + 'get')); + * + * // When "file" inputs are present, you must include the "enctype" + * echo Form::open(NULL, array('enctype' => 'multipart/form-data')); + * + * @param mixed form action, defaults to the current request URI, or [Request] class to use + * @param array html attributes + * @return string + * @uses Request::instance + * @uses URL::site + * @uses HTML::attributes + */ + public static function open($action = NULL, array $attributes = NULL) + { + if ($action instanceof Request) + { + // Use the current URI + $action = $action->uri(); + } + + if ($action === '') + { + // Use only the base URI + $action = Kohana::$base_url; + } + elseif (strpos($action, '://') === FALSE) + { + // Make the URI absolute + $action = URL::site($action); + } + + // Add the form action to the attributes + $attributes['action'] = $action; + + // Only accept the default character set + $attributes['accept-charset'] = Kohana::$charset; + + if ( ! isset($attributes['method'])) + { + // Use POST method + $attributes['method'] = 'post'; + } + + return ''; + } + + /** + * Creates the closing form tag. + * + * echo Form::close(); + * + * @return string + */ + public static function close() + { + return ''; + } + + /** + * Creates a form input. If no type is specified, a "text" type input will + * be returned. + * + * echo Form::input('username', $username); + * + * @param string input name + * @param string input value + * @param array html attributes + * @return string + * @uses HTML::attributes + */ + public static function input($name, $value = NULL, array $attributes = NULL) + { + // Set the input name + $attributes['name'] = $name; + + // Set the input value + $attributes['value'] = $value; + + if ( ! isset($attributes['type'])) + { + // Default type is text + $attributes['type'] = 'text'; + } + + return ''; + } + + /** + * Creates a hidden form input. + * + * echo Form::hidden('csrf', $token); + * + * @param string input name + * @param string input value + * @param array html attributes + * @return string + * @uses Form::input + */ + public static function hidden($name, $value = NULL, array $attributes = NULL) + { + $attributes['type'] = 'hidden'; + + return Form::input($name, $value, $attributes); + } + + /** + * Creates a password form input. + * + * echo Form::password('password'); + * + * @param string input name + * @param string input value + * @param array html attributes + * @return string + * @uses Form::input + */ + public static function password($name, $value = NULL, array $attributes = NULL) + { + $attributes['type'] = 'password'; + + return Form::input($name, $value, $attributes); + } + + /** + * Creates a file upload form input. No input value can be specified. + * + * echo Form::file('image'); + * + * @param string input name + * @param array html attributes + * @return string + * @uses Form::input + */ + public static function file($name, array $attributes = NULL) + { + $attributes['type'] = 'file'; + + return Form::input($name, NULL, $attributes); + } + + /** + * Creates a checkbox form input. + * + * echo Form::checkbox('remember_me', 1, (bool) $remember); + * + * @param string input name + * @param string input value + * @param boolean checked status + * @param array html attributes + * @return string + * @uses Form::input + */ + public static function checkbox($name, $value = NULL, $checked = FALSE, array $attributes = NULL) + { + $attributes['type'] = 'checkbox'; + + if ($checked === TRUE) + { + // Make the checkbox active + $attributes['checked'] = 'checked'; + } + + return Form::input($name, $value, $attributes); + } + + /** + * Creates a radio form input. + * + * echo Form::radio('like_cats', 1, $cats); + * echo Form::radio('like_cats', 0, ! $cats); + * + * @param string input name + * @param string input value + * @param boolean checked status + * @param array html attributes + * @return string + * @uses Form::input + */ + public static function radio($name, $value = NULL, $checked = FALSE, array $attributes = NULL) + { + $attributes['type'] = 'radio'; + + if ($checked === TRUE) + { + // Make the radio active + $attributes['checked'] = 'checked'; + } + + return Form::input($name, $value, $attributes); + } + + /** + * Creates a textarea form input. + * + * echo Form::textarea('about', $about); + * + * @param string textarea name + * @param string textarea body + * @param array html attributes + * @param boolean encode existing HTML characters + * @return string + * @uses HTML::attributes + * @uses HTML::chars + */ + public static function textarea($name, $body = '', array $attributes = NULL, $double_encode = TRUE) + { + // Set the input name + $attributes['name'] = $name; + + // Add default rows and cols attributes (required) + $attributes += array('rows' => 10, 'cols' => 50); + + return ''.HTML::chars($body, $double_encode).''; + } + + /** + * Creates a select form input. + * + * echo Form::select('country', $countries, $country); + * + * [!!] Support for multiple selected options was added in v3.0.7. + * + * @param string input name + * @param array available options + * @param mixed selected option string, or an array of selected options + * @param array html attributes + * @return string + * @uses HTML::attributes + */ + public static function select($name, array $options = NULL, $selected = NULL, array $attributes = NULL) + { + // Set the input name + $attributes['name'] = $name; + + if (is_array($selected)) + { + // This is a multi-select, god save us! + $attributes['multiple'] = 'multiple'; + } + + if ( ! is_array($selected)) + { + if ($selected === NULL) + { + // Use an empty array + $selected = array(); + } + else + { + // Convert the selected options to an array + $selected = array( (string) $selected); + } + } + + if (empty($options)) + { + // There are no options + $options = ''; + } + else + { + foreach ($options as $value => $name) + { + if (is_array($name)) + { + // Create a new optgroup + $group = array('label' => $value); + + // Create a new list of options + $_options = array(); + + foreach ($name as $_value => $_name) + { + // Force value to be string + $_value = (string) $_value; + + // Create a new attribute set for this option + $option = array('value' => $_value); + + if (in_array($_value, $selected)) + { + // This option is selected + $option['selected'] = 'selected'; + } + + // Change the option to the HTML string + $_options[] = ''.HTML::chars($_name, FALSE).''; + } + + // Compile the options into a string + $_options = "\n".implode("\n", $_options)."\n"; + + $options[$value] = ''.$_options.''; + } + else + { + // Force value to be string + $value = (string) $value; + + // Create a new attribute set for this option + $option = array('value' => $value); + + if (in_array($value, $selected)) + { + // This option is selected + $option['selected'] = 'selected'; + } + + // Change the option to the HTML string + $options[$value] = ''.HTML::chars($name, FALSE).''; + } + } + + // Compile the options into a single string + $options = "\n".implode("\n", $options)."\n"; + } + + return ''.$options.''; + } + + /** + * Creates a submit form input. + * + * echo Form::submit(NULL, 'Login'); + * + * @param string input name + * @param string input value + * @param array html attributes + * @return string + * @uses Form::input + */ + public static function submit($name, $value, array $attributes = NULL) + { + $attributes['type'] = 'submit'; + + return Form::input($name, $value, $attributes); + } + + /** + * Creates a image form input. + * + * echo Form::image(NULL, NULL, array('src' => 'media/img/login.png')); + * + * @param string input name + * @param string input value + * @param array html attributes + * @param boolean add index file to URL? + * @return string + * @uses Form::input + */ + public static function image($name, $value, array $attributes = NULL, $index = FALSE) + { + if ( ! empty($attributes['src'])) + { + if (strpos($attributes['src'], '://') === FALSE) + { + // Add the base URL + $attributes['src'] = URL::base($index).$attributes['src']; + } + } + + $attributes['type'] = 'image'; + + return Form::input($name, $value, $attributes); + } + + /** + * Creates a button form input. Note that the body of a button is NOT escaped, + * to allow images and other HTML to be used. + * + * echo Form::button('save', 'Save Profile', array('type' => 'submit')); + * + * @param string input name + * @param string input value + * @param array html attributes + * @return string + * @uses HTML::attributes + */ + public static function button($name, $body, array $attributes = NULL) + { + // Set the input name + $attributes['name'] = $name; + + return ''.$body.''; + } + + /** + * Creates a form label. Label text is not automatically translated. + * + * echo Form::label('username', 'Username'); + * + * @param string target input + * @param string label text + * @param array html attributes + * @return string + * @uses HTML::attributes + */ + public static function label($input, $text = NULL, array $attributes = NULL) + { + if ($text === NULL) + { + // Use the input name as the text + $text = ucwords(preg_replace('/[\W_]+/', ' ', $input)); + } + + // Set the label target + $attributes['for'] = $input; + + return ''.$text.''; + } + +} // End form diff --git a/includes/kohana/system/classes/kohana/fragment.php b/includes/kohana/system/classes/kohana/fragment.php new file mode 100644 index 0000000..fc6f074 --- /dev/null +++ b/includes/kohana/system/classes/kohana/fragment.php @@ -0,0 +1,147 @@ + cache key + */ + protected static $_caches = array(); + + /** + * Generate the cache key name for a fragment. + * + * $key = Fragment::_cache_key('footer', TRUE); + * + * @param string fragment name + * @param boolean multilingual fragment support + * @return string + * @uses I18n::lang + * @since 3.0.4 + */ + protected static function _cache_key($name, $i18n = NULL) + { + if ($i18n === NULL) + { + // Use the default setting + $i18n = Fragment::$i18n; + } + + // Language prefix for cache key + $i18n = ($i18n === TRUE) ? I18n::lang() : ''; + + // Note: $i18n and $name need to be delimited to prevent naming collisions + return 'Fragment::cache('.$i18n.'+'.$name.')'; + } + + /** + * Load a fragment from cache and display it. Multiple fragments can + * be nested with different life times. + * + * if ( ! Fragment::load('footer')) { + * // Anything that is echo'ed here will be saved + * Fragment::save(); + * } + * + * @param string fragment name + * @param integer fragment cache lifetime + * @param boolean multilingual fragment support + * @return boolean + */ + public static function load($name, $lifetime = NULL, $i18n = NULL) + { + // Set the cache lifetime + $lifetime = ($lifetime === NULL) ? Fragment::$lifetime : (int) $lifetime; + + // Get the cache key name + $cache_key = Fragment::_cache_key($name, $i18n); + + if ($fragment = Kohana::cache($cache_key, NULL, $lifetime)) + { + // Display the cached fragment now + echo $fragment; + + return TRUE; + } + else + { + // Start the output buffer + ob_start(); + + // Store the cache key by the buffer level + Fragment::$_caches[ob_get_level()] = $cache_key; + + return FALSE; + } + } + + /** + * Saves the currently open fragment in the cache. + * + * Fragment::save(); + * + * @return void + */ + public static function save() + { + // Get the buffer level + $level = ob_get_level(); + + if (isset(Fragment::$_caches[$level])) + { + // Get the cache key based on the level + $cache_key = Fragment::$_caches[$level]; + + // Delete the cache key, we don't need it anymore + unset(Fragment::$_caches[$level]); + + // Get the output buffer and display it at the same time + $fragment = ob_get_flush(); + + // Cache the fragment + Kohana::cache($cache_key, $fragment); + } + } + + /** + * Delete a cached fragment. + * + * Fragment::delete($key); + * + * @param string fragment name + * @param boolean multilingual fragment support + * @return void + */ + public static function delete($name, $i18n = NULL) + { + // Invalid the cache + Kohana::cache(Fragment::_cache_key($name, $i18n), NULL, -3600); + } + +} // End Fragment diff --git a/includes/kohana/system/classes/kohana/html.php b/includes/kohana/system/classes/kohana/html.php new file mode 100644 index 0000000..66fb40a --- /dev/null +++ b/includes/kohana/system/classes/kohana/html.php @@ -0,0 +1,386 @@ +'.$title.''; + } + + /** + * Creates an HTML anchor to a file. Note that the title is not escaped, + * to allow HTML elements within links (images, etc). + * + * echo HTML::file_anchor('media/doc/user_guide.pdf', 'User Guide'); + * + * @param string name of file to link to + * @param string link text + * @param array HTML anchor attributes + * @param mixed protocol to pass to URL::base() + * @param boolean include the index page + * @return string + * @uses URL::base + * @uses HTML::attributes + */ + public static function file_anchor($file, $title = NULL, array $attributes = NULL, $protocol = NULL, $index = FALSE) + { + if ($title === NULL) + { + // Use the file name as the title + $title = basename($file); + } + + // Add the file link to the attributes + $attributes['href'] = URL::base($protocol, $index).$file; + + return ''.$title.''; + } + + /** + * Generates an obfuscated version of a string. Text passed through this + * method is less likely to be read by web crawlers and robots, which can + * be helpful for spam prevention, but can prevent legitimate robots from + * reading your content. + * + * echo HTML::obfuscate($text); + * + * @param string string to obfuscate + * @return string + * @since 3.0.3 + */ + public static function obfuscate($string) + { + $safe = ''; + foreach (str_split($string) as $letter) + { + switch (rand(1, 3)) + { + // HTML entity code + case 1: + $safe .= '&#'.ord($letter).';'; + break; + + // Hex character code + case 2: + $safe .= '&#x'.dechex(ord($letter)).';'; + break; + + // Raw (no) encoding + case 3: + $safe .= $letter; + } + } + + return $safe; + } + + /** + * Generates an obfuscated version of an email address. Helps prevent spam + * robots from finding email addresses. + * + * echo HTML::email($address); + * + * @param string email address + * @return string + * @uses HTML::obfuscate + */ + public static function email($email) + { + // Make sure the at sign is always obfuscated + return str_replace('@', '@', HTML::obfuscate($email)); + } + + /** + * Creates an email (mailto:) anchor. Note that the title is not escaped, + * to allow HTML elements within links (images, etc). + * + * echo HTML::mailto($address); + * + * @param string email address to send to + * @param string link text + * @param array HTML anchor attributes + * @return string + * @uses HTML::email + * @uses HTML::attributes + */ + public static function mailto($email, $title = NULL, array $attributes = NULL) + { + // Obfuscate email address + $email = HTML::email($email); + + if ($title === NULL) + { + // Use the email address as the title + $title = $email; + } + + return ''.$title.''; + } + + /** + * Creates a style sheet link element. + * + * echo HTML::style('media/css/screen.css'); + * + * @param string file name + * @param array default attributes + * @param mixed protocol to pass to URL::base() + * @param boolean include the index page + * @return string + * @uses URL::base + * @uses HTML::attributes + */ + public static function style($file, array $attributes = NULL, $protocol = NULL, $index = FALSE) + { + if (strpos($file, '://') === FALSE) + { + // Add the base URL + $file = URL::base($protocol, $index).$file; + } + + // Set the stylesheet link + $attributes['href'] = $file; + + // Set the stylesheet rel + $attributes['rel'] = 'stylesheet'; + + // Set the stylesheet type + $attributes['type'] = 'text/css'; + + return ''; + } + + /** + * Creates a script link. + * + * echo HTML::script('media/js/jquery.min.js'); + * + * @param string file name + * @param array default attributes + * @param mixed protocol to pass to URL::base() + * @param boolean include the index page + * @return string + * @uses URL::base + * @uses HTML::attributes + */ + public static function script($file, array $attributes = NULL, $protocol = NULL, $index = FALSE) + { + if (strpos($file, '://') === FALSE) + { + // Add the base URL + $file = URL::base($protocol, $index).$file; + } + + // Set the script link + $attributes['src'] = $file; + + // Set the script type + $attributes['type'] = 'text/javascript'; + + return ''; + } + + /** + * Creates a image link. + * + * echo HTML::image('media/img/logo.png', array('alt' => 'My Company')); + * + * @param string file name + * @param array default attributes + * @param mixed protocol to pass to URL::base() + * @param boolean include the index page + * @return string + * @uses URL::base + * @uses HTML::attributes + */ + public static function image($file, array $attributes = NULL, $protocol = NULL, $index = FALSE) + { + if (strpos($file, '://') === FALSE) + { + // Add the base URL + $file = URL::base($protocol, $index).$file; + } + + // Add the image link + $attributes['src'] = $file; + + return ''; + } + + /** + * Compiles an array of HTML attributes into an attribute string. + * Attributes will be sorted using HTML::$attribute_order for consistency. + * + * echo ''.$content.'
                    '; + * + * @param array attribute list + * @return string + */ + public static function attributes(array $attributes = NULL) + { + if (empty($attributes)) + return ''; + + $sorted = array(); + foreach (HTML::$attribute_order as $key) + { + if (isset($attributes[$key])) + { + // Add the attribute to the sorted list + $sorted[$key] = $attributes[$key]; + } + } + + // Combine the sorted attributes + $attributes = $sorted + $attributes; + + $compiled = ''; + foreach ($attributes as $key => $value) + { + if ($value === NULL) + { + // Skip attributes that have NULL values + continue; + } + + if (is_int($key)) + { + // Assume non-associative keys are mirrored attributes + $key = $value; + } + + // Add the attribute value + $compiled .= ' '.$key.'="'.HTML::chars($value).'"'; + } + + return $compiled; + } + +} // End html diff --git a/includes/kohana/system/classes/kohana/http.php b/includes/kohana/system/classes/kohana/http.php new file mode 100644 index 0000000..966c704 --- /dev/null +++ b/includes/kohana/system/classes/kohana/http.php @@ -0,0 +1,160 @@ + $value) + { + // If the header has not already been set + if ( ! isset($headers[$matches[1][$key]])) + { + // Apply the header directly + $headers[$matches[1][$key]] = $matches[2][$key]; + } + // Otherwise there is an existing entry + else + { + // If the entry is an array + if (is_array($headers[$matches[1][$key]])) + { + // Apply the new entry to the array + $headers[$matches[1][$key]][] = $matches[2][$key]; + } + // Otherwise create a new array with the entries + else + { + $headers[$matches[1][$key]] = array( + $headers[$matches[1][$key]], + $matches[2][$key], + ); + } + } + } + } + + // Return the headers + return new HTTP_Header($headers); + } + + /** + * Parses the the HTTP request headers and returns an array containing + * key value pairs. This method is slow, but provides an accurate + * representation of the HTTP request. + * + * // Get http headers into the request + * $request->headers = HTTP::request_headers(); + * + * @return HTTP_Header + */ + public static function request_headers() + { + // If running on apache server + if (function_exists('apache_request_headers')) + { + // Return the much faster method + return new HTTP_Header(apache_request_headers()); + } + // If the PECL HTTP tools are installed + elseif (extension_loaded('http')) + { + // Return the much faster method + return new HTTP_Header(http_get_request_headers()); + } + + // Setup the output + $headers = array(); + + // Parse the content type + if ( ! empty($_SERVER['CONTENT_TYPE'])) + { + $headers['content-type'] = $_SERVER['CONTENT_TYPE']; + } + + // Parse the content length + if ( ! empty($_SERVER['CONTENT_LENGTH'])) + { + $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; + } + + foreach ($_SERVER as $key => $value) + { + // If there is no HTTP header here, skip + if (strpos($key, 'HTTP_') !== 0) + { + continue; + } + + // This is a dirty hack to ensure HTTP_X_FOO_BAR becomes x-foo-bar + $headers[str_replace(array('HTTP_', '_'), array('', '-'), $key)] = $value; + } + + return new HTTP_Header($headers); + } + + /** + * Processes an array of key value pairs and encodes + * the values to meet RFC 3986 + * + * @param array $params Params + * @return string + */ + public static function www_form_urlencode(array $params = array()) + { + if ( ! $params) + return; + + $encoded = array(); + + foreach ($params as $key => $value) + { + $encoded[] = $key.'='.rawurlencode($value); + } + + return implode('&', $encoded); + } +} // End Kohana_HTTP \ No newline at end of file diff --git a/includes/kohana/system/classes/kohana/http/exception.php b/includes/kohana/system/classes/kohana/http/exception.php new file mode 100644 index 0000000..85e12da --- /dev/null +++ b/includes/kohana/system/classes/kohana/http/exception.php @@ -0,0 +1,34 @@ + $user)); + * + * @param string status message, custom content to display with error + * @param array translation variables + * @param integer the http status code + * @return void + */ + public function __construct($message = NULL, array $variables = NULL, $code = 0) + { + if ($code == 0) + { + $code = $this->_code; + } + + if ( ! isset(Response::$messages[$code])) + throw new Kohana_Exception('Unrecognized HTTP status code: :code . Only valid HTTP status codes are acceptable, see RFC 2616.', array(':code' => $code)); + + parent::__construct($message, $variables, $code); + } + +} // End Kohana_HTTP_Exception \ No newline at end of file diff --git a/includes/kohana/system/classes/kohana/http/exception/400.php b/includes/kohana/system/classes/kohana/http/exception/400.php new file mode 100644 index 0000000..935d74f --- /dev/null +++ b/includes/kohana/system/classes/kohana/http/exception/400.php @@ -0,0 +1,10 @@ + 'max-age=200; public')); + * + * @param array $header_values Values to parse + * @param array $header_commas_allowed Header values where commas are not delimiters (usually date) + * @return array + */ + public static function parse_header_values(array $header_values, array $header_commas_allowed = array('user-agent', 'date', 'expires', 'last-modified')) + { + /** + * @see http://www.w3.org/Protocols/rfc2616/rfc2616.html + * + * HTTP header declarations should be treated as case-insensitive + */ + $header_values = array_change_key_case($header_values, CASE_LOWER); + + // Foreach of the header values applied + foreach ($header_values as $key => $value) + { + if (is_array($value)) + { + $values = array(); + + if (Arr::is_assoc($value)) + { + + foreach ($value as $k => $v) + { + $values[] = HTTP_Header::parse_header_values($v); + } + } + else + { + // RFC 2616 allows multiple headers with same name if they can be + // concatinated using commas without altering the original message. + // This usually occurs with multiple Set-Cookie: headers + $array = array(); + foreach ($value as $k => $v) + { + // Break value into component parts + $v = explode(';', $v); + + // Do some nasty parsing to flattern the array into components, + // parsing key values + $array = Arr::flatten(array_map('HTTP_Header_Value::parse_key_value', $v)); + + // Get the K/V component and extract the first element + $key_value_component = array_slice($array, 0, 1, TRUE); + array_shift($array); + + // Create the HTTP_Header_Value component array + $http_header['key'] = key($key_value_component); + $http_header['value'] = current($key_value_component); + $http_header['properties'] = $array; + + // Create the HTTP_Header_Value + $values[] = new HTTP_Header_Value($http_header); + } + } + + // Assign HTTP_Header_Value array to the header + $header_values[$key] = $values; + continue; + } + + // If the key allows commas or no commas are found + if (in_array($key, $header_commas_allowed) or (strpos($value, ',') === FALSE)) + { + // If the key is user-agent, we don't want to parse the string + if ($key === 'user-agent') + { + $header_values[$key] = new HTTP_Header_Value($value, TRUE); + } + // Else, behave normally + else + { + $header_values[$key] = new HTTP_Header_Value($value); + } + + // Move to next header + continue; + } + + // Create an array of the values and clear any whitespace + $value = array_map('trim', explode(',', $value)); + + $parsed_values = array(); + + // Foreach value + foreach ($value as $v) + { + $v = new HTTP_Header_Value($v); + + // Convert the value string into an object + if ($v->key === NULL) + { + $parsed_values[] = $v; + } + else + { + $parsed_values[$v->key] = $v; + } + } + + // Apply parsed value to the header + $header_values[$key] = $parsed_values; + } + + // Return the parsed header values + return $header_values; + } + + /** + * Constructor method for [Kohana_HTTP_Header]. Uses the standard constructor + * of the parent `ArrayObject` class. + * + * $header_object = new HTTP_Header(array('x-powered-by' => 'Kohana 3.1.x', 'expires' => '...')); + * + * @param mixed Input array + * @param int Flags + * @param string The iterator class to use + */ + public function __construct($input, $flags = NULL, $iterator_class = 'ArrayIterator') + { + // Parse the values into [HTTP_Header_Values] + parent::__construct(HTTP_Header::parse_header_values($input), $flags, $iterator_class); + + // If sort by quality is set, sort the fields by q=0.0 value + if (HTTP_Header::$sort_by_quality) + { + $this->sort_values_by_quality(); + } + } + + /** + * Returns the header object as a string, including + * the terminating new line + * + * // Return the header as a string + * echo (string) $request->headers(); + * + * @return string + */ + public function __toString() + { + $header = ''; + + foreach ($this as $key => $value) + { + if (is_array($value)) + { + $header .= $key.': '.(implode(', ', $value))."\r\n"; + } + else + { + $header .= $key.': '.$value."\r\n"; + } + } + + return $header."\n"; + } + + /** + * Overloads the `ArrayObject::exchangeArray()` method to ensure all + * values passed are parsed correctly into a [Kohana_HTTP_Header_Value]. + * + * // Input new headers + * $headers->exchangeArray(array( + * 'date' => 'Wed, 24 Nov 2010 21:09:23 GMT', + * 'cache-control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' + * )); + * + * @param array $array Array to exchange + * @return array + */ + public function exchangeArray($array) + { + return parent::exchangeArray(HTTP_Header::parse_header_values($array)); + } + + /** + * Overloads the `ArrayObject::offsetSet` method to ensure any + * access is correctly converted to the correct object type. + * + * // Add a new header from encoded string + * $headers['cache-control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' + * + * @param mixed $index Key + * @param mixed $newval Value + * @return void + */ + public function offsetSet($index, $newval) + { + if (is_array($newval) AND (current($newval) instanceof HTTP_Header_Value)) + return parent::offsetSet(strtolower($index), $newval); + elseif ( ! $newval instanceof HTTP_Header_Value) + { + $newval = new HTTP_Header_Value($newval); + } + + parent::offsetSet(strtolower($index), $newval); + } + + /** + * Sort the headers by quality property if the header matches the + * [Kohana_HTTP_Header::$default_sort_filter] definition. + * + * #### Default sort values + * + * - Accept + * - Accept-Chars + * - Accept-Encoding + * - Accept-Lang + * + * @param array $filter Header fields to parse + * @return self + */ + public function sort_values_by_quality(array $filter = array()) + { + // If a filter argument is supplied + if ($filter) + { + // Apply filter and store previous + $previous_filter = HTTP_Header::$default_sort_filter; + HTTP_Header::$default_sort_filter = $filter; + } + + // Get a copy of this ArrayObject + $values = $this->getArrayCopy(); + + foreach ($values as $key => $value) + { + if ( ! is_array($value) or ! in_array($key, HTTP_Header::$default_sort_filter)) + { + unset($values[$key]); + continue; + } + + // Sort them by comparison + uasort($value, array($this, '_sort_by_comparison')); + + $values[$key] = $value; + } + + // Return filter to previous state if required + if ($filter) + { + HTTP_Header::$default_sort_filter = $previous_filter; + } + + foreach ($values as $key => $value) + { + $this[$key] = $value; + } + + // Return this + return $this; + } + + protected function _sort_by_comparison($value_a, $value_b) + { + // Test for correct instance type + if ( ! $value_a instanceof HTTP_Header_Value OR ! $value_b instanceof HTTP_Header_Value) + { + // Return neutral if cannot test value + return 0; + } + + // Extract the qualities + $a = (float) Arr::get($value_a->properties, 'q', HTTP_Header_Value::$default_quality); + $b = (float) Arr::get($value_b->properties, 'q', HTTP_Header_Value::$default_quality); + + if ($a == $b) + return 0; + elseif ($a < $b) + return 1; + elseif ($a > $b) + return -1; + } + +} // End Kohana_HTTP_Header \ No newline at end of file diff --git a/includes/kohana/system/classes/kohana/http/header/value.php b/includes/kohana/system/classes/kohana/http/header/value.php new file mode 100644 index 0000000..7ddef8e --- /dev/null +++ b/includes/kohana/system/classes/kohana/http/header/value.php @@ -0,0 +1,219 @@ + $parts[1]); + } + } + + /** + * @var array + */ + public $properties = array(); + + /** + * @var void|string + */ + public $key; + + /** + * @var array + */ + public $value = array(); + + /** + * Builds the header field + * + * @param mixed value configuration array passed + * @param boolean no_parse skip parsing of the string (i.e. user-agent) + * @throws Kohana_HTTP_Exception + */ + public function __construct($value, $no_parse = FALSE) + { + // If no parse is set, set the value and get out of here (user-agent) + if ($no_parse) + { + $this->key = NULL; + $this->value = $value; + return; + } + + // If configuration array passed + if (is_array($value)) + { + // Parse each value + foreach ($value as $k => $v) + { + // If the key is a property + if (property_exists($this, $k)) + { + // Map values + $this->$k = $v; + } + } + + } + // If value is a string + elseif (is_string($value)) + { + // Detect properties + if (strpos($value, ';') !== FALSE) + { + // Remove properties from the string + $parts = explode(';', $value); + $value = array_shift($parts); + + // Parse the properties + $properties = array(); + + // Foreach part + foreach ($parts as $part) + { + // Merge the parsed values + $properties = array_merge(HTTP_Header_Value::parse_key_value($part), $properties); + } + + // Apply the parsed values + $this->properties = $properties; + } + + // Parse the value and get key + $value = HTTP_Header_Value::parse_key_value($value); + $key = key($value); + + // If the key is a string + if (is_string($key)) + { + // Apply the key as a property + $this->key = $key; + } + + // Apply the value + $this->value = current($value); + } + // Unrecognised value type + else + { + throw new HTTP_Exception_500(__METHOD__.' unknown header value type: :type. array or string allowed.', array(':type' => gettype($value))); + } + } + + /** + * Provides direct access to the key of this header value + * + * @param string $key Key value to set + * @return mixed + */ + public function key($key = NULL) + { + if ($key === NULL) + { + return $this->key; + } + else + { + $this->key = $key; + return $this; + } + } + + /** + * Provides direct access to the value of this header value + * + * @param string $value Value to set + * @return mixed + */ + public function value($value = NULL) + { + if ($value === NULL) + { + return $this->value; + } + else + { + $this->value = $value; + return $this; + } + } + + /** + * Provides direct access to the properties of this header value + * + * @param array $properties Properties to set to this value + * @return mixed + */ + public function properties(array $properties = array()) + { + if ( ! $properties) + { + return $this->properties; + } + else + { + $this->properties = $properties; + return $this; + } + } + + /** + * Magic method to handle object being cast to + * string. Produces the following header value syntax + * + * [key=]value[; property[=property_value][; ... ]] + * + * @return string + */ + public function __toString() + { + + $string = ($this->key !== NULL) ? ($this->key.'='.$this->value) : $this->value; + + if ($this->properties) + { + $props = array($string); + foreach ($this->properties as $k => $v) + { + $props[] = is_int($k) ? $v : ($k.'='.$v); + } + $string = implode('; ', $props); + } + + return $string; + } + +} // End Kohana_HTTP_Header_Value \ No newline at end of file diff --git a/includes/kohana/system/classes/kohana/http/interaction.php b/includes/kohana/system/classes/kohana/http/interaction.php new file mode 100644 index 0000000..064aaa7 --- /dev/null +++ b/includes/kohana/system/classes/kohana/http/interaction.php @@ -0,0 +1,56 @@ +status(404); + * + * // Get the current status + * $status = $response->status(); + * + * @param integer $code Status to set to this response + * @return mixed + */ + public function status($code = NULL); + +} \ No newline at end of file diff --git a/includes/kohana/system/classes/kohana/i18n.php b/includes/kohana/system/classes/kohana/i18n.php new file mode 100644 index 0000000..673dea4 --- /dev/null +++ b/includes/kohana/system/classes/kohana/i18n.php @@ -0,0 +1,166 @@ + $username)); + * + * @package Kohana + * @category Base + * @author Kohana Team + * @copyright (c) 2008-2011 Kohana Team + * @license http://kohanaframework.org/license + */ +class Kohana_I18n { + + /** + * @var string target language: en-us, es-es, zh-cn, etc + */ + public static $lang = 'en-us'; + + /** + * @var string source language: en-us, es-es, zh-cn, etc + */ + public static $source = 'en-us'; + + /** + * @var array cache of loaded languages + */ + protected static $_cache = array(); + + /** + * Get and set the target language. + * + * // Get the current language + * $lang = I18n::lang(); + * + * // Change the current language to Spanish + * I18n::lang('es-es'); + * + * @param string new language setting + * @return string + * @since 3.0.2 + */ + public static function lang($lang = NULL) + { + if ($lang) + { + // Normalize the language + I18n::$lang = strtolower(str_replace(array(' ', '_'), '-', $lang)); + } + + return I18n::$lang; + } + + /** + * Returns translation of a string. If no translation exists, the original + * string will be returned. No parameters are replaced. + * + * $hello = I18n::get('Hello friends, my name is :name'); + * + * @param string text to translate + * @param string target language + * @return string + */ + public static function get($string, $lang = NULL) + { + if ( ! $lang) + { + // Use the global target language + $lang = I18n::$lang; + } + + // Load the translation table for this language + $table = I18n::load($lang); + + // Return the translated string if it exists + return isset($table[$string]) ? $table[$string] : $string; + } + + /** + * Returns the translation table for a given language. + * + * // Get all defined Spanish messages + * $messages = I18n::load('es-es'); + * + * @param string language to load + * @return array + */ + public static function load($lang) + { + if (isset(I18n::$_cache[$lang])) + { + return I18n::$_cache[$lang]; + } + + // New translation table + $table = array(); + + // Split the language: language, region, locale, etc + $parts = explode('-', $lang); + + do + { + // Create a path for this set of parts + $path = implode(DIRECTORY_SEPARATOR, $parts); + + if ($files = Kohana::find_file('i18n', $path, NULL, TRUE)) + { + $t = array(); + foreach ($files as $file) + { + // Merge the language strings into the sub table + $t = array_merge($t, Kohana::load($file)); + } + + // Append the sub table, preventing less specific language + // files from overloading more specific files + $table += $t; + } + + // Remove the last part + array_pop($parts); + } + while ($parts); + + // Cache the translation table locally + return I18n::$_cache[$lang] = $table; + } + +} // End I18n + +if ( ! function_exists('__')) +{ + /** + * Kohana translation/internationalization function. The PHP function + * [strtr](http://php.net/strtr) is used for replacing parameters. + * + * __('Welcome back, :user', array(':user' => $username)); + * + * [!!] The target language is defined by [I18n::$lang]. + * + * @uses I18n::get + * @param string text to translate + * @param array values to replace in the translated text + * @param string source language + * @return string + */ + function __($string, array $values = NULL, $lang = 'en-us') + { + if ($lang !== I18n::$lang) + { + // The message and target languages are different + // Get the translation for this message + $string = I18n::get($string); + } + + return empty($values) ? $string : strtr($string, $values); + } +} diff --git a/includes/kohana/system/classes/kohana/inflector.php b/includes/kohana/system/classes/kohana/inflector.php new file mode 100644 index 0000000..1fb937a --- /dev/null +++ b/includes/kohana/system/classes/kohana/inflector.php @@ -0,0 +1,269 @@ +uncountable; + + // Make uncountables mirrored + Inflector::$uncountable = array_combine(Inflector::$uncountable, Inflector::$uncountable); + } + + return isset(Inflector::$uncountable[strtolower($str)]); + } + + /** + * Makes a plural word singular. + * + * echo Inflector::singular('cats'); // "cat" + * echo Inflector::singular('fish'); // "fish", uncountable + * + * You can also provide the count to make inflection more intelligent. + * In this case, it will only return the singular value if the count is + * greater than one and not zero. + * + * echo Inflector::singular('cats', 2); // "cats" + * + * [!!] Special inflections are defined in `config/inflector.php`. + * + * @param string word to singularize + * @param integer count of thing + * @return string + * @uses Inflector::uncountable + */ + public static function singular($str, $count = NULL) + { + // $count should always be a float + $count = ($count === NULL) ? 1.0 : (float) $count; + + // Do nothing when $count is not 1 + if ($count != 1) + return $str; + + // Remove garbage + $str = strtolower(trim($str)); + + // Cache key name + $key = 'singular_'.$str.$count; + + if (isset(Inflector::$cache[$key])) + return Inflector::$cache[$key]; + + if (Inflector::uncountable($str)) + return Inflector::$cache[$key] = $str; + + if (empty(Inflector::$irregular)) + { + // Cache irregular words + Inflector::$irregular = Kohana::config('inflector')->irregular; + } + + if ($irregular = array_search($str, Inflector::$irregular)) + { + $str = $irregular; + } + elseif (preg_match('/us$/', $str)) + { + // http://en.wikipedia.org/wiki/Plural_form_of_words_ending_in_-us + // Already singular, do nothing + } + elseif (preg_match('/[sxz]es$/', $str) OR preg_match('/[^aeioudgkprt]hes$/', $str)) + { + // Remove "es" + $str = substr($str, 0, -2); + } + elseif (preg_match('/[^aeiou]ies$/', $str)) + { + // Replace "ies" with "y" + $str = substr($str, 0, -3).'y'; + } + elseif (substr($str, -1) === 's' AND substr($str, -2) !== 'ss') + { + // Remove singular "s" + $str = substr($str, 0, -1); + } + + return Inflector::$cache[$key] = $str; + } + + /** + * Makes a singular word plural. + * + * echo Inflector::plural('fish'); // "fish", uncountable + * echo Inflector::plural('cat'); // "cats" + * + * You can also provide the count to make inflection more intelligent. + * In this case, it will only return the plural value if the count is + * not one. + * + * echo Inflector::singular('cats', 3); // "cats" + * + * [!!] Special inflections are defined in `config/inflector.php`. + * + * @param string word to pluralize + * @param integer count of thing + * @return string + * @uses Inflector::uncountable + */ + public static function plural($str, $count = NULL) + { + // $count should always be a float + $count = ($count === NULL) ? 0.0 : (float) $count; + + // Do nothing with singular + if ($count == 1) + return $str; + + // Remove garbage + $str = trim($str); + + // Cache key name + $key = 'plural_'.$str.$count; + + // Check uppercase + $is_uppercase = ctype_upper($str); + + if (isset(Inflector::$cache[$key])) + return Inflector::$cache[$key]; + + if (Inflector::uncountable($str)) + return Inflector::$cache[$key] = $str; + + if (empty(Inflector::$irregular)) + { + // Cache irregular words + Inflector::$irregular = Kohana::config('inflector')->irregular; + } + + if (isset(Inflector::$irregular[$str])) + { + $str = Inflector::$irregular[$str]; + } + elseif (preg_match('/[sxz]$/', $str) OR preg_match('/[^aeioudgkprt]h$/', $str)) + { + $str .= 'es'; + } + elseif (preg_match('/[^aeiou]y$/', $str)) + { + // Change "y" to "ies" + $str = substr_replace($str, 'ies', -1); + } + else + { + $str .= 's'; + } + + // Convert to uppsecase if nessasary + if ($is_uppercase) + { + $str = strtoupper($str); + } + + // Set the cache and return + return Inflector::$cache[$key] = $str; + } + + /** + * Makes a phrase camel case. Spaces and underscores will be removed. + * + * $str = Inflector::camelize('mother cat'); // "motherCat" + * $str = Inflector::camelize('kittens in bed'); // "kittensInBed" + * + * @param string phrase to camelize + * @return string + */ + public static function camelize($str) + { + $str = 'x'.strtolower(trim($str)); + $str = ucwords(preg_replace('/[\s_]+/', ' ', $str)); + + return substr(str_replace(' ', '', $str), 1); + } + + /** + * Converts a camel case phrase into a spaced phrase. + * + * $str = Inflector::decamelize('houseCat'); // "house cat" + * $str = Inflector::decamelize('kingAllyCat'); // "king ally cat" + * + * @param string phrase to camelize + * @param string word separator + * @return string + */ + public static function decamelize($str, $sep = ' ') + { + return strtolower(preg_replace('/([a-z])([A-Z])/', '$1'.$sep.'$2', trim($str))); + } + + /** + * Makes a phrase underscored instead of spaced. + * + * $str = Inflector::underscore('five cats'); // "five_cats"; + * + * @param string phrase to underscore + * @return string + */ + public static function underscore($str) + { + return preg_replace('/\s+/', '_', trim($str)); + } + + /** + * Makes an underscored or dashed phrase human-readable. + * + * $str = Inflector::humanize('kittens-are-cats'); // "kittens are cats" + * $str = Inflector::humanize('dogs_as_well'); // "dogs as well" + * + * @param string phrase to make human-readable + * @return string + */ + public static function humanize($str) + { + return preg_replace('/[_-]+/', ' ', trim($str)); + } + +} // End Inflector diff --git a/includes/kohana/system/classes/kohana/kohana/exception.php b/includes/kohana/system/classes/kohana/kohana/exception.php new file mode 100644 index 0000000..101c839 --- /dev/null +++ b/includes/kohana/system/classes/kohana/kohana/exception.php @@ -0,0 +1,206 @@ + human readable name + */ + public static $php_errors = array( + E_ERROR => 'Fatal Error', + E_USER_ERROR => 'User Error', + E_PARSE => 'Parse Error', + E_WARNING => 'Warning', + E_USER_WARNING => 'User Warning', + E_STRICT => 'Strict', + E_NOTICE => 'Notice', + E_RECOVERABLE_ERROR => 'Recoverable Error', + ); + + /** + * @var string error rendering view + */ + public static $error_view = 'kohana/error'; + + /** + * Creates a new translated exception. + * + * throw new Kohana_Exception('Something went terrible wrong, :user', + * array(':user' => $user)); + * + * @param string error message + * @param array translation variables + * @param integer|string the exception code + * @return void + */ + public function __construct($message, array $variables = NULL, $code = 0) + { + if (defined('E_DEPRECATED')) + { + // E_DEPRECATED only exists in PHP >= 5.3.0 + Kohana_Exception::$php_errors[E_DEPRECATED] = 'Deprecated'; + } + + // Save the unmodified code + // @link http://bugs.php.net/39615 + $this->code = $code; + + // Set the message + $message = __($message, $variables); + + // Pass the message and integer code to the parent + parent::__construct($message, (int) $code); + } + + /** + * Magic object-to-string method. + * + * echo $exception; + * + * @uses Kohana_Exception::text + * @return string + */ + public function __toString() + { + return Kohana_Exception::text($this); + } + + /** + * Inline exception handler, displays the error message, source of the + * exception, and the stack trace of the error. + * + * @uses Kohana_Exception::text + * @param object exception object + * @return boolean + */ + public static function handler(Exception $e) + { + try + { + // Get the exception information + $type = get_class($e); + $code = $e->getCode(); + $message = $e->getMessage(); + $file = $e->getFile(); + $line = $e->getLine(); + + // Get the exception backtrace + $trace = $e->getTrace(); + + if ($e instanceof ErrorException) + { + if (isset(Kohana_Exception::$php_errors[$code])) + { + // Use the human-readable error name + $code = Kohana_Exception::$php_errors[$code]; + } + + if (version_compare(PHP_VERSION, '5.3', '<')) + { + // Workaround for a bug in ErrorException::getTrace() that exists in + // all PHP 5.2 versions. @see http://bugs.php.net/bug.php?id=45895 + for ($i = count($trace) - 1; $i > 0; --$i) + { + if (isset($trace[$i - 1]['args'])) + { + // Re-position the args + $trace[$i]['args'] = $trace[$i - 1]['args']; + + // Remove the args + unset($trace[$i - 1]['args']); + } + } + } + } + + // Create a text version of the exception + $error = Kohana_Exception::text($e); + + if (is_object(Kohana::$log)) + { + // Add this exception to the log + Kohana::$log->add(Log::ERROR, $error); + + // Make sure the logs are written + Kohana::$log->write(); + } + + if (Kohana::$is_cli) + { + // Just display the text of the exception + echo "\n{$error}\n"; + + exit(1); + } + + if ( ! headers_sent()) + { + // Make sure the proper http header is sent + $http_header_status = ($e instanceof HTTP_Exception) ? $code : 500; + + header('Content-Type: text/html; charset='.Kohana::$charset, TRUE, $http_header_status); + } + + if (Request::$current !== NULL AND Request::current()->is_ajax() === TRUE) + { + // Just display the text of the exception + echo "\n{$error}\n"; + + exit(1); + } + + // Start an output buffer + ob_start(); + + // Include the exception HTML + if ($view_file = Kohana::find_file('views', Kohana_Exception::$error_view)) + { + include $view_file; + } + else + { + throw new Kohana_Exception('Error view file does not exist: views/:file', array( + ':file' => Kohana_Exception::$error_view, + )); + } + + // Display the contents of the output buffer + echo ob_get_clean(); + + exit(1); + } + 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); + } + } + + /** + * Get a single line of text representing the exception: + * + * Error [ Code ]: Message ~ File [ Line ] + * + * @param object Exception + * @return string + */ + public static function text(Exception $e) + { + return sprintf('%s [ %s ]: %s ~ %s [ %d ]', + get_class($e), $e->getCode(), strip_tags($e->getMessage()), Debug::path($e->getFile()), $e->getLine()); + } + +} // End Kohana_Exception diff --git a/includes/kohana/system/classes/kohana/log.php b/includes/kohana/system/classes/kohana/log.php new file mode 100644 index 0000000..46240ef --- /dev/null +++ b/includes/kohana/system/classes/kohana/log.php @@ -0,0 +1,205 @@ +attach($writer); + * + * @param object Log_Writer instance + * @param mixed array of messages levels to write OR max level to write + * @param integer min level to write IF $levels is not an array + * @return Log + */ + public function attach(Log_Writer $writer, $levels = array(), $min_level = 0) + { + if ( ! is_array($levels)) + { + $levels = range($min_level, $levels); + } + + $this->_writers["{$writer}"] = array + ( + 'object' => $writer, + 'levels' => $levels + ); + + return $this; + } + + /** + * Detaches a log writer. The same writer object must be used. + * + * $log->detach($writer); + * + * @param object Log_Writer instance + * @return Log + */ + public function detach(Log_Writer $writer) + { + // Remove the writer + unset($this->_writers["{$writer}"]); + + return $this; + } + + /** + * Adds a message to the log. Replacement values must be passed in to be + * replaced using [strtr](http://php.net/strtr). + * + * $log->add(Log::ERROR, 'Could not locate user: :user', array( + * ':user' => $username, + * )); + * + * @param string level of message + * @param string message body + * @param array values to replace in the message + * @return Log + */ + public function add($level, $message, array $values = NULL) + { + if ($values) + { + // Insert the values into the message + $message = strtr($message, $values); + } + + // Create a new message and timestamp it + $this->_messages[] = array + ( + 'time' => Date::formatted_time('now', Log::$timestamp, Log::$timezone), + 'level' => $level, + 'body' => $message, + ); + + if (Log::$write_on_add) + { + // Write logs as they are added + $this->write(); + } + + return $this; + } + + /** + * Write and clear all of the messages. + * + * $log->write(); + * + * @return void + */ + public function write() + { + if (empty($this->_messages)) + { + // There is nothing to write, move along + return; + } + + // Import all messages locally + $messages = $this->_messages; + + // Reset the messages array + $this->_messages = array(); + + foreach ($this->_writers as $writer) + { + if (empty($writer['levels'])) + { + // Write all of the messages + $writer['object']->write($messages); + } + else + { + // Filtered messages + $filtered = array(); + + foreach ($messages as $message) + { + if (in_array($message['level'], $writer['levels'])) + { + // Writer accepts this kind of message + $filtered[] = $message; + } + } + + // Write the filtered messages + $writer['object']->write($filtered); + } + } + } + +} // End Kohana_Log diff --git a/includes/kohana/system/classes/kohana/log/file.php b/includes/kohana/system/classes/kohana/log/file.php new file mode 100644 index 0000000..9bb2750 --- /dev/null +++ b/includes/kohana/system/classes/kohana/log/file.php @@ -0,0 +1,95 @@ + Debug::path($directory))); + } + + // Determine the directory path + $this->_directory = realpath($directory).DIRECTORY_SEPARATOR; + } + + /** + * Writes each of the messages into the log file. The log file will be + * appended to the `YYYY/MM/DD.log.php` file, where YYYY is the current + * year, MM is the current month, and DD is the current day. + * + * $writer->write($messages); + * + * @param array messages + * @return void + */ + public function write(array $messages) + { + // Set the yearly directory name + $directory = $this->_directory.date('Y'); + + if ( ! is_dir($directory)) + { + // Create the yearly directory + mkdir($directory, 02777); + + // Set permissions (must be manually set to fix umask issues) + chmod($directory, 02777); + } + + // Add the month to the directory + $directory .= DIRECTORY_SEPARATOR.date('m'); + + if ( ! is_dir($directory)) + { + // Create the yearly directory + mkdir($directory, 02777); + + // Set permissions (must be manually set to fix umask issues) + chmod($directory, 02777); + } + + // Set the name of the log file + $filename = $directory.DIRECTORY_SEPARATOR.date('d').EXT; + + if ( ! file_exists($filename)) + { + // Create the log file + file_put_contents($filename, Kohana::FILE_SECURITY.' ?>'.PHP_EOL); + + // Allow anyone to write to log files + chmod($filename, 0666); + } + + foreach ($messages as $message) + { + // Write each message into the log file + // Format: time --- level: body + file_put_contents($filename, PHP_EOL.$message['time'].' --- '.$this->_log_levels[$message['level']].': '.$message['body'], FILE_APPEND); + } + } + +} // End Kohana_Log_File \ No newline at end of file diff --git a/includes/kohana/system/classes/kohana/log/stderr.php b/includes/kohana/system/classes/kohana/log/stderr.php new file mode 100644 index 0000000..f75b9ee --- /dev/null +++ b/includes/kohana/system/classes/kohana/log/stderr.php @@ -0,0 +1,31 @@ +write($messages); + * + * @param array messages + * @return void + */ + public function write(array $messages) + { + // Set the log line format + $format = 'time --- type: body'; + + foreach ($messages as $message) + { + // Writes out each message + fwrite(STDERR, PHP_EOL.strtr($format, $message)); + } + } +} // End Kohana_Log_StdErr diff --git a/includes/kohana/system/classes/kohana/log/stdout.php b/includes/kohana/system/classes/kohana/log/stdout.php new file mode 100644 index 0000000..2e7e801 --- /dev/null +++ b/includes/kohana/system/classes/kohana/log/stdout.php @@ -0,0 +1,31 @@ +write($messages); + * + * @param array messages + * @return void + */ + public function write(array $messages) + { + // Set the log line format + $format = 'time --- type: body'; + + foreach ($messages as $message) + { + // Writes out each message + fwrite(STDOUT, PHP_EOL.strtr($format, $message)); + } + } +} // End Kohana_Log_StdOut diff --git a/includes/kohana/system/classes/kohana/log/syslog.php b/includes/kohana/system/classes/kohana/log/syslog.php new file mode 100644 index 0000000..459c7b6 --- /dev/null +++ b/includes/kohana/system/classes/kohana/log/syslog.php @@ -0,0 +1,70 @@ + LOG_ERR, + 'CRITICAL' => LOG_CRIT, + 'STRACE' => LOG_ALERT, + 'ALERT' => LOG_WARNING, + 'INFO' => LOG_INFO, + 'DEBUG' => LOG_DEBUG); + + /** + * Creates a new syslog logger. + * + * @see http://us2.php.net/openlog + * + * @param string syslog identifier + * @param int facility to log to + * @return void + */ + public function __construct($ident = 'KohanaPHP', $facility = LOG_USER) + { + $this->_ident = $ident; + + // Open the connection to syslog + openlog($this->_ident, LOG_CONS, $facility); + } + + /** + * Writes each of the messages into the syslog. + * + * @param array messages + * @return void + */ + public function write(array $messages) + { + foreach ($messages as $message) + { + syslog($message['level'], $message['body']); + } + } + + /** + * Closes the syslog connection + * + * @return void + */ + public function __destruct() + { + // Close connection to syslog + closelog(); + } + +} // End Kohana_Log_Syslog diff --git a/includes/kohana/system/classes/kohana/log/writer.php b/includes/kohana/system/classes/kohana/log/writer.php new file mode 100644 index 0000000..2b46b1c --- /dev/null +++ b/includes/kohana/system/classes/kohana/log/writer.php @@ -0,0 +1,49 @@ + 'EMERGENCY', + LOG_CRIT => 'CRITICAL', + LOG_ERR => 'ERROR', + LOG_WARNING => 'WARNING', + LOG_NOTICE => 'NOTICE', + LOG_INFO => 'INFO', + LOG_DEBUG => 'DEBUG', + ); + + /** + * Write an array of messages. + * + * $writer->write($messages); + * + * @param array messages + * @return void + */ + abstract public function write(array $messages); + + /** + * Allows the writer to have a unique key when stored. + * + * echo $writer; + * + * @return string + */ + final public function __toString() + { + return spl_object_hash($this); + } + +} // End Kohana_Log_Writer diff --git a/includes/kohana/system/classes/kohana/model.php b/includes/kohana/system/classes/kohana/model.php new file mode 100644 index 0000000..e41aefc --- /dev/null +++ b/includes/kohana/system/classes/kohana/model.php @@ -0,0 +1,29 @@ + power of 2 that defines the unit's size + */ + public static $byte_units = array + ( + 'B' => 0, + 'K' => 10, + 'Ki' => 10, + 'KB' => 10, + 'KiB' => 10, + 'M' => 20, + 'Mi' => 20, + 'MB' => 20, + 'MiB' => 20, + 'G' => 30, + 'Gi' => 30, + 'GB' => 30, + 'GiB' => 30, + 'T' => 40, + 'Ti' => 40, + 'TB' => 40, + 'TiB' => 40, + 'P' => 50, + 'Pi' => 50, + 'PB' => 50, + 'PiB' => 50, + 'E' => 60, + 'Ei' => 60, + 'EB' => 60, + 'EiB' => 60, + 'Z' => 70, + 'Zi' => 70, + 'ZB' => 70, + 'ZiB' => 70, + 'Y' => 80, + 'Yi' => 80, + 'YB' => 80, + 'YiB' => 80, + ); + + /** + * Returns the English ordinal suffix (th, st, nd, etc) of a number. + * + * echo 2, Num::ordinal(2); // "2nd" + * echo 10, Num::ordinal(10); // "10th" + * echo 33, Num::ordinal(33); // "33rd" + * + * @param integer number + * @return string + */ + public static function ordinal($number) + { + if ($number % 100 > 10 AND $number % 100 < 14) + { + return 'th'; + } + + switch ($number % 10) + { + case 1: + return 'st'; + case 2: + return 'nd'; + case 3: + return 'rd'; + default: + return 'th'; + } + } + + /** + * Locale-aware number and monetary formatting. + * + * // In English, "1,200.05" + * // In Spanish, "1200,05" + * // In Portuguese, "1 200,05" + * echo Num::format(1200.05, 2); + * + * // In English, "1,200.05" + * // In Spanish, "1.200,05" + * // In Portuguese, "1.200.05" + * echo Num::format(1200.05, 2, TRUE); + * + * @param float number to format + * @param integer decimal places + * @param boolean monetary formatting? + * @return string + * @since 3.0.2 + */ + public static function format($number, $places, $monetary = FALSE) + { + $info = localeconv(); + + if ($monetary) + { + $decimal = $info['mon_decimal_point']; + $thousands = $info['mon_thousands_sep']; + } + else + { + $decimal = $info['decimal_point']; + $thousands = $info['thousands_sep']; + } + + return number_format($number, $places, $decimal, $thousands); + } + + /** + * Round a number to a specified precision, using a specified tie breaking technique + * + * @param float $value Number to round + * @param integer $precision Desired precision + * @param integer $mode Tie breaking mode, accepts the PHP_ROUND_HALF_* constants + * @param boolean $native Set to false to force use of the userland implementation + * @return float Rounded number + */ + public static function round($value, $precision = 0, $mode = self::ROUND_HALF_UP, $native = true) + { + if (version_compare(PHP_VERSION, '5.3', '>=') AND $native) + { + return round($value, $precision, $mode); + } + + if ($mode === self::ROUND_HALF_UP) + { + return round($value, $precision); + } + else + { + $factor = ($precision === 0) ? 1 : pow(10, $precision); + + switch ($mode) + { + case self::ROUND_HALF_DOWN: + case self::ROUND_HALF_EVEN: + case self::ROUND_HALF_ODD: + // Check if we have a rounding tie, otherwise we can just call round() + if (($value * $factor) - floor($value * $factor) === 0.5) + { + if ($mode === self::ROUND_HALF_DOWN) + { + // Round down operation, so we round down unless the value + // is -ve because up is down and down is up down there. ;) + $up = ($value < 0); + } + else + { + // Round up if the integer is odd and the round mode is set to even + // or the integer is even and the round mode is set to odd. + // Any other instance round down. + $up = ( ! ( ! (floor($value * $factor) & 1)) === ($mode === self::ROUND_HALF_EVEN)); + } + + if ($up) + { + $value = ceil($value * $factor); + } + else + { + $value = floor($value * $factor); + } + return $value / $factor; + } + else + { + return round($value, $precision); + } + break; + } + } + } + + /** + * Converts a file size number to a byte value. File sizes are defined in + * the format: SB, where S is the size (1, 8.5, 300, etc.) and B is the + * byte unit (K, MiB, GB, etc.). All valid byte units are defined in + * Num::$byte_units + * + * echo Num::bytes('200K'); // 204800 + * echo Num::bytes('5MiB'); // 5242880 + * echo Num::bytes('1000'); // 1000 + * echo Num::bytes('2.5GB'); // 2684354560 + * + * @param string file size in SB format + * @return float + */ + public static function bytes($size) + { + // Prepare the size + $size = trim( (string) $size); + + // Construct an OR list of byte units for the regex + $accepted = implode('|', array_keys(Num::$byte_units)); + + // Construct the regex pattern for verifying the size format + $pattern = '/^([0-9]+(?:\.[0-9]+)?)('.$accepted.')?$/Di'; + + // Verify the size format and store the matching parts + if ( ! preg_match($pattern, $size, $matches)) + throw new Kohana_Exception('The byte unit size, ":size", is improperly formatted.', array( + ':size' => $size, + )); + + // Find the float value of the size + $size = (float) $matches[1]; + + // Find the actual unit, assume B if no unit specified + $unit = Arr::get($matches, 2, 'B'); + + // Convert the size into bytes + $bytes = $size * pow(2, Num::$byte_units[$unit]); + + return $bytes; + } + +} // End num diff --git a/includes/kohana/system/classes/kohana/profiler.php b/includes/kohana/system/classes/kohana/profiler.php new file mode 100644 index 0000000..2ca24d3 --- /dev/null +++ b/includes/kohana/system/classes/kohana/profiler.php @@ -0,0 +1,385 @@ + strtolower($group), + 'name' => (string) $name, + + // Start the benchmark + 'start_time' => microtime(TRUE), + 'start_memory' => memory_get_usage(), + + // Set the stop keys without values + 'stop_time' => FALSE, + 'stop_memory' => FALSE, + ); + + return $token; + } + + /** + * Stops a benchmark. + * + * Profiler::stop($token); + * + * @param string token + * @return void + */ + public static function stop($token) + { + // Stop the benchmark + Profiler::$_marks[$token]['stop_time'] = microtime(TRUE); + Profiler::$_marks[$token]['stop_memory'] = memory_get_usage(); + } + + /** + * Deletes a benchmark. If an error occurs during the benchmark, it is + * recommended to delete the benchmark to prevent statistics from being + * adversely affected. + * + * Profiler::delete($token); + * + * @param string token + * @return void + */ + public static function delete($token) + { + // Remove the benchmark + unset(Profiler::$_marks[$token]); + } + + /** + * Returns all the benchmark tokens by group and name as an array. + * + * $groups = Profiler::groups(); + * + * @return array + */ + public static function groups() + { + $groups = array(); + + foreach (Profiler::$_marks as $token => $mark) + { + // Sort the tokens by the group and name + $groups[$mark['group']][$mark['name']][] = $token; + } + + return $groups; + } + + /** + * Gets the min, max, average and total of a set of tokens as an array. + * + * $stats = Profiler::stats($tokens); + * + * @param array profiler tokens + * @return array min, max, average, total + * @uses Profiler::total + */ + public static function stats(array $tokens) + { + // Min and max are unknown by default + $min = $max = array( + 'time' => NULL, + 'memory' => NULL); + + // Total values are always integers + $total = array( + 'time' => 0, + 'memory' => 0); + + foreach ($tokens as $token) + { + // Get the total time and memory for this benchmark + list($time, $memory) = Profiler::total($token); + + if ($max['time'] === NULL OR $time > $max['time']) + { + // Set the maximum time + $max['time'] = $time; + } + + if ($min['time'] === NULL OR $time < $min['time']) + { + // Set the minimum time + $min['time'] = $time; + } + + // Increase the total time + $total['time'] += $time; + + if ($max['memory'] === NULL OR $memory > $max['memory']) + { + // Set the maximum memory + $max['memory'] = $memory; + } + + if ($min['memory'] === NULL OR $memory < $min['memory']) + { + // Set the minimum memory + $min['memory'] = $memory; + } + + // Increase the total memory + $total['memory'] += $memory; + } + + // Determine the number of tokens + $count = count($tokens); + + // Determine the averages + $average = array( + 'time' => $total['time'] / $count, + 'memory' => $total['memory'] / $count); + + return array( + 'min' => $min, + 'max' => $max, + 'total' => $total, + 'average' => $average); + } + + /** + * Gets the min, max, average and total of profiler groups as an array. + * + * $stats = Profiler::group_stats('test'); + * + * @param mixed single group name string, or array with group names; all groups by default + * @return array min, max, average, total + * @uses Profiler::groups + * @uses Profiler::stats + */ + public static function group_stats($groups = NULL) + { + // Which groups do we need to calculate stats for? + $groups = ($groups === NULL) + ? Profiler::groups() + : array_intersect_key(Profiler::groups(), array_flip( (array) $groups)); + + // All statistics + $stats = array(); + + foreach ($groups as $group => $names) + { + foreach ($names as $name => $tokens) + { + // Store the stats for each subgroup. + // We only need the values for "total". + $_stats = Profiler::stats($tokens); + $stats[$group][$name] = $_stats['total']; + } + } + + // Group stats + $groups = array(); + + foreach ($stats as $group => $names) + { + // Min and max are unknown by default + $groups[$group]['min'] = $groups[$group]['max'] = array( + 'time' => NULL, + 'memory' => NULL); + + // Total values are always integers + $groups[$group]['total'] = array( + 'time' => 0, + 'memory' => 0); + + foreach ($names as $total) + { + if ( ! isset($groups[$group]['min']['time']) OR $groups[$group]['min']['time'] > $total['time']) + { + // Set the minimum time + $groups[$group]['min']['time'] = $total['time']; + } + if ( ! isset($groups[$group]['min']['memory']) OR $groups[$group]['min']['memory'] > $total['memory']) + { + // Set the minimum memory + $groups[$group]['min']['memory'] = $total['memory']; + } + + if ( ! isset($groups[$group]['max']['time']) OR $groups[$group]['max']['time'] < $total['time']) + { + // Set the maximum time + $groups[$group]['max']['time'] = $total['time']; + } + if ( ! isset($groups[$group]['max']['memory']) OR $groups[$group]['max']['memory'] < $total['memory']) + { + // Set the maximum memory + $groups[$group]['max']['memory'] = $total['memory']; + } + + // Increase the total time and memory + $groups[$group]['total']['time'] += $total['time']; + $groups[$group]['total']['memory'] += $total['memory']; + } + + // Determine the number of names (subgroups) + $count = count($names); + + // Determine the averages + $groups[$group]['average']['time'] = $groups[$group]['total']['time'] / $count; + $groups[$group]['average']['memory'] = $groups[$group]['total']['memory'] / $count; + } + + return $groups; + } + + /** + * Gets the total execution time and memory usage of a benchmark as a list. + * + * list($time, $memory) = Profiler::total($token); + * + * @param string token + * @return array execution time, memory + */ + public static function total($token) + { + // Import the benchmark data + $mark = Profiler::$_marks[$token]; + + if ($mark['stop_time'] === FALSE) + { + // The benchmark has not been stopped yet + $mark['stop_time'] = microtime(TRUE); + $mark['stop_memory'] = memory_get_usage(); + } + + return array + ( + // Total time in seconds + $mark['stop_time'] - $mark['start_time'], + + // Amount of memory in bytes + $mark['stop_memory'] - $mark['start_memory'], + ); + } + + /** + * Gets the total application run time and memory usage. Caches the result + * so that it can be compared between requests. + * + * list($time, $memory) = Profiler::application(); + * + * @return array execution time, memory + * @uses Kohana::cache + */ + public static function application() + { + // Load the stats from cache, which is valid for 1 day + $stats = Kohana::cache('profiler_application_stats', NULL, 3600 * 24); + + if ( ! is_array($stats) OR $stats['count'] > Profiler::$rollover) + { + // Initialize the stats array + $stats = array( + 'min' => array( + 'time' => NULL, + 'memory' => NULL), + 'max' => array( + 'time' => NULL, + 'memory' => NULL), + 'total' => array( + 'time' => NULL, + 'memory' => NULL), + 'count' => 0); + } + + // Get the application run time + $time = microtime(TRUE) - KOHANA_START_TIME; + + // Get the total memory usage + $memory = memory_get_usage() - KOHANA_START_MEMORY; + + // Calculate max time + if ($stats['max']['time'] === NULL OR $time > $stats['max']['time']) + { + $stats['max']['time'] = $time; + } + + // Calculate min time + if ($stats['min']['time'] === NULL OR $time < $stats['min']['time']) + { + $stats['min']['time'] = $time; + } + + // Add to total time + $stats['total']['time'] += $time; + + // Calculate max memory + if ($stats['max']['memory'] === NULL OR $memory > $stats['max']['memory']) + { + $stats['max']['memory'] = $memory; + } + + // Calculate min memory + if ($stats['min']['memory'] === NULL OR $memory < $stats['min']['memory']) + { + $stats['min']['memory'] = $memory; + } + + // Add to total memory + $stats['total']['memory'] += $memory; + + // Another mark has been added to the stats + $stats['count']++; + + // Determine the averages + $stats['average'] = array( + 'time' => $stats['total']['time'] / $stats['count'], + 'memory' => $stats['total']['memory'] / $stats['count']); + + // Cache the new stats + Kohana::cache('profiler_application_stats', $stats); + + // Set the current application execution time and memory + // Do NOT cache these, they are specific to the current request only + $stats['current']['time'] = $time; + $stats['current']['memory'] = $memory; + + // Return the total application run time and memory usage + return $stats; + } + +} // End Profiler diff --git a/includes/kohana/system/classes/kohana/request.php b/includes/kohana/system/classes/kohana/request.php new file mode 100644 index 0000000..eddd2d7 --- /dev/null +++ b/includes/kohana/system/classes/kohana/request.php @@ -0,0 +1,1501 @@ +query($_GET); + $request->post($_POST); + + if (isset($protocol)) + { + // Set the request protocol + $request->protocol($protocol); + } + + if (isset($method)) + { + // Set the request method + $request->method($method); + } + + if (isset($referrer)) + { + // Set the referrer + $request->referrer($referrer); + } + + if (isset($requested_with)) + { + // Apply the requested with variable + $request->requested_with($requested_with); + } + + if (isset($body)) + { + // Set the request body (probably a PUT type) + $request->body($body); + } + } + else + { + $request = new Request($uri, $cache, $injected_routes); + } + + return $request; + } + + /** + * Automatically detects the URI of the main request using PATH_INFO, + * REQUEST_URI, PHP_SELF or REDIRECT_URL. + * + * $uri = Request::detect_uri(); + * + * @return string URI of the main request + * @throws Kohana_Exception + * @since 3.0.8 + */ + public static function detect_uri() + { + if ( ! empty($_SERVER['PATH_INFO'])) + { + // PATH_INFO does not contain the docroot or index + $uri = $_SERVER['PATH_INFO']; + } + else + { + // REQUEST_URI and PHP_SELF include the docroot and index + + if (isset($_SERVER['REQUEST_URI'])) + { + /** + * We use REQUEST_URI as the fallback value. The reason + * for this is we might have a malformed URL such as: + * + * http://localhost/http://example.com/judge.php + * + * which parse_url can't handle. So rather than leave empty + * handed, we'll use this. + */ + $uri = $_SERVER['REQUEST_URI']; + + if ($request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)) + { + // Valid URL path found, set it. + $uri = $request_uri; + } + + // Decode the request URI + $uri = rawurldecode($uri); + } + elseif (isset($_SERVER['PHP_SELF'])) + { + $uri = $_SERVER['PHP_SELF']; + } + elseif (isset($_SERVER['REDIRECT_URL'])) + { + $uri = $_SERVER['REDIRECT_URL']; + } + else + { + // If you ever see this error, please report an issue at http://dev.kohanaphp.com/projects/kohana3/issues + // along with any relevant information about your web server setup. Thanks! + throw new Kohana_Exception('Unable to detect the URI using PATH_INFO, REQUEST_URI, PHP_SELF or REDIRECT_URL'); + } + + // Get the path from the base URL, including the index file + $base_url = parse_url(Kohana::$base_url, PHP_URL_PATH); + + if (strpos($uri, $base_url) === 0) + { + // Remove the base URL from the URI + $uri = (string) substr($uri, strlen($base_url)); + } + + if (Kohana::$index_file AND strpos($uri, Kohana::$index_file) === 0) + { + // Remove the index file from the URI + $uri = (string) substr($uri, strlen(Kohana::$index_file)); + } + } + + return $uri; + } + + /** + * Return the currently executing request. This is changed to the current + * request when [Request::execute] is called and restored when the request + * is completed. + * + * $request = Request::current(); + * + * @return Request + * @since 3.0.5 + */ + public static function current() + { + return Request::$current; + } + + /** + * Returns the first request encountered by this framework. This will should + * only be set once during the first [Request::factory] invocation. + * + * // Get the first request + * $request = Request::initial(); + * + * // Test whether the current request is the first request + * if (Request::initial() === Request::current()) + * // Do something useful + * + * @return Request + * @since 3.1.0 + */ + public static function initial() + { + return Request::$initial; + } + + /** + * Returns information about the client user agent. + * + * // Returns "Chrome" when using Google Chrome + * $browser = Request::user_agent('browser'); + * + * Multiple values can be returned at once by using an array: + * + * // Get the browser and platform with a single call + * $info = Request::user_agent(array('browser', 'platform')); + * + * When using an array for the value, an associative array will be returned. + * + * @param mixed $value String to return: browser, version, robot, mobile, platform; or array of values + * @return mixed requested information, FALSE if nothing is found + * @uses Kohana::config + * @uses Request::$user_agent + */ + public static function user_agent($value) + { + if (is_array($value)) + { + $agent = array(); + foreach ($value as $v) + { + // Add each key to the set + $agent[$v] = Request::user_agent($v); + } + + return $agent; + } + + static $info; + + if (isset($info[$value])) + { + // This value has already been found + return $info[$value]; + } + + if ($value === 'browser' OR $value == 'version') + { + // Load browsers + $browsers = Kohana::config('user_agents')->browser; + + foreach ($browsers as $search => $name) + { + if (stripos(Request::$user_agent, $search) !== FALSE) + { + // Set the browser name + $info['browser'] = $name; + + if (preg_match('#'.preg_quote($search).'[^0-9.]*+([0-9.][0-9.a-z]*)#i', Request::$user_agent, $matches)) + { + // Set the version number + $info['version'] = $matches[1]; + } + else + { + // No version number found + $info['version'] = FALSE; + } + + return $info[$value]; + } + } + } + else + { + // Load the search group for this type + $group = Kohana::config('user_agents')->$value; + + foreach ($group as $search => $name) + { + if (stripos(Request::$user_agent, $search) !== FALSE) + { + // Set the value name + return $info[$value] = $name; + } + } + } + + // The value requested could not be found + return $info[$value] = FALSE; + } + + /** + * Returns the accepted content types. If a specific type is defined, + * the quality of that type will be returned. + * + * $types = Request::accept_type(); + * + * @param string $type Content MIME type + * @return mixed An array of all types or a specific type as a string + * @uses Request::_parse_accept + */ + public static function accept_type($type = NULL) + { + static $accepts; + + if ($accepts === NULL) + { + // Parse the HTTP_ACCEPT header + $accepts = Request::_parse_accept($_SERVER['HTTP_ACCEPT'], array('*/*' => 1.0)); + } + + if (isset($type)) + { + // Return the quality setting for this type + return isset($accepts[$type]) ? $accepts[$type] : $accepts['*/*']; + } + + return $accepts; + } + + /** + * Returns the accepted languages. If a specific language is defined, + * the quality of that language will be returned. If the language is not + * accepted, FALSE will be returned. + * + * $langs = Request::accept_lang(); + * + * @param string $lang Language code + * @return mixed An array of all types or a specific type as a string + * @uses Request::_parse_accept + */ + public static function accept_lang($lang = NULL) + { + static $accepts; + + if ($accepts === NULL) + { + // Parse the HTTP_ACCEPT_LANGUAGE header + $accepts = Request::_parse_accept($_SERVER['HTTP_ACCEPT_LANGUAGE']); + } + + if (isset($lang)) + { + // Return the quality setting for this lang + return isset($accepts[$lang]) ? $accepts[$lang] : FALSE; + } + + return $accepts; + } + + /** + * Returns the accepted encodings. If a specific encoding is defined, + * the quality of that encoding will be returned. If the encoding is not + * accepted, FALSE will be returned. + * + * $encodings = Request::accept_encoding(); + * + * @param string $type Encoding type + * @return mixed An array of all types or a specific type as a string + * @uses Request::_parse_accept + */ + public static function accept_encoding($type = NULL) + { + static $accepts; + + if ($accepts === NULL) + { + // Parse the HTTP_ACCEPT_LANGUAGE header + $accepts = Request::_parse_accept($_SERVER['HTTP_ACCEPT_ENCODING']); + } + + if (isset($type)) + { + // Return the quality setting for this type + return isset($accepts[$type]) ? $accepts[$type] : FALSE; + } + + return $accepts; + } + + /** + * Determines if a file larger than the post_max_size has been uploaded. PHP + * does not handle this situation gracefully on its own, so this method + * helps to solve that problem. + * + * @return boolean + * @uses Num::bytes + * @uses Arr::get + */ + public static function post_max_size_exceeded() + { + // Make sure the request method is POST + if (Request::$initial->method() !== HTTP_Request::POST) + return FALSE; + + // Get the post_max_size in bytes + $max_bytes = Num::bytes(ini_get('post_max_size')); + + // Error occurred if method is POST, and content length is too long + return (Arr::get($_SERVER, 'CONTENT_LENGTH') > $max_bytes); + } + + /** + * Process URI + * + * @param string $uri URI + * @param array $routes Route + * @return array + */ + public static function process_uri($uri, $routes = NULL) + { + // Load routes + $routes = (empty($routes)) ? Route::all() : $routes; + $params = NULL; + + foreach ($routes as $name => $route) + { + // We found something suitable + if ($params = $route->matches($uri)) + { + return array( + 'params' => $params, + 'route' => $route, + ); + } + } + + return NULL; + } + + /** + * Parses an accept header and returns an array (type => quality) of the + * accepted types, ordered by quality. + * + * $accept = Request::_parse_accept($header, $defaults); + * + * @param string $header Header to parse + * @param array $accepts Default values + * @return array + */ + protected static function _parse_accept( & $header, array $accepts = NULL) + { + if ( ! empty($header)) + { + // Get all of the types + $types = explode(',', $header); + + foreach ($types as $type) + { + // Split the type into parts + $parts = explode(';', $type); + + // Make the type only the MIME + $type = trim(array_shift($parts)); + + // Default quality is 1.0 + $quality = 1.0; + + foreach ($parts as $part) + { + // Prevent undefined $value notice below + if (strpos($part, '=') === FALSE) + continue; + + // Separate the key and value + list ($key, $value) = explode('=', trim($part)); + + if ($key === 'q') + { + // There is a quality for this type + $quality = (float) trim($value); + } + } + + // Add the accept type and quality + $accepts[$type] = $quality; + } + } + + // Make sure that accepts is an array + $accepts = (array) $accepts; + + // Order by quality + arsort($accepts); + + return $accepts; + } + + /** + * @var string the x-requested-with header which most likely + * will be xmlhttprequest + */ + protected $_requested_with; + + /** + * @var string method: GET, POST, PUT, DELETE, HEAD, etc + */ + protected $_method = 'GET'; + + /** + * @var string protocol: HTTP/1.1, FTP, CLI, etc + */ + protected $_protocol; + + /** + * @var string referring URL + */ + protected $_referrer; + + /** + * @var Route route matched for this request + */ + protected $_route; + + /** + * @var Route array of routes to manually look at instead of the global namespace + */ + protected $_routes; + + /** + * @var Kohana_Response response + */ + protected $_response; + + /** + * @var Kohana_HTTP_Header headers to sent as part of the request + */ + protected $_header; + + /** + * @var string the body + */ + protected $_body; + + /** + * @var string controller directory + */ + protected $_directory = ''; + + /** + * @var string controller to be executed + */ + protected $_controller; + + /** + * @var string action to be executed in the controller + */ + protected $_action; + + /** + * @var string the URI of the request + */ + protected $_uri; + + /** + * @var boolean external request + */ + protected $_external = FALSE; + + /** + * @var array parameters from the route + */ + protected $_params = array(); + + /** + * @var array query parameters + */ + protected $_get = array(); + + /** + * @var array post parameters + */ + protected $_post = array(); + + /** + * @var array cookies to send with the request + */ + protected $_cookies = array(); + + /** + * @var Kohana_Request_Client + */ + protected $_client; + + /** + * Creates a new request object for the given URI. New requests should be + * created using the [Request::instance] or [Request::factory] methods. + * + * $request = new Request($uri); + * + * If $cache parameter is set, the response for the request will attempt to + * be retrieved from the cache. + * + * @param string $uri URI of the request + * @param Cache $cache + * @param array $injected_routes an array of routes to use, for testing + * @return void + * @throws Kohana_Request_Exception + * @uses Route::all + * @uses Route::matches + */ + public function __construct($uri, Cache $cache = NULL, $injected_routes = array()) + { + // Initialise the header + $this->_header = new HTTP_Header(array()); + + // Assign injected routes + $this->_injected_routes = $injected_routes; + + // Cleanse query parameters from URI (faster that parse_url()) + $split_uri = explode('?', $uri); + $uri = array_shift($split_uri); + + // Initial request has global $_GET already applied + if (Request::$initial !== NULL) + { + if ($split_uri) + { + parse_str($split_uri[0], $this->_get); + } + } + + // Detect protocol (if present) + // Always default to an internal request if we don't have an initial. + // This prevents the default index.php from being able to proxy + // external pages. + if (Request::$initial === NULL OR strpos($uri, '://') === FALSE) + { + // Remove trailing slashes from the URI + $uri = trim($uri, '/'); + + $processed_uri = Request::process_uri($uri, $this->_injected_routes); + + if ($processed_uri === NULL) + { + throw new HTTP_Exception_404('Unable to find a route to match the URI: :uri', array( + ':uri' => $uri, + )); + } + + // Store the URI + $this->_uri = $uri; + + // Store the matching route + $this->_route = $processed_uri['route']; + $params = $processed_uri['params']; + + // Is this route external? + $this->_external = $this->_route->is_external(); + + if (isset($params['directory'])) + { + // Controllers are in a sub-directory + $this->_directory = $params['directory']; + } + + // Store the controller + $this->_controller = $params['controller']; + + if (isset($params['action'])) + { + // Store the action + $this->_action = $params['action']; + } + else + { + // Use the default action + $this->_action = Route::$default_action; + } + + // These are accessible as public vars and can be overloaded + unset($params['controller'], $params['action'], $params['directory']); + + // Params cannot be changed once matched + $this->_params = $params; + + // Apply the client + $this->_client = new Request_Client_Internal(array('cache' => $cache)); + } + else + { + // Create a route + $this->_route = new Route($uri); + + // Store the URI + $this->_uri = $uri; + + // Set external state + $this->_external = TRUE; + + // Setup the client + $this->_client = new Request_Client_External(array('cache' => $cache)); + } + } + + /** + * Returns the response as the string representation of a request. + * + * echo $request; + * + * @return string + */ + public function __toString() + { + return $this->render(); + } + + /** + * Generates a relative URI for the current route. + * + * $request->uri($params); + * + * @param array $params Additional route parameters + * @return string + * @uses Route::uri + */ + public function uri(array $params = NULL) + { + if ( ! isset($params['directory'])) + { + // Add the current directory + $params['directory'] = $this->directory(); + } + + if ( ! isset($params['controller'])) + { + // Add the current controller + $params['controller'] = $this->controller(); + } + + if ( ! isset($params['action'])) + { + // Add the current action + $params['action'] = $this->action(); + } + + // Add the current parameters + $params += $this->_params; + + $uri = $this->_route->uri($params); + + return $uri; + } + + /** + * Create a URL from the current request. This is a shortcut for: + * + * echo URL::site($this->request->uri($params), $protocol); + * + * @param array $params URI parameters + * @param mixed $protocol protocol string or Request object + * @return string + * @since 3.0.7 + * @uses URL::site + */ + public function url(array $params = NULL, $protocol = NULL) + { + // Create a URI with the current route and convert it to a URL + $url = URL::site($this->uri($params), $protocol); + + return $url; + } + + /** + * Retrieves a value from the route parameters. + * + * $id = $request->param('id'); + * + * @param string $key Key of the value + * @param mixed $default Default value if the key is not set + * @return mixed + */ + public function param($key = NULL, $default = NULL) + { + if ($key === NULL) + { + // Return the full array + return $this->_params; + } + + return isset($this->_params[$key]) ? $this->_params[$key] : $default; + } + + /** + * Sends the response status and all set headers. The current server + * protocol (HTTP/1.0 or HTTP/1.1) will be used when available. If not + * available, HTTP/1.1 will be used. + * + * $request->send_headers(); + * + * @return $this + * @uses Request::$messages + * @deprecated This should not be here, it belongs in\n + * Response::send_headers() where it is implemented correctly. + */ + public function send_headers() + { + if ( ! ($response = $this->response()) instanceof Response) + return $this; + + $response->send_headers(); + return $this; + } + + /** + * Redirects as the request response. If the URL does not include a + * protocol, it will be converted into a complete URL. + * + * $request->redirect($url); + * + * [!!] No further processing can be done after this method is called! + * + * @param string $url Redirect location + * @param integer $code Status code: 301, 302, etc + * @return void + * @uses URL::site + * @uses Request::send_headers + */ + public function redirect($url = '', $code = 302) + { + if (strpos($url, '://') === FALSE) + { + // Make the URI into a URL + $url = URL::site($url, TRUE); + } + + // Redirect + $response = $this->create_response(); + + // Set the response status + $response->status($code); + + // Set the location header + $response->headers('Location', $url); + + // Send headers + $response->send_headers(); + + // Stop execution + exit; + } + + /** + * Sets and gets the referrer from the request. + * + * @param string $referrer + * @return mixed + */ + public function referrer($referrer = NULL) + { + if ($referrer === NULL) + { + // Act as a getter + return $this->_referrer; + } + + // Act as a setter + $this->_referrer = (string) $referrer; + + return $this; + } + + /** + * Sets and gets the route from the request. + * + * @param string $route + * @return mixed + */ + public function route(Route $route = NULL) + { + if ($route === NULL) + { + // Act as a getter + return $this->_route; + } + + // Act as a setter + $this->_route = $route; + + return $this; + } + + /** + * Sets and gets the directory for the controller. + * + * @param string $directory Directory to execute the controller from + * @return mixed + */ + public function directory($directory = NULL) + { + if ($directory === NULL) + { + // Act as a getter + return $this->_directory; + } + + // Act as a setter + $this->_directory = (string) $directory; + + return $this; + } + + /** + * Sets and gets the controller for the matched route. + * + * @param string $controller Controller to execute the action + * @return mixed + */ + public function controller($controller = NULL) + { + if ($controller === NULL) + { + // Act as a getter + return $this->_controller; + } + + // Act as a setter + $this->_controller = (string) $controller; + + return $this; + } + + /** + * Sets and gets the action for the controller. + * + * @param string $action Action to execute the controller from + * @return mixed + */ + public function action($action = NULL) + { + if ($action === NULL) + { + // Act as a getter + return $this->_action; + } + + // Act as a setter + $this->_action = (string) $action; + + return $this; + } + + /** + * Provides readonly access to the [Request_Client], + * useful for accessing the caching methods within the + * request client. + * + * @return Request_Client + */ + public function get_client() + { + return $this->_client; + } + + /** + * Gets and sets the requested with property, which should + * be relative to the x-requested-with pseudo header. + * + * @param string $requested_with Requested with value + * @return mixed + */ + public function requested_with($requested_with = NULL) + { + if ($requested_with === NULL) + { + // Act as a getter + return $this->_requested_with; + } + + // Act as a setter + $this->_requested_with = strtolower($requested_with); + + return $this; + } + + /** + * Processes the request, executing the controller action that handles this + * request, determined by the [Route]. + * + * 1. Before the controller action is called, the [Controller::before] method + * will be called. + * 2. Next the controller action will be called. + * 3. After the controller action is called, the [Controller::after] method + * will be called. + * + * By default, the output from the controller is captured and returned, and + * no headers are sent. + * + * $request->execute(); + * + * @return Response + * @throws Kohana_Exception + * @uses [Kohana::$profiling] + * @uses [Profiler] + */ + public function execute() + { + if ( ! $this->_client instanceof Kohana_Request_Client) + { + throw new Kohana_Request_Exception('Unable to execute :uri without a Kohana_Request_Client', array( + ':uri' => $this->_uri, + )); + } + + return $this->_client->execute($this); + } + + /** + * Returns whether this request is the initial request Kohana received. + * Can be used to test for sub requests. + * + * if ( ! $request->is_initial()) + * // This is a sub request + * + * @return boolean + */ + public function is_initial() + { + return ($this === Request::$initial); + } + + /** + * Returns whether this is an ajax request (as used by JS frameworks) + * + * @return boolean + */ + public function is_ajax() + { + return ($this->requested_with() === 'xmlhttprequest'); + } + + /** + * Generates an [ETag](http://en.wikipedia.org/wiki/HTTP_ETag) from the + * request response. + * + * $etag = $request->generate_etag(); + * + * [!!] If the request response is empty when this method is called, an + * exception will be thrown! + * + * @return string + * @throws Kohana_Request_Exception + */ + public function generate_etag() + { + if ($this->_response === NULL) + { + throw new Kohana_Request_Exception('No response yet associated with request - cannot auto generate resource ETag'); + } + + // Generate a unique hash for the response + return '"'.sha1($this->_response).'"'; + } + + /** + * Set or get the response for this request + * + * @param Response $response Response to apply to this request + * @return Response + * @return void + */ + public function response(Response $response = NULL) + { + if ($response === NULL) + { + // Act as a getter + return $this->_response; + } + + // Act as a setter + $this->_response = $response; + + return $this; + } + + /** + * Creates a response based on the type of request, i.e. an + * Request_HTTP will produce a Response_HTTP, and the same applies + * to CLI. + * + * // Create a response to the request + * $response = $request->create_response(); + * + * @param boolean $bind Bind to this request + * @return Response + * @since 3.1.0 + */ + public function create_response($bind = TRUE) + { + $response = new Response(array('_protocol' => $this->protocol())); + + if ($bind) + { + // Bind a new response to the request + $this->_response = $response; + } + + return $response; + } + + /** + * Gets or sets the HTTP method. Usually GET, POST, PUT or DELETE in + * traditional CRUD applications. + * + * @param string $method Method to use for this request + * @return mixed + */ + public function method($method = NULL) + { + if ($method === NULL) + { + // Act as a getter + return $this->_method; + } + + // Act as a setter + $this->_method = strtoupper($method); + + return $this; + } + + /** + * Gets or sets the HTTP protocol. The standard protocol to use + * is `http`. + * + * @param string $protocol Protocol to set to the request/response + * @return mixed + */ + public function protocol($protocol = NULL) + { + if ($protocol === NULL) + { + if ($this->_protocol) + { + // Act as a getter + return $this->_protocol; + } + else + { + // Get the default protocol + return HTTP::$protocol; + } + } + + // Act as a setter + $this->_protocol = strtolower($protocol); + + return $this; + } + + /** + * Gets or sets HTTP headers to the request or response. All headers + * are included immediately after the HTTP protocol definition during + * transmission. This method provides a simple array or key/value + * interface to the headers. + * + * @param mixed $key Key or array of key/value pairs to set + * @param string $value Value to set to the supplied key + * @return mixed + */ + public function headers($key = NULL, $value = NULL) + { + if ($key instanceof HTTP_Header) + { + // Act a setter, replace all headers + $this->_header = $key; + + return $this; + } + + if (is_array($key)) + { + // Act as a setter, replace all headers + $this->_header->exchangeArray($key); + + return $this; + } + + if ($this->_header->count() === 0 AND $this->is_initial()) + { + // Lazy load the request headers + $this->_header = HTTP::request_headers(); + } + + if ($key === NULL) + { + // Act as a getter, return all headers + return $this->_header; + } + elseif ($value === NULL) + { + // Act as a getter, single header + return ($this->_header->offsetExists($key)) ? $this->_header->offsetGet($key) : NULL; + } + + // Act as a setter for a single header + $this->_header[$key] = $value; + + return $this; + } + + /** + * Set and get cookies values for this request. + * + * @param mixed $key Cookie name, or array of cookie values + * @param string $value Value to set to cookie + * @return string + * @return mixed + */ + public function cookie($key = NULL, $value = NULL) + { + if (is_array($key)) + { + // Act as a setter, replace all cookies + $this->_cookies = $key; + } + + if ($key === NULL) + { + // Act as a getter, all cookies + return $this->_cookies; + } + elseif ($value === NULL) + { + // Act as a getting, single cookie + return isset($this->_cookies[$key]) ? $this->_cookies[$key] : NULL; + } + + // Act as a setter for a single cookie + $this->_cookies[$key] = (string) $value; + + return $this; + } + + /** + * Gets or sets the HTTP body to the request or response. The body is + * included after the header, separated by a single empty new line. + * + * @param string $content Content to set to the object + * @return mixed + */ + public function body($content = NULL) + { + if ($content === NULL) + { + // Act as a getter + return $this->_body; + } + + // Act as a setter + $this->_body = $content; + + return $this; + } + + /** + * Renders the HTTP_Interaction to a string, producing + * + * - Protocol + * - Headers + * - Body + * + * If there are variables set to the `Kohana_Request::$_post` + * they will override any values set to body. + * + * @param boolean $response Return the rendered response, else returns the rendered request + * @return string + */ + public function render($response = TRUE) + { + if ($response) + { + // Act as a getter + return (string) $this->_response; + } + + if ( ! $post = $this->post()) + { + $body = $this->body(); + } + else + { + $this->headers('content-type', 'application/x-www-form-urlencoded'); + $body = http_build_query($post, NULL, '&'); + } + + // Prepare cookies + if ($this->_cookies) + { + $cookie_string = array(); + + // Parse each + foreach ($this->_cookies as $key => $value) + { + $cookie_string[] = $key.'='.$value; + } + + // Create the cookie string + $this->_header['cookie'] = implode('; ', $cookie_string); + } + + $output = $this->method().' '.$this->uri($this->param()).' '.strtoupper($this->protocol()).'/'.HTTP::$version."\n"; + $output .= (string) $this->_header; + $output .= $body; + + return $output; + } + + /** + * Gets or sets HTTP query string. + * + * @param mixed $key Key or key value pairs to set + * @param string $value Value to set to a key + * @return mixed + */ + public function query($key = NULL, $value = NULL) + { + if (is_array($key)) + { + // Act as a setter, replace all query strings + $this->_get = $key; + + return $this; + } + + if ($key === NULL) + { + // Act as a getter, all query strings + return $this->_get; + } + elseif ($value === NULL) + { + // Act as a getter, single query string + return Arr::get($this->_get, $key); + } + + // Act as a setter, single query string + $this->_get[$key] = $value; + + return $this; + } + + /** + * Gets or sets HTTP POST parameters to the request. + * + * @param mixed $key Key or key value pairs to set + * @param string $value Value to set to a key + * @return mixed + */ + public function post($key = NULL, $value = NULL) + { + if (is_array($key)) + { + // Act as a setter, replace all fields + $this->_post = $key; + + return $this; + } + + if ($key === NULL) + { + // Act as a getter, all fields + return $this->_post; + } + elseif ($value === NULL) + { + // Act as a getter, single field + return Arr::get($this->_post, $key); + } + + // Act as a setter, single field + $this->_post[$key] = $value; + + return $this; + } + +} // End Request diff --git a/includes/kohana/system/classes/kohana/request/client.php b/includes/kohana/system/classes/kohana/request/client.php new file mode 100644 index 0000000..ae4facb --- /dev/null +++ b/includes/kohana/system/classes/kohana/request/client.php @@ -0,0 +1,328 @@ + $value) + { + if (method_exists($this, $key)) + { + if (property_exists($this, $key) OR property_exists($this, '_'.$key)) + { + $method = trim($key, '_'); + $this->$method($value); + } + } + } + } + } + + /** + * Processes the request, executing the controller action that handles this + * request, determined by the [Route]. + * + * 1. Before the controller action is called, the [Controller::before] method + * will be called. + * 2. Next the controller action will be called. + * 3. After the controller action is called, the [Controller::after] method + * will be called. + * + * By default, the output from the controller is captured and returned, and + * no headers are sent. + * + * $request->execute(); + * + * @param Request $request + * @return Response + * @throws Kohana_Exception + * @uses [Kohana::$profiling] + * @uses [Profiler] + */ + abstract public function execute(Request $request); + + /** + * 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 ( ! $this->_cache instanceof Cache) + return; + + $this->_cache->delete($this->_create_cache_key($request)); + + 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. + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + * @param boolean 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; + } + + /** + * Creates a cache key for the request to use for caching + * [Kohana_Response] returned by [Request::execute]. + * + * @param Request request + * @return string + * @return boolean + */ + public function create_cache_key(Request $request) + { + return sha1($request->url()); + } + + /** + * Controls whether the response can be cached. Uses HTTP + * protocol to determine whether the response can be cached. + * + * @see RFC 2616 http://www.w3.org/Protocols/rfc2616/ + * @param Response $response The Response + * @return boolean + */ + public function set_cache(Response $response) + { + $headers = (array) $response->headers(); + if ($cache_control = arr::get($headers, 'cache-control')) + { + // Parse the cache control + $cache_control = Response::parse_cache_control( (string) $cache_control); + + // If the no-cache or no-store directive is set, return + if (array_intersect_key($cache_control, array('no-cache' => NULL, 'no-store' => NULL))) + return FALSE; + + // Get the directives + $directives = array_keys($cache_control); + + // Check for private cache and get out of here if invalid + if ( ! $this->_allow_private_cache and in_array('private', $directives)) + { + 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 (int) $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( (string) $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 Request $request The request + * @param Response $response Response + * @return mixed + */ + public function cache_response(Request $request, Response $response = NULL) + { + if ( ! $this->_cache instanceof Cache) + return FALSE; + + // Check for Pragma: no-cache + if ($pragma = $request->headers('pragma')) + { + if ($pragma instanceof HTTP_Header_Value and $pragma->key == 'no-cache') + return FALSE; + elseif (is_array($pragma) and isset($pragma['no-cache'])) + return FALSE; + } + + if ( ! $response) + { + $response = $this->_cache->get($this->create_cache_key($request)); + return ($response !== NULL) ? $response : FALSE; + } + else + { + if (($ttl = $this->cache_lifetime($response)) === FALSE) + return FALSE; + + return $this->_cache->set($this->create_cache_key($request), $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( (string) $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( (string) $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 = Response::parse_cache_control( (string) $cache_control); + + if (isset($cache_control['max-age'])) + { + $ttl = (int) $cache_control['max-age']; + } + + if (isset($cache_control['s-maxage']) AND isset($cache_control['private']) AND $this->_allow_private_cache) + { + $ttl = (int) $cache_control['s-maxage']; + } + + if (isset($cache_control['max-stale']) AND ! isset($cache_control['must-revalidate'])) + { + $ttl = $current_age + (int) $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( (string) $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; + } +} \ No newline at end of file diff --git a/includes/kohana/system/classes/kohana/request/client/external.php b/includes/kohana/system/classes/kohana/request/client/external.php new file mode 100644 index 0000000..86fb5ef --- /dev/null +++ b/includes/kohana/system/classes/kohana/request/client/external.php @@ -0,0 +1,427 @@ + $value) + $headers[$matches[1][$key]] = $matches[2][$key]; + } + + // If there are headers to apply + if ($headers) + { + Request_Client_External::$_processed_headers += $headers; + } + + return strlen($header); + } + + /** + * @var array additional curl options to use on execution + */ + protected $_options = array(); + + /** + * Processes the request, executing the controller action that handles this + * request, determined by the [Route]. + * + * 1. Before the controller action is called, the [Controller::before] method + * will be called. + * 2. Next the controller action will be called. + * 3. After the controller action is called, the [Controller::after] method + * will be called. + * + * By default, the output from the controller is captured and returned, and + * no headers are sent. + * + * $request->execute(); + * + * @param Request $request A request object + * @return Response + * @throws Kohana_Exception + * @uses [Kohana::$profiling] + * @uses [Profiler] + */ + public function execute(Request $request) + { + // Check for cache existance + if ($this->_cache instanceof Cache AND ($response = $this->cache_response($request)) instanceof Response) + return $response; + + if (Kohana::$profiling) + { + // Set the benchmark name + $benchmark = '"'.$request->uri().'"'; + + if ($request !== Request::$initial AND Request::$current) + { + // Add the parent request uri + $benchmark .= ' « "'.Request::$current->uri().'"'; + } + + // Start benchmarking + $benchmark = Profiler::start('Requests', $benchmark); + } + + // Store the current active request and replace current with new request + $previous = Request::$current; + Request::$current = $request; + + // Resolve the POST fields + if ($post = $request->post()) + { + $request->body(http_build_query($post, NULL, '&')) + ->headers('content-type', 'application/x-www-form-urlencoded'); + } + + try + { + // If PECL_HTTP is present, use extension to complete request + if (extension_loaded('http')) + { + $this->_http_execute($request); + } + // Else if CURL is present, use extension to complete request + elseif (extension_loaded('curl')) + { + $this->_curl_execute($request); + } + // Else use the sloooow method + else + { + $this->_native_execute($request); + } + } + catch (Exception $e) + { + // Restore the previous request + Request::$current = $previous; + + if (isset($benchmark)) + { + // Delete the benchmark, it is invalid + Profiler::delete($benchmark); + } + + // Re-throw the exception + throw $e; + } + + // Restore the previous request + Request::$current = $previous; + + if (isset($benchmark)) + { + // Stop the benchmark + Profiler::stop($benchmark); + } + + // Cache the response if cache is available + if ($this->_cache instanceof Cache) + { + $this->cache_response($request, $request->response()); + } + + // Return the response + return $request->response(); + } + + /** + * Set and get options for this request. + * + * @param mixed $key Option name, or array of options + * @param mixed $value Option value + * @return mixed + * @return Request_Client_External + */ + public function options($key = NULL, $value = NULL) + { + if ($key === NULL) + return $this->_options; + + if (is_array($key)) + { + $this->_options = $key; + } + elseif ( ! $value) + { + return Arr::get($this->_options, $key); + } + else + { + $this->_options[$key] = $value; + } + + return $this; + } + + /** + * Execute the request using the PECL HTTP extension. (recommended) + * + * @param Request $request Request to execute + * @return Response + */ + protected function _http_execute(Request $request) + { + $http_method_mapping = array( + HTTP_Request::GET => HTTPRequest::METH_GET, + HTTP_Request::HEAD => HTTPRequest::METH_HEAD, + HTTP_Request::POST => HTTPRequest::METH_POST, + HTTP_Request::PUT => HTTPRequest::METH_PUT, + HTTP_Request::DELETE => HTTPRequest::METH_DELETE, + HTTP_Request::OPTIONS => HTTPRequest::METH_OPTIONS, + HTTP_Request::TRACE => HTTPRequest::METH_TRACE, + HTTP_Request::CONNECT => HTTPRequest::METH_CONNECT, + ); + + // Create an http request object + $http_request = new HTTPRequest($request->uri(), $http_method_mapping[$request->method()]); + + if ($this->_options) + { + // Set custom options + $http_request->setOptions($this->_options); + } + + // Set headers + $http_request->setHeaders($request->headers()->getArrayCopy()); + + // Set cookies + $http_request->setCookies($request->cookie()); + + // Set the body + $http_request->setBody($request->body()); + + // Set the query + $http_request->setQueryData($request->query()); + + try + { + $http_request->send(); + } + catch (HTTPRequestException $e) + { + throw new Kohana_Request_Exception($e->getMessage()); + } + catch (HTTPMalformedHeaderException $e) + { + throw new Kohana_Request_Exception($e->getMessage()); + } + catch (HTTPEncodingException $e) + { + throw new Kohana_Request_Exception($e->getMessage()); + } + + // Create the response + $response = $request->create_response(); + + // Build the response + $response->status($http_request->getResponseCode()) + ->headers($http_request->getResponseHeader()) + ->cookie($http_request->getResponseCookies()) + ->body($http_request->getResponseBody()); + + return $response; + } + + /** + * Execute the request using the CURL extension. (recommended) + * + * @param Request $request Request to execute + * @return Response + */ + protected function _curl_execute(Request $request) + { + // Reset the headers + Request_Client_External::$_processed_headers = array(); + + // Set the request method + $options[CURLOPT_CUSTOMREQUEST] = $request->method(); + + // Set the request body. This is perfectly legal in CURL even + // if using a request other than POST. PUT does support this method + // and DOES NOT require writing data to disk before putting it, if + // reading the PHP docs you may have got that impression. SdF + $options[CURLOPT_POSTFIELDS] = $request->body(); + + // Process headers + if ($headers = $request->headers()) + { + $http_headers = array(); + + foreach ($headers as $key => $value) + { + $http_headers[] = $key.': '.$value; + } + + $options[CURLOPT_HTTPHEADER] = $http_headers; + } + + // Process cookies + if ($cookies = $request->cookie()) + { + $options[CURLOPT_COOKIE] = http_build_query($cookies, NULL, '; '); + } + + // The transfer must always be returned + $options[CURLOPT_RETURNTRANSFER] = TRUE; + + // Apply any additional options set to Request_Client_External::$_options + $options += $this->_options; + + $uri = $request->uri(); + + if ($query = $request->query()) + { + $uri .= '?'.http_build_query($query, NULL, '&'); + } + + // Open a new remote connection + $curl = curl_init($uri); + + // Set connection options + if ( ! curl_setopt_array($curl, $options)) + { + throw new Kohana_Request_Exception('Failed to set CURL options, check CURL documentation: :url', + array(':url' => 'http://php.net/curl_setopt_array')); + } + + // Get the response body + $body = curl_exec($curl); + + // Get the response information + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + + if ($body === FALSE) + { + $error = curl_error($curl); + } + + // Close the connection + curl_close($curl); + + if (isset($error)) + { + throw new Kohana_Request_Exception('Error fetching remote :url [ status :code ] :error', + array(':url' => $request->url(), ':code' => $code, ':error' => $error)); + } + + // Create response + $response = $request->create_response(); + + $response->status($code) + ->headers(Request_Client_External::$_processed_headers) + ->body($body); + + return $response; + } + + /** + * Execute the request using PHP stream. (not recommended) + * + * @param Request $request Request to execute + * @return Response + */ + protected function _native_execute(Request $request) + { + // Reset the headers + Request_Client_External::$_processed_headers = array(); + + // Calculate stream mode + $mode = ($request->method() === HTTP_Request::GET) ? 'r' : 'r+'; + + // Process cookies + if ($cookies = $request->cookie()) + { + $request->headers('cookie', http_build_query($cookies, NULL, '; ')); + } + + // Get the message body + $body = $request->body(); + + // Set the content length + $request->headers('content-length', strlen($body)); + + // Create the context + $options = array( + $request->protocol() => array( + 'method' => $request->method(), + 'header' => (string) $request->headers(), + 'content' => $body, + 'user-agent' => 'Kohana Framework '.Kohana::VERSION.' ('.Kohana::CODENAME.')' + ) + ); + + // Create the context stream + $context = stream_context_create($options); + + stream_context_set_option($context, $this->_options); + + $uri = $request->uri(); + + if ($query = $request->query()) + { + $uri .= '?'.http_build_query($query, NULL, '&'); + } + + $stream = fopen($uri, $mode, FALSE, $context); + + $meta_data = stream_get_meta_data($stream); + + // Get the HTTP response code + $http_response = array_shift($meta_data['wrapper_data']); + + if (preg_match_all('/(\w+\/\d\.\d) (\d{3})/', $http_response, $matches) !== FALSE) + { + $protocol = $matches[1][0]; + $status = (int) $matches[2][0]; + } + else + { + $protocol = NULL; + $status = NULL; + } + + // Process headers + array_map(array('Request_Client_External', '_parse_headers'), array(), $meta_data['wrapper_data']); + + // Create a response + $response = $request->create_response(); + + $response->status($status) + ->protocol($protocol) + ->headers(Request_Client_External::$_processed_headers) + ->body(stream_get_contents($stream)); + + // Close the stream after use + fclose($stream); + + return $response; + } +} // End Kohana_Request_Client_External \ No newline at end of file diff --git a/includes/kohana/system/classes/kohana/request/client/internal.php b/includes/kohana/system/classes/kohana/request/client/internal.php new file mode 100644 index 0000000..07632ec --- /dev/null +++ b/includes/kohana/system/classes/kohana/request/client/internal.php @@ -0,0 +1,179 @@ +execute(); + * + * @param Request $request + * @return Response + * @throws Kohana_Exception + * @uses [Kohana::$profiling] + * @uses [Profiler] + * @deprecated passing $params to controller methods deprecated since version 3.1 + * will be removed in 3.2 + */ + public function execute(Request $request) + { + // Check for cache existance + if ($this->_cache instanceof Cache AND ($response = $this->cache_response($request)) instanceof Response) + return $response; + + // Create the class prefix + $prefix = 'controller_'; + + // Directory + $directory = $request->directory(); + + // Controller + $controller = $request->controller(); + + if ($directory) + { + // Add the directory name to the class prefix + $prefix .= str_replace(array('\\', '/'), '_', trim($directory, '/')).'_'; + } + + if (Kohana::$profiling) + { + // Set the benchmark name + $benchmark = '"'.$request->uri().'"'; + + if ($request !== Request::$initial AND Request::$current) + { + // Add the parent request uri + $benchmark .= ' « "'.Request::$current->uri().'"'; + } + + // Start benchmarking + $benchmark = Profiler::start('Requests', $benchmark); + } + + // Store the currently active request + $previous = Request::$current; + + // Change the current request to this request + Request::$current = $request; + + // Is this the initial request + $initial_request = ($request === Request::$initial); + + try + { + // Initiate response time + $this->_response_time = time(); + + if ( ! class_exists($prefix.$controller)) + { + throw new HTTP_Exception_404('The requested URL :uri was not found on this server.', + array(':uri' => $request->uri())); + } + + // Load the controller using reflection + $class = new ReflectionClass($prefix.$controller); + + if ($class->isAbstract()) + { + throw new Kohana_Exception('Cannot create instances of abstract :controller', + array(':controller' => $prefix.$controller)); + } + + // Create a new instance of the controller + $controller = $class->newInstance($request, $request->response() ? $request->response() : $request->create_response()); + + $class->getMethod('before')->invoke($controller); + + // Determine the action to use + $action = $request->action(); + + $params = $request->param(); + + // If the action doesn't exist, it's a 404 + if ( ! $class->hasMethod('action_'.$action)) + { + throw new HTTP_Exception_404('The requested URL :uri was not found on this server.', + array(':uri' => $request->uri())); + } + + $method = $class->getMethod('action_'.$action); + + /** + * Execute the main action with the parameters + * + * @deprecated $params passing is deprecated since version 3.1 + * will be removed in 3.2. + */ + $method->invokeArgs($controller, $params); + + // Execute the "after action" method + $class->getMethod('after')->invoke($controller); + + // Stop response time + $this->_response_time = (time() - $this->_response_time); + + // Add the default Content-Type header to initial request if not present + if ($initial_request AND ! $request->headers('content-type')) + { + $request->headers('content-type', Kohana::$content_type.'; charset='.Kohana::$charset); + } + } + catch (Exception $e) + { + // Restore the previous request + Request::$current = $previous; + + if (isset($benchmark)) + { + // Delete the benchmark, it is invalid + Profiler::delete($benchmark); + } + + // Re-throw the exception + throw $e; + } + + // Restore the previous request + Request::$current = $previous; + + if (isset($benchmark)) + { + // Stop the benchmark + Profiler::stop($benchmark); + } + + // Cache the response if cache is available + if ($this->_cache instanceof Cache) + { + $this->cache_response($request, $request->response()); + } + + // Return the response + return $request->response(); + } +} // End Kohana_Request_Client_Internal diff --git a/includes/kohana/system/classes/kohana/request/exception.php b/includes/kohana/system/classes/kohana/request/exception.php new file mode 100644 index 0000000..e24ccf8 --- /dev/null +++ b/includes/kohana/system/classes/kohana/request/exception.php @@ -0,0 +1,9 @@ + 200)); + * + * @param array $config Setup the response object + * @return Response + */ + public static function factory(array $config = array()) + { + return new Response($config); + } + + /** + * Generates a [Cache-Control HTTP](http://en.wikipedia.org/wiki/List_of_HTTP_headers) + * header based on the supplied array. + * + * // Set the cache control headers you want to use + * $cache_control = array( + * 'max-age' => 3600, + * 'must-revalidate' => NULL, + * 'public' => NULL + * ); + * + * // Create the cache control header, creates : + * // cache-control: max-age=3600, must-revalidate, public + * $response->header['cache-control'] = Response::create_cache_control($cache_control); + * + * @param array $cache_control Cache_control parts to render + * @return string + */ + public static function create_cache_control(array $cache_control) + { + // Create a buffer + $parts = array(); + + // Foreach cache control entry + foreach ($cache_control as $key => $value) + { + // Create a cache control fragment + $parts[] = empty($value) ? $key : ($key.'='.$value); + } + // Return the rendered parts + return implode(', ', $parts); + } + + /** + * Parses the Cache-Control header and returning an array representation of the Cache-Control + * header. + * + * // Create the cache control header + * $response->header['cache-control'] = 'max-age=3600, must-revalidate, public'; + * + * // Parse the cache control header + * if ($cache_control = Request::parse_cache_control($response->header['cache-control'])) + * { + * // Cache-Control header was found + * $maxage = $cache_control['max-age']; + * } + * + * @param array $cache_control Array of headers + * @return mixed + */ + public static function parse_cache_control($cache_control) + { + // If no Cache-Control parts are detected + if ( (bool) preg_match_all('/(?[a-z\-]+)=?(?\w+)?/', $cache_control, $matches)) + { + // Return combined cache-control key/value pairs + return array_combine($matches['key'], $matches['value']); + } + else + { + // Return + return FALSE; + } + } + + // HTTP status codes and messages + public static $messages = array( + // Informational 1xx + 100 => 'Continue', + 101 => 'Switching Protocols', + + // Success 2xx + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + + // Redirection 3xx + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', // 1.1 + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + // 306 is deprecated but reserved + 307 => 'Temporary Redirect', + + // Client Error 4xx + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + + // Server Error 5xx + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 509 => 'Bandwidth Limit Exceeded' + ); + + /** + * @var integer The response http status + */ + protected $_status = 200; + + /** + * @var HTTP_Header Headers returned in the response + */ + protected $_header; + + /** + * @var string The response body + */ + protected $_body = ''; + + /** + * @var array Cookies to be returned in the response + */ + protected $_cookies = array(); + + /** + * @var string The response protocol + */ + protected $_protocol; + + /** + * Sets up the response object + * + * @param array $config Setup the response object + * @return void + */ + public function __construct(array $config = array()) + { + $this->_header = new HTTP_Header(array()); + + foreach ($config as $key => $value) + { + if (property_exists($this, $key)) + { + if ($key == '_header') + { + $this->headers($value); + } + else + { + $this->$key = $value; + } + } + } + } + + /** + * Outputs the body when cast to string + * + * @return string + */ + public function __toString() + { + return $this->_body; + } + + /** + * Gets or sets the body of the response + * + * @return mixed + */ + public function body($content = NULL) + { + if ($content === NULL) + return $this->_body; + + $this->_body = (string) $content; + return $this; + } + + /** + * Gets or sets the HTTP protocol. The standard protocol to use + * is `HTTP/1.1`. + * + * @param string $protocol Protocol to set to the request/response + * @return mixed + */ + public function protocol($protocol = NULL) + { + if ($protocol) + { + $this->_protocol = $protocol; + return $this; + } + + return $this->_protocol; + } + + /** + * Sets or gets the HTTP status from this response. + * + * // Set the HTTP status to 404 Not Found + * $response = Response::factory() + * ->status(404); + * + * // Get the current status + * $status = $response->status(); + * + * @param integer $status Status to set to this response + * @return mixed + */ + public function status($status = NULL) + { + if ($status === NULL) + { + return $this->_status; + } + elseif (array_key_exists($status, Response::$messages)) + { + $this->_status = (int) $status; + return $this; + } + else + { + throw new Kohana_Exception(__METHOD__.' unknown status value : :value', array(':value' => $status)); + } + } + + /** + * Gets and sets headers to the [Response], allowing chaining + * of response methods. If chaining isn't required, direct + * access to the property should be used instead. + * + * // Get a header + * $accept = $response->headers('Content-Type'); + * + * // Set a header + * $response->headers('Content-Type', 'text/html'); + * + * // Get all headers + * $headers = $response->headers(); + * + * // Set multiple headers + * $response->headers(array('Content-Type' => 'text/html', 'Cache-Control' => 'no-cache')); + * + * @param mixed $key + * @param string $value + * @return mixed + */ + public function headers($key = NULL, $value = NULL) + { + if ($key === NULL) + { + return $this->_header; + } + elseif (is_array($key)) + { + $this->_header->exchangeArray($key); + return $this; + } + elseif ($value === NULL) + { + return Arr::get($this->_header, $key); + } + else + { + $this->_header[$key] = $value; + return $this; + } + } + + /** + * Returns the length of the body for use with + * content header + * + * @return integer + */ + public function content_length() + { + return strlen($this->_body); + } + + /** + * Set and get cookies values for this response. + * + * // Get the cookies set to the response + * $cookies = $response->cookie(); + * + * // Set a cookie to the response + * $response->cookie('session', array( + * 'value' => $value, + * 'expiration' => 12352234 + * )); + * + * @param mixed cookie name, or array of cookie values + * @param string value to set to cookie + * @return string + * @return void + * @return [Response] + */ + public function cookie($key = NULL, $value = NULL) + { + // Handle the get cookie calls + if ($key === NULL) + return $this->_cookies; + elseif ( ! is_array($key) AND ! $value) + return Arr::get($this->_cookies, $key); + + // Handle the set cookie calls + if (is_array($key)) + { + reset($key); + while (list($_key, $_value) = each($key)) + { + $this->cookie($_key, $_value); + } + } + else + { + if ( ! is_array($value)) + { + $value = array( + 'value' => $value, + 'expiration' => Cookie::$expiration + ); + } + elseif ( ! isset($value['expiration'])) + { + $value['expiration'] = Cookie::$expiration; + } + + $this->_cookies[$key] = $value; + } + + return $this; + } + + /** + * Deletes a cookie set to the response + * + * @param string name + * @return Response + */ + public function delete_cookie($name) + { + unset($this->_cookies[$name]); + return $this; + } + + /** + * Deletes all cookies from this response + * + * @return Response + */ + public function delete_cookies() + { + $this->_cookies = array(); + return $this; + } + + /** + * Sends the response status and all set headers. + * + * @return Response + */ + public function send_headers() + { + if ( ! headers_sent()) + { + if (isset($_SERVER['SERVER_PROTOCOL'])) + { + // Use the default server protocol + $protocol = $_SERVER['SERVER_PROTOCOL']; + } + else + { + // Default to using newer protocol + $protocol = strtoupper(HTTP::$protocol).'/'.HTTP::$version; + } + + // Default to text/html; charset=utf8 if no content type set + if ( ! $this->_header->offsetExists('content-type')) + { + $this->_header['content-type'] = Kohana::$content_type.'; charset='.Kohana::$charset; + } + + // Add the X-Powered-By header + if (Kohana::$expose) + { + $this->_header['x-powered-by'] = 'Kohana Framework '.Kohana::VERSION.' ('.Kohana::CODENAME.')'; + } + + if ( ! Kohana::$is_cli) + { + // HTTP status line + header($protocol.' '.$this->_status.' '.Response::$messages[$this->_status]); + + foreach ($this->_header as $name => $value) + { + if (is_string($name)) + { + // Combine the name and value to make a raw header + $value = $name.': '.$value; + } + + // Send the raw header + header($value, TRUE); + } + } + + // Send cookies + foreach ($this->_cookies as $name => $value) + { + Cookie::set($name, $value['value'], $value['expiration']); + } + } + + return $this; + } + + /** + * Send file download as the response. All execution will be halted when + * this method is called! Use TRUE for the filename to send the current + * response as the file content. The third parameter allows the following + * options to be set: + * + * Type | Option | Description | Default Value + * ----------|-----------|------------------------------------|-------------- + * `boolean` | inline | Display inline instead of download | `FALSE` + * `string` | mime_type | Manual mime type | Automatic + * `boolean` | delete | Delete the file after sending | `FALSE` + * + * Download a file that already exists: + * + * $request->send_file('media/packages/kohana.zip'); + * + * Download generated content as a file: + * + * $request->response($content); + * $request->send_file(TRUE, $filename); + * + * [!!] No further processing can be done after this method is called! + * + * @param string filename with path, or TRUE for the current response + * @param string downloaded file name + * @param array additional options + * @return void + * @throws Kohana_Exception + * @uses File::mime_by_ext + * @uses File::mime + * @uses Request::send_headers + */ + public function send_file($filename, $download = NULL, array $options = NULL) + { + if ( ! empty($options['mime_type'])) + { + // The mime-type has been manually set + $mime = $options['mime_type']; + } + + if ($filename === TRUE) + { + if (empty($download)) + { + throw new Kohana_Exception('Download name must be provided for streaming files'); + } + + // Temporary files will automatically be deleted + $options['delete'] = FALSE; + + if ( ! isset($mime)) + { + // Guess the mime using the file extension + $mime = File::mime_by_ext(strtolower(pathinfo($download, PATHINFO_EXTENSION))); + } + + // Force the data to be rendered if + $file_data = (string) $this->_body; + + // Get the content size + $size = strlen($file_data); + + // Create a temporary file to hold the current response + $file = tmpfile(); + + // Write the current response into the file + fwrite($file, $file_data); + + // File data is no longer needed + unset($file_data); + } + else + { + // Get the complete file path + $filename = realpath($filename); + + if (empty($download)) + { + // Use the file name as the download file name + $download = pathinfo($filename, PATHINFO_BASENAME); + } + + // Get the file size + $size = filesize($filename); + + if ( ! isset($mime)) + { + // Get the mime type + $mime = File::mime($filename); + } + + // Open the file for reading + $file = fopen($filename, 'rb'); + } + + if ( ! is_resource($file)) + { + throw new Kohana_Exception('Could not read file to send: :file', array( + ':file' => $download, + )); + } + + // Inline or download? + $disposition = empty($options['inline']) ? 'attachment' : 'inline'; + + // Calculate byte range to download. + list($start, $end) = $this->_calculate_byte_range($size); + + if ( ! empty($options['resumable'])) + { + if ($start > 0 OR $end < ($size - 1)) + { + // Partial Content + $this->_status = 206; + } + + // Range of bytes being sent + $this->_header['content-range'] = 'bytes '.$start.'-'.$end.'/'.$size; + $this->_header['accept-ranges'] = 'bytes'; + } + + // Set the headers for a download + $this->_header['content-disposition'] = $disposition.'; filename="'.$download.'"'; + $this->_header['content-type'] = $mime; + $this->_header['content-length'] = (string) (($end - $start) + 1); + + if (Request::user_agent('browser') === 'Internet Explorer') + { + // Naturally, IE does not act like a real browser... + if (Request::$initial->protocol() === 'https') + { + // http://support.microsoft.com/kb/316431 + $this->_header['pragma'] = $this->_header['cache-control'] = 'public'; + } + + if (version_compare(Request::user_agent('version'), '8.0', '>=')) + { + // http://ajaxian.com/archives/ie-8-security + $this->_header['x-content-type-options'] = 'nosniff'; + } + } + + // Send all headers now + $this->send_headers(); + + while (ob_get_level()) + { + // Flush all output buffers + ob_end_flush(); + } + + // Manually stop execution + ignore_user_abort(TRUE); + + if ( ! Kohana::$safe_mode) + { + // Keep the script running forever + set_time_limit(0); + } + + // Send data in 16kb blocks + $block = 1024 * 16; + + fseek($file, $start); + + while ( ! feof($file) AND ($pos = ftell($file)) <= $end) + { + if (connection_aborted()) + break; + + if ($pos + $block > $end) + { + // Don't read past the buffer. + $block = $end - $pos + 1; + } + + // Output a block of the file + echo fread($file, $block); + + // Send the data now + flush(); + } + + // Close the file + fclose($file); + + if ( ! empty($options['delete'])) + { + try + { + // Attempt to remove the file + unlink($filename); + } + catch (Exception $e) + { + // Create a text version of the exception + $error = Kohana_Exception::text($e); + + if (is_object(Kohana::$log)) + { + // Add this exception to the log + Kohana::$log->add(Log::ERROR, $error); + + // Make sure the logs are written + Kohana::$log->write(); + } + + // Do NOT display the exception, it will corrupt the output! + } + } + + // Stop execution + exit; + } + + /** + * Renders the HTTP_Interaction to a string, producing + * + * - Protocol + * - Headers + * - Body + * + * @return string + */ + public function render() + { + if ( ! $this->_header->offsetExists('content-type')) + { + // Add the default Content-Type header if required + $this->_header['content-type'] = Kohana::$content_type.'; charset='.Kohana::$charset; + } + + $content_length = $this->content_length(); + + // Set the content length for the body if required + if ($content_length > 0) + { + $this->_header['content-length'] = (string) $content_length; + } + + // Prepare cookies + if ($this->_cookies) + { + if (extension_loaded('http')) + { + $this->_header['set-cookie'] = http_build_cookie($this->_cookies); + } + else + { + $cookies = array(); + + // Parse each + foreach ($this->_cookies as $key => $value) + { + $string = $key.'='.$value['value'].'; expires='.date('l, d M Y H:i:s T', $value['expiration']); + $cookies[] = $string; + } + + // Create the cookie string + $this->_header['set-cookie'] = $cookies; + } + } + + $output = $this->_protocol.' '.$this->_status.' '.Response::$messages[$this->_status]."\n"; + $output .= (string) $this->_header; + $output .= $this->_body; + + return $output; + } + + /** + * Generate ETag + * Generates an ETag from the response ready to be returned + * + * @throws Kohana_Request_Exception + * @return String Generated ETag + */ + public function generate_etag() + { + if ($this->_body === NULL) + { + throw new Kohana_Request_Exception('No response yet associated with request - cannot auto generate resource ETag'); + } + + // Generate a unique hash for the response + return '"'.sha1($this->render()).'"'; + } + + /** + * Check Cache + * Checks the browser cache to see the response needs to be returned + * + * @param string $etag Resource ETag + * @param Request $request The request to test against + * @return Response + * @throws Kohana_Request_Exception + */ + public function check_cache($etag = NULL, Request $request = NULL) + { + if ( ! $etag) + { + $etag = $this->generate_etag(); + } + + if ( ! $request) + throw new Kohana_Request_Exception('A Request object must be supplied with an etag for evaluation'); + + // Set the ETag header + $this->_header['etag'] = $etag; + + // Add the Cache-Control header if it is not already set + // This allows etags to be used with max-age, etc + if ($this->_header->offsetExists('cache-control')) + { + if (is_array($this->_header['cache-control'])) + { + $this->_header['cache-control'][] = new HTTP_Header_Value('must-revalidate'); + } + else + { + $this->_header['cache-control'] = $this->_header['cache-control'].', must-revalidate'; + } + } + else + { + $this->_header['cache-control'] = 'must-revalidate'; + } + + if ($request->headers('if-none-match') AND (string) $request->headers('if-none-match') === $etag) + { + // No need to send data again + $this->_status = 304; + $this->send_headers(); + + // Stop execution + exit; + } + + return $this; + } + + /** + * Serializes the object to json - handy if you + * need to pass the response data to other + * systems + * + * @param array array of data to serialize + * @return string + * @throws Kohana_Exception + */ + public function serialize(array $to_serialize = array()) + { + // Serialize the class properties + $to_serialize += array + ( + '_status' => $this->_status, + '_header' => $this->_header, + '_cookies' => $this->_cookies, + '_body' => $this->_body + ); + + $serialized = serialize($to_serialize); + + if (is_string($serialized)) + { + return $serialized; + } + else + { + throw new Kohana_Exception('Unable to serialize object'); + } + } + + /** + * JSON encoded object + * + * @param string json encoded object + * @return bool + * @throws Kohana_Exception + */ + public function unserialize($string) + { + // Unserialise object + $unserialized = unserialize($string); + + // If failed + if ($unserialized === NULL) + { + // Throw exception + throw new Kohana_Exception('Unable to correctly unserialize string: :string', array(':string' => $string)); + } + + // Foreach key/value pair + foreach ($unserialized as $key => $value) + { + $this->$key = $value; + } + + return TRUE; + } + + /** + * Parse the byte ranges from the HTTP_RANGE header used for + * resumable downloads. + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 + * @return array|FALSE + */ + protected function _parse_byte_range() + { + if ( ! isset($_SERVER['HTTP_RANGE'])) + { + return FALSE; + } + + // TODO, speed this up with the use of string functions. + preg_match_all('/(-?[0-9]++(?:-(?![0-9]++))?)(?:-?([0-9]++))?/', $_SERVER['HTTP_RANGE'], $matches, PREG_SET_ORDER); + + return $matches[0]; + } + + /** + * Calculates the byte range to use with send_file. If HTTP_RANGE doesn't + * exist then the complete byte range is returned + * + * @param integer $size + * @return array + */ + protected function _calculate_byte_range($size) + { + // Defaults to start with when the HTTP_RANGE header doesn't exist. + $start = 0; + $end = $size - 1; + + if ($range = $this->_parse_byte_range()) + { + // We have a byte range from HTTP_RANGE + $start = $range[1]; + + if ($start[0] === '-') + { + // A negative value means we start from the end, so -500 would be the + // last 500 bytes. + $start = $size - abs($start); + } + + if (isset($range[2])) + { + // Set the end range + $end = $range[2]; + } + } + + // Normalize values. + $start = abs(intval($start)); + + // Keep the the end value in bounds and normalize it. + $end = min(abs(intval($end)), $size - 1); + + // Keep the start in bounds. + $start = ($end < $start) ? 0 : max($start, 0); + + return array($start, $end); + } +} // End Kohana_Response diff --git a/includes/kohana/system/classes/kohana/route.php b/includes/kohana/system/classes/kohana/route.php new file mode 100644 index 0000000..8dcd318 --- /dev/null +++ b/includes/kohana/system/classes/kohana/route.php @@ -0,0 +1,541 @@ + will be translated to a regular expression using a default + * regular expression pattern. You can override the default pattern by providing + * a pattern for the key: + * + * // This route will only match when is a digit + * Route::set('user', 'user//', array('id' => '\d+')); + * + * // This route will match when is anything + * Route::set('file', '', array('path' => '.*')); + * + * It is also possible to create optional segments by using parentheses in + * the URI definition: + * + * // This is the standard default route, and no keys are required + * Route::set('default', '((/(/)))'); + * + * // This route only requires the key + * Route::set('file', '(/)(.)', array('path' => '.*', 'format' => '\w+')); + * + * Routes also provide a way to generate URIs (called "reverse routing"), which + * makes them an extremely powerful and flexible way to generate internal links. + * + * @package Kohana + * @category Base + * @author Kohana Team + * @copyright (c) 2008-2011 Kohana Team + * @license http://kohanaframework.org/license + */ +class Kohana_Route { + + // Defines the pattern of a + const REGEX_KEY = '<([a-zA-Z0-9_]++)>'; + + // What can be part of a value + const REGEX_SEGMENT = '[^/.,;?\n]++'; + + // What must be escaped in the route regex + const REGEX_ESCAPE = '[.\\+*?[^\\]${}=!|]'; + + /** + * @var string default protocol for all routes + * + * @example 'http://' + */ + public static $default_protocol = 'http://'; + + /** + * @var array list of valid localhost entries + */ + public static $localhosts = array(FALSE, '', 'local', 'localhost'); + + /** + * @var string default action for all routes + */ + public static $default_action = 'index'; + + /** + * @var bool Indicates whether routes are cached + */ + public static $cache = FALSE; + + /** + * @var array + */ + protected static $_routes = array(); + + /** + * Stores a named route and returns it. The "action" will always be set to + * "index" if it is not defined. + * + * Route::set('default', '((/(/)))') + * ->defaults(array( + * 'controller' => 'welcome', + * )); + * + * @param string route name + * @param string URI pattern + * @param array regex patterns for route keys + * @return Route + */ + public static function set($name, $uri_callback = NULL, $regex = NULL) + { + return Route::$_routes[$name] = new Route($uri_callback, $regex); + } + + /** + * Retrieves a named route. + * + * $route = Route::get('default'); + * + * @param string route name + * @return Route + * @throws Kohana_Exception + */ + public static function get($name) + { + if ( ! isset(Route::$_routes[$name])) + { + throw new Kohana_Exception('The requested route does not exist: :route', + array(':route' => $name)); + } + + return Route::$_routes[$name]; + } + + /** + * Retrieves all named routes. + * + * $routes = Route::all(); + * + * @return array routes by name + */ + public static function all() + { + return Route::$_routes; + } + + /** + * Get the name of a route. + * + * $name = Route::name($route) + * + * @param object Route instance + * @return string + */ + public static function name(Route $route) + { + return array_search($route, Route::$_routes); + } + + /** + * Saves or loads the route cache. If your routes will remain the same for + * a long period of time, use this to reload the routes from the cache + * rather than redefining them on every page load. + * + * if ( ! Route::cache()) + * { + * // Set routes here + * Route::cache(TRUE); + * } + * + * @param boolean cache the current routes + * @return void when saving routes + * @return boolean when loading routes + * @uses Kohana::cache + */ + public static function cache($save = FALSE) + { + if ($save === TRUE) + { + // Cache all defined routes + Kohana::cache('Route::cache()', Route::$_routes); + } + else + { + if ($routes = Kohana::cache('Route::cache()')) + { + Route::$_routes = $routes; + + // Routes were cached + return Route::$cache = TRUE; + } + else + { + // Routes were not cached + return Route::$cache = FALSE; + } + } + } + + /** + * Create a URL from a route name. This is a shortcut for: + * + * echo URL::site(Route::get($name)->uri($params), $protocol); + * + * @param string route name + * @param array URI parameters + * @param mixed protocol string or boolean, adds protocol and domain + * @return string + * @since 3.0.7 + * @uses URL::site + */ + public static function url($name, array $params = NULL, $protocol = NULL) + { + $route = Route::get($name); + + // Create a URI with the route and convert it to a URL + if ($route->is_external()) + return Route::get($name)->uri($params); + else + return URL::site(Route::get($name)->uri($params), $protocol); + } + + /** + * Returns the compiled regular expression for the route. This translates + * keys and optional groups to a proper PCRE regular expression. + * + * $compiled = Route::compile( + * '(/(/))', + * array( + * 'controller' => '[a-z]+', + * 'id' => '\d+', + * ) + * ); + * + * @return string + * @uses Route::REGEX_ESCAPE + * @uses Route::REGEX_SEGMENT + */ + public static function compile($uri, array $regex = NULL) + { + if ( ! is_string($uri)) + return; + + // The URI should be considered literal except for keys and optional parts + // Escape everything preg_quote would escape except for : ( ) < > + $expression = preg_replace('#'.Route::REGEX_ESCAPE.'#', '\\\\$0', $uri); + + if (strpos($expression, '(') !== FALSE) + { + // Make optional parts of the URI non-capturing and optional + $expression = str_replace(array('(', ')'), array('(?:', ')?'), $expression); + } + + // Insert default regex for keys + $expression = str_replace(array('<', '>'), array('(?P<', '>'.Route::REGEX_SEGMENT.')'), $expression); + + if ($regex) + { + $search = $replace = array(); + foreach ($regex as $key => $value) + { + $search[] = "<$key>".Route::REGEX_SEGMENT; + $replace[] = "<$key>$value"; + } + + // Replace the default regex with the user-specified regex + $expression = str_replace($search, $replace, $expression); + } + + return '#^'.$expression.'$#uD'; + } + + /** + * @var callback The callback method for routes + */ + protected $_callback; + + /** + * @var string route URI + */ + protected $_uri = ''; + + /** + * @var array + */ + protected $_regex = array(); + + /** + * @var array + */ + protected $_defaults = array('action' => 'index', 'host' => FALSE); + + /** + * @var string + */ + protected $_route_regex; + + /** + * Creates a new route. Sets the URI and regular expressions for keys. + * Routes should always be created with [Route::set] or they will not + * be properly stored. + * + * $route = new Route($uri, $regex); + * + * The $uri parameter can either be a string for basic regex matching or it + * can be a valid callback or anonymous function (php 5.3+). If you use a + * callback or anonymous function, your method should return an array + * containing the proper keys for the route. If you want the route to be + * "reversable", you need pass the route string as the third parameter. + * + * $route = new Route(function($uri) + * { + * if (list($controller, $action, $param) = explode('/', $uri) AND $controller == 'foo' AND $action == 'bar') + * { + * return array( + * 'controller' => 'foobar', + * 'action' => $action, + * 'id' => $param, + * ); + * }, + * 'foo/bar/' + * }); + * + * @param mixed route URI pattern or lambda/callback function + * @param array key patterns + * @return void + * @uses Route::_compile + */ + public function __construct($uri = NULL, $regex = NULL) + { + if ($uri === NULL) + { + // Assume the route is from cache + return; + } + + if ( ! is_string($uri) AND is_callable($uri)) + { + $this->_callback = $uri; + $this->_uri = $regex; + $regex = NULL; + } + elseif ( ! empty($uri)) + { + $this->_uri = $uri; + } + + if ( ! empty($regex)) + { + $this->_regex = $regex; + } + + // Store the compiled regex locally + $this->_route_regex = Route::compile($uri, $regex); + } + + /** + * Provides default values for keys when they are not present. The default + * action will always be "index" unless it is overloaded here. + * + * $route->defaults(array( + * 'controller' => 'welcome', + * 'action' => 'index' + * )); + * + * @param array key values + * @return $this + */ + public function defaults(array $defaults = NULL) + { + $this->_defaults = $defaults; + + return $this; + } + + /** + * Tests if the route matches a given URI. A successful match will return + * all of the routed parameters as an array. A failed match will return + * boolean FALSE. + * + * // Params: controller = users, action = edit, id = 10 + * $params = $route->matches('users/edit/10'); + * + * This method should almost always be used within an if/else block: + * + * if ($params = $route->matches($uri)) + * { + * // Parse the parameters + * } + * + * @param string URI to match + * @return array on success + * @return FALSE on failure + */ + public function matches($uri) + { + if ($this->_callback) + { + $closure = $this->_callback; + $params = call_user_func($closure, $uri); + + if ( ! is_array($params)) + return FALSE; + } + else + { + if ( ! preg_match($this->_route_regex, $uri, $matches)) + return FALSE; + + $params = array(); + foreach ($matches as $key => $value) + { + if (is_int($key)) + { + // Skip all unnamed keys + continue; + } + + // Set the value for all matched keys + $params[$key] = $value; + } + } + + foreach ($this->_defaults as $key => $value) + { + if ( ! isset($params[$key]) OR $params[$key] === '') + { + // Set default values for any key that was not matched + $params[$key] = $value; + } + } + + return $params; + } + + /** + * Returns whether this route is an external route + * to a remote controller. + * + * @return boolean + */ + public function is_external() + { + return ! in_array(Arr::get($this->_defaults, 'host', FALSE), Route::$localhosts); + } + + /** + * Generates a URI for the current route based on the parameters given. + * + * // Using the "default" route: "users/profile/10" + * $route->uri(array( + * 'controller' => 'users', + * 'action' => 'profile', + * 'id' => '10' + * )); + * + * @param array URI parameters + * @return string + * @throws Kohana_Exception + * @uses Route::REGEX_Key + */ + public function uri(array $params = NULL) + { + // Start with the routed URI + $uri = $this->_uri; + + if (strpos($uri, '<') === FALSE AND strpos($uri, '(') === FALSE) + { + // This is a static route, no need to replace anything + + if ( ! $this->is_external()) + return $uri; + + // If the localhost setting does not have a protocol + if (strpos($this->_defaults['host'], '://') === FALSE) + { + // Use the default defined protocol + $params['host'] = Route::$default_protocol.$this->_defaults['host']; + } + else + { + // Use the supplied host with protocol + $params['host'] = $this->_defaults['host']; + } + + // Compile the final uri and return it + return rtrim($params['host'], '/').'/'.$uri; + } + + while (preg_match('#\([^()]++\)#', $uri, $match)) + { + // Search for the matched value + $search = $match[0]; + + // Remove the parenthesis from the match as the replace + $replace = substr($match[0], 1, -1); + + while (preg_match('#'.Route::REGEX_KEY.'#', $replace, $match)) + { + list($key, $param) = $match; + + if (isset($params[$param])) + { + // Replace the key with the parameter value + $replace = str_replace($key, $params[$param], $replace); + } + else + { + // This group has missing parameters + $replace = ''; + break; + } + } + + // Replace the group in the URI + $uri = str_replace($search, $replace, $uri); + } + + while (preg_match('#'.Route::REGEX_KEY.'#', $uri, $match)) + { + list($key, $param) = $match; + + if ( ! isset($params[$param])) + { + // Look for a default + if (isset($this->_defaults[$param])) + { + $params[$param] = $this->_defaults[$param]; + } + else + { + // Ungrouped parameters are required + throw new Kohana_Exception('Required route parameter not passed: :param', array( + ':param' => $param, + )); + } + } + + $uri = str_replace($key, $params[$param], $uri); + } + + // Trim all extra slashes from the URI + $uri = preg_replace('#//+#', '/', rtrim($uri, '/')); + + if ($this->is_external()) + { + // Need to add the host to the URI + $host = $this->_defaults['host']; + + if (strpos($host, '://') === FALSE) + { + // Use the default defined protocol + $host = Route::$default_protocol.$host; + } + + // Clean up the host and prepend it to the URI + $uri = rtrim($host, '/').'/'.$uri; + } + + return $uri; + } + +} // End Route diff --git a/includes/kohana/system/classes/kohana/security.php b/includes/kohana/system/classes/kohana/security.php new file mode 100644 index 0000000..e85af5b --- /dev/null +++ b/includes/kohana/system/classes/kohana/security.php @@ -0,0 +1,103 @@ +rules('csrf', array( + * 'not_empty' => NULL, + * 'Security::check' => NULL, + * )); + * + * This provides a basic, but effective, method of preventing CSRF attacks. + * + * @param boolean force a new token to be generated? + * @return string + * @uses Session::instance + */ + public static function token($new = FALSE) + { + $session = Session::instance(); + + // Get the current token + $token = $session->get(Security::$token_name); + + if ($new === TRUE OR ! $token) + { + // Generate a new unique token + $token = sha1(uniqid(NULL, TRUE)); + + // Store the new token + $session->set(Security::$token_name, $token); + } + + return $token; + } + + /** + * Check that the given token matches the currently stored security token. + * + * if (Security::check($token)) + * { + * // Pass + * } + * + * @param string token to check + * @return boolean + * @uses Security::token + */ + public static function check($token) + { + return Security::token() === $token; + } + + /** + * Remove image tags from a string. + * + * $str = Security::strip_image_tags($str); + * + * @param string string to sanitize + * @return string + */ + public static function strip_image_tags($str) + { + return preg_replace('#\s]*)["\']?[^>]*)?>#is', '$1', $str); + } + + /** + * Encodes PHP tags in a string. + * + * $str = Security::encode_php_tags($str); + * + * @param string string to sanitize + * @return string + */ + public static function encode_php_tags($str) + { + return str_replace(array(''), array('<?', '?>'), $str); + } + +} // End security diff --git a/includes/kohana/system/classes/kohana/session.php b/includes/kohana/system/classes/kohana/session.php new file mode 100644 index 0000000..64be88c --- /dev/null +++ b/includes/kohana/system/classes/kohana/session.php @@ -0,0 +1,434 @@ +get($type); + + // Set the session class name + $class = 'Session_'.ucfirst($type); + + // Create a new session instance + Session::$instances[$type] = $session = new $class($config, $id); + + // Write the session at shutdown + register_shutdown_function(array($session, 'write')); + } + + return Session::$instances[$type]; + } + + /** + * @var string cookie name + */ + protected $_name = 'session'; + + /** + * @var int cookie lifetime + */ + protected $_lifetime = 0; + + /** + * @var bool encrypt session data? + */ + protected $_encrypted = FALSE; + + /** + * @var array session data + */ + protected $_data = array(); + + /** + * @var bool session destroyed? + */ + protected $_destroyed = FALSE; + + /** + * Overloads the name, lifetime, and encrypted session settings. + * + * [!!] Sessions can only be created using the [Session::instance] method. + * + * @param array configuration + * @param string session id + * @return void + * @uses Session::read + */ + public function __construct(array $config = NULL, $id = NULL) + { + if (isset($config['name'])) + { + // Cookie name to store the session id in + $this->_name = (string) $config['name']; + } + + if (isset($config['lifetime'])) + { + // Cookie lifetime + $this->_lifetime = (int) $config['lifetime']; + } + + if (isset($config['encrypted'])) + { + if ($config['encrypted'] === TRUE) + { + // Use the default Encrypt instance + $config['encrypted'] = 'default'; + } + + // Enable or disable encryption of data + $this->_encrypted = $config['encrypted']; + } + + // Load the session + $this->read($id); + } + + /** + * Session object is rendered to a serialized string. If encryption is + * enabled, the session will be encrypted. If not, the output string will + * be encoded using [base64_encode]. + * + * echo $session; + * + * @return string + * @uses Encrypt::encode + */ + public function __toString() + { + // Serialize the data array + $data = serialize($this->_data); + + if ($this->_encrypted) + { + // Encrypt the data using the default key + $data = Encrypt::instance($this->_encrypted)->encode($data); + } + else + { + // Obfuscate the data with base64 encoding + $data = base64_encode($data); + } + + return $data; + } + + /** + * Returns the current session array. The returned array can also be + * assigned by reference. + * + * // Get a copy of the current session data + * $data = $session->as_array(); + * + * // Assign by reference for modification + * $data =& $session->as_array(); + * + * @return array + */ + public function & as_array() + { + return $this->_data; + } + + /** + * Get the current session id, if the session supports it. + * + * $id = $session->id(); + * + * [!!] Not all session types have ids. + * + * @return string + * @since 3.0.8 + */ + public function id() + { + return NULL; + } + + /** + * Get the current session cookie name. + * + * $name = $session->name(); + * + * @return string + * @since 3.0.8 + */ + public function name() + { + return $this->_name; + } + + /** + * Get a variable from the session array. + * + * $foo = $session->get('foo'); + * + * @param string variable name + * @param mixed default value to return + * @return mixed + */ + public function get($key, $default = NULL) + { + return array_key_exists($key, $this->_data) ? $this->_data[$key] : $default; + } + + /** + * Get and delete a variable from the session array. + * + * $bar = $session->get_once('bar'); + * + * @param string variable name + * @param mixed default value to return + * @return mixed + */ + public function get_once($key, $default = NULL) + { + $value = $this->get($key, $default); + + unset($this->_data[$key]); + + return $value; + } + + /** + * Set a variable in the session array. + * + * $session->set('foo', 'bar'); + * + * @param string variable name + * @param mixed value + * @return $this + */ + public function set($key, $value) + { + $this->_data[$key] = $value; + + return $this; + } + + /** + * Set a variable by reference. + * + * $session->bind('foo', $foo); + * + * @param string variable name + * @param mixed referenced value + * @return $this + */ + public function bind($key, & $value) + { + $this->_data[$key] =& $value; + + return $this; + } + + /** + * Removes a variable in the session array. + * + * $session->delete('foo'); + * + * @param string variable name + * @param ... + * @return $this + */ + public function delete($key) + { + $args = func_get_args(); + + foreach ($args as $key) + { + unset($this->_data[$key]); + } + + return $this; + } + + /** + * Loads existing session data. + * + * $session->read(); + * + * @param string session id + * @return void + */ + public function read($id = NULL) + { + $data = NULL; + + try + { + if (is_string($data = $this->_read($id))) + { + if ($this->_encrypted) + { + // Decrypt the data using the default key + $data = Encrypt::instance($this->_encrypted)->decode($data); + } + else + { + // Decode the base64 encoded data + $data = base64_decode($data); + } + + // Unserialize the data + $data = unserialize($data); + } + else + { + // Ignore these, session is valid, likely no data though. + } + } + catch (Exception $e) + { + // Ignore all reading errors, but log them + Kohana::$log->add(Log::ERROR, 'Error reading session data: '.$id); + } + + if (is_array($data)) + { + // Load the data locally + $this->_data = $data; + } + } + + /** + * Generates a new session id and returns it. + * + * $id = $session->regenerate(); + * + * @return string + */ + public function regenerate() + { + return $this->_regenerate(); + } + + /** + * Sets the last_active timestamp and saves the session. + * + * $session->write(); + * + * [!!] Any errors that occur during session writing will be logged, + * but not displayed, because sessions are written after output has + * been sent. + * + * @return boolean + * @uses Kohana::$log + */ + public function write() + { + if (headers_sent() OR $this->_destroyed) + { + // Session cannot be written when the headers are sent or when + // the session has been destroyed + return FALSE; + } + + // Set the last active timestamp + $this->_data['last_active'] = time(); + + try + { + return $this->_write(); + } + catch (Exception $e) + { + // Log & ignore all errors when a write fails + Kohana::$log->add(Log::ERROR, Kohana_Exception::text($e))->write(); + + return FALSE; + } + } + + /** + * Completely destroy the current session. + * + * $success = $session->destroy(); + * + * @return boolean + */ + public function destroy() + { + if ($this->_destroyed === FALSE) + { + if ($this->_destroyed = $this->_destroy()) + { + // The session has been destroyed, clear all data + $this->_data = array(); + } + } + + return $this->_destroyed; + } + + /** + * Loads the raw session data string and returns it. + * + * @param string session id + * @return string + */ + abstract protected function _read($id = NULL); + + /** + * Generate a new session id and return it. + * + * @return string + */ + abstract protected function _regenerate(); + + /** + * Writes the current session. + * + * @return boolean + */ + abstract protected function _write(); + + /** + * Destroys the current session. + * + * @return boolean + */ + abstract protected function _destroy(); + +} // End Session diff --git a/includes/kohana/system/classes/kohana/session/cookie.php b/includes/kohana/system/classes/kohana/session/cookie.php new file mode 100644 index 0000000..dc12c9c --- /dev/null +++ b/includes/kohana/system/classes/kohana/session/cookie.php @@ -0,0 +1,47 @@ +_name, NULL); + } + + /** + * @return null + */ + protected function _regenerate() + { + // Cookie sessions have no id + return NULL; + } + + /** + * @return bool + */ + protected function _write() + { + return Cookie::set($this->_name, $this->__toString(), $this->_lifetime); + } + + /** + * @return bool + */ + protected function _destroy() + { + return Cookie::delete($this->_name); + } + +} // End Session_Cookie diff --git a/includes/kohana/system/classes/kohana/session/native.php b/includes/kohana/system/classes/kohana/session/native.php new file mode 100644 index 0000000..1d2c996 --- /dev/null +++ b/includes/kohana/system/classes/kohana/session/native.php @@ -0,0 +1,93 @@ +_lifetime, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly); + + // Do not allow PHP to send Cache-Control headers + session_cache_limiter(FALSE); + + // Set the session cookie name + session_name($this->_name); + + if ($id) + { + // Set the session id + session_id($id); + } + + // Start the session + session_start(); + + // Use the $_SESSION global for storing data + $this->_data =& $_SESSION; + + return NULL; + } + + /** + * @return string + */ + protected function _regenerate() + { + // Regenerate the session id + session_regenerate_id(); + + return session_id(); + } + + /** + * @return bool + */ + protected function _write() + { + // Write and close the session + session_write_close(); + + return TRUE; + } + + /** + * @return bool + */ + protected function _destroy() + { + // Destroy the current session + session_destroy(); + + // Did destruction work? + $status = ! session_id(); + + if ($status) + { + // Make sure the session cannot be restarted + Cookie::delete($this->_name); + } + + return $status; + } + +} // End Session_Native diff --git a/includes/kohana/system/classes/kohana/text.php b/includes/kohana/system/classes/kohana/text.php new file mode 100644 index 0000000..44b0d21 --- /dev/null +++ b/includes/kohana/system/classes/kohana/text.php @@ -0,0 +1,590 @@ + 'billion', + 1000000 => 'million', + 1000 => 'thousand', + 100 => 'hundred', + 90 => 'ninety', + 80 => 'eighty', + 70 => 'seventy', + 60 => 'sixty', + 50 => 'fifty', + 40 => 'fourty', + 30 => 'thirty', + 20 => 'twenty', + 19 => 'nineteen', + 18 => 'eighteen', + 17 => 'seventeen', + 16 => 'sixteen', + 15 => 'fifteen', + 14 => 'fourteen', + 13 => 'thirteen', + 12 => 'twelve', + 11 => 'eleven', + 10 => 'ten', + 9 => 'nine', + 8 => 'eight', + 7 => 'seven', + 6 => 'six', + 5 => 'five', + 4 => 'four', + 3 => 'three', + 2 => 'two', + 1 => 'one', + ); + + /** + * Limits a phrase to a given number of words. + * + * $text = Text::limit_words($text); + * + * @param string phrase to limit words of + * @param integer number of words to limit to + * @param string end character or entity + * @return string + */ + public static function limit_words($str, $limit = 100, $end_char = NULL) + { + $limit = (int) $limit; + $end_char = ($end_char === NULL) ? '…' : $end_char; + + if (trim($str) === '') + return $str; + + if ($limit <= 0) + return $end_char; + + preg_match('/^\s*+(?:\S++\s*+){1,'.$limit.'}/u', $str, $matches); + + // Only attach the end character if the matched string is shorter + // than the starting string. + return rtrim($matches[0]).((strlen($matches[0]) === strlen($str)) ? '' : $end_char); + } + + /** + * Limits a phrase to a given number of characters. + * + * $text = Text::limit_chars($text); + * + * @param string phrase to limit characters of + * @param integer number of characters to limit to + * @param string end character or entity + * @param boolean enable or disable the preservation of words while limiting + * @return string + * @uses UTF8::strlen + */ + public static function limit_chars($str, $limit = 100, $end_char = NULL, $preserve_words = FALSE) + { + $end_char = ($end_char === NULL) ? '…' : $end_char; + + $limit = (int) $limit; + + if (trim($str) === '' OR UTF8::strlen($str) <= $limit) + return $str; + + if ($limit <= 0) + return $end_char; + + if ($preserve_words === FALSE) + return rtrim(UTF8::substr($str, 0, $limit)).$end_char; + + // Don't preserve words. The limit is considered the top limit. + // No strings with a length longer than $limit should be returned. + if ( ! preg_match('/^.{0,'.$limit.'}\s/us', $str, $matches)) + return $end_char; + + return rtrim($matches[0]).((strlen($matches[0]) === strlen($str)) ? '' : $end_char); + } + + /** + * Alternates between two or more strings. + * + * echo Text::alternate('one', 'two'); // "one" + * echo Text::alternate('one', 'two'); // "two" + * echo Text::alternate('one', 'two'); // "one" + * + * Note that using multiple iterations of different strings may produce + * unexpected results. + * + * @param string strings to alternate between + * @return string + */ + public static function alternate() + { + static $i; + + if (func_num_args() === 0) + { + $i = 0; + return ''; + } + + $args = func_get_args(); + return $args[($i++ % count($args))]; + } + + /** + * Generates a random string of a given type and length. + * + * + * $str = Text::random(); // 8 character random string + * + * The following types are supported: + * + * alnum + * : Upper and lower case a-z, 0-9 (default) + * + * alpha + * : Upper and lower case a-z + * + * hexdec + * : Hexadecimal characters a-f, 0-9 + * + * distinct + * : Uppercase characters and numbers that cannot be confused + * + * You can also create a custom type by providing the "pool" of characters + * as the type. + * + * @param string a type of pool, or a string of characters to use as the pool + * @param integer length of string to return + * @return string + * @uses UTF8::split + */ + public static function random($type = NULL, $length = 8) + { + if ($type === NULL) + { + // Default is to generate an alphanumeric string + $type = 'alnum'; + } + + $utf8 = FALSE; + + switch ($type) + { + case 'alnum': + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'alpha': + $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'hexdec': + $pool = '0123456789abcdef'; + break; + case 'numeric': + $pool = '0123456789'; + break; + case 'nozero': + $pool = '123456789'; + break; + case 'distinct': + $pool = '2345679ACDEFHJKLMNPRSTUVWXYZ'; + break; + default: + $pool = (string) $type; + $utf8 = ! UTF8::is_ascii($pool); + break; + } + + // Split the pool into an array of characters + $pool = ($utf8 === TRUE) ? UTF8::str_split($pool, 1) : str_split($pool, 1); + + // Largest pool key + $max = count($pool) - 1; + + $str = ''; + for ($i = 0; $i < $length; $i++) + { + // Select a random character from the pool and add it to the string + $str .= $pool[mt_rand(0, $max)]; + } + + // Make sure alnum strings contain at least one letter and one digit + if ($type === 'alnum' AND $length > 1) + { + if (ctype_alpha($str)) + { + // Add a random digit + $str[mt_rand(0, $length - 1)] = chr(mt_rand(48, 57)); + } + elseif (ctype_digit($str)) + { + // Add a random letter + $str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90)); + } + } + + return $str; + } + + /** + * Reduces multiple slashes in a string to single slashes. + * + * $str = Text::reduce_slashes('foo//bar/baz'); // "foo/bar/baz" + * + * @param string string to reduce slashes of + * @return string + */ + public static function reduce_slashes($str) + { + return preg_replace('#(? '#####', + * )); + * + * @param string phrase to replace words in + * @param array words to replace + * @param string replacement string + * @param boolean replace words across word boundries (space, period, etc) + * @return string + * @uses UTF8::strlen + */ + public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = TRUE) + { + foreach ( (array) $badwords as $key => $badword) + { + $badwords[$key] = str_replace('\*', '\S*?', preg_quote( (string) $badword)); + } + + $regex = '('.implode('|', $badwords).')'; + + if ($replace_partial_words === FALSE) + { + // Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself + $regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)'; + } + + $regex = '!'.$regex.'!ui'; + + if (UTF8::strlen($replacement) == 1) + { + $regex .= 'e'; + return preg_replace($regex, 'str_repeat($replacement, UTF8::strlen(\'$1\'))', $str); + } + + return preg_replace($regex, $replacement, $str); + } + + /** + * Finds the text that is similar between a set of words. + * + * $match = Text::similar(array('fred', 'fran', 'free'); // "fr" + * + * @param array words to find similar text of + * @return string + */ + public static function similar(array $words) + { + // First word is the word to match against + $word = current($words); + + for ($i = 0, $max = strlen($word); $i < $max; ++$i) + { + foreach ($words as $w) + { + // Once a difference is found, break out of the loops + if ( ! isset($w[$i]) OR $w[$i] !== $word[$i]) + break 2; + } + } + + // Return the similar text + return substr($word, 0, $i); + } + + /** + * Converts text email addresses and anchors into links. Existing links + * will not be altered. + * + * echo Text::auto_link($text); + * + * [!!] This method is not foolproof since it uses regex to parse HTML. + * + * @param string text to auto link + * @return string + * @uses Text::auto_link_urls + * @uses Text::auto_link_emails + */ + public static function auto_link($text) + { + // Auto link emails first to prevent problems with "www.domain.com@example.com" + return Text::auto_link_urls(Text::auto_link_emails($text)); + } + + /** + * Converts text anchors into links. Existing links will not be altered. + * + * echo Text::auto_link_urls($text); + * + * [!!] This method is not foolproof since it uses regex to parse HTML. + * + * @param string text to auto link + * @return string + * @uses HTML::anchor + */ + public static function auto_link_urls($text) + { + // Find and replace all http/https/ftp/ftps links that are not part of an existing html anchor + $text = preg_replace_callback('~\b(?)(?:ht|f)tps?://\S+(?:/|\b)~i', 'Text::_auto_link_urls_callback1', $text); + + // Find and replace all naked www.links.com (without http://) + return preg_replace_callback('~\b(?)www(?:\.[a-z0-9][-a-z0-9]*+)+\.[a-z]{2,6}\b~i', 'Text::_auto_link_urls_callback2', $text); + } + + protected static function _auto_link_urls_callback1($matches) + { + return HTML::anchor($matches[0]); + } + + protected static function _auto_link_urls_callback2($matches) + { + return HTML::anchor('http://'.$matches[0], $matches[0]); + } + + /** + * Converts text email addresses into links. Existing links will not + * be altered. + * + * echo Text::auto_link_emails($text); + * + * [!!] This method is not foolproof since it uses regex to parse HTML. + * + * @param string text to auto link + * @return string + * @uses HTML::mailto + */ + public static function auto_link_emails($text) + { + // Find and replace all email addresses that are not part of an existing html mailto anchor + // Note: The "58;" negative lookbehind prevents matching of existing encoded html mailto anchors + // The html entity for a colon (:) is : or : or : etc. + return preg_replace_callback('~\b(?)~i', 'Text::_auto_link_emails_callback', $text); + } + + protected static function _auto_link_emails_callback($matches) + { + return HTML::mailto($matches[0]); + } + + /** + * Automatically applies "p" and "br" markup to text. + * Basically [nl2br](http://php.net/nl2br) on steroids. + * + * echo Text::auto_p($text); + * + * [!!] This method is not foolproof since it uses regex to parse HTML. + * + * @param string subject + * @param boolean convert single linebreaks to
                    + * @return string + */ + public static function auto_p($str, $br = TRUE) + { + // Trim whitespace + if (($str = trim($str)) === '') + return ''; + + // Standardize newlines + $str = str_replace(array("\r\n", "\r"), "\n", $str); + + // Trim whitespace on each line + $str = preg_replace('~^[ \t]+~m', '', $str); + $str = preg_replace('~[ \t]+$~m', '', $str); + + // The following regexes only need to be executed if the string contains html + if ($html_found = (strpos($str, '<') !== FALSE)) + { + // Elements that should not be surrounded by p tags + $no_p = '(?:p|div|h[1-6r]|ul|ol|li|blockquote|d[dlt]|pre|t[dhr]|t(?:able|body|foot|head)|c(?:aption|olgroup)|form|s(?:elect|tyle)|a(?:ddress|rea)|ma(?:p|th))'; + + // Put at least two linebreaks before and after $no_p elements + $str = preg_replace('~^<'.$no_p.'[^>]*+>~im', "\n$0", $str); + $str = preg_replace('~$~im', "$0\n", $str); + } + + // Do the

                    magic! + $str = '

                    '.trim($str).'

                    '; + $str = preg_replace('~\n{2,}~', "

                    \n\n

                    ", $str); + + // The following regexes only need to be executed if the string contains html + if ($html_found !== FALSE) + { + // Remove p tags around $no_p elements + $str = preg_replace('~

                    (?=]*+>)~i', '', $str); + $str = preg_replace('~(]*+>)

                    ~i', '$1', $str); + } + + // Convert single linebreaks to
                    + if ($br === TRUE) + { + $str = preg_replace('~(?\n", $str); + } + + return $str; + } + + /** + * Returns human readable sizes. Based on original functions written by + * [Aidan Lister](http://aidanlister.com/repos/v/function.size_readable.php) + * and [Quentin Zervaas](http://www.phpriot.com/d/code/strings/filesize-format/). + * + * echo Text::bytes(filesize($file)); + * + * @param integer size in bytes + * @param string a definitive unit + * @param string the return string format + * @param boolean whether to use SI prefixes or IEC + * @return string + */ + public static function bytes($bytes, $force_unit = NULL, $format = NULL, $si = TRUE) + { + // Format string + $format = ($format === NULL) ? '%01.2f %s' : (string) $format; + + // IEC prefixes (binary) + if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE) + { + $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'); + $mod = 1024; + } + // SI prefixes (decimal) + else + { + $units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB'); + $mod = 1000; + } + + // Determine unit to use + if (($power = array_search( (string) $force_unit, $units)) === FALSE) + { + $power = ($bytes > 0) ? floor(log($bytes, $mod)) : 0; + } + + return sprintf($format, $bytes / pow($mod, $power), $units[$power]); + } + + /** + * Format a number to human-readable text. + * + * // Display: one thousand and twenty-four + * echo Text::number(1024); + * + * // Display: five million, six hundred and thirty-two + * echo Text::number(5000632); + * + * @param integer number to format + * @return string + * @since 3.0.8 + */ + public static function number($number) + { + // The number must always be an integer + $number = (int) $number; + + // Uncompiled text version + $text = array(); + + // Last matched unit within the loop + $last_unit = NULL; + + // The last matched item within the loop + $last_item = ''; + + foreach (Text::$units as $unit => $name) + { + if ($number / $unit >= 1) + { + // $value = the number of times the number is divisble by unit + $number -= $unit * ($value = (int) floor($number / $unit)); + // Temporary var for textifying the current unit + $item = ''; + + if ($unit < 100) + { + if ($last_unit < 100 AND $last_unit >= 20) + { + $last_item .= '-'.$name; + } + else + { + $item = $name; + } + } + else + { + $item = Text::number($value).' '.$name; + } + + // In the situation that we need to make a composite number (i.e. twenty-three) + // then we need to modify the previous entry + if (empty($item)) + { + array_pop($text); + + $item = $last_item; + } + + $last_item = $text[] = $item; + $last_unit = $unit; + } + } + + if (count($text) > 1) + { + $and = array_pop($text); + } + + $text = implode(', ', $text); + + if (isset($and)) + { + $text .= ' and '.$and; + } + + return $text; + } + + /** + * Prevents [widow words](http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin) + * by inserting a non-breaking space between the last two words. + * + * echo Text::widont($text); + * + * @param string text to remove widows from + * @return string + */ + public static function widont($str) + { + $str = rtrim($str); + $space = strrpos($str, ' '); + + if ($space !== FALSE) + { + $str = substr($str, 0, $space).' '.substr($str, $space + 1); + } + + return $str; + } + +} // End text diff --git a/includes/kohana/system/classes/kohana/upload.php b/includes/kohana/system/classes/kohana/upload.php new file mode 100644 index 0000000..7f0821b --- /dev/null +++ b/includes/kohana/system/classes/kohana/upload.php @@ -0,0 +1,190 @@ +check()) + * { + * // Upload is valid, save it + * Upload::save($array['file']); + * } + * + * @param array uploaded file data + * @param string new filename + * @param string new directory + * @param integer chmod mask + * @return string on success, full path to new file + * @return FALSE on failure + */ + public static function save(array $file, $filename = NULL, $directory = NULL, $chmod = 0644) + { + if ( ! isset($file['tmp_name']) OR ! is_uploaded_file($file['tmp_name'])) + { + // Ignore corrupted uploads + return FALSE; + } + + if ($filename === NULL) + { + // Use the default filename, with a timestamp pre-pended + $filename = uniqid().$file['name']; + } + + if (Upload::$remove_spaces === TRUE) + { + // Remove spaces from the filename + $filename = preg_replace('/\s+/u', '_', $filename); + } + + if ($directory === NULL) + { + // Use the pre-configured upload directory + $directory = Upload::$default_directory; + } + + if ( ! is_dir($directory) OR ! is_writable(realpath($directory))) + { + throw new Kohana_Exception('Directory :dir must be writable', + array(':dir' => Debug::path($directory))); + } + + // Make the filename into a complete path + $filename = realpath($directory).DIRECTORY_SEPARATOR.$filename; + + if (move_uploaded_file($file['tmp_name'], $filename)) + { + if ($chmod !== FALSE) + { + // Set permissions on filename + chmod($filename, $chmod); + } + + // Return new file path + return $filename; + } + + return FALSE; + } + + /** + * Tests if upload data is valid, even if no file was uploaded. If you + * _do_ require a file to be uploaded, add the [Upload::not_empty] rule + * before this rule. + * + * $array->rule('file', 'Upload::valid') + * + * @param array $_FILES item + * @return bool + */ + public static function valid($file) + { + return (isset($file['error']) + AND isset($file['name']) + AND isset($file['type']) + AND isset($file['tmp_name']) + AND isset($file['size'])); + } + + /** + * Tests if a successful upload has been made. + * + * $array->rule('file', 'Upload::not_empty'); + * + * @param array $_FILES item + * @return bool + */ + public static function not_empty(array $file) + { + return (isset($file['error']) + AND isset($file['tmp_name']) + AND $file['error'] === UPLOAD_ERR_OK + AND is_uploaded_file($file['tmp_name'])); + } + + /** + * Test if an uploaded file is an allowed file type, by extension. + * + * $array->rule('file', 'Upload::type', array(':value', array('jpg', 'png', 'gif'))); + * + * @param array $_FILES item + * @param array allowed file extensions + * @return bool + */ + public static function type(array $file, array $allowed) + { + if ($file['error'] !== UPLOAD_ERR_OK) + return TRUE; + + $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + + return in_array($ext, $allowed); + } + + /** + * Validation rule to test if an uploaded file is allowed by file size. + * File sizes are defined as: SB, where S is the size (1, 8.5, 300, etc.) + * and B is the byte unit (K, MiB, GB, etc.). All valid byte units are + * defined in Num::$byte_units + * + * $array->rule('file', 'Upload::size', array(':value', '1M')) + * $array->rule('file', 'Upload::size', array(':value', '2.5KiB')) + * + * @param array $_FILES item + * @param string maximum file size allowed + * @return bool + */ + public static function size(array $file, $size) + { + if ($file['error'] === UPLOAD_ERR_INI_SIZE) + { + // Upload is larger than PHP allowed size (upload_max_filesize) + return FALSE; + } + + if ($file['error'] !== UPLOAD_ERR_OK) + { + // The upload failed, no size to check + return TRUE; + } + + // Convert the provided size to bytes for comparison + $size = Num::bytes($size); + + // Test that the file is under or equal to the max size + return ($file['size'] <= $size); + } + +} // End upload diff --git a/includes/kohana/system/classes/kohana/url.php b/includes/kohana/system/classes/kohana/url.php new file mode 100644 index 0000000..9c02643 --- /dev/null +++ b/includes/kohana/system/classes/kohana/url.php @@ -0,0 +1,194 @@ +protocol(); + } + + if ( ! $protocol) + { + // Use the configured default protocol + $protocol = parse_url($base_url, PHP_URL_SCHEME); + } + + if ($index === TRUE AND ! empty(Kohana::$index_file)) + { + // Add the index file to the URL + $base_url .= Kohana::$index_file.'/'; + } + + if (is_string($protocol)) + { + if ($port = parse_url($base_url, PHP_URL_PORT)) + { + // Found a port, make it usable for the URL + $port = ':'.$port; + } + + if ($domain = parse_url($base_url, PHP_URL_HOST)) + { + // Remove everything but the path from the URL + $base_url = parse_url($base_url, PHP_URL_PATH); + } + else + { + // Attempt to use HTTP_HOST and fallback to SERVER_NAME + $domain = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']; + } + + // Add the protocol and domain to the base URL + $base_url = $protocol.'://'.$domain.$port.$base_url; + } + + return $base_url; + } + + /** + * Fetches an absolute site URL based on a URI segment. + * + * echo URL::site('foo/bar'); + * + * @param string $uri Site URI to convert + * @param mixed $protocol Protocol string or [Request] class to use protocol from + * @param boolean $index Include the index_page in the URL + * @return string + * @uses URL::base + */ + public static function site($uri = '', $protocol = NULL, $index = TRUE) + { + // Chop off possible scheme, host, port, user and pass parts + $path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/')); + + if ( ! UTF8::is_ascii($path)) + { + // Encode all non-ASCII characters, as per RFC 1738 + $path = preg_replace('~([^/]+)~e', 'rawurlencode("$1")', $path); + } + + // Concat the URL + return URL::base($protocol, $index).$path; + } + + /** + * Merges the current GET parameters with an array of new or overloaded + * parameters and returns the resulting query string. + * + * // Returns "?sort=title&limit=10" combined with any existing GET values + * $query = URL::query(array('sort' => 'title', 'limit' => 10)); + * + * Typically you would use this when you are sorting query results, + * or something similar. + * + * [!!] Parameters with a NULL value are left out. + * + * @param array $params Array of GET parameters + * @param boolean $use_get Include current request GET parameters + * @return string + */ + public static function query(array $params = NULL, $use_get = TRUE) + { + if ($use_get) + { + if ($params === NULL) + { + // Use only the current parameters + $params = $_GET; + } + else + { + // Merge the current and new parameters + $params = array_merge($_GET, $params); + } + } + + if (empty($params)) + { + // No query parameters + return ''; + } + + // Note: http_build_query returns an empty string for a params array with only NULL values + $query = http_build_query($params, '', '&'); + + // Don't prepend '?' to an empty string + return ($query === '') ? '' : ('?'.$query); + } + + /** + * Convert a phrase to a URL-safe title. + * + * echo URL::title('My Blog Post'); // "my-blog-post" + * + * @param string $title Phrase to convert + * @param string $separator Word separator (any single character) + * @param boolean $ascii_only Transliterate to ASCII? + * @return string + * @uses UTF8::transliterate_to_ascii + */ + public static function title($title, $separator = '-', $ascii_only = FALSE) + { + if ($ascii_only === TRUE) + { + // Transliterate non-ASCII characters + $title = UTF8::transliterate_to_ascii($title); + + // Remove all characters that are not the separator, a-z, 0-9, or whitespace + $title = preg_replace('![^'.preg_quote($separator).'a-z0-9\s]+!', '', strtolower($title)); + } + else + { + // Remove all characters that are not the separator, letters, numbers, or whitespace + $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', UTF8::strtolower($title)); + } + + // Replace all separator characters and whitespace by a single separator + $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title); + + // Trim separators from the beginning and end + return trim($title, $separator); + } + +} // End url \ No newline at end of file diff --git a/includes/kohana/system/classes/kohana/utf8.php b/includes/kohana/system/classes/kohana/utf8.php new file mode 100644 index 0000000..fde08e0 --- /dev/null +++ b/includes/kohana/system/classes/kohana/utf8.php @@ -0,0 +1,767 @@ + $val) + { + // Recursion! + $var[self::clean($key)] = self::clean($val); + } + } + elseif (is_string($var) AND $var !== '') + { + // Remove control characters + $var = self::strip_ascii_ctrl($var); + + if ( ! self::is_ascii($var)) + { + // Disable notices + $error_reporting = error_reporting(~E_NOTICE); + + // iconv is expensive, so it is only used when needed + $var = iconv($charset, $charset.'//IGNORE', $var); + + // Turn notices back on + error_reporting($error_reporting); + } + } + + return $var; + } + + /** + * Tests whether a string contains only 7-bit ASCII bytes. This is used to + * determine when to use native functions or UTF-8 functions. + * + * $ascii = UTF8::is_ascii($str); + * + * @param mixed string or array of strings to check + * @return boolean + */ + public static function is_ascii($str) + { + if (is_array($str)) + { + $str = implode($str); + } + + return ! preg_match('/[^\x00-\x7F]/S', $str); + } + + /** + * Strips out device control codes in the ASCII range. + * + * $str = UTF8::strip_ascii_ctrl($str); + * + * @param string string to clean + * @return string + */ + public static function strip_ascii_ctrl($str) + { + return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str); + } + + /** + * Strips out all non-7bit ASCII bytes. + * + * $str = UTF8::strip_non_ascii($str); + * + * @param string string to clean + * @return string + */ + public static function strip_non_ascii($str) + { + return preg_replace('/[^\x00-\x7F]+/S', '', $str); + } + + /** + * Replaces special/accented UTF-8 characters by ASCII-7 "equivalents". + * + * $ascii = UTF8::transliterate_to_ascii($utf8); + * + * @author Andreas Gohr + * @param string string to transliterate + * @param integer -1 lowercase only, +1 uppercase only, 0 both cases + * @return string + */ + public static function transliterate_to_ascii($str, $case = 0) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _transliterate_to_ascii($str, $case); + } + + /** + * Returns the length of the given string. This is a UTF8-aware version + * of [strlen](http://php.net/strlen). + * + * $length = UTF8::strlen($str); + * + * @param string string being measured for length + * @return integer + * @uses UTF8::$server_utf8 + */ + public static function strlen($str) + { + if (UTF8::$server_utf8) + return mb_strlen($str, Kohana::$charset); + + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _strlen($str); + } + + /** + * Finds position of first occurrence of a UTF-8 string. This is a + * UTF8-aware version of [strpos](http://php.net/strpos). + * + * $position = UTF8::strpos($str, $search); + * + * @author Harry Fuecks + * @param string haystack + * @param string needle + * @param integer offset from which character in haystack to start searching + * @return integer position of needle + * @return boolean FALSE if the needle is not found + * @uses UTF8::$server_utf8 + */ + public static function strpos($str, $search, $offset = 0) + { + if (UTF8::$server_utf8) + return mb_strpos($str, $search, $offset, Kohana::$charset); + + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _strpos($str, $search, $offset); + } + + /** + * Finds position of last occurrence of a char in a UTF-8 string. This is + * a UTF8-aware version of [strrpos](http://php.net/strrpos). + * + * $position = UTF8::strrpos($str, $search); + * + * @author Harry Fuecks + * @param string haystack + * @param string needle + * @param integer offset from which character in haystack to start searching + * @return integer position of needle + * @return boolean FALSE if the needle is not found + * @uses UTF8::$server_utf8 + */ + public static function strrpos($str, $search, $offset = 0) + { + if (UTF8::$server_utf8) + return mb_strrpos($str, $search, $offset, Kohana::$charset); + + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _strrpos($str, $search, $offset); + } + + /** + * Returns part of a UTF-8 string. This is a UTF8-aware version + * of [substr](http://php.net/substr). + * + * $sub = UTF8::substr($str, $offset); + * + * @author Chris Smith + * @param string input string + * @param integer offset + * @param integer length limit + * @return string + * @uses UTF8::$server_utf8 + * @uses Kohana::$charset + */ + public static function substr($str, $offset, $length = NULL) + { + if (UTF8::$server_utf8) + return ($length === NULL) + ? mb_substr($str, $offset, mb_strlen($str), Kohana::$charset) + : mb_substr($str, $offset, $length, Kohana::$charset); + + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _substr($str, $offset, $length); + } + + /** + * Replaces text within a portion of a UTF-8 string. This is a UTF8-aware + * version of [substr_replace](http://php.net/substr_replace). + * + * $str = UTF8::substr_replace($str, $replacement, $offset); + * + * @author Harry Fuecks + * @param string input string + * @param string replacement string + * @param integer offset + * @return string + */ + public static function substr_replace($str, $replacement, $offset, $length = NULL) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _substr_replace($str, $replacement, $offset, $length); + } + + /** + * Makes a UTF-8 string lowercase. This is a UTF8-aware version + * of [strtolower](http://php.net/strtolower). + * + * $str = UTF8::strtolower($str); + * + * @author Andreas Gohr + * @param string mixed case string + * @return string + * @uses UTF8::$server_utf8 + */ + public static function strtolower($str) + { + if (UTF8::$server_utf8) + return mb_strtolower($str, Kohana::$charset); + + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _strtolower($str); + } + + /** + * Makes a UTF-8 string uppercase. This is a UTF8-aware version + * of [strtoupper](http://php.net/strtoupper). + * + * @author Andreas Gohr + * @param string mixed case string + * @return string + * @uses UTF8::$server_utf8 + * @uses Kohana::$charset + */ + public static function strtoupper($str) + { + if (UTF8::$server_utf8) + return mb_strtoupper($str, Kohana::$charset); + + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _strtoupper($str); + } + + /** + * Makes a UTF-8 string's first character uppercase. This is a UTF8-aware + * version of [ucfirst](http://php.net/ucfirst). + * + * $str = UTF8::ucfirst($str); + * + * @author Harry Fuecks + * @param string mixed case string + * @return string + */ + public static function ucfirst($str) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _ucfirst($str); + } + + /** + * Makes the first character of every word in a UTF-8 string uppercase. + * This is a UTF8-aware version of [ucwords](http://php.net/ucwords). + * + * $str = UTF8::ucwords($str); + * + * @author Harry Fuecks + * @param string mixed case string + * @return string + * @uses UTF8::$server_utf8 + */ + public static function ucwords($str) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _ucwords($str); + } + + /** + * Case-insensitive UTF-8 string comparison. This is a UTF8-aware version + * of [strcasecmp](http://php.net/strcasecmp). + * + * $compare = UTF8::strcasecmp($str1, $str2); + * + * @author Harry Fuecks + * @param string string to compare + * @param string string to compare + * @return integer less than 0 if str1 is less than str2 + * @return integer greater than 0 if str1 is greater than str2 + * @return integer 0 if they are equal + */ + public static function strcasecmp($str1, $str2) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _strcasecmp($str1, $str2); + } + + /** + * Returns a string or an array with all occurrences of search in subject + * (ignoring case) and replaced with the given replace value. This is a + * UTF8-aware version of [str_ireplace](http://php.net/str_ireplace). + * + * [!!] This function is very slow compared to the native version. Avoid + * using it when possible. + * + * @author Harry Fuecks + * @param string input string + * @param string needle + * @return string matched substring if found + * @return FALSE if the substring was not found + */ + public static function stristr($str, $search) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _stristr($str, $search); + } + + /** + * Finds the length of the initial segment matching mask. This is a + * UTF8-aware version of [strspn](http://php.net/strspn). + * + * $found = UTF8::strspn($str, $mask); + * + * @author Harry Fuecks + * @param string input string + * @param string mask for search + * @param integer start position of the string to examine + * @param integer length of the string to examine + * @return integer length of the initial segment that contains characters in the mask + */ + public static function strspn($str, $mask, $offset = NULL, $length = NULL) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _strspn($str, $mask, $offset, $length); + } + + /** + * Finds the length of the initial segment not matching mask. This is a + * UTF8-aware version of [strcspn](http://php.net/strcspn). + * + * $found = UTF8::strcspn($str, $mask); + * + * @author Harry Fuecks + * @param string input string + * @param string mask for search + * @param integer start position of the string to examine + * @param integer length of the string to examine + * @return integer length of the initial segment that contains characters not in the mask + */ + public static function strcspn($str, $mask, $offset = NULL, $length = NULL) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _strcspn($str, $mask, $offset, $length); + } + + /** + * Pads a UTF-8 string to a certain length with another string. This is a + * UTF8-aware version of [str_pad](http://php.net/str_pad). + * + * $str = UTF8::str_pad($str, $length); + * + * @author Harry Fuecks + * @param string input string + * @param integer desired string length after padding + * @param string string to use as padding + * @param string padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH + * @return string + */ + public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _str_pad($str, $final_str_length, $pad_str, $pad_type); + } + + /** + * Converts a UTF-8 string to an array. This is a UTF8-aware version of + * [str_split](http://php.net/str_split). + * + * $array = UTF8::str_split($str); + * + * @author Harry Fuecks + * @param string input string + * @param integer maximum length of each chunk + * @return array + */ + public static function str_split($str, $split_length = 1) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _str_split($str, $split_length); + } + + /** + * Reverses a UTF-8 string. This is a UTF8-aware version of [strrev](http://php.net/strrev). + * + * $str = UTF8::strrev($str); + * + * @author Harry Fuecks + * @param string string to be reversed + * @return string + */ + public static function strrev($str) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _strrev($str); + } + + /** + * Strips whitespace (or other UTF-8 characters) from the beginning and + * end of a string. This is a UTF8-aware version of [trim](http://php.net/trim). + * + * $str = UTF8::trim($str); + * + * @author Andreas Gohr + * @param string input string + * @param string string of characters to remove + * @return string + */ + public static function trim($str, $charlist = NULL) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _trim($str, $charlist); + } + + /** + * Strips whitespace (or other UTF-8 characters) from the beginning of + * a string. This is a UTF8-aware version of [ltrim](http://php.net/ltrim). + * + * $str = UTF8::ltrim($str); + * + * @author Andreas Gohr + * @param string input string + * @param string string of characters to remove + * @return string + */ + public static function ltrim($str, $charlist = NULL) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _ltrim($str, $charlist); + } + + /** + * Strips whitespace (or other UTF-8 characters) from the end of a string. + * This is a UTF8-aware version of [rtrim](http://php.net/rtrim). + * + * $str = UTF8::rtrim($str); + * + * @author Andreas Gohr + * @param string input string + * @param string string of characters to remove + * @return string + */ + public static function rtrim($str, $charlist = NULL) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _rtrim($str, $charlist); + } + + /** + * Returns the unicode ordinal for a character. This is a UTF8-aware + * version of [ord](http://php.net/ord). + * + * $digit = UTF8::ord($character); + * + * @author Harry Fuecks + * @param string UTF-8 encoded character + * @return integer + */ + public static function ord($chr) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _ord($chr); + } + + /** + * Takes an UTF-8 string and returns an array of ints representing the Unicode characters. + * Astral planes are supported i.e. the ints in the output can be > 0xFFFF. + * Occurrences of the BOM are ignored. Surrogates are not allowed. + * + * $array = UTF8::to_unicode($str); + * + * The Original Code is Mozilla Communicator client code. + * The Initial Developer of the Original Code is Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer. + * Ported to PHP by Henri Sivonen , see + * Slight modifications to fit with phputf8 library by Harry Fuecks + * + * @param string UTF-8 encoded string + * @return array unicode code points + * @return FALSE if the string is invalid + */ + public static function to_unicode($str) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _to_unicode($str); + } + + /** + * Takes an array of ints representing the Unicode characters and returns a UTF-8 string. + * Astral planes are supported i.e. the ints in the input can be > 0xFFFF. + * Occurrances of the BOM are ignored. Surrogates are not allowed. + * + * $str = UTF8::to_unicode($array); + * + * The Original Code is Mozilla Communicator client code. + * The Initial Developer of the Original Code is Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer. + * Ported to PHP by Henri Sivonen , see http://hsivonen.iki.fi/php-utf8/ + * Slight modifications to fit with phputf8 library by Harry Fuecks . + * + * @param array unicode code points representing a string + * @return string utf8 string of characters + * @return boolean FALSE if a code point cannot be found + */ + public static function from_unicode($arr) + { + if ( ! isset(self::$called[__FUNCTION__])) + { + require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT; + + // Function has been called + self::$called[__FUNCTION__] = TRUE; + } + + return _from_unicode($arr); + } + +} // End UTF8 + +if (Kohana_UTF8::$server_utf8 === NULL) +{ + // Determine if this server supports UTF-8 natively + Kohana_UTF8::$server_utf8 = extension_loaded('mbstring'); +} diff --git a/includes/kohana/system/classes/kohana/valid.php b/includes/kohana/system/classes/kohana/valid.php new file mode 100644 index 0000000..a2d0a7e --- /dev/null +++ b/includes/kohana/system/classes/kohana/valid.php @@ -0,0 +1,518 @@ +getArrayCopy(); + } + + // Value cannot be NULL, FALSE, '', or an empty array + return ! in_array($value, array(NULL, FALSE, '', array()), TRUE); + } + + /** + * Checks a field against a regular expression. + * + * @param string value + * @param string regular expression to match (including delimiters) + * @return boolean + */ + public static function regex($value, $expression) + { + return (bool) preg_match($expression, (string) $value); + } + + /** + * Checks that a field is long enough. + * + * @param string value + * @param integer minimum length required + * @return boolean + */ + public static function min_length($value, $length) + { + return UTF8::strlen($value) >= $length; + } + + /** + * Checks that a field is short enough. + * + * @param string value + * @param integer maximum length required + * @return boolean + */ + public static function max_length($value, $length) + { + return UTF8::strlen($value) <= $length; + } + + /** + * Checks that a field is exactly the right length. + * + * @param string value + * @param integer exact length required + * @return boolean + */ + public static function exact_length($value, $length) + { + return UTF8::strlen($value) === $length; + } + + /** + * Checks that a field is exactly the value required. + * + * @param string value + * @param string required value + * @return boolean + */ + public static function equals($value, $required) + { + return ($value === $required); + } + + /** + * Check an email address for correct format. + * + * @link http://www.iamcal.com/publish/articles/php/parsing_email/ + * @link http://www.w3.org/Protocols/rfc822/ + * + * @param string email address + * @param boolean strict RFC compatibility + * @return boolean + */ + public static function email($email, $strict = FALSE) + { + if ($strict === TRUE) + { + $qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'; + $dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]'; + $atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+'; + $pair = '\\x5c[\\x00-\\x7f]'; + + $domain_literal = "\\x5b($dtext|$pair)*\\x5d"; + $quoted_string = "\\x22($qtext|$pair)*\\x22"; + $sub_domain = "($atom|$domain_literal)"; + $word = "($atom|$quoted_string)"; + $domain = "$sub_domain(\\x2e$sub_domain)*"; + $local_part = "$word(\\x2e$word)*"; + + $expression = "/^$local_part\\x40$domain$/D"; + } + else + { + $expression = '/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(? 253) + return FALSE; + + // An extra check for the top level domain + // It must start with a letter + $tld = ltrim(substr($matches[1], (int) strrpos($matches[1], '.')), '.'); + return ctype_alpha($tld[0]); + } + + /** + * Validate an IP. + * + * @param string IP address + * @param boolean allow private IP networks + * @return boolean + */ + public static function ip($ip, $allow_private = TRUE) + { + // Do not allow reserved addresses + $flags = FILTER_FLAG_NO_RES_RANGE; + + if ($allow_private === FALSE) + { + // Do not allow private or reserved addresses + $flags = $flags | FILTER_FLAG_NO_PRIV_RANGE; + } + + return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags); + } + + /** + * Validates a credit card number, with a Luhn check if possible. + * + * @param integer credit card number + * @param string|array card type, or an array of card types + * @return boolean + * @uses Validate::luhn + */ + public static function credit_card($number, $type = NULL) + { + // Remove all non-digit characters from the number + if (($number = preg_replace('/\D+/', '', $number)) === '') + return FALSE; + + if ($type == NULL) + { + // Use the default type + $type = 'default'; + } + elseif (is_array($type)) + { + foreach ($type as $t) + { + // Test each type for validity + if (Valid::credit_card($number, $t)) + return TRUE; + } + + return FALSE; + } + + $cards = Kohana::config('credit_cards'); + + // Check card type + $type = strtolower($type); + + if ( ! isset($cards[$type])) + return FALSE; + + // Check card number length + $length = strlen($number); + + // Validate the card length by the card type + if ( ! in_array($length, preg_split('/\D+/', $cards[$type]['length']))) + return FALSE; + + // Check card number prefix + if ( ! preg_match('/^'.$cards[$type]['prefix'].'/', $number)) + return FALSE; + + // No Luhn check required + if ($cards[$type]['luhn'] == FALSE) + return TRUE; + + return Valid::luhn($number); + } + + /** + * Validate a number against the [Luhn](http://en.wikipedia.org/wiki/Luhn_algorithm) + * (mod10) formula. + * + * @param string number to check + * @return boolean + */ + public static function luhn($number) + { + // Force the value to be a string as this method uses string functions. + // Converting to an integer may pass PHP_INT_MAX and result in an error! + $number = (string) $number; + + if ( ! ctype_digit($number)) + { + // Luhn can only be used on numbers! + return FALSE; + } + + // Check number length + $length = strlen($number); + + // Checksum of the card number + $checksum = 0; + + for ($i = $length - 1; $i >= 0; $i -= 2) + { + // Add up every 2nd digit, starting from the right + $checksum += substr($number, $i, 1); + } + + for ($i = $length - 2; $i >= 0; $i -= 2) + { + // Add up every 2nd digit doubled, starting from the right + $double = substr($number, $i, 1) * 2; + + // Subtract 9 from the double where value is greater than 10 + $checksum += ($double >= 10) ? ($double - 9) : $double; + } + + // If the checksum is a multiple of 10, the number is valid + return ($checksum % 10 === 0); + } + + /** + * Checks if a phone number is valid. + * + * @param string phone number to check + * @return boolean + */ + public static function phone($number, $lengths = NULL) + { + if ( ! is_array($lengths)) + { + $lengths = array(7,10,11); + } + + // Remove all non-digit characters from the number + $number = preg_replace('/\D+/', '', $number); + + // Check if the number is within range + return in_array(strlen($number), $lengths); + } + + /** + * Tests if a string is a valid date string. + * + * @param string date to check + * @return boolean + */ + public static function date($str) + { + return (strtotime($str) !== FALSE); + } + + /** + * Checks whether a string consists of alphabetical characters only. + * + * @param string input string + * @param boolean trigger UTF-8 compatibility + * @return boolean + */ + public static function alpha($str, $utf8 = FALSE) + { + $str = (string) $str; + + if ($utf8 === TRUE) + { + return (bool) preg_match('/^\pL++$/uD', $str); + } + else + { + return ctype_alpha($str); + } + } + + /** + * Checks whether a string consists of alphabetical characters and numbers only. + * + * @param string input string + * @param boolean trigger UTF-8 compatibility + * @return boolean + */ + public static function alpha_numeric($str, $utf8 = FALSE) + { + if ($utf8 === TRUE) + { + return (bool) preg_match('/^[\pL\pN]++$/uD', $str); + } + else + { + return ctype_alnum($str); + } + } + + /** + * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only. + * + * @param string input string + * @param boolean trigger UTF-8 compatibility + * @return boolean + */ + public static function alpha_dash($str, $utf8 = FALSE) + { + if ($utf8 === TRUE) + { + $regex = '/^[-\pL\pN_]++$/uD'; + } + else + { + $regex = '/^[-a-z0-9_]++$/iD'; + } + + return (bool) preg_match($regex, $str); + } + + /** + * Checks whether a string consists of digits only (no dots or dashes). + * + * @param string input string + * @param boolean trigger UTF-8 compatibility + * @return boolean + */ + public static function digit($str, $utf8 = FALSE) + { + if ($utf8 === TRUE) + { + return (bool) preg_match('/^\pN++$/uD', $str); + } + else + { + return (is_int($str) AND $str >= 0) OR ctype_digit($str); + } + } + + /** + * Checks whether a string is a valid number (negative and decimal numbers allowed). + * + * Uses {@link http://www.php.net/manual/en/function.localeconv.php locale conversion} + * to allow decimal point to be locale specific. + * + * @param string input string + * @return boolean + */ + public static function numeric($str) + { + // Get the decimal point for the current locale + list($decimal) = array_values(localeconv()); + + // A lookahead is used to make sure the string contains at least one digit (before or after the decimal point) + return (bool) preg_match('/^-?+(?=.*[0-9])[0-9]*+'.preg_quote($decimal).'?+[0-9]*+$/D', (string) $str); + } + + /** + * Tests if a number is within a range. + * + * @param string number to check + * @param integer minimum value + * @param integer maximum value + * @return boolean + */ + public static function range($number, $min, $max) + { + return ($number >= $min AND $number <= $max); + } + + /** + * Checks if a string is a proper decimal format. Optionally, a specific + * number of digits can be checked too. + * + * @param string number to check + * @param integer number of decimal places + * @param integer number of digits + * @return boolean + */ + public static function decimal($str, $places = 2, $digits = NULL) + { + if ($digits > 0) + { + // Specific number of digits + $digits = '{'.( (int) $digits).'}'; + } + else + { + // Any number of digits + $digits = '+'; + } + + // Get the decimal point for the current locale + list($decimal) = array_values(localeconv()); + + return (bool) preg_match('/^[0-9]'.$digits.preg_quote($decimal).'[0-9]{'.( (int) $places).'}$/D', $str); + } + + /** + * Checks if a string is a proper hexadecimal HTML color value. The validation + * is quite flexible as it does not require an initial "#" and also allows for + * the short notation using only three instead of six hexadecimal characters. + * + * @param string input string + * @return boolean + */ + public static function color($str) + { + return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str); + } + + /** + * Checks if a field matches the value of another field. + * + * @param array array of values + * @param string field name + * @param string field name to match + * @return boolean + */ + public static function matches($array, $field, $match) + { + return ($array[$field] === $array[$match]); + } + +} // End Valid diff --git a/includes/kohana/system/classes/kohana/validation.php b/includes/kohana/system/classes/kohana/validation.php new file mode 100644 index 0000000..5756659 --- /dev/null +++ b/includes/kohana/system/classes/kohana/validation.php @@ -0,0 +1,518 @@ + rule + protected $_errors = array(); + + /** + * Sets the unique "any field" key and creates an ArrayObject from the + * passed array. + * + * @param array array to validate + * @return void + */ + public function __construct(array $array) + { + parent::__construct($array, ArrayObject::STD_PROP_LIST); + } + + /** + * Copies the current rule to a new array. + * + * $copy = $array->copy($new_data); + * + * @param array new data set + * @return Validation + * @since 3.0.5 + */ + public function copy(array $array) + { + // Create a copy of the current validation set + $copy = clone $this; + + // Replace the data set + $copy->exchangeArray($array); + + return $copy; + } + + /** + * Returns the array representation of the current object. + * + * @return array + */ + public function as_array() + { + return $this->getArrayCopy(); + } + + /** + * Sets or overwrites the label name for a field. + * + * @param string field name + * @param string label + * @return $this + */ + public function label($field, $label) + { + // Set the label for this field + $this->_labels[$field] = $label; + + return $this; + } + + /** + * Sets labels using an array. + * + * @param array list of field => label names + * @return $this + */ + public function labels(array $labels) + { + $this->_labels = $labels + $this->_labels; + + return $this; + } + + /** + * Overwrites or appends rules to a field. Each rule will be executed once. + * All rules must be string names of functions method names. Parameters must + * match the parameters of the callback function exactly + * + * Aliases you can use in callback parameters: + * - :validation - the validation object + * - :field - the field name + * - :value - the value of the field + * + * // The "username" must not be empty and have a minimum length of 4 + * $validation->rule('username', 'not_empty') + * ->rule('username', 'min_length', array('username', 4)); + * + * // The "password" field must match the "password_repeat" field + * $validation->rule('password', 'matches', array(':validation', 'password', 'password_repeat')); + * + * @param string field name + * @param callback valid PHP callback + * @param array extra parameters for the rule + * @return $this + */ + public function rule($field, $rule, array $params = NULL) + { + if ($params === NULL) + { + // Default to array(':value') + $params = array(':value'); + } + + if ($field !== TRUE AND ! isset($this->_labels[$field])) + { + // Set the field label to the field name + $this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field); + } + + // Store the rule and params for this rule + $this->_rules[$field][] = array($rule, $params); + + return $this; + } + + /** + * Add rules using an array. + * + * @param string field name + * @param array list of callbacks + * @return $this + */ + public function rules($field, array $rules) + { + foreach ($rules as $rule) + { + $this->rule($field, $rule[0], Arr::get($rule, 1)); + } + + return $this; + } + + /** + * Bind a value to a parameter definition. + * + * // This allows you to use :model in the parameter definition of rules + * $validation->bind(':model', $model) + * ->rule('status', 'valid_status', array(':model')); + * + * @param string variable name or an array of variables + * @param mixed value + * @return $this + */ + public function bind($key, $value = NULL) + { + if (is_array($key)) + { + foreach ($key as $name => $value) + { + $this->_bound[$name] = $value; + } + } + else + { + $this->_bound[$key] = $value; + } + + return $this; + } + + /** + * Executes all validation rules. This should + * typically be called within an if/else block. + * + * if ($validation->check()) + * { + * // The data is valid, do something here + * } + * + * @param boolean allow empty array? + * @return boolean + */ + public function check() + { + if (Kohana::$profiling === TRUE) + { + // Start a new benchmark + $benchmark = Profiler::start('Validation', __FUNCTION__); + } + + // New data set + $data = $this->_errors = array(); + + // Store the original data because this class should not modify it post-validation + $original = $this->getArrayCopy(); + + // Get a list of the expected fields + $expected = Arr::merge(array_keys($original), array_keys($this->_labels)); + + // Import the rules locally + $rules = $this->_rules; + + foreach ($expected as $field) + { + // Use the submitted value or NULL if no data exists + $data[$field] = Arr::get($this, $field); + + if (isset($rules[TRUE])) + { + if ( ! isset($rules[$field])) + { + // Initialize the rules for this field + $rules[$field] = array(); + } + + // Append the rules + $rules[$field] = array_merge($rules[$field], $rules[TRUE]); + } + } + + // Overload the current array with the new one + $this->exchangeArray($data); + + // Remove the rules that apply to every field + unset($rules[TRUE]); + + // Bind the validation object to :validation + $this->bind(':validation', $this); + + // Execute the rules + foreach ($rules as $field => $set) + { + // Get the field value + $value = $this[$field]; + + // Bind the field name and value to :field and :value respectively + $this->bind(array + ( + ':field' => $field, + ':value' => $value, + )); + + foreach ($set as $array) + { + // Rules are defined as array($rule, $params) + list($rule, $params) = $array; + + foreach ($params as $key => $param) + { + if (is_string($param) AND array_key_exists($param, $this->_bound)) + { + // Replace with bound value + $params[$key] = $this->_bound[$param]; + } + } + + // Default the error name to be the rule (except array and lambda rules) + $error_name = $rule; + + if (is_array($rule)) + { + // This is an array callback, the method name is the error name + $error_name = $rule[1]; + $passed = call_user_func_array($rule, $params); + } + elseif ( ! is_string($rule)) + { + // This is a lambda function, there is no error name (errors must be added manually) + $error_name = FALSE; + $passed = call_user_func_array($rule, $params); + } + elseif (method_exists('Valid', $rule)) + { + // Use a method in this object + $method = new ReflectionMethod('Valid', $rule); + + // Call static::$rule($this[$field], $param, ...) with Reflection + $passed = $method->invokeArgs(NULL, $params); + } + elseif (strpos($rule, '::') === FALSE) + { + // Use a function call + $function = new ReflectionFunction($rule); + + // Call $function($this[$field], $param, ...) with Reflection + $passed = $function->invokeArgs($params); + } + else + { + // Split the class and method of the rule + list($class, $method) = explode('::', $rule, 2); + + // Use a static method call + $method = new ReflectionMethod($class, $method); + + // Call $Class::$method($this[$field], $param, ...) with Reflection + $passed = $method->invokeArgs(NULL, $params); + } + + // Ignore return values from rules when the field is empty + if ( ! in_array($rule, $this->_empty_rules) AND ! Valid::not_empty($value)) + continue; + + if ($passed === FALSE AND $error_name !== FALSE) + { + // Add the rule to the errors + $this->error($field, $error_name, $params); + + // This field has an error, stop executing rules + break; + } + } + } + + // Restore the data to its original form + $this->exchangeArray($original); + + if (isset($benchmark)) + { + // Stop benchmarking + Profiler::stop($benchmark); + } + + return empty($this->_errors); + } + + /** + * Add an error to a field. + * + * @param string field name + * @param string error message + * @return $this + */ + public function error($field, $error, array $params = NULL) + { + $this->_errors[$field] = array($error, $params); + + return $this; + } + + /** + * Returns the error messages. If no file is specified, the error message + * will be the name of the rule that failed. When a file is specified, the + * message will be loaded from "field/rule", or if no rule-specific message + * exists, "field/default" will be used. If neither is set, the returned + * message will be "file/field/rule". + * + * By default all messages are translated using the default language. + * A string can be used as the second parameter to specified the language + * that the message was written in. + * + * // Get errors from messages/forms/login.php + * $errors = $Validation->errors('forms/login'); + * + * @uses Kohana::message + * @param string file to load error messages from + * @param mixed translate the message + * @return array + */ + public function errors($file = NULL, $translate = TRUE) + { + if ($file === NULL) + { + // Return the error list + return $this->_errors; + } + + // Create a new message list + $messages = array(); + + foreach ($this->_errors as $field => $set) + { + list($error, $params) = $set; + + // Get the label for this field + $label = $this->_labels[$field]; + + if ($translate) + { + if (is_string($translate)) + { + // Translate the label using the specified language + $label = __($label, NULL, $translate); + } + else + { + // Translate the label + $label = __($label); + } + } + + // Start the translation values list + $values = array( + ':field' => $label, + ':value' => Arr::get($this, $field), + ); + + if (is_array($values[':value'])) + { + // All values must be strings + $values[':value'] = implode(', ', Arr::flatten($values[':value'])); + } + + if ($params) + { + foreach ($params as $key => $value) + { + if (is_array($value)) + { + // All values must be strings + $value = implode(', ', Arr::flatten($value)); + } + elseif (is_object($value)) + { + // Objects cannot be used in message files + continue; + } + + // Check if a label for this parameter exists + if (isset($this->_labels[$value])) + { + // Use the label as the value, eg: related field name for "matches" + $value = $this->_labels[$value]; + + if ($translate) + { + if (is_string($translate)) + { + // Translate the value using the specified language + $value = __($value, NULL, $translate); + } + else + { + // Translate the value + $value = __($value); + } + } + } + + // Add each parameter as a numbered value, starting from 1 + $values[':param'.($key + 1)] = $value; + } + } + + if ($message = Kohana::message($file, "{$field}.{$error}")) + { + // Found a message for this field and error + } + elseif ($message = Kohana::message($file, "{$field}.default")) + { + // Found a default message for this field + } + elseif ($message = Kohana::message($file, $error)) + { + // Found a default message for this error + } + elseif ($message = Kohana::message('validation', $error)) + { + // Found a default message for this error + } + else + { + // No message exists, display the path expected + $message = "{$file}.{$field}.{$error}"; + } + + if ($translate) + { + if (is_string($translate)) + { + // Translate the message using specified language + $message = __($message, $values, $translate); + } + else + { + // Translate the message using the default language + $message = __($message, $values); + } + } + else + { + // Do not translate, just replace the values + $message = strtr($message, $values); + } + + // Set the message for this field + $messages[$field] = $message; + } + + return $messages; + } + +} // End Validation diff --git a/includes/kohana/system/classes/kohana/validation/exception.php b/includes/kohana/system/classes/kohana/validation/exception.php new file mode 100644 index 0000000..db12404 --- /dev/null +++ b/includes/kohana/system/classes/kohana/validation/exception.php @@ -0,0 +1,29 @@ +array = $array; + + parent::__construct($message, $values, $code); + } + +} // End Kohana_Validation_Exception diff --git a/includes/kohana/system/classes/kohana/view.php b/includes/kohana/system/classes/kohana/view.php new file mode 100644 index 0000000..086f440 --- /dev/null +++ b/includes/kohana/system/classes/kohana/view.php @@ -0,0 +1,346 @@ + $value) + { + View::$_global_data[$key2] = $value; + } + } + else + { + View::$_global_data[$key] = $value; + } + } + + /** + * Assigns a global variable by reference, similar to [View::bind], except + * that the variable will be accessible to all views. + * + * View::bind_global($key, $value); + * + * @param string variable name + * @param mixed referenced variable + * @return void + */ + public static function bind_global($key, & $value) + { + View::$_global_data[$key] =& $value; + } + + // View filename + protected $_file; + + // Array of local variables + protected $_data = array(); + + /** + * Sets the initial view filename and local data. Views should almost + * always only be created using [View::factory]. + * + * $view = new View($file); + * + * @param string view filename + * @param array array of values + * @return void + * @uses View::set_filename + */ + public function __construct($file = NULL, array $data = NULL) + { + if ($file !== NULL) + { + $this->set_filename($file); + } + + if ($data !== NULL) + { + // Add the values to the current data + $this->_data = $data + $this->_data; + } + } + + /** + * Magic method, searches for the given variable and returns its value. + * Local variables will be returned before global variables. + * + * $value = $view->foo; + * + * [!!] If the variable has not yet been set, an exception will be thrown. + * + * @param string variable name + * @return mixed + * @throws Kohana_Exception + */ + public function & __get($key) + { + if (array_key_exists($key, $this->_data)) + { + return $this->_data[$key]; + } + elseif (array_key_exists($key, View::$_global_data)) + { + return View::$_global_data[$key]; + } + else + { + throw new Kohana_Exception('View variable is not set: :var', + array(':var' => $key)); + } + } + + /** + * Magic method, calls [View::set] with the same parameters. + * + * $view->foo = 'something'; + * + * @param string variable name + * @param mixed value + * @return void + */ + public function __set($key, $value) + { + $this->set($key, $value); + } + + /** + * Magic method, determines if a variable is set. + * + * isset($view->foo); + * + * [!!] `NULL` variables are not considered to be set by [isset](http://php.net/isset). + * + * @param string variable name + * @return boolean + */ + public function __isset($key) + { + return (isset($this->_data[$key]) OR isset(View::$_global_data[$key])); + } + + /** + * Magic method, unsets a given variable. + * + * unset($view->foo); + * + * @param string variable name + * @return void + */ + public function __unset($key) + { + unset($this->_data[$key], View::$_global_data[$key]); + } + + /** + * Magic method, returns the output of [View::render]. + * + * @return string + * @uses View::render + */ + public function __toString() + { + try + { + return $this->render(); + } + catch (Exception $e) + { + // Display the exception message + Kohana_Exception::handler($e); + + return ''; + } + } + + /** + * Sets the view filename. + * + * $view->set_filename($file); + * + * @param string view filename + * @return View + * @throws Kohana_View_Exception + */ + public function set_filename($file) + { + if (($path = Kohana::find_file('views', $file)) === FALSE) + { + throw new Kohana_View_Exception('The requested view :file could not be found', array( + ':file' => $file, + )); + } + + // Store the file path locally + $this->_file = $path; + + return $this; + } + + /** + * Assigns a variable by name. Assigned values will be available as a + * variable within the view file: + * + * // This value can be accessed as $foo within the view + * $view->set('foo', 'my value'); + * + * You can also use an array to set several values at once: + * + * // Create the values $food and $beverage in the view + * $view->set(array('food' => 'bread', 'beverage' => 'water')); + * + * @param string variable name or an array of variables + * @param mixed value + * @return $this + */ + public function set($key, $value = NULL) + { + if (is_array($key)) + { + foreach ($key as $name => $value) + { + $this->_data[$name] = $value; + } + } + else + { + $this->_data[$key] = $value; + } + + return $this; + } + + /** + * Assigns a value by reference. The benefit of binding is that values can + * be altered without re-setting them. It is also possible to bind variables + * before they have values. Assigned values will be available as a + * variable within the view file: + * + * // This reference can be accessed as $ref within the view + * $view->bind('ref', $bar); + * + * @param string variable name + * @param mixed referenced variable + * @return $this + */ + public function bind($key, & $value) + { + $this->_data[$key] =& $value; + + return $this; + } + + /** + * Renders the view object to a string. Global and local data are merged + * and extracted to create local variables within the view file. + * + * $output = $view->render(); + * + * [!!] Global variables with the same key name as local variables will be + * overwritten by the local variable. + * + * @param string view filename + * @return string + * @throws Kohana_View_Exception + * @uses View::capture + */ + public function render($file = NULL) + { + if ($file !== NULL) + { + $this->set_filename($file); + } + + if (empty($this->_file)) + { + throw new Kohana_View_Exception('You must set the file to use within your view before rendering'); + } + + // Combine local and global data and capture the output + return View::capture($this->_file, $this->_data); + } + +} // End View diff --git a/includes/kohana/system/classes/kohana/view/exception.php b/includes/kohana/system/classes/kohana/view/exception.php new file mode 100644 index 0000000..77200c0 --- /dev/null +++ b/includes/kohana/system/classes/kohana/view/exception.php @@ -0,0 +1,9 @@ + array( + 'length' => '13,14,15,16,17,18,19', + 'prefix' => '', + 'luhn' => TRUE, + ), + + 'american express' => array( + 'length' => '15', + 'prefix' => '3[47]', + 'luhn' => TRUE, + ), + + 'diners club' => array( + 'length' => '14,16', + 'prefix' => '36|55|30[0-5]', + 'luhn' => TRUE, + ), + + 'discover' => array( + 'length' => '16', + 'prefix' => '6(?:5|011)', + 'luhn' => TRUE, + ), + + 'jcb' => array( + 'length' => '15,16', + 'prefix' => '3|1800|2131', + 'luhn' => TRUE, + ), + + 'maestro' => array( + 'length' => '16,18', + 'prefix' => '50(?:20|38)|6(?:304|759)', + 'luhn' => TRUE, + ), + + 'mastercard' => array( + 'length' => '16', + 'prefix' => '5[1-5]', + 'luhn' => TRUE, + ), + + 'visa' => array( + 'length' => '13,16', + 'prefix' => '4', + 'luhn' => TRUE, + ), + +); \ No newline at end of file diff --git a/includes/kohana/system/config/curl.php b/includes/kohana/system/config/curl.php new file mode 100644 index 0000000..8bb3638 --- /dev/null +++ b/includes/kohana/system/config/curl.php @@ -0,0 +1,9 @@ + 'Mozilla/5.0 (compatible; Kohana v'.Kohana::VERSION.' +http://kohanaframework.org/)', + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_TIMEOUT => 5, + CURLOPT_HEADERFUNCTION => array('Request_Client_External', '_parse_headers'), + CURLOPT_HEADER => FALSE, +); \ No newline at end of file diff --git a/includes/kohana/system/config/encrypt.php b/includes/kohana/system/config/encrypt.php new file mode 100644 index 0000000..ae9126a --- /dev/null +++ b/includes/kohana/system/config/encrypt.php @@ -0,0 +1,17 @@ + array( + /** + * The following options must be set: + * + * string key secret passphrase + * integer mode encryption mode, one of MCRYPT_MODE_* + * integer cipher encryption cipher, one of the Mcrpyt cipher constants + */ + 'cipher' => MCRYPT_RIJNDAEL_128, + 'mode' => MCRYPT_MODE_NOFB, + ), + +); diff --git a/includes/kohana/system/config/inflector.php b/includes/kohana/system/config/inflector.php new file mode 100644 index 0000000..623c379 --- /dev/null +++ b/includes/kohana/system/config/inflector.php @@ -0,0 +1,71 @@ + array( + 'access', + 'advice', + 'aircraft', + 'art', + 'baggage', + 'bison', + 'dances', + 'deer', + 'equipment', + 'fish', + 'fuel', + 'furniture', + 'heat', + 'honey', + 'homework', + 'impatience', + 'information', + 'knowledge', + 'luggage', + 'media', + 'money', + 'moose', + 'music', + 'news', + 'patience', + 'progress', + 'pollution', + 'research', + 'rice', + 'salmon', + 'sand', + 'series', + 'sheep', + 'sms', + 'spam', + 'species', + 'staff', + 'swine', + 'toothpaste', + 'traffic', + 'understanding', + 'water', + 'weather', + 'work', + ), + + 'irregular' => array( + 'child' => 'children', + 'clothes' => 'clothing', + 'man' => 'men', + 'movie' => 'movies', + 'person' => 'people', + 'woman' => 'women', + 'mouse' => 'mice', + 'goose' => 'geese', + 'ox' => 'oxen', + 'leaf' => 'leaves', + 'course' => 'courses', + 'size' => 'sizes', + 'was' => 'were', + 'is' => 'are', + 'verse' => 'verses', + 'hero' => 'heroes', + 'purchase' => 'purchases', + ), +); diff --git a/includes/kohana/system/config/mimes.php b/includes/kohana/system/config/mimes.php new file mode 100644 index 0000000..fe79951 --- /dev/null +++ b/includes/kohana/system/config/mimes.php @@ -0,0 +1,226 @@ + array('text/h323'), + '7z' => array('application/x-7z-compressed'), + 'abw' => array('application/x-abiword'), + 'acx' => array('application/internet-property-stream'), + 'ai' => array('application/postscript'), + 'aif' => array('audio/x-aiff'), + 'aifc' => array('audio/x-aiff'), + 'aiff' => array('audio/x-aiff'), + 'amf' => array('application/x-amf'), + 'asf' => array('video/x-ms-asf'), + 'asr' => array('video/x-ms-asf'), + 'asx' => array('video/x-ms-asf'), + 'atom' => array('application/atom+xml'), + 'avi' => array('video/avi', 'video/msvideo', 'video/x-msvideo'), + 'bin' => array('application/octet-stream','application/macbinary'), + 'bmp' => array('image/bmp'), + 'c' => array('text/x-csrc'), + 'c++' => array('text/x-c++src'), + 'cab' => array('application/x-cab'), + 'cc' => array('text/x-c++src'), + 'cda' => array('application/x-cdf'), + 'class' => array('application/octet-stream'), + 'cpp' => array('text/x-c++src'), + 'cpt' => array('application/mac-compactpro'), + 'csh' => array('text/x-csh'), + 'css' => array('text/css'), + 'csv' => array('text/x-comma-separated-values', 'application/vnd.ms-excel', 'text/comma-separated-values', 'text/csv'), + 'dbk' => array('application/docbook+xml'), + 'dcr' => array('application/x-director'), + 'deb' => array('application/x-debian-package'), + 'diff' => array('text/x-diff'), + 'dir' => array('application/x-director'), + 'divx' => array('video/divx'), + 'dll' => array('application/octet-stream', 'application/x-msdos-program'), + 'dmg' => array('application/x-apple-diskimage'), + 'dms' => array('application/octet-stream'), + 'doc' => array('application/msword'), + 'docx' => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document'), + 'dvi' => array('application/x-dvi'), + 'dxr' => array('application/x-director'), + 'eml' => array('message/rfc822'), + 'eps' => array('application/postscript'), + 'evy' => array('application/envoy'), + 'exe' => array('application/x-msdos-program', 'application/octet-stream'), + 'fla' => array('application/octet-stream'), + 'flac' => array('application/x-flac'), + 'flc' => array('video/flc'), + 'fli' => array('video/fli'), + 'flv' => array('video/x-flv'), + 'gif' => array('image/gif'), + 'gtar' => array('application/x-gtar'), + 'gz' => array('application/x-gzip'), + 'h' => array('text/x-chdr'), + 'h++' => array('text/x-c++hdr'), + 'hh' => array('text/x-c++hdr'), + 'hpp' => array('text/x-c++hdr'), + 'hqx' => array('application/mac-binhex40'), + 'hs' => array('text/x-haskell'), + 'htm' => array('text/html'), + 'html' => array('text/html'), + 'ico' => array('image/x-icon'), + 'ics' => array('text/calendar'), + 'iii' => array('application/x-iphone'), + 'ins' => array('application/x-internet-signup'), + 'iso' => array('application/x-iso9660-image'), + 'isp' => array('application/x-internet-signup'), + 'jar' => array('application/java-archive'), + 'java' => array('application/x-java-applet'), + 'jpe' => array('image/jpeg', 'image/pjpeg'), + 'jpeg' => array('image/jpeg', 'image/pjpeg'), + 'jpg' => array('image/jpeg', 'image/pjpeg'), + 'js' => array('application/x-javascript'), + 'json' => array('application/json'), + 'latex' => array('application/x-latex'), + 'lha' => array('application/octet-stream'), + 'log' => array('text/plain', 'text/x-log'), + 'lzh' => array('application/octet-stream'), + 'm4a' => array('audio/mpeg'), + 'm4p' => array('video/mp4v-es'), + 'm4v' => array('video/mp4'), + 'man' => array('application/x-troff-man'), + 'mdb' => array('application/x-msaccess'), + 'midi' => array('audio/midi'), + 'mid' => array('audio/midi'), + 'mif' => array('application/vnd.mif'), + 'mka' => array('audio/x-matroska'), + 'mkv' => array('video/x-matroska'), + 'mov' => array('video/quicktime'), + 'movie' => array('video/x-sgi-movie'), + 'mp2' => array('audio/mpeg'), + 'mp3' => array('audio/mpeg'), + 'mp4' => array('application/mp4','audio/mp4','video/mp4'), + 'mpa' => array('video/mpeg'), + 'mpe' => array('video/mpeg'), + 'mpeg' => array('video/mpeg'), + 'mpg' => array('video/mpeg'), + 'mpg4' => array('video/mp4'), + 'mpga' => array('audio/mpeg'), + 'mpp' => array('application/vnd.ms-project'), + 'mpv' => array('video/x-matroska'), + 'mpv2' => array('video/mpeg'), + 'ms' => array('application/x-troff-ms'), + 'msg' => array('application/msoutlook','application/x-msg'), + 'msi' => array('application/x-msi'), + 'nws' => array('message/rfc822'), + 'oda' => array('application/oda'), + 'odb' => array('application/vnd.oasis.opendocument.database'), + 'odc' => array('application/vnd.oasis.opendocument.chart'), + 'odf' => array('application/vnd.oasis.opendocument.forumla'), + 'odg' => array('application/vnd.oasis.opendocument.graphics'), + 'odi' => array('application/vnd.oasis.opendocument.image'), + 'odm' => array('application/vnd.oasis.opendocument.text-master'), + 'odp' => array('application/vnd.oasis.opendocument.presentation'), + 'ods' => array('application/vnd.oasis.opendocument.spreadsheet'), + 'odt' => array('application/vnd.oasis.opendocument.text'), + 'oga' => array('audio/ogg'), + 'ogg' => array('application/ogg'), + 'ogv' => array('video/ogg'), + 'otg' => array('application/vnd.oasis.opendocument.graphics-template'), + 'oth' => array('application/vnd.oasis.opendocument.web'), + 'otp' => array('application/vnd.oasis.opendocument.presentation-template'), + 'ots' => array('application/vnd.oasis.opendocument.spreadsheet-template'), + 'ott' => array('application/vnd.oasis.opendocument.template'), + 'p' => array('text/x-pascal'), + 'pas' => array('text/x-pascal'), + 'patch' => array('text/x-diff'), + 'pbm' => array('image/x-portable-bitmap'), + 'pdf' => array('application/pdf', 'application/x-download'), + 'php' => array('application/x-httpd-php'), + 'php3' => array('application/x-httpd-php'), + 'php4' => array('application/x-httpd-php'), + 'php5' => array('application/x-httpd-php'), + 'phps' => array('application/x-httpd-php-source'), + 'phtml' => array('application/x-httpd-php'), + 'pl' => array('text/x-perl'), + 'pm' => array('text/x-perl'), + 'png' => array('image/png', 'image/x-png'), + 'po' => array('text/x-gettext-translation'), + 'pot' => array('application/vnd.ms-powerpoint'), + 'pps' => array('application/vnd.ms-powerpoint'), + 'ppt' => array('application/powerpoint'), + 'pptx' => array('application/vnd.openxmlformats-officedocument.presentationml.presentation'), + 'ps' => array('application/postscript'), + 'psd' => array('application/x-photoshop', 'image/x-photoshop'), + 'pub' => array('application/x-mspublisher'), + 'py' => array('text/x-python'), + 'qt' => array('video/quicktime'), + 'ra' => array('audio/x-realaudio'), + 'ram' => array('audio/x-realaudio', 'audio/x-pn-realaudio'), + 'rar' => array('application/rar'), + 'rgb' => array('image/x-rgb'), + 'rm' => array('audio/x-pn-realaudio'), + 'rpm' => array('audio/x-pn-realaudio-plugin', 'application/x-redhat-package-manager'), + 'rss' => array('application/rss+xml'), + 'rtf' => array('text/rtf'), + 'rtx' => array('text/richtext'), + 'rv' => array('video/vnd.rn-realvideo'), + 'sea' => array('application/octet-stream'), + 'sh' => array('text/x-sh'), + 'shtml' => array('text/html'), + 'sit' => array('application/x-stuffit'), + 'smi' => array('application/smil'), + 'smil' => array('application/smil'), + 'so' => array('application/octet-stream'), + 'src' => array('application/x-wais-source'), + 'svg' => array('image/svg+xml'), + 'swf' => array('application/x-shockwave-flash'), + 't' => array('application/x-troff'), + 'tar' => array('application/x-tar'), + 'tcl' => array('text/x-tcl'), + 'tex' => array('application/x-tex'), + 'text' => array('text/plain'), + 'texti' => array('application/x-texinfo'), + 'textinfo' => array('application/x-texinfo'), + 'tgz' => array('application/x-tar'), + 'tif' => array('image/tiff'), + 'tiff' => array('image/tiff'), + 'torrent' => array('application/x-bittorrent'), + 'tr' => array('application/x-troff'), + 'tsv' => array('text/tab-separated-values'), + 'txt' => array('text/plain'), + 'wav' => array('audio/x-wav'), + 'wax' => array('audio/x-ms-wax'), + 'wbxml' => array('application/wbxml'), + 'wm' => array('video/x-ms-wm'), + 'wma' => array('audio/x-ms-wma'), + 'wmd' => array('application/x-ms-wmd'), + 'wmlc' => array('application/wmlc'), + 'wmv' => array('video/x-ms-wmv', 'application/octet-stream'), + 'wmx' => array('video/x-ms-wmx'), + 'wmz' => array('application/x-ms-wmz'), + 'word' => array('application/msword', 'application/octet-stream'), + 'wp5' => array('application/wordperfect5.1'), + 'wpd' => array('application/vnd.wordperfect'), + 'wvx' => array('video/x-ms-wvx'), + 'xbm' => array('image/x-xbitmap'), + 'xcf' => array('image/xcf'), + 'xhtml' => array('application/xhtml+xml'), + 'xht' => array('application/xhtml+xml'), + 'xl' => array('application/excel', 'application/vnd.ms-excel'), + 'xla' => array('application/excel', 'application/vnd.ms-excel'), + 'xlc' => array('application/excel', 'application/vnd.ms-excel'), + 'xlm' => array('application/excel', 'application/vnd.ms-excel'), + 'xls' => array('application/excel', 'application/vnd.ms-excel'), + 'xlsx' => array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'), + 'xlt' => array('application/excel', 'application/vnd.ms-excel'), + 'xml' => array('text/xml', 'application/xml'), + 'xof' => array('x-world/x-vrml'), + 'xpm' => array('image/x-xpixmap'), + 'xsl' => array('text/xml'), + 'xvid' => array('video/x-xvid'), + 'xwd' => array('image/x-xwindowdump'), + 'z' => array('application/x-compress'), + 'zip' => array('application/x-zip', 'application/zip', 'application/x-zip-compressed') +); diff --git a/includes/kohana/system/config/session.php b/includes/kohana/system/config/session.php new file mode 100644 index 0000000..78ac9fa --- /dev/null +++ b/includes/kohana/system/config/session.php @@ -0,0 +1,7 @@ + array( + 'encrypted' => FALSE, + ), +); diff --git a/includes/kohana/system/config/user_agents.php b/includes/kohana/system/config/user_agents.php new file mode 100644 index 0000000..93bc599 --- /dev/null +++ b/includes/kohana/system/config/user_agents.php @@ -0,0 +1,106 @@ + array( + 'windows nt 6.1' => 'Windows 7', + 'windows nt 6.0' => 'Windows Vista', + 'windows nt 5.2' => 'Windows 2003', + 'windows nt 5.1' => 'Windows XP', + 'windows nt 5.0' => 'Windows 2000', + 'windows nt 4.0' => 'Windows NT', + 'winnt4.0' => 'Windows NT', + 'winnt 4.0' => 'Windows NT', + 'winnt' => 'Windows NT', + 'windows 98' => 'Windows 98', + 'win98' => 'Windows 98', + 'windows 95' => 'Windows 95', + 'win95' => 'Windows 95', + 'windows' => 'Unknown Windows OS', + 'os x' => 'Mac OS X', + 'intel mac' => 'Intel Mac', + 'ppc mac' => 'PowerPC Mac', + 'powerpc' => 'PowerPC', + 'ppc' => 'PowerPC', + 'cygwin' => 'Cygwin', + 'linux' => 'Linux', + 'debian' => 'Debian', + 'openvms' => 'OpenVMS', + 'sunos' => 'Sun Solaris', + 'amiga' => 'Amiga', + 'beos' => 'BeOS', + 'apachebench' => 'ApacheBench', + 'freebsd' => 'FreeBSD', + 'netbsd' => 'NetBSD', + 'bsdi' => 'BSDi', + 'openbsd' => 'OpenBSD', + 'os/2' => 'OS/2', + 'warp' => 'OS/2', + 'aix' => 'AIX', + 'irix' => 'Irix', + 'osf' => 'DEC OSF', + 'hp-ux' => 'HP-UX', + 'hurd' => 'GNU/Hurd', + 'unix' => 'Unknown Unix OS', + ), + + 'browser' => array( + 'Opera' => 'Opera', + 'MSIE' => 'Internet Explorer', + 'Internet Explorer' => 'Internet Explorer', + 'Shiira' => 'Shiira', + 'Firefox' => 'Firefox', + 'Chimera' => 'Chimera', + 'Phoenix' => 'Phoenix', + 'Firebird' => 'Firebird', + 'Camino' => 'Camino', + 'Navigator' => 'Netscape', + 'Netscape' => 'Netscape', + 'OmniWeb' => 'OmniWeb', + 'Chrome' => 'Chrome', + 'Safari' => 'Safari', + 'CFNetwork' => 'Safari', // Core Foundation for OSX, WebKit/Safari + 'Konqueror' => 'Konqueror', + 'Epiphany' => 'Epiphany', + 'Galeon' => 'Galeon', + 'Mozilla' => 'Mozilla', + 'icab' => 'iCab', + 'lynx' => 'Lynx', + 'links' => 'Links', + 'hotjava' => 'HotJava', + 'amaya' => 'Amaya', + 'IBrowse' => 'IBrowse', + ), + + 'mobile' => array( + 'mobileexplorer' => 'Mobile Explorer', + 'openwave' => 'Open Wave', + 'opera mini' => 'Opera Mini', + 'operamini' => 'Opera Mini', + 'elaine' => 'Palm', + 'palmsource' => 'Palm', + 'digital paths' => 'Palm', + 'avantgo' => 'Avantgo', + 'xiino' => 'Xiino', + 'palmscape' => 'Palmscape', + 'nokia' => 'Nokia', + 'ericsson' => 'Ericsson', + 'blackBerry' => 'BlackBerry', + 'motorola' => 'Motorola', + 'iphone' => 'iPhone', + 'ipad' => 'iPad', + 'ipod' => 'iPod', + 'android' => 'Android', + ), + + 'robot' => array( + 'googlebot' => 'Googlebot', + 'msnbot' => 'MSNBot', + 'slurp' => 'Inktomi Slurp', + 'yahoo' => 'Yahoo', + 'askjeeves' => 'AskJeeves', + 'fastcrawler' => 'FastCrawler', + 'infoseek' => 'InfoSeek Robot 1.0', + 'lycos' => 'Lycos', + ), +); diff --git a/includes/kohana/system/config/userguide.php b/includes/kohana/system/config/userguide.php new file mode 100644 index 0000000..3c49362 --- /dev/null +++ b/includes/kohana/system/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' + 'kohana' => array( + + // Whether this modules userguide pages should be shown + 'enabled' => TRUE, + + // The name that should show up on the userguide index page + 'name' => 'Kohana', + + // A short description of this module, shown on the index page + 'description' => 'Documentation for Kohana core/system.', + + // Copyright message, shown in the footer for this module + 'copyright' => '© 2008–2010 Kohana Team', + ) + ) +); \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/autoloading.md b/includes/kohana/system/guide/kohana/autoloading.md new file mode 100644 index 0000000..def15bc --- /dev/null +++ b/includes/kohana/system/guide/kohana/autoloading.md @@ -0,0 +1,69 @@ +# Loading Classes + +Kohana takes advantage of PHP [autoloading](http://php.net/manual/language.oop5.autoload.php). This removes the need to call [include](http://php.net/include) or [require](http://php.net/require) before using a class. When you use a class Kohana will find and include the class file for you. For instance, when you want to use the [Cookie::set] method, you simply call: + + Cookie::set('mycookie', 'any string value'); + +Or to load an [Encrypt] instance, just call [Encrypt::instance]: + + $encrypt = Encrypt::instance(); + +Classes are loaded via the [Kohana::auto_load] method, which makes a simple conversion from class name to file name: + +1. Classes are placed in the `classes/` directory of the [filesystem](files) +2. Any underscore characters in the class name are converted to slashes +2. The filename is lowercase + +When calling a class that has not been loaded (eg: `Session_Cookie`), Kohana will search the filesystem using [Kohana::find_file] for a file named `classes/session/cookie.php`. + +If your classes do not follow this convention, they cannot be autoloaded by Kohana. You will have to manually included your files, or add your own [autoload function.](http://us3.php.net/manual/en/function.spl-autoload-register.php) + +## Custom Autoloaders + +Kohana's default autoloader is enabled in `application/bootstrap.php` using [spl_autoload_register](http://php.net/spl_autoload_register): + + spl_autoload_register(array('Kohana', 'auto_load')); + +This allows [Kohana::auto_load] to attempt to find and include any class that does not yet exist when the class is first used. + +### Example: Zend + +You can easily gain access to other libraries if they include an autoloader. For example, here is how to enable Zend's autoloader so you can use Zend libraries in your Kohana application. + +#### Download and install the Zend Framework files + +- [Download the latest Zend Framework files](http://framework.zend.com/download/latest). +- Create a `vendor` directory at `application/vendor`. This keeps third party software separate from your application classes. +- Move the decompressed Zend folder containing Zend Framework to `application/vendor/Zend`. + + +#### Include Zend's Autoloader in your bootstrap + +Somewhere in `application/bootstrap.php`, copy the following code: + + /** + * Enable Zend Framework autoloading + */ + if ($path = Kohana::find_file('vendor', 'Zend/Loader')) + { + ini_set('include_path', + ini_get('include_path').PATH_SEPARATOR.dirname(dirname($path))); + + require_once 'Zend/Loader/Autoloader.php'; + Zend_Loader_Autoloader::getInstance(); + } + +#### Usage example + +You can now autoload any Zend Framework classes from inside your Kohana application. + + if ($validate($_POST)) + { + $mailer = new Zend_Mail; + + $mailer->setBodyHtml($view) + ->setFrom(Kohana::config('site')->email_from) + ->addTo($email) + ->setSubject($message) + ->send(); + } \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/bootstrap.md b/includes/kohana/system/guide/kohana/bootstrap.md new file mode 100644 index 0000000..2b8c26a --- /dev/null +++ b/includes/kohana/system/guide/kohana/bootstrap.md @@ -0,0 +1,111 @@ +# Bootstrap + +The bootstrap is located at `application/bootstrap.php`. It is responsible for setting up the Kohana environment and executing the main response. It is included by `index.php` (see [Request flow](flow)) + +[!!] The bootstrap is responsible for the flow of your application. In previous versions of Kohana the bootstrap was in `system` and was somewhat of an unseen, uneditible force. In Kohana 3 the bootstrap takes on a much more integral and versatile role. Do not be afraid to edit and change your bootstrap however you see fit. + +## Environment setup + +First the bootstrap sets the timezone and the locale, and adds Kohana's autoloader so the [cascading filesystem](files) works. You could add any other settings that all your application needed here. + +~~~ +// Sample excerpt from bootstrap.php with comments trimmed down + +// Set the default time zone. +date_default_timezone_set('America/Chicago'); + +// Set the default locale. +setlocale(LC_ALL, 'en_US.utf-8'); + +// Enable the Kohana auto-loader. +spl_autoload_register(array('Kohana', 'auto_load')); + +// Enable the Kohana auto-loader for unserialization. +ini_set('unserialize_callback_func', 'spl_autoload_call'); +~~~ + +## Initialization and Configuration + +Kohana is then initialized by calling [Kohana::init], and the log and [config](files/config) reader/writers are enabled. + +~~~ +// Sample excerpt from bootstrap.php with comments trimmed down + +Kohana::init(array(' + base_url' => '/kohana/', + index_file => false, +)); + +// Attach the file writer to logging. Multiple writers are supported. +Kohana::$log->attach(new Kohana_Log_File(APPPATH.'logs')); + +// Attach a file reader to config. Multiple readers are supported. +Kohana::$config->attach(new Kohana_Config_File); +~~~ + +You can add conditional statements to make the bootstrap have different values based on certain settings. For example, detect whether we are live by checking `$_SERVER['HTTP_HOST']` and set caching, profiling, etc. accordingly. This is just an example, there are many different ways to accomplish the same thing. + +~~~ +// Excerpt from http://github.com/isaiahdw/kohanaphp.com/blob/f2afe8e28b/application/bootstrap.php +... [trimmed] + +/** + * Set the environment status by the domain. + */ +if (strpos($_SERVER['HTTP_HOST'], 'kohanaphp.com') !== FALSE) +{ + // We are live! + Kohana::$environment = Kohana::PRODUCTION; + + // Turn off notices and strict errors + error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT); +} + +/** + * Initialize Kohana, setting the default options. + ... [trimmed] + */ +Kohana::init(array( + 'base_url' => Kohana::$environment === Kohana::PRODUCTION ? '/' : '/kohanaphp.com/', + 'caching' => Kohana::$environment === Kohana::PRODUCTION, + 'profile' => Kohana::$environment !== Kohana::PRODUCTION, + 'index_file' => FALSE, +)); + +... [trimmed] + +~~~ + +[!!] Note: The default bootstrap will set `Kohana::$environment = $_ENV['KOHANA_ENV']` if set. Docs on how to supply this variable are available in your web server's documentation (e.g. [Apache](http://httpd.apache.org/docs/1.3/mod/mod_env.html#setenv), [Lighttpd](http://redmine.lighttpd.net/wiki/1/Docs:ModSetEnv#Options)). This is considered better practice than many alternative methods to set `Kohana::$enviroment`, as you can change the setting per server, without having to rely on config options or hostnames. + +## Modules + +**Read the [Modules](modules) page for a more detailed description.** + +[Modules](modules) are then loaded using [Kohana::modules()]. Including modules is optional. + +Each key in the array should be the name of the module, and the value is the path to the module, either relative or absolute. +~~~ +// Example excerpt from bootstrap.php + +Kohana::modules(array( + 'database' => MODPATH.'database', + 'orm' => MODPATH.'orm', + 'userguide' => MODPATH.'userguide', +)); +~~~ + +## Routes + +**Read the [Routing](routing) page for a more detailed description and more examples.** + +[Routes](routing) are then defined via [Route::set()]. + +~~~ +// The default route that comes with Kohana 3 +Route::set('default', '((/(/)))') + ->defaults(array( + 'controller' => 'welcome', + 'action' => 'index', + )); +~~~ diff --git a/includes/kohana/system/guide/kohana/controllers.md b/includes/kohana/system/guide/kohana/controllers.md new file mode 100644 index 0000000..d95d98a --- /dev/null +++ b/includes/kohana/system/guide/kohana/controllers.md @@ -0,0 +1 @@ +This will discuss controller basics, like before() and after(), private function, and about extending controllers like the Controller_Template, or using a parent::before() for authentication. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/conventions.md b/includes/kohana/system/guide/kohana/conventions.md new file mode 100644 index 0000000..984c67d --- /dev/null +++ b/includes/kohana/system/guide/kohana/conventions.md @@ -0,0 +1,417 @@ +# Conventions and Coding Style + +It is encouraged that you follow Kohana's coding style. This makes code more readable and allows for easier code sharing and contributing. + +## Class Names and File Location + +Class names in Kohana follow a strict convention to facilitate [autoloading](autoloading). Class names should have uppercase first letters with underscores to separate words. Underscores are significant as they directly reflect the file location in the filesystem. + +The following conventions apply: + +1. CamelCased class names should not be used, except when it is undesirable to create a new directory level. +2. All class file names and directory names are lowercase. +3. All classes should be in the `classes` directory. This may be at any level in the [cascading filesystem](files). + +### Examples {#class-name-examples} + +Remember that in a class, an underscore means a new directory. Consider the following examples: + +Class Name | File Path +----------------------|------------------------------- +Controller_Template | classes/controller/template.php +Model_User | classes/model/user.php +Database | classes/database.php +Database_Query | classes/database/query.php +Form | classes/form.php + +## Coding Standards + +In order to produce highly consistent source code, we ask that everyone follow the coding standards as closely as possible. + +### Brackets + +Please use [BSD/Allman Style](http://en.wikipedia.org/wiki/Indent_style#BSD.2FAllman_style) bracketing. + +#### Curly Brackets + +Curly brackets are placed on their own line, indented to the same level as the control statement. + + // Correct + if ($a === $b) + { + ... + } + else + { + ... + } + + // Incorrect + if ($a === $b) { + ... + } else { + ... + } + +#### Class Brackets + +The only exception to the curly bracket rule is, the opening bracket of a class goes on the same line. + + // Correct + class Foo { + + // Incorrect + class Foo + { + +#### Empty Brackets + +Don't put any characters inside empty brackets. + + // Correct + class Foo {} + + // Incorrect + class Foo { } + +#### Array Brackets + +Arrays may be single line or multi-line. + + array('a' => 'b', 'c' => 'd') + + array( + 'a' => 'b', + 'c' => 'd', + ) + +##### Opening Parenthesis + +The opening array parenthesis goes on the same line. + + // Correct + array( + ... + ) + + // Incorrect: + array + ( + ... + ) + +##### Closing parenthesis + +###### Single Dimension + +The closing parenthesis of a multi-line single dimension array is placed on its own line, indented to the same level as the assignment or statement. + + // Correct + $array = array( + ... + ) + + // Incorrect + $array = array( + ... + ) + +###### Multidimensional + +The nested array is indented one tab to the right, following the single dimension rules. + + // Correct + array( + 'arr' => array( + ... + ), + 'arr' => array( + ... + ), + ) + + array( + 'arr' => array(...), + 'arr' => array(...), + ) + +##### Arrays as Function Arguments + + + // Correct + do(array( + ... + )) + + // Incorrect + do(array( + ... + )) + +As noted at the start of the array bracket section, single line syntax is also valid. + + // Correct + do(array(...)) + + // Alternative for wrapping long lines + do($bar, 'this is a very long line', + array(...)); + +### Naming Conventions + +Kohana uses under_score naming, not camelCase naming. + +#### Classes + + // Controller class, uses Controller_ prefix + class Controller_Apple extends Controller { + + // Model class, uses Model_ prefix + class Model_Cheese extends Model { + + // Regular class + class Peanut { + +When creating an instance of a class, don't use parentheses if you're not passing something on to the constructor: + + // Correct: + $db = new Database; + + // Incorrect: + $db = new Database(); + +#### Functions and Methods + +Functions should be all lowercase, and use under_scores to separate words: + + function drink_beverage($beverage) + { + +#### Variables + +All variables should be lowercase and use under_score, not camelCase: + + // Correct: + $foo = 'bar'; + $long_example = 'uses underscores'; + + // Incorrect: + $weDontWantThis = 'understood?'; + +### Indentation + +You must use tabs to indent your code. Using spaces for tabbing is strictly forbidden. + +Vertical spacing (for multi-line) is done with spaces. Tabs are not good for vertical alignment because different people have different tab widths. + + $text = 'this is a long text block that is wrapped. Normally, we aim for ' + .'wrapping at 80 chars. Vertical alignment is very important for ' + .'code readability. Remember that all indentation is done with tabs,' + .'but vertical alignment should be completed with spaces, after ' + .'indenting with tabs.'; + +### String Concatenation + +Do not put spaces around the concatenation operator: + + // Correct: + $str = 'one'.$var.'two'; + + // Incorrect: + $str = 'one'. $var .'two'; + $str = 'one' . $var . 'two'; + +### Single Line Statements + +Single-line IF statements should only be used when breaking normal execution (e.g. return or continue): + + // Acceptable: + if ($foo == $bar) + return $foo; + + if ($foo == $bar) + continue; + + if ($foo == $bar) + break; + + if ($foo == $bar) + throw new Exception('You screwed up!'); + + // Not acceptable: + if ($baz == $bun) + $baz = $bar + 2; + +### Comparison Operations + +Please use OR and AND for comparison: + + // Correct: + if (($foo AND $bar) OR ($b AND $c)) + + // Incorrect: + if (($foo && $bar) || ($b && $c)) + +Please use elseif, not else if: + + // Correct: + elseif ($bar) + + // Incorrect: + else if($bar) + +### Switch Structures + +Each case, break and default should be on a separate line. The block inside a case or default must be indented by 1 tab. + + switch ($var) + { + case 'bar': + case 'foo': + echo 'hello'; + break; + case 1: + echo 'one'; + break; + default: + echo 'bye'; + break; + } + +### Parentheses + +There should be one space after statement name, followed by a parenthesis. The ! (bang) character must have a space on either side to ensure maximum readability. Except in the case of a bang or type casting, there should be no whitespace after an opening parenthesis or before a closing parenthesis. + + // Correct: + if ($foo == $bar) + if ( ! $foo) + + // Incorrect: + if($foo == $bar) + if(!$foo) + if ((int) $foo) + if ( $foo == $bar ) + if (! $foo) + +### Ternaries + +All ternary operations should follow a standard format. Use parentheses around expressions only, not around just variables. + + $foo = ($bar == $foo) ? $foo : $bar; + $foo = $bar ? $foo : $bar; + +All comparisons and operations must be done inside of a parentheses group: + + $foo = ($bar > 5) ? ($bar + $foo) : strlen($bar); + +When separating complex ternaries (ternaries where the first part goes beyond ~80 chars) into multiple lines, spaces should be used to line up operators, which should be at the front of the successive lines: + + $foo = ($bar == $foo) + ? $foo + : $bar; + +### Type Casting + +Type casting should be done with spaces on each side of the cast: + + // Correct: + $foo = (string) $bar; + if ( (string) $bar) + + // Incorrect: + $foo = (string)$bar; + +When possible, please use type casting instead of ternary operations: + + // Correct: + $foo = (bool) $bar; + + // Incorrect: + $foo = ($bar == TRUE) ? TRUE : FALSE; + +When casting type to integer or boolean, use the short format: + + // Correct: + $foo = (int) $bar; + $foo = (bool) $bar; + + // Incorrect: + $foo = (integer) $bar; + $foo = (boolean) $bar; + +### Constants + +Always use uppercase for constants: + + // Correct: + define('MY_CONSTANT', 'my_value'); + $a = TRUE; + $b = NULL; + + // Incorrect: + define('MyConstant', 'my_value'); + $a = True; + $b = null; + +Place constant comparisons at the end of tests: + + // Correct: + if ($foo !== FALSE) + + // Incorrect: + if (FALSE !== $foo) + +This is a slightly controversial choice, so I will explain the reasoning. If we were to write the previous example in plain English, the correct example would read: + + if variable $foo is not exactly FALSE + +And the incorrect example would read: + + if FALSE is not exactly variable $foo + +Since we are reading left to right, it simply doesn't make sense to put the constant first. + +### Comments + +#### One-line Comments + +Use //, preferably above the line of code you're commenting on. Leave a space after it and start with a capital. Never use #. + + // Correct + + //Incorrect + // incorrect + # Incorrect + +### Regular Expressions + +When coding regular expressions please use PCRE rather than the POSIX flavor. PCRE is considered more powerful and faster. + + // Correct: + if (preg_match('/abc/i'), $str) + + // Incorrect: + if (eregi('abc', $str)) + +Use single quotes around your regular expressions rather than double quotes. Single-quoted strings are more convenient because of their simplicity. Unlike double-quoted strings they don't support variable interpolation nor integrated backslash sequences like \n or \t, etc. + + // Correct: + preg_match('/abc/', $str); + + // Incorrect: + preg_match("/abc/", $str); + +When performing a regular expression search and replace, please use the $n notation for backreferences. This is preferred over \\n. + + // Correct: + preg_replace('/(\d+) dollar/', '$1 euro', $str); + + // Incorrect: + preg_replace('/(\d+) dollar/', '\\1 euro', $str); + +Finally, please note that the $ character for matching the position at the end of the line allows for a following newline character. Use the D modifier to fix this if needed. [More info](http://blog.php-security.org/archives/76-Holes-in-most-preg_match-filters.html). + + $str = "email@example.com\n"; + + preg_match('/^.+@.+$/', $str); // TRUE + preg_match('/^.+@.+$/D', $str); // FALSE \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/cookies.md b/includes/kohana/system/guide/kohana/cookies.md new file mode 100644 index 0000000..b53a130 --- /dev/null +++ b/includes/kohana/system/guide/kohana/cookies.md @@ -0,0 +1,100 @@ +# Cookies + +Kohana provides classes that make it easy to work with both cookies and sessions. At a high level both sessions and cookies provide the same functionality. They allow the developer to store temporary or persistent information about a specific client for later retrieval, usually to make something persistent between requests. + +[Cookies](http://en.wikipedia.org/wiki/HTTP_cookie) should be used for storing non-private data that is persistent for a long period of time. For example storing a user preference or a language setting. Use the [Cookie] class for getting and setting cookies. + +[!!] Kohana uses "signed" cookies. Every cookie that is stored is combined with a secure hash to prevent modification of the cookie. If a cookie is modified outside of Kohana the hash will be incorrect and the cookie will be deleted. This hash is generated using [Cookie::salt()], which uses the [Cookie::$salt] property. You must define this setting in your bootstrap.php: + + Cookie::$salt = 'foobar'; + +Or define an extended cookie class in your application: + + class Cookie extends Kohana_Cookie + { + public static $salt = 'foobar'; + } + +You should set the salt to a secure value. The example above is only for demonstrative purposes. + +Nothing stops you from using `$_COOKIE` like normal, but you can not mix using the Cookie class and the regular `$_COOKIE` global, because the hash that Kohana uses to sign cookies will not be present, and Kohana will delete the cookie. + +## Storing, Retrieving, and Deleting Data + +[Cookie] and [Session] provide a very similar API for storing data. The main difference between them is that sessions are accessed using an object, and cookies are accessed using a static class. + +### Storing Data + +Storing session or cookie data is done using the [Cookie::set] method: + + // Set cookie data + Cookie::set($key, $value); + + // Store a user id + Cookie::set('user_id', 10); + +### Retrieving Data + +Getting session or cookie data is done using the [Cookie::get] method: + + // Get cookie data + $data = Cookie::get($key, $default_value); + + // Get the user id + $user = Cookie::get('user_id'); + +### Deleting Data + +Deleting session or cookie data is done using the [Cookie::delete] method: + + // Delete cookie data + Cookie::delete($key); + + // Delete the user id + Cookie::delete('user_id'); + +## Cookie Settings + +All of the cookie settings are changed using static properties. You can either change these settings in `bootstrap.php` or by using [transparent extension](extension). Always check these settings before making your application live, as many of them will have a direct affect on the security of your application. + +The most important setting is [Cookie::$salt], which is used for secure signing. This value should be changed and kept secret: + + Cookie::$salt = 'your secret is safe with me'; + +[!!] Changing this value will render all cookies that have been set before invalid. + +By default, cookies are stored until the browser is closed. To use a specific lifetime, change the [Cookie::$expiration] setting: + + // Set cookies to expire after 1 week + Cookie::$expiration = 604800; + + // Alternative to using raw integers, for better clarity + Cookie::$expiration = Date::WEEK; + +The path that the cookie can be accessed from can be restricted using the [Cookie::$path] setting. + + // Allow cookies only when going to /public/* + Cookie::$path = '/public/'; + +The domain that the cookie can be accessed from can also be restricted, using the [Cookie::$domain] setting. + + // Allow cookies only on the domain www.example.com + Cookie::$domain = 'www.example.com'; + +If you want to make the cookie accessible on all subdomains, use a dot at the beginning of the domain. + + // Allow cookies to be accessed on example.com and *.example.com + Cookie::$domain = '.example.com'; + +To only allow the cookie to be accessed over a secure (HTTPS) connection, use the [Cookie::$secure] setting. + + // Allow cookies to be accessed only on a secure connection + Cookie::$secure = TRUE; + + // Allow cookies to be accessed on any connection + Cookie::$secure = FALSE; + +To prevent cookies from being accessed using Javascript, you can change the [Cookie::$httponly] setting. + + // Make cookies inaccessible to Javascript + Cookie::$httponly = TRUE; \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/debugging.md b/includes/kohana/system/guide/kohana/debugging.md new file mode 100644 index 0000000..251e557 --- /dev/null +++ b/includes/kohana/system/guide/kohana/debugging.md @@ -0,0 +1,20 @@ +# Debugging + +Kohana includes several tools to help you debug your application. + +The most basic of these is [Debug::vars]. This simple method will display any number of variables, similar to [var_export](http://php.net/var_export) or [print_r](http://php.net/print_r), but using HTML for extra formatting. + + // Display a dump of the $foo and $bar variables + echo Debug::vars($foo, $bar); + +Kohana also provides a method to show the source code of a particular file using [Debug::source]. + + // Display this line of source code + echo Debug::source(__FILE__, __LINE__); + +If you want to display information about your application files without exposing the installation directory, you can use [Debug::path]: + + // Displays "APPPATH/cache" rather than the real path + echo Debug::path(APPPATH.'cache'); + +If you are having trouble getting something to work correctly, you could check your Kohana logs and your webserver logs, as well as using a debugging tool like [Xdebug](http://www.xdebug.org/). \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/errors.md b/includes/kohana/system/guide/kohana/errors.md new file mode 100644 index 0000000..8ac5651 --- /dev/null +++ b/includes/kohana/system/guide/kohana/errors.md @@ -0,0 +1,76 @@ +# Error/Exception Handling + +Kohana provides both an exception handler and an error handler that transforms errors into exceptions using PHP's [ErrorException](http://php.net/errorexception) class. Many details of the error and the internal state of the application is displayed by the handler: + +1. Exception class +2. Error level +3. Error message +4. Source of the error, with the error line highlighted +5. A [debug backtrace](http://php.net/debug_backtrace) of the execution flow +6. Included files, loaded extensions, and global variables + +## Example + +Click any of the links to toggle the display of additional information: + +
                    {{userguide/examples/error}}
                    + +## Disabling Error/Exception Handling + +If you do not want to use the internal error handling, you can disable it (highly discouraged) when calling [Kohana::init]: + + Kohana::init(array('errors' => FALSE)); + +## Error Reporting + +By default, Kohana displays all errors, including strict mode warnings. This is set using [error_reporting](http://php.net/error_reporting): + + error_reporting(E_ALL | E_STRICT); + +When you application is live and in production, a more conservative setting is recommended, such as ignoring notices: + + error_reporting(E_ALL & ~E_NOTICE); + +If you get a white screen when an error is triggered, your host probably has disabled displaying errors. You can turn it on again by adding this line just after your `error_reporting` call: + + ini_set('display_errors', TRUE); + +Errors should **always** be displayed, even in production, because it allows you to use [exception and error handling](debugging.errors) to serve a nice error page rather than a blank white screen when an error happens. + +## HTTP Exception Handling + +Kohana comes with a robust system for handing http errors. It includes exception classes for each http status code. To trigger a 404 in your application (the most common scenario): + + throw new HTTP_Exception_404('File not found!'); + +There is no default method to handle these errors in Kohana. It's recommended that you setup an exception handler (and register it) to handle these kinds of errors. Here's a simple example that would go in */application/classes/foobar/exception/handler.php*: + + class Foobar_Exception_Handler + { + public static function handle(Exception $e) + { + switch (get_class($e)) + { + case 'Http_Exception_404': + $response = new Response; + $response->status(404); + $view = new View('error_404'); + $view->message = $e->getMessage(); + $view->title = 'File Not Found'; + echo $response->body($view)->send_headers()->body(); + return TRUE; + break; + default: + return Kohana_Exception::handler($e); + break; + } + } + } + +And put something like this in your bootstrap to register the handler. + + set_exception_handler(array('Foobar_Exception_Handler', 'handle')); + + > *Note:* Be sure to place `set_exception_handler()` **after** `Kohana::init()` in your bootstrap, or it won't work. + + > If you receive *Fatal error: Exception thrown without a stack frame in Unknown on line 0*, it means there was an error within your exception handler. If using the example above, be sure *404.php* exists under */application/views/error/*. diff --git a/includes/kohana/system/guide/kohana/extension.md b/includes/kohana/system/guide/kohana/extension.md new file mode 100644 index 0000000..49bad05 --- /dev/null +++ b/includes/kohana/system/guide/kohana/extension.md @@ -0,0 +1,101 @@ +# Transparent Class Extension + +The [cascading filesystem](files) allows transparent class extension. For instance, the class [Cookie] is defined in `SYSPATH/classes/cookie.php` as: + + class Cookie extends Kohana_Cookie {} + +The default Kohana classes, and many extensions, use this definition so that almost all classes can be extended. You extend any class transparently, by defining your own class in `APPPATH/classes/cookie.php` to add your own methods. + +[!!] You should **never** modify any of the files that are distributed with Kohana. Always make modifications to classes using transparent extension to prevent upgrade issues. + +For instance, if you wanted to create method that sets encrypted cookies using the [Encrypt] class, you would create a file at `application/classes/cookie.php` that extends Kohana_Cookie, and adds your functions: + + encode((string) $value); + + parent::set($name, $value, $expiration); + } + + /** + * Gets an encrypted cookie. + * + * @uses Cookie::get + * @uses Encrypt::decode + */ + public static function decrypt($name, $default = NULL) + { + if ($value = parent::get($name, NULL)) + { + $value = Encrypt::instance(Cookie::$encryption)->decode($value); + } + + return isset($value) ? $value : $default; + } + + } // End Cookie + +Now calling `Cookie::encrypt('secret', $data)` will create an encrypted cookie which we can decrypt with `$data = Cookie::decrypt('secret')`. + +## How it works + +To understand how this works, let's look at what happens normally. When you use the Cookie class, [Kohana::autoload] looks for `classes/cookie.php` in the [cascading filesystem](files). It looks in `application`, then each module, then `system`. The file is found in `system` and is included. Of coures, `system/classes/cookie.php` is just an empty class which extends `Kohana_Cookie`. Again, [Kohana::autoload] is called this time looking for `classes/kohana/cookie.php` which it finds in `system`. + +When you add your transparently extended cookie class at `application/classes/cookie.php` this file essentially "replaces" the file at `system/classes/cookie.php` without actually touching it. This happens because this time when we use the Cookie class [Kohana::autoload] looks for `classes/cookie.php` and finds the file in `application` and includes that one, instead of the one in system. + +## Example: changing [Cookie] settings + +If you are using the [Cookie](cookies) class, and want to change a setting, you should do so using transparent extension, rather than editing the file in the system folder. If you edit it directly, and in the future you upgrade your Kohana version by replacing the system folder, your changes will be reverted and your cookies will probably be invalid. Instead, create a cookie.php file either in `application/classes/cookie.php` or a module (`MODPATH//classes/cookie.php`). + + class Cookie extends Kohana_Cookie { + + // Set a new salt + public $salt = "some new better random salt phrase"; + + // Don't allow javascript access to cookies + public $httponly = TRUE; + + } + +## Example: TODO: an example + +Just post the code and brief description of what function it adds, you don't have to do the "How it works" like above. + +## Example: TODO: something else + +Just post the code and brief description of what function it adds, you don't have to do the "How it works" like above. + +## More examples + +TODO: Provide some links to modules on github, etc that have examples of transparent extension in use. + +## Multiple Levels of Extension + +If you are extending a Kohana class in a module, you should maintain transparent extensions. In other words, do not include any variables or function in the "base" class (eg. Cookie). Instead make your own namespaced class, and have the "base" class extend that one. With our Encrypted cookie example we can create `MODPATH/mymod/encrypted/cookie.php`: + + class Encrypted_Cookie extends Kohana_Cookie { + + // Use the same encrypt() and decrypt() methods as above + + } + +And create `MODPATH/mymod/cookie.php`: + + class Cookie extends Encrypted_Cookie {} + +This will still allow users to add their own extension to [Cookie] while leaving your extensions intact. To do that they would make a cookie class that extends `Encrypted_Cookie` (rather than `Kohana_Cookie`) in their application folder. diff --git a/includes/kohana/system/guide/kohana/files.md b/includes/kohana/system/guide/kohana/files.md new file mode 100644 index 0000000..0eb593e --- /dev/null +++ b/includes/kohana/system/guide/kohana/files.md @@ -0,0 +1,83 @@ +# Cascading Filesystem + +The Kohana filesystem is a hierarchy of similar directory structures that cascade. The hierarchy in Kohana (used when a file is loaded by [Kohana::find_file]) is in the following order: + +1. **Application Path** + Defined as `APPPATH` in `index.php`. The default value is `application`. + +2. **Module Paths** + This is set as an associative array using [Kohana::modules] in `APPPATH/bootstrap.php`. Each of the values of the array will be searched **in the order that the modules are defined**. + +3. **System Path** + Defined as `SYSPATH` in `index.php`. The default value is `system`. All of the main or "core" files and classes are defined here. + +Files that are in directories higher up the include path order take precedence over files of the same name lower down the order, which makes it is possible to overload any file by placing a file with the same name in a "higher" directory: + +![Cascading Filesystem Infographic](cascading_filesystem.png) + +This image is only shows certain files, but we can use it to illustrate some examples of the cascading filesystem: + +* If Kohana catches an error, it would display the `kohana/error.php` view, So it would call `Kohana::find_file('views', 'kohana/error')`. This would return `application/views/kohana/error.php` because it takes precidence over `system/views/kohana/error.php`. By doing this we can change the error view without editing the system folder. + +* If we used `View::factory('welcome')` it would call `Kohana::find_file('views','welcome')` which would return `application/views/welcome.php` because it takes precidence over `modules/common/views/welcome.php`. By doing this, you can overwrite things in a module without editing the modules files. + +* If use the Cookie class, [Kohana::auto_load] will call `Kohana::find_file('classes', 'cookie')` which will return `application/classes/cookie.php`. Assuming Cookie extends Kohana_Cookie, the autoloader would then call `Kohana::find_file('classes','kohana/cookie')` which will return `system/classes/kohana/cookie.php` because that file does not exist anywhere higher in the cascade. This is an example of [transparent extension](extension). + +* If you used `View::factory('user')` it would call `Kohana::find_file('views','user')` which would return `modules/common/views/user.php`. + +* If we wanted to change something in `config/database.php` we could copy the file to `application/config/database.php` and make the changes there. Keep in mind that [config files are merged](files/config#merge) rather than overwritten by the cascade. + +## Types of Files + +The top level directories of the application, module, and system paths have the following default directories: + +classes/ +: All classes that you want to [autoload](autoloading) should be stored here. This includes [controllers](mvc/controllers), [models](mvc/models), and all other classes. All classes must follow the [class naming conventions](conventions#class-names-and-file-location). + +config/ +: Configuration files return an associative array of options that can be loaded using [Kohana::config]. Config files are merged rather than overwritten by the cascade. See [config files](files/config) for more information. + +i18n/ +: Translation files return an associative array of strings. Translation is done using the `__()` method. To translate "Hello, world!" into Spanish, you would call `__('Hello, world!')` with [I18n::$lang] set to "es-es". I18n files are merged rather than overwritten by the cascade. See [I18n files](files/i18n) for more information. + +messages/ +: Message files return an associative array of strings that can be loaded using [Kohana::message]. Messages and i18n files differ in that messages are not translated, but always written in the default language and referred to by a single key. Message files are merged rather than overwritten by the cascade. See [message files](files/messages) for more information. + +views/ +: Views are plain PHP files which are used to generate HTML or other output. The view file is loaded into a [View] object and assigned variables, which it then converts into an HTML fragment. Multiple views can be used within each other. See [views](mvc/views) for more information. + +*other* +: You can include any other folders in your cascading filesystem. Examples include, but are not limited to, `guide`, `vendor`, `media`, whatever you want. For example, to find `media/logo.png` in the cascading filesystem you would call `Kohana::find_file('media','logo','png')`. + +## Finding Files + +The path to any file within the filesystem can be found by calling [Kohana::find_file]: + + // Find the full path to "classes/cookie.php" + $path = Kohana::find_file('classes', 'cookie'); + + // Find the full path to "views/user/login.php" + $path = Kohana::find_file('views', 'user/login'); + +If the file doesn't have a `.php` extension, pass the extension as the third param. + + // Find the full path to "guide/menu.md" + $path = Kohana::find_file('guide', 'menu', 'md'); + + // If $name is "2000-01-01-first-post" this would look for "posts/2000-01-01-first-post.textile" + $path = Kohana::find_file('posts', $name, '.textile'); + + +## Vendor Extensions + +We call extensions or external libraries that are not specific to Kohana "vendor" extensions, and they go in the vendor folder, either in application or in a module. Because these libraries do not follow Kohana's file naming conventions, they cannot be autoloaded by Kohana, so you will have to manually included them. Some examples of vendor libraries are [Markdown](http://daringfireball.net/projects/markdown/), [DOMPDF](http://code.google.com/p/dompdf), [Mustache](http://github.com/bobthecow/mustache.php) and [Swiftmailer](http://swiftmailer.org/). + +For example, if you wanted to use [DOMPDF](http://code.google.com/p/dompdf), you would copy it to `application/vendor/dompdf` and include the DOMPDF autoloading class. It can be useful to do this in a controller's before method, as part of a module's init.php, or the contstructor of a singleton class. + + require Kohana::find_file('vendor', 'dompdf/dompdf/dompdf_config','inc'); + +Now you can use DOMPDF without loading any more files: + + $pdf = new DOMPDF; + +[!!] If you want to convert views into PDFs using DOMPDF, try the [PDFView](http://github.com/shadowhand/pdfview) module. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/files/classes.md b/includes/kohana/system/guide/kohana/files/classes.md new file mode 100644 index 0000000..898a4ac --- /dev/null +++ b/includes/kohana/system/guide/kohana/files/classes.md @@ -0,0 +1,41 @@ +# Classes + +TODO: Brief intro to classes. + +[Models](mvc/models) and [Controllers](mvc/controllers) are classes as well, but are treated slightly differently by Kohana. Read their respective pages to learn more. + +## Helper or Library? + +Kohana 3 does not differentiate between "helper" classes and "library" classes like in previous versions. They are all placed in the `classes/` folder and follow the same conventions. The distinction is that in general, a "helper" class is used statically, (for examples see the [helpers included in Kohana](helpers)), and library classes are typically instantiated and used as objects (like the [Database query builders](../database/query/builder)). The distinction is not black and white, and is irrelevant anyways, since they are treated the same by Kohana. + +## Creating a class + +To create a new class, simply place a file in the `classes/` directory at any point in the [Cascading Filesystem](files), that follows the [Class naming conventions](conventions#class-names-and-file-location). For example, lets create a `Foobar` class. + + // classes/foobar.php + + class Foobar { + static function magic() { + // Does something + } + } + +We can now call `Foobar::magic()` any where and Kohana will [autoload](autoloading) the file for us. + +We can also put classes in subdirectories. + + // classes/professor/baxter.php + + class Professor_Baxter { + static function teach() { + // Does something + } + } + +We could now call `Professor_Baxter::teach()` any where we want. + +For examples of how to create and use classes, simply look at the 'classes' folder in `system` or any module. + +## Namespacing your classes + +TODO: Discuss namespacing to provide transparent extension functionality in your own classes/modules. diff --git a/includes/kohana/system/guide/kohana/files/config.md b/includes/kohana/system/guide/kohana/files/config.md new file mode 100644 index 0000000..c1b8197 --- /dev/null +++ b/includes/kohana/system/guide/kohana/files/config.md @@ -0,0 +1,102 @@ +# Config Files + +Configuration files are used to store any kind of configuration needed for a module, class, or anything else you want. They are plain PHP files, stored in the `config/` directory, which return an associative array: + + 'value', + 'options' => array( + 'foo' => 'bar', + ), + ); + +If the above configuration file was called `myconf.php`, you could access it using: + + $config = Kohana::config('myconf'); + $options = $config['options']; + +[Kohana::config] also provides a shortcut for accessing individual keys from configuration arrays using "dot paths" similar to [Arr::path]. + +Get the "options" array: + + $options = Kohana::config('myconf.options'); + +Get the "foo" key from the "options" array: + + $foo = Kohana::config('myconf.options.foo'); + +Configuration arrays can also be accessed as objects, if you prefer that method: + + $options = Kohana::config('myconf')->options; + +Please note that you can only access the top level of keys as object properties, all child keys must be accessed using standard array syntax: + + $foo = Kohana::config('myconf')->options['foo']; + +## Merge + +Configuration files are slightly different from most other files within the [cascading filesystem](files) in that they are **merged** rather than overloaded. This means that all configuration files with the same file path are combined to produce the final configuration. The end result is that you can overload *individual* settings rather than duplicating an entire file. + +For example, if we wanted to change or add to an entry in the inflector configuration file, we would not need to duplicate all the other entries from the default configuration file. + + // config/inflector.php + + array( + 'die' => 'dice', // does not exist in default config file + 'mouse' => 'mouses', // overrides 'mouse' => 'mice' in the default config file + ); + + +## Creating your own config files + +Let's say we want a config file to store and easily change things like the title of a website, or the google analytics code. We would create a config file, let's call it `site.php`: + + // config/site.php + + 'Our Shiny Website', + 'analytics' => FALSE, // analytics code goes here, set to FALSE to disable + ); + +We could now call `Kohana::config('site.title')` to get the site name, and `Kohana::config('site.analytics')` to get the analytics code. + +Let's say we want an archive of versions of some software. We could use config files to store each version, and include links to download, documentation, and issue tracking. + + // config/versions.php + + array( + 'codename' => 'Frog', + 'download' => 'files/ourapp-1.0.0.tar.gz', + 'documentation' => 'docs/1.0.0', + 'released' => '06/05/2009', + 'issues' => 'link/to/bug/tracker', + ), + '1.1.0' => array( + 'codename' => 'Lizard', + 'download' => 'files/ourapp-1.1.0.tar.gz', + 'documentation' => 'docs/1.1.0', + 'released' => '10/15/2009', + 'issues' => 'link/to/bug/tracker', + ), + /// ... etc ... + ); + +You could then do the following: + + // In your controller + $view->versions = Kohana::config('versions'); + + // In your view: + foreach ($versions as $version) + { + // echo some html to display each version + } diff --git a/includes/kohana/system/guide/kohana/files/i18n.md b/includes/kohana/system/guide/kohana/files/i18n.md new file mode 100644 index 0000000..52efc81 --- /dev/null +++ b/includes/kohana/system/guide/kohana/files/i18n.md @@ -0,0 +1,67 @@ +# I18n + +Kohana has a fairly simple and easy to use i18n system. It is slightly modeled after gettext, but is not as featureful. If you need the features of gettext, please use that :) + +## __() + +Kohana has a __() function to do your translations for you. This function is only meant for small sections of text, not entire paragraphs or pages of translated text. + +To echo a translated string: + + + +This will echo 'Home' unless you've changed the defined language, which is explained below. + +## Changing the displayed language + +Use the I18n::lang() method to change the displayed language: + + I18n::lang('fr'); + +This will change the language to 'es-es'. + +## Defining language files + +To define the language file for the above language change, create a `i18n/fr.php` that contains: + + 'Bonjour, monde!', + ); + +Now when you do `__('Hello, world!')`, you will get `Bonjour, monde!` + +## I18n variables + +You can define variables in your __() calls like so: + + echo __('Hello, :user', array(':user' => $username)); + +Your i18n key in your translation file will need to be defined as: + + 'Bonjour, :user', + ); + +## Defining your own __() function + +You can define your own __() function by simply defining your own i18n class: + + 'Hello, world!', + ); + +You can also look in subfolders and sub-keys: + + Kohana::message('forms/contact', 'foobar.bar'); + +This will look in the `messages/forms/contact.php` for the `[foobar][bar]` key: + + array( + 'bar' => 'Hello, world!', + ), + ); + +## Notes + + * Don't use __() in your messages files, as these files can be cached and will not work properly. + * Messages are merged by the cascading file system, not overwritten like config files. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/flow.md b/includes/kohana/system/guide/kohana/flow.md new file mode 100644 index 0000000..96c55cb --- /dev/null +++ b/includes/kohana/system/guide/kohana/flow.md @@ -0,0 +1,27 @@ +# Request Flow + +Every application follows the same flow: + +1. Application starts from `index.php`. + 1. The application, module, and system paths are set. (`APPPATH`, `MODPATH`, and `SYSPATH`) + 2. Error reporting levels are set. + 3. Install file is loaded, if it exists. + 4. The [Kohana] class is loaded. + 5. The bootstrap file, `APPPATH/bootstrap.php`, is included. +2. Once we are in `bootstrap.php`: + 7. [Kohana::init] is called, which sets up error handling, caching, and logging. + 8. [Kohana_Config] readers and [Kohana_Log] writers are attached. + 9. [Kohana::modules] is called to enable additional modules. + * Module paths are added to the [cascading filesystem](files). + * Includes each module's `init.php` file, if it exists. + * The `init.php` file can perform additional environment setup, including adding routes. + 10. [Route::set] is called multiple times to define the [application routes](routing). + 11. [Request::instance] is called to start processing the request. + 1. Checks each route that has been set until a match is found. + 2. Creates the controller instance and passes the request to it. + 3. Calls the [Controller::before] method. + 4. Calls the controller action, which generates the request response. + 5. Calls the [Controller::after] method. + * The above 5 steps can be repeated multiple times when using [HMVC sub-requests](requests). +3. Application flow returns to index.php + 12. The main [Request] response is displayed \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/fragments.md b/includes/kohana/system/guide/kohana/fragments.md new file mode 100644 index 0000000..fa4f0e3 --- /dev/null +++ b/includes/kohana/system/guide/kohana/fragments.md @@ -0,0 +1,135 @@ +# Fragments + +Fragments are a quick and simple way to cache HTML or other output. Fragments are not useful for caching objects or raw database results, in which case you should use a more robust caching method, which can be achieved with the [Cache module](../cache). Fragments use [Kohana::cache()] and will be placed in the cache directory (`application/cache` by default). + +You should use Fragment (or any caching solution) when reading the cache is faster than reprocessing the result. Reading and parsing a remote file, parsing a complicated template, calculating something, etc. + +Fragments are typically used in view files. + +## Usage + +Fragments are used by calling [Fragment::load()] in an `if` statement at the beginning of what you want cached, and [Fragment::save()] at the end. They use [output buffering](http://www.php.net/manual/en/function.ob-start.php) to capture the output between the two function calls. + +You can specify the lifetime (in seconds) of the Fragment using the second parameter of [Fragment::load()]. The default lifetime is 30 seconds. You can use the [Date] helper to make more readable times. + +Fragments will store a different cache for each language (using [I18n]) if you pass `true` as the third parameter to [Fragment::load()]; + +You can force the deletion of a Fragment using [Fragment::delete()], or specify a lifetime of 0. + +~~~ +// Cache for 5 minutes, and cache each language +if ( ! Fragment::load('foobar', Date::MINUTE * 5, true)) +{ + // Anything that is echo'ed here will be saved + Fragment::save(); +} +~~~ + +## Example: Calculating Pi + +In this example we will calculate pi to 1000 places, and cache the result using a fragment. The first time you run this it will probably take a few seconds, but subsequent loads will be much faster, until the fragment lifetime runs out. + +~~~ +if ( ! Fragment::load('pi1000', Date::HOUR * 4)) +{ + // Change function nesting limit + ini_set('xdebug.max_nesting_level',1000); + + // Source: http://mgccl.com/2007/01/22/php-calculate-pi-revisited + function bcfact($n) + { + return ($n == 0 || $n== 1) ? 1 : bcmul($n,bcfact($n-1)); + } + function bcpi($precision) + { + $num = 0;$k = 0; + bcscale($precision+3); + $limit = ($precision+3)/14; + while($k < $limit) + { + $num = bcadd($num, bcdiv(bcmul(bcadd('13591409',bcmul('545140134', $k)),bcmul(bcpow(-1, $k), bcfact(6*$k))),bcmul(bcmul(bcpow('640320',3*$k+1),bcsqrt('640320')), bcmul(bcfact(3*$k), bcpow(bcfact($k),3))))); + ++$k; + } + return bcdiv(1,(bcmul(12,($num))),$precision); + } + + echo bcpi(1000); + + Fragment::save(); +} + +echo View::factory('profiler/stats'); + +?> +~~~ + +## Example: Recent Wikipedia edits + +In this example we will use the [Feed] class to retrieve and parse an RSS feed of recent edits to [http://en.wikipedia.org](http://en.wikipedia.org), then use Fragment to cache the results. + +~~~ +$feed = "http://en.wikipedia.org/w/index.php?title=Special:RecentChanges&feed=rss"; +$limit = 50; + +// Displayed feeds are cached for 30 seconds (default) +if ( ! Fragment::load('rss:'.$feed)): + + // Parse the feed + $items = Feed::parse($feed, $limit); + + foreach ($items as $item): + + // Convert $item to object + $item = (object) $item; + + echo HTML::anchor($item->link,$item->title); + + ?> +
                    +

                    author: creator ?>

                    +

                    date: pubDate ?>

                    +
                    + Home page stuff

                    "; + + // Pretend like we are actually doing something :) + sleep(2); + + // Cache this every hour since it doesn't change as often + if ( ! Fragment::load('homepage-subfragment', Date::HOUR)): + + echo "

                    Home page special thingy

                    "; + + // Pretend like this takes a long time + sleep(5); + + Fragment::save(); endif; + + echo "

                    More home page stuff

                    "; + + Fragment::save(); + +endif; + +echo View::factory('profiler/stats'); +~~~ \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/helpers.md b/includes/kohana/system/guide/kohana/helpers.md new file mode 100644 index 0000000..fb0559d --- /dev/null +++ b/includes/kohana/system/guide/kohana/helpers.md @@ -0,0 +1,53 @@ +# Helpers + +Kohana comes with many static "helper" functions to make certain tasks easier. + +You can make your own helpers by simply making a class and putting it in the `classes` directory, and you can also extend any helper to modify or add new functions using transparent extension. + + - **[Arr]** - Array functions. Get an array key or default to a set value, get an array key by path, etc. + + - **[CLI]** - Parse command line options. + + - **[Cookie]** - Covered in more detail on the [Cookies](cookies) page. + + - **[Date]** - Useful date functions and constants. Time between two dates, convert between am/pm and military, date offset, etc. + + - **[Encrypt]** - Covered in more detail on the [Security](security) page. + + - **[Feed]** - Parse and create RSS feeds. + + - **[File]** - Get file type by mime, split and merge a file into small pieces. + + - **[Form]** - Create HTML form elements. + + - **[Fragment]** - Simple file based caching. Covered in more detail on the [Fragments](fragments) page. + + - **[HTML]** - Useful HTML functions. Encode, obfuscate, create script, anchor, and image tags, etc. + + - **[I18n]** - Internationalization helper for creating multilanguage sites. + + - **[Inflector]** - Change a word into plural or singular form, camelize or humanize a phrase, etc. + + - **[Kohana]** - The Kohana class is also a helper. Debug variables (like print_r but better), file loading, etc. + + - **[Num]** - Provides locale aware formating and english ordinals (th, st, nd, etc). + + - **[Profiler]** - Covered in more detail on the [Profiling](profiling) page. + + - **[Remote]** - Remote server access helper using [CURL](http://php.net/curl). + + - **[Request]** - Get the current request url, create expire tags, send a file, get the user agent, etc. + + - **[Route]** - Create routes, create an internal link using a route. + + - **[Security]** - Covered in more detail on the [Security](security) page. + + - **[Session]** - Covered in more detail on the [Sessions](sessions) page. + + - **[Text]** - Autolink, prevent window words, convert a number to text, etc. + + - **[URL]** - Create a relative or absolute URL, make a URL-safe title, etc. + + - **[UTF8]** - Provides multi-byte aware string functions like strlen, strpos, substr, etc. + + - **[Upload]** - Helper for uploading files from a form. diff --git a/includes/kohana/system/guide/kohana/index.md b/includes/kohana/system/guide/kohana/index.md new file mode 100644 index 0000000..8e08466 --- /dev/null +++ b/includes/kohana/system/guide/kohana/index.md @@ -0,0 +1,19 @@ +# What is Kohana? + +Kohana is an open source, [object oriented](http://wikipedia.org/wiki/Object-Oriented_Programming) [MVC](http://wikipedia.org/wiki/Model–View–Controller "Model View Controller") [web framework](http://wikipedia.org/wiki/Web_Framework) built using [PHP5](http://php.net/manual/intro-whatis "PHP Hypertext Preprocessor") by a team of volunteers that aims to be swift, secure, and small. + +[!!] Kohana is licensed under a [BSD license](http://kohanaframework.org/license), so you can legally use it for any kind of open source, commercial, or personal project. + +## What makes Kohana great? + +Anything can be extended using the unique [filesystem](about.filesystem) design, little or no [configuration](about.configuration) is necessary, [error handling](debugging.errors) helps locate the source of errors quickly, and [debugging](debugging) and [profiling](debugging.profiling) provide insight into the application. + +To help secure your applications, tools for [input validation](security.validation), [signed cookies](security.cookies), [form](security.forms) and [HTML](security.html) generators are all included. The [database](security.database) layer provides protection against [SQL injection](http://wikipedia.org/wiki/SQL_Injection). Of course, all official code is carefully written and reviewed for security. + +## Contribute to the Documentation + +We are working very hard to provide complete documentation. To help improve the guide, please [fork the userguide](http://github.com/kohana/userguide), make your changes, and send a pull request. If you are not familiar with git, you can also submit a [feature request](http://dev.kohanaframework.org/projects/kohana3/issues) (requires registration). + +## Unofficial Documentation + +If you are having trouble finding an answer here, have a look through the [unofficial wiki](http://kerkness.ca/wiki/doku.php). Your answer may also be found by searching the [forum](http://forum.kohanaphp.com/) or [stackoverflow](http://stackoverflow.com/questions/tagged/kohana) followed by asking your question on either. Additionally, you can chat with the community of developers on the freenode [#kohana](irc://irc.freenode.net/kohana) IRC channel. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/install.md b/includes/kohana/system/guide/kohana/install.md new file mode 100644 index 0000000..c440d45 --- /dev/null +++ b/includes/kohana/system/guide/kohana/install.md @@ -0,0 +1,26 @@ +# Installation + +1. Download the latest **stable** release from the [Kohana website](http://kohanaframework.org/). +2. Unzip the downloaded package to create a `kohana` directory. +3. Upload the contents of this folder to your webserver. +4. Open `application/bootstrap.php` and make the following changes: + - Set the default [timezone](http://php.net/timezones) for your application. + - Set the `base_url` in the [Kohana::init] call to reflect the location of the kohana folder on your server relative to the document root. +6. Make sure the `application/cache` and `application/logs` directories are writable by the web server. +7. Test your installation by opening the URL you set as the `base_url` in your favorite browser. + +[!!] Depending on your platform, the installation's subdirs may have lost their permissions thanks to zip extraction. Chmod them all to 755 by running `find . -type d -exec chmod 0755 {} \;` from the root of your Kohana installation. + +You should see the installation page. If it reports any errors, you will need to correct them before continuing. + +![Install Page](install.png "Example of install page") + +Once your install page reports that your environment is set up correctly you need to either rename or delete `install.php` in the root directory. Kohana is now installed and you should see the output of the welcome controller: + +![Welcome Page](welcome.png "Example of welcome page") + +## Installing Kohana 3.1 From GitHub + +The [source code](http://github.com/kohana/kohana) for Kohana 3.1 is hosted with [GitHub](http://github.com). To install Kohana using the github source code first you need to install git. Visit [http://help.github.com](http://help.github.com) for details on how to install git on your platform. + +[!!] For more information on installing Kohana using git submodules, see the [Working with Git](tutorials/git) tutorial. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/menu.md b/includes/kohana/system/guide/kohana/menu.md new file mode 100644 index 0000000..b47b513 --- /dev/null +++ b/includes/kohana/system/guide/kohana/menu.md @@ -0,0 +1,47 @@ +## [Kohana]() + +- Getting Started + - [Installation](install) + - [Conventions and Style](conventions) + - [Model View Controller](mvc) + - [Controllers](mvc/controllers) + - [Models](mvc/models) + - [Views](mvc/views) + - [Cascading Filesystem](files) + - [Class Files](files/classes) + - [Config Files](files/config) + - [Translation Files](files/i18n) + - [Message Files](files/messages) + - [Request Flow](flow) + - [Bootstrap](bootstrap) + - [Modules](modules) + - [Routing](routing) + - [Error Handling](errors) + - [Tips & Common Mistakes](tips) + - [Upgrading from v3.0](upgrading) +- Basic Usage + - [Debugging](debugging) + - [Loading Classes](autoloading) + - [Transparent Extension](extension) + - [Helpers](helpers) + - [Requests](requests) + - [Sessions](sessions) + - [Cookies](cookies) + - [Fragments](fragments) + - [Profiling](profiling) +- [Security](security) + - [XSS](security/xss) + - [Validation](security/validation) + - [Cookies](security/cookies) + - [Database](security/database) + - [Encryption](security/encryption) + - [Deploying](security/deploying) +- [Tutorials](tutorials) + - [Hello World](tutorials/hello-world) + - [Simple MVC](tutorials/simple-mvc) + - [Custom Error Pages](tutorials/error-pages) + - [Content Translation](tutorials/translation) + - [Clean URLs](tutorials/clean-urls) + - [Sharing Kohana](tutorials/sharing-kohana) + - [Template Driven Site](tutorials/templates) + - [Working with Git](tutorials/git) diff --git a/includes/kohana/system/guide/kohana/modules.md b/includes/kohana/system/guide/kohana/modules.md new file mode 100644 index 0000000..bc906c4 --- /dev/null +++ b/includes/kohana/system/guide/kohana/modules.md @@ -0,0 +1,40 @@ +# Modules + +Modules are simply an addition to the [Cascading Filesystem](files). A module can add any kind of file (controllers, views, classes, config files, etc.) to the filesystem available to Kohana (via [Kohana::find_file]). This is useful to make any part of your application more transportable or shareable between different apps. For example, creating a new modeling system, a search engine, a css/js manager, etc. + +## Where to find modules + +Kolanos has created [kohana-universe](http://github.com/kolanos/kohana-universe/tree/master/modules/), a fairly comprehensive list of modules that are available on Github. To get your module listed there, send him a message via Github. + +Mon Geslani created a [very nice site](http://kohana.mongeslani.com/) that allows you to sort Github modules by activity, watchers, forks, etc. It seems to not be as comprehensive as kohana-universe. + +Andrew Hutchings has created [kohana-modules](http://www.kohana-modules.com) which is similar to the above sites. + +## Enabling modules + +Modules are enabled by calling [Kohana::modules] and passing an array of `'name' => 'path'`. The name isn't important, but the path obviously is. A module's path does not have to be in `MODPATH`, but usually is. You can only call [Kohana::modules] once. + + Kohana::modules(array( + 'auth' => MODPATH.'auth', // Basic authentication + 'cache' => MODPATH.'cache', // Caching with multiple backends + 'codebench' => MODPATH.'codebench', // Benchmarking tool + 'database' => MODPATH.'database', // Database access + 'image' => MODPATH.'image', // Image manipulation + 'orm' => MODPATH.'orm', // Object Relationship Mapping + 'oauth' => MODPATH.'oauth', // OAuth authentication + 'pagination' => MODPATH.'pagination', // Paging of results + 'unittest' => MODPATH.'unittest', // Unit testing + 'userguide' => MODPATH.'userguide', // User guide and API documentation + )); + +## Init.php + +When a module is activated, if an `init.php` file exists in that module's directory, it is included. This is the ideal place to have a module include routes or other initialization necessary for the module to function. The Userguide and Codebench modules have init.php files you can look at. + +## How modules work + +A file in an enabled module is virtually the same as having that exact file in the same place in the application folder. The main difference being that it can be overwritten by a file of the same name in a higher location (a module enabled after it, or the application folder) via the [Cascading Filesystem](files). It also provides an easy way to organize and share your code. + +## Creating your own module + +To create a module simply create a folder (usually in `DOCROOT/modules`) and place the files you want to be in the module there, and activate that module in your bootstrap. To share your module, you can upload it to [Github](http://github.com). You can look at examples of modules made by [Kohana](http://github.com/kohana) or [other users](#where-to-find-modules). \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/mvc.md b/includes/kohana/system/guide/kohana/mvc.md new file mode 100644 index 0000000..2ee8549 --- /dev/null +++ b/includes/kohana/system/guide/kohana/mvc.md @@ -0,0 +1,3 @@ + + +Discus the MVC pattern, as it pertains to Kohana. Perhaps have an image, etc. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/mvc/controllers.md b/includes/kohana/system/guide/kohana/mvc/controllers.md new file mode 100644 index 0000000..f8d840b --- /dev/null +++ b/includes/kohana/system/guide/kohana/mvc/controllers.md @@ -0,0 +1,182 @@ +# Controllers + +A Controller is a class file that stands in between the models and the views in an application. It passes information on to the model when data needs to be changed and it requests information from the model when data needs to be loaded. Controllers then pass on the information of the model to the views where the final output can be rendered for the users. Controllers essentially control the flow of the application. + +Controllers are called by the [Request::execute()] function based on the [Route] that the url matched. Be sure to read the [routing](routing) page to understand how to use routes to map urls to your controllers. + +## Creating Controllers + +In order to function, a controller must do the following: + +* Reside in `classes/controller` (or a sub-directory) +* Filename must be lowercase, e.g. `articles.php` +* The class name must map to the filename (with `/` replaced with `_`) and each word is capitalized +* Must have the Controller class as a (grand)parent + +Some examples of controller names and file locations: + + // classes/controller/foobar.php + class Controller_Foobar extends Controller { + + // classes/controller/admin.php + class Controller_Admin extends Controller { + +Controllers can be in sub-folders: + + // classes/controller/baz/bar.php + class Controller_Baz_Bar extends Controller { + + // classes/controller/product/category.php + class Controller_Product_Category extends Controller { + +[!!] Note that controllers in sub-folders can not be called by the default route, you will need to define a route that has a [directory](routing#directory) param or sets a default value for directory. + +Controllers can extend other controllers. + + // classes/controller/users.php + class Controller_Users extends Controller_Template + + // classes/controller/api.php + class Controller_Api extends Controller_REST + +[!!] [Controller_Template] and [Controller_REST] are some example controllers provided in Kohana. + +You can also have a controller extend another controller to share common things, such as requiring you to be logged in to use all of those controllers. + + // classes/controller/admin.php + class Controller_Admin extends Controller { + // This controller would have a before() that checks if the user is logged in + + // classes/controller/admin/plugins.php + class Controller_Admin_Plugins extends Controller_Admin { + // Because this controller extends Controller_Admin, it would have the same logged in check + +## $this->request + +Every controller has the `$this->request` property which is the [Request] object that called the controller. You can use this to get information about the current request, as well as set the response body via `$this->response->body($ouput)`. + +Here is a partial list of the properties and methods available to `$this->request`. These can also be accessed via `Request::instance()`, but `$this->request` is provided as a shortcut. See the [Request] class for more information on any of these. + +Property/method | What it does +--- | --- +[$this->request->route()](../api/Request#property:route) | The [Route] that matched the current request url +[$this->request->directory()](../api/Request#property:directory),
                    [$this->request->controller](../api/Request#property:controller),
                    [$this->request->action](../api/Request#property:action) | The directory, controller and action that matched for the current route +[$this->request->param()](../api/Request#param) | Any other params defined in your route +[$this->request->redirect()](../api/Request#redirect) | Redirect the request to a different url + +## $this->response +[$this->response->body()](../api/Response#property:body) | The content to return for this request +[$this->response->status()](../api/Response#property:status) | The HTTP status for the request (200, 404, 500, etc.) +[$this->response->headers()](../api/Response#property:headers) | The HTTP headers to return with the response + + +## Actions + +You create actions for your controller by defining a public function with an `action_` prefix. Any method that is not declared as `public` and prefixed with `action_` can NOT be called via routing. + +An action method will decide what should be done based on the current request, it *controls* the application. Did the user want to save a blog post? Did they provide the necessary fields? Do they have permission to do that? The controller will call other classes, including models, to accomplish this. Every action should set `$this->response->body($view)` to the [view file](mvc/views) to be sent to the browser, unless it [redirected](../api/Request#redirect) or otherwise ended the script earlier. + +A very basic action method that simply loads a [view](mvc/views) file. + + public function action_hello() + { + $this->response->body(View::factory('hello/world')); // This will load views/hello/world.php + } + +### Parameters + +Parameters are accessed by calling `$this->request->param('name')` where `name` is the name defined in the route. + + // Assuming Route::set('example','(/(/(/)))'); + + public function action_foobar() + { + $id = $this->request->param('id'); + $new = $this->request->param('new'); + +If that parameter is not set it will be returned as NULL. You can provide a second parameter to set a default value if that param is not set. + + public function action_foobar() + { + // $id will be false if it was not supplied in the url + $id = $this->request->param('user',FALSE); + +### Examples + +A view action for a product page. + + public function action_view() + { + $product = new Model_Product($this->request->param('id')); + + if ( ! $product->loaded()) + { + throw new HTTP_Exception_404('Product not found!'); + } + + $this->response->body(View::factory('product/view') + ->set('product', $product)); + } + +A user login action. + + public function action_login() + { + $view = View::factory('user/login'); + + if ($_POST) + { + // Try to login + if (Auth::instance()->login(arr::get($_POST, 'username'), arr::get($_POST, 'password'))) + { + Request::current()->redirect('home'); + } + + $view->errors = 'Invalid email or password'; + } + + $this->response->body($view); + } + +## Before and after + +You can use the `before()` and `after()` functions to have code executed before or after the action is executed. For example, you could check if the user is logged in, set a template view, loading a required file, etc. + +For example, if you look in `Controller_Template` you can see that in the be + +You can check what action has been requested (via `$this->request->action`) and do something based on that, such as requiring the user to be logged in to use a controller, unless they are using the login action. + + // Checking auth/login in before, and redirecting if necessary: + + Controller_Admin extends Controller { + + public function before() + { + // If this user doesn't have the admin role, and is not trying to login, redirect to login + if ( ! Auth::instance()->logged_in('admin') AND $this->request->action !== 'login') + { + $this->request->redirect('admin/login'); + } + } + + public function action_login() { + ... + +### Custom __construct() function + +In general, you should not have to change the `__construct()` function, as anything you need for all actions can be done in `before()`. If you need to change the controller constructor, you must preserve the parameters or PHP will complain. This is so the Request object that called the controller is available. *Again, in most cases you should probably be using `before()`, and not changing the constructor*, but if you really, *really* need to it should look like this: + + // You should almost never need to do this, use before() instead! + + // Be sure Kohana_Request is in the params + public function __construct(Request $request, Response $response) + { + // You must call parent::__construct at some point in your function + parent::__construct($request, $response); + + // Do whatever else you want + } + +## Extending other controllers + +TODO: More description and examples of extending other controllers, multiple extension, etc. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/mvc/models.md b/includes/kohana/system/guide/kohana/mvc/models.md new file mode 100644 index 0000000..bfcf240 --- /dev/null +++ b/includes/kohana/system/guide/kohana/mvc/models.md @@ -0,0 +1,35 @@ +# Models + +From Wikipedia: + + > The model manages the behavior and data of the application domain, + > responds to requests for information about its state (usually from the view), + > and responds to instructions to change state (usually from the controller). + +Creating a simple model: + + class Model_Post extends Model + { + public function do_stuff() + { + // This is where you do domain logic... + } + } + +If you want database access, have your model extend the Model_Database class: + + class Model_Post extends Model_Database + { + public function do_stuff() + { + // This is where you do domain logic... + } + + public function get_stuff() + { + // Get stuff from the database: + return $this->db->query(...); + } + } + +If you want CRUD/ORM capabilities, see the [ORM Module](../../guide/orm) \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/mvc/views.md b/includes/kohana/system/guide/kohana/mvc/views.md new file mode 100644 index 0000000..4ff0543 --- /dev/null +++ b/includes/kohana/system/guide/kohana/mvc/views.md @@ -0,0 +1,153 @@ +# Views + +Views are files that contain the display information for your application. This is most commonly HTML, CSS and Javascript but can be anything you require such as XML or JSON for AJAX output. The purpose of views is to keep this information separate from your application logic for easy reusability and cleaner code. + +Views themselves can contain code used for displaying the data you pass into them. For example, looping through an array of product information and display each one on a new table row. Views are still PHP files so you can use any code you normally would. However, you should try to keep your views as "dumb" as possible and retreive all data you need in your controllers, then pass it to the view. + +# Creating View Files + +View files are stored in the `views` directory of the [filesystem](files). You can also create sub-directories within the `views` directory to organize your files. All of the following examples are reasonable view files: + + APPPATH/views/home.php + APPPATH/views/pages/about.php + APPPATH/views/products/details.php + MODPATH/error/views/errors/404.php + MODPATH/common/views/template.php + +## Loading Views + +[View] objects will typically be created inside a [Controller](mvc/controllers) using the [View::factory] method. Typically the view is then assigned as the [Request::$response] property or to another view. + + public function action_about() + { + $this->response->body(View::factory('pages/about')); + } + +When a view is assigned as the [Response::body], as in the example above, it will automatically be rendered when necessary. To get the rendered result of a view you can call the [View::render] method or just type cast it to a string. When a view is rendered, the view file is loaded and HTML is generated. + + public function action_index() + { + $view = View::factory('pages/about'); + + // Render the view + $about_page = $view->render(); + + // Or just type cast it to a string + $about_page = (string) $view; + + $this->response->body($about_page); + } + +## Variables in Views + +Once view has been loaded, variables can be assigned to it using the [View::set] and [View::bind] methods. + + public function action_roadtrip() + { + $view = View::factory('user/roadtrip') + ->set('places', array('Rome', 'Paris', 'London', 'New York', 'Tokyo')); + ->bind('user', $this->user); + + // The view will have $places and $user variables + $this->response->body($view); + } + +[!!] The only difference between `set()` and `bind()` is that `bind()` assigns the variable by reference. If you `bind()` a variable before it has been defined, the variable will be created with a value of `NULL`. + +You can also assign variables directly to the View object. This is identical to calling `set()`; + + public function action_roadtrip() + { + $view = View::factory('user/roadtrip'); + + $view->places = array('Rome', 'Paris', 'London', 'New York', 'Tokyo'); + $view->user = $this->user; + + // The view will have $places and $user variables + $this->response->body($view); + } + +### Global Variables + +An application may have several view files that need access to the same variables. For example, to display a page title in both the header of your template and in the body of the page content. You can create variables that are accessible in any view using the [View::set_global] and [View::bind_global] methods. + + // Assign $page_title to all views + View::bind_global('page_title', $page_title); + +If the application has three views that are rendered for the home page: `template`, `template/sidebar`, and `pages/home`. First, an abstract controller to create the template will be created: + + abstract class Controller_Website extends Controller_Template { + + public $page_title; + + public function before() + { + parent::before(); + + // Make $page_title available to all views + View::bind_global('page_title', $this->page_title); + + // Load $sidebar into the template as a view + $this->template->sidebar = View::factory('template/sidebar'); + } + + } + +Next, the home controller will extend `Controller_Website`: + + class Controller_Home extends Controller_Website { + + public function action_index() + { + $this->page_title = 'Home'; + + $this->template->content = View::factory('pages/home'); + } + + } + +## Views Within Views + +If you want to include another view within a view, there are two choices. By calling [View::factory] you can sandbox the included view. This means that you will have to provide all of the variables to the view using [View::set] or [View::bind]: + + // In your view file: + + // Only the $user variable will be available in "views/user/login.php" + bind('user', $user) ?> + +The other option is to include the view directly, which makes all of the current variables available to the included view: + + // In your view file: + + // Any variable defined in this view will be included in "views/message.php" + + +You can also assign a variable of your parent view to be the child view from within your controller. For example: + + // In your controller: + + public functin action_index() + { + $view = View::factory('common/template); + + $view->title = "Some title"; + $view->body = View::factory('pages/foobar'); + } + + // In views/common/template.php: + + + + <?php echo $title> + + + + + + + +Of course, you can also load an entire [Request] within a view: + + execute() ?> + +This is an example of \[HMVC], which makes it possible to create and read calls to other URLs within your application. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/profiling.md b/includes/kohana/system/guide/kohana/profiling.md new file mode 100644 index 0000000..e7303c8 --- /dev/null +++ b/includes/kohana/system/guide/kohana/profiling.md @@ -0,0 +1,54 @@ +# Profiling + +Kohana provides a very simple way to display statistics about your application: + +1. Common [Kohana] method calls, such as [Kohana::find_file()]. +2. Requests. Including the main request, as well as any sub-requests. +3. [Database] queries +4. Average execution times for your application + +[!!] In order for profiling to work, the `profile` setting must be `TRUE` in your [Kohana::init()] call in your bootstrap. + +## Profiling your code + +You can easily add profiling to your own functions and code. This is done using the [Profiler::start()] function. The first parameter is the group, the second parameter is the name of the benchmark. + + public function foobar($input) + { + // Be sure to only profile if it's enabled + if (Kohana::$profiling === TRUE) + { + // Start a new benchmark + $benchmark = Profiler::start('Your Category', __FUNCTION__); + } + + // Do some stuff + + if (isset($benchmark)) + { + // Stop the benchmark + Profiler::stop($benchmark); + } + + return $something; + } + +## How to read the profiling report + +The benchmarks are sorted into groups. Each benchmark will show its name, how many times it was run (show in parenthesis after the benchmark name), and then the min, max, average, and total time and memory spent on that benchmark. The total column will have shaded backgrounds to show the relative times between benchmarks in the same group. + +At the very end is a group called "Application Execution". This keeps track of how long each execution has taken. The number in parenthesis is how many executions are being compared. It shows the fastest, slowest, and average time and memory usage of the last several requsets. The last box is the time and memory usage of the current request. + +((This could use a picture of a profiler with some database queries, etc. with annotations to point out each area as just described.)) + +## Displaying the profiler + +You can display or collect the current [profiler] statistics at any time: + + + +## Preview + +(This is the actual profiler stats for this page.) + +{{profiler/stats}} \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/requests.md b/includes/kohana/system/guide/kohana/requests.md new file mode 100644 index 0000000..52720d7 --- /dev/null +++ b/includes/kohana/system/guide/kohana/requests.md @@ -0,0 +1,65 @@ +# Requests + +Kohana includes a flexible HMVC request system. It supports out of the box support for internal requests and external requests. + +HMVC stands for `Hierarchical Model View Controller` and basically means requests can each have MVC triads called from inside each other. + +The Request object in Kohana is HTTP/1.1 compliant. + +## Creating Requests + +Creating a request is very easy: + +### Internal Requests + +An internal request is a request calling to the internal application. It utilizes [routes](routing) to direct the application based on the URI that is passed to it. A basic internal request might look something like: + + $request = Request::factory('welcome'); + +In this example, the URI is 'welcome'. + +#### The initial request + +Since Kohana uses HMVC, you can call many requests inside each other. The first request (usually called from `index.php`) is called the "initial request". You can access this request via: + + Request::initial(); + +You should only use this method if you are absolutely sure you want the initial request. Otherwise you should use the `Request::current()` method. + +#### Sub-requests + +You can call a request at any time in your application by using the `Request::factory()` syntax. All of these requests will be considered sub-requests. + +Other than this difference, they are exactly the same. You can detect if the request is a sub-request in your controller with the is_initial() method: + + $sub_request = ! $this->request->is_initial() + +### External Requests + +An external request calls out to a third party website. + +You can use this to scrape HTML from a remote site, or make a REST call to a third party API: + + // This uses GET + $request = Request::factory('http://www.google.com/'); + + // This uses PUT + $request = Request::factory('http://example.com/put_api')->method(Request::PUT)->body(json_encode('the body'))->headers('Content-Type', 'application/json'); + + // This uses POST + $request = Request::factory('http://example.com/post_api')->method(Request::POST)->post(array('foo' => 'bar', 'bar' => 'baz')); + +## Executing Requests + +To execute a request, use the `execute()` method on it. This will give you a [response](responses) object. + + $request = Request::factory('welcome'); + $response = $request->execute(); + +## Request Cache Control + +You can cache requests for fast execution by passing a cache instance in as the second parameter of factory: + + $request = Request::factory('welcome', Cache::instance()); + +TODO diff --git a/includes/kohana/system/guide/kohana/routing.md b/includes/kohana/system/guide/kohana/routing.md new file mode 100644 index 0000000..85eacbd --- /dev/null +++ b/includes/kohana/system/guide/kohana/routing.md @@ -0,0 +1,271 @@ +# Routing + +Kohana provides a very powerful routing system. In essence, routes provide an interface between the urls and your controllers and actions. With the correct routes you could make almost any url scheme correspond to almost any arrangement of controllers, and you could change one without impacting the other. + +As mentioned in the [Request Flow](flow) section, a request is handled by the [Request] class, which will look for a matching [Route] and load the appropriate controller to handle that request. + +[!!] It is important to understand that **routes are matched in the order they are added**, and as soon as a URL matches a route, routing is essentially "stopped" and *the remaining routes are never tried*. Because the default route matches almost anything, including an empty url, new routes must be place before it. + +## Creating routes + +If you look in `APPPATH/bootstrap.php` you will see the "default" route as follows: + + Route::set('default', '((/(/)))') + ->defaults(array( + 'controller' => 'welcome', + 'action' => 'index', + )); + +[!!] The default route is simply provided as a sample, you can remove it and replace it with your own routes. + +So this creates a route with the name `default` that will match urls in the format of `((/(/)))`. + +Let's take a closer look at each of the parameters of [Route::set], which are `name`, `uri`, and an optional array `regex`. + +### Name + +The name of the route must be a **unique** string. If it is not it will overwrite the older route with the same name. The name is used for creating urls by reverse routing, or checking which route was matched. + +### URI + +The uri is a string that represents the format of urls that should be matched. The tokens surrounded with `<>` are *keys* and anything surrounded with `()` are *optional* parts of the uri. In Kohana routes, any character is allowed and treated literally aside from `()<>`. The `/` has no meaning besides being a character that must match in the uri. Usually the `/` is used as a static seperator but as long as the regex makes sense, there are no restrictions to how you can format your routes. + +Lets look at the default route again, the uri is `((/(/)))`. We have three keys or params: controller, action, and id. In this case, the entire uri is optional, so a blank uri would match and the default controller and action (set by defaults(), [covered below](#defaults)) would be assumed resulting in the `Controller_Welcome` class being loaded and the `action_index` method being called to handle the request. + +You can use any name you want for your keys, but the following keys have special meaning to the [Request] object, and will influence which controller and action are called: + + * **Directory** - The sub-directory of `classes/controller` to look for the controller (\[covered below]\(#directory)) + * **Controller** - The controller that the request should execute. + * **Action** - The action method to call. + +### Regex + +The Kohana route system uses [perl compatible regular expressions](http://perldoc.perl.org/perlre.html) in its matching process. By default each key (surrounded by `<>`) will match `[^/.,;?\n]++` (or in english: anything that is not a slash, period, comma, semicolon, question mark, or newline). You can define your own patterns for each key by passing an associative array of keys and patterns as an additional third argument to Route::set. + +In this example, we have controllers in two directories, `admin` and `affiliate`. Because this route will only match urls that begin with `admin` or `affiliate`, the default route would still work for controllers in `classes/controller`. + + Route::set('sections', '(/(/(/)))', + array( + 'directory' => '(admin|affiliate)' + )) + ->defaults(array( + 'controller' => 'home', + 'action' => 'index', + )); + +You can also use a less restrictive regex to match unlimited parameters, or to ignore overflow in a route. In this example, the url `foobar/baz/and-anything/else_that/is-on-the/url` would be routed to `Controller_Foobar::action_baz()` and the `"stuff"` parameter would be `"and-anything/else_that/is-on-the/url"`. If you wanted to use this for unlimited parameters, you could [explode](http://php.net/manual/en/function.explode.php) it, or you just ignore the overflow. + + Route::set('default', '((/(/)))', array('stuff' => '.*')) + ->defaults(array( + 'controller' => 'welcome', + 'action' => 'index', + )); + + +### Default values + +If a key in a route is optional (or not present in the route), you can provide a default value for that key by passing an associated array of keys and default values to [Route::defaults], chained after your [Route::set]. This can be useful to provide a default controller or action for your site, among other things. + +[!!] The `controller` and `action` key must always have a value, so they either need to be required in your route (not inside of parentheses) or have a default value provided. + +In the default route, all the keys are optional, and the controller and action are given a default. If we called an empty url, the defaults would fill in and `Controller_Welcome::action_index()` would be called. If we called `foobar` then only the default for action would be used, so it would call `Controller_Foobar::action_index()` and finally, if we called `foobar/baz` then neither default would be used and `Controller_Foobar::action_baz()` would be called. + +TODO: need an example here + +You can also use defaults to set a key that isn't in the route at all. + +TODO: example of either using directory or controller where it isn't in the route, but set by defaults + +### Directory + +## Lambda/Callback route logic + +In 3.1, you can specify advanced routing schemes by using lambda routes. Instead of a URI, you can use an anonymous function or callback syntax to specify a function that will process your routes. Here's a simple example: + +If you want to use reverse routing with lambda routes, you must pass the third parameter: + + Route::set('testing', function($uri) + { + if ($uri == 'foo/bar') + return array( + 'controller' => 'welcome', + 'action' => 'foobar', + ); + }, + 'foo/bar' + ); + +As you can see in the below route, the reverse uri parameter might not make sense. + + Route::set('testing', function($uri) + { + if ($uri == '(.+)') + { + Cookie::set('language', $match[1]); + return array( + 'controller' => 'welcome', + 'action' => 'foobar' + ); + } + }, + '/ + ); + +If you are using php 5.2, you can still use callbacks for this behavior (this example omits the reverse route): + + Route::set('testing', array('Class', 'method_to_process_my_uri')); + +## Examples + +There are countless other possibilities for routes. Here are some more examples: + + /* + * Authentication shortcuts + */ + Route::set('auth', '', + array( + 'action' => '(login|logout)' + )) + ->defaults(array( + 'controller' => 'auth' + )); + + /* + * Multi-format feeds + * 452346/comments.rss + * 5373.json + */ + Route::set('feeds', '(/).', + array( + 'user_id' => '\d+', + 'format' => '(rss|atom|json)', + )) + ->defaults(array( + 'controller' => 'feeds', + 'action' => 'status', + )); + + /* + * Static pages + */ + Route::set('static', '.html', + array( + 'path' => '[a-zA-Z0-9_/]+', + )) + ->defaults(array( + 'controller' => 'static', + 'action' => 'index', + )); + + /* + * You don't like slashes? + * EditGallery:bahamas + * Watch:wakeboarding + */ + Route::set('gallery', '():', + array( + 'controller' => '[A-Z][a-z]++', + 'action' => '[A-Z][a-z]++', + )) + ->defaults(array( + 'controller' => 'Slideshow', + )); + + /* + * Quick search + */ + Route::set('search', ':', array('query' => '.*')) + ->defaults(array( + 'controller' => 'search', + 'action' => 'index', + )); + +## Request parameters + +The `directory`, `controller` and `action` can be accessed from the [Request] as public properties like so: + + // From within a controller: + $this->request->action(); + $this->request->controller(); + $this->request->directory(); + + // Can be used anywhere: + Request::current()->action(); + Request::current()->controller(); + Request::current()->directory(); + +All other keys specified in a route can be accessed via [Request::param()]: + + // From within a controller: + $this->request->param('key_name'); + + // Can be used anywhere: + Request::current()->param('key_name'); + +The [Request::param] method takes an optional second argument to specify a default return value in case the key is not set by the route. If no arguments are given, all keys are returned as an associative array. In addition, `action`, `controller` and `directory` are not accessible via [Request::param()]. + +For example, with the following route: + + Route::set('ads','ad/(/)') + ->defaults(array( + 'controller' => 'ads', + 'action' => 'index', + )); + +If a url matches the route, then `Controller_Ads::index()` will be called. You could access the parameters in two ways: + +First, any non-special parameters (parameters other than controller, action, and directory) in a route are passed as parameters to the action method in the order they appear in the route. Be sure to define a default value for optional parameters if you don't define them in the route's `->defaults()`. + + class Controller_Ads extends Controller { + public function action_index($ad, $affiliate = NULL) + { + + } + +Secondly, you can access the parameters using the `param()` method of the [Request] class. Again, remember to define a default value (via the second, optional parameter of [Request::param]) if you didn't in `->defaults()`. + + class Controller_Ads extends Controller { + public function action_index() + { + $ad = $this->request->param('ad'); + $affiliate = $this->request->param('affiliate',NULL); + } + + +## Where should routes be defined? + +The established convention is to either place your custom routes in the `MODPATH//init.php` file of your module if the routes belong to a module, or simply insert them into the `APPPATH/bootstrap.php` file (be sure to put them **above** the default route) if they are specific to the application. Of course, nothing stops you from including them from an external file, or even generating them dynamically. + +## A deeper look at how routes work + +TODO: talk about how routes are compiled + +## Creating URLs and links using routes + +Along with Kohana's powerful routing capabilities are included some methods for generating URLs for your routes' uris. You can always specify your uris as a string using [URL::site] to create a full URL like so: + + URL::site('admin/edit/user/'.$user_id); + +However, Kohana also provides a method to generate the uri from the route's definition. This is extremely useful if your routing could ever change since it would relieve you from having to go back through your code and change everywhere that you specified a uri as a string. Here is an example of dynamic generation that corresponds to the `feeds` route example from above: + + Route::get('feeds')->uri(array( + 'user_id' => $user_id, + 'action' => 'comments', + 'format' => 'rss' + )); + +Let's say you decided later to make that route definition more verbose by changing it to `feeds/(/).`. If you wrote your code with the above uri generation method you wouldn't have to change a single line! When a part of the uri is enclosed in parentheses and specifies a key for which there in no value provided for uri generation and no default value specified in the route, then that part will be removed from the uri. An example of this is the `(/)` part of the default route; this will not be included in the generated uri if an id is not provided. + +One method you might use frequently is the shortcut [Request::uri] which is the same as the above except it assumes the current route, directory, controller and action. If our current route is the default and the uri was `users/list`, we can do the following to generate uris in the format `users/view/$id`: + + $this->request->uri(array('action' => 'view', 'id' => $user_id)); + +Or if within a view, the preferable method is: + + Request::instance()->uri(array('action' => 'view', 'id' => $user_id)); + +TODO: examples of using html::anchor in addition to the above examples + +## Testing routes + +TODO: mention bluehawk's devtools module \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/security.md b/includes/kohana/system/guide/kohana/security.md new file mode 100644 index 0000000..2f90a04 --- /dev/null +++ b/includes/kohana/system/guide/kohana/security.md @@ -0,0 +1 @@ +General security concerns, like using the Security class, CSRF, and a brief intro to XSS, database security, etc. Also mention the security features that Kohana provides, like cleaning globals. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/security/cookies.md b/includes/kohana/system/guide/kohana/security/cookies.md new file mode 100644 index 0000000..3966f6c --- /dev/null +++ b/includes/kohana/system/guide/kohana/security/cookies.md @@ -0,0 +1,3 @@ +Discuss security of cookies, like changing the encryption key in the config. + +Not sure why I'm linking to this: \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/security/database.md b/includes/kohana/system/guide/kohana/security/database.md new file mode 100644 index 0000000..e6190b7 --- /dev/null +++ b/includes/kohana/system/guide/kohana/security/database.md @@ -0,0 +1,5 @@ +Discuss database security. + +How to avoid injection, etc. + +Not sure why I'm linking to this: \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/security/deploying.md b/includes/kohana/system/guide/kohana/security/deploying.md new file mode 100644 index 0000000..2c0a0fa --- /dev/null +++ b/includes/kohana/system/guide/kohana/security/deploying.md @@ -0,0 +1,61 @@ +Changes that should happen when you deploy. (Production) + +Security settings from: + + + + +## Setting up a production environment + +There are a few things you'll want to do with your application before moving into production. + +1. See the [Bootstrap page](bootstrap) in the docs. + This covers most of the global settings that would change between environments. + As a general rule, you should enable caching and disable profiling ([Kohana::init] settings) for production sites. + [Route::cache] can also help if you have a lot of routes. +2. Turn on APC or some kind of opcode caching. + This is the single easiest performance boost you can make to PHP itself. The more complex your application, the bigger the benefit of using opcode caching. + + /** + * Set the environment string by the domain (defaults to Kohana::DEVELOPMENT). + */ + Kohana::$environment = ($_SERVER['SERVER_NAME'] !== 'localhost') ? Kohana::PRODUCTION : Kohana::DEVELOPMENT; + /** + * Initialise Kohana based on environment + */ + Kohana::init(array( + 'base_url' => '/', + 'index_file' => FALSE, + 'profile' => Kohana::$environment !== Kohana::PRODUCTION, + 'caching' => Kohana::$environment === Kohana::PRODUCTION, + )); + +`index.php`: + + /** + * Execute the main request using PATH_INFO. If no URI source is specified, + * the URI will be automatically detected. + */ + $request = Request::instance($_SERVER['PATH_INFO']); + + // Attempt to execute the response + $request->execute(); + + if ($request->send_headers()->response) + { + // Get the total memory and execution time + $total = array( + '{memory_usage}' => number_format((memory_get_peak_usage() - KOHANA_START_MEMORY) / 1024, 2).'KB', + '{execution_time}' => number_format(microtime(TRUE) - KOHANA_START_TIME, 5).' seconds'); + + // Insert the totals into the response + $request->response = str_replace(array_keys($total), $total, $request->response); + } + + + /** + * Display the request response. + */ + echo $request->response; + + diff --git a/includes/kohana/system/guide/kohana/security/encryption.md b/includes/kohana/system/guide/kohana/security/encryption.md new file mode 100644 index 0000000..8ba6ecf --- /dev/null +++ b/includes/kohana/system/guide/kohana/security/encryption.md @@ -0,0 +1 @@ +Discuss using encryption, including setting the encryption key in config. \ No newline at end of file diff --git a/includes/kohana/system/guide/kohana/security/validation.md b/includes/kohana/system/guide/kohana/security/validation.md new file mode 100644 index 0000000..ddd137f --- /dev/null +++ b/includes/kohana/system/guide/kohana/security/validation.md @@ -0,0 +1,234 @@ +# Validation + +*This page needs to be reviewed for accuracy by the development team. Better examples would be helpful.* + +Validation can be performed on any array using the [Validation] class. Labels and rules can be attached to a Validate object by the array key, called a "field name". + +labels +: A label is a human-readable version of the field name. + +rules +: A rule is a callback used to decide whether or not to add an error to a field + +[!!] Note that any valid [PHP callback](http://php.net/manual/language.pseudo-types.php#language.types.callback) can be used as a rule. + +Using `TRUE` as the field name when adding a rule will be applied to all named fields. + +Creating a validation object is done using the [Validation::factory] method: + + $post = Validation::factory($_POST); + +[!!] The `$post` object will be used for the rest of this tutorial. This tutorial will show you how to validate the registration of a new user. + +## Provided Rules + +Kohana provides a set of useful rules in the [Valid] class: + +Rule name | Function +------------------------- |------------------------------------------------- +[Valid::not_empty] | Value must be a non-empty value +[Valid::regex] | Match the value against a regular expression +[Valid::min_length] | Minimum number of characters for value +[Valid::max_length] | Maximum number of characters for value +[Valid::exact_length] | Value must be an exact number of characters +[Valid::email] | An email address is required +[Validate::email_domain] | Check that the domain of the email exists +[Valid::url] | Value must be a URL +[Valid::ip] | Value must be an IP address +[Valid::phone] | Value must be a phone number +[Valid::credit_card] | Require a credit card number +[Valid::date] | Value must be a date (and time) +[Valid::alpha] | Only alpha characters allowed +[Valid::alpha_dash] | Only alpha and hyphens allowed +[Valid::alpha_numeric] | Only alpha and numbers allowed +[Valid::digit] | Value must be an integer digit +[Valid::decimal] | Value must be a decimal or float value +[Valid::numeric] | Only numeric characters allowed +[Valid::range] | Value must be within a range +[Valid::color] | Value must be a valid HEX color +[Valid::matches] | Value matches another field value + +## Adding Rules + +All validation rules are defined as a field name, a method or function (using the [PHP callback](http://php.net/callback) syntax), and an array of parameters: + + $object->rule($field, $callback, array($parameter1, $parameter2)); + +If no parameters are specified, the field value will be passed to the callback. The following two rules are equivalent: + + $object->rule($field, 'not_empty'); + $object->rule($field, 'not_empty', array(':value')); + +Rules defined in the [Valid] class can be added by using the method name alone. The following three rules are equivalent: + + $object->rule('number', 'phone'); + $object->rule('number', array('Valid', 'phone')); + $object->rule('number', 'Valid::phone'); + +## Binding Variables + +The [Validation] class allows you to bind variables to certain strings so that they can be used when defining rules. Variables are bound by calling the [Validation::bind] method. + + $object->bind(':model', $user_model); + // Future code will be able to use :model to reference the object + $object->rule('username', 'some_rule', array(':model')); + +By default, the validation object will automatically bind the following values for you to use as rule parameters: + +- `:validation` - references the validation object +- `:field` - references the field name the rule is for +- `:value` - references the value of the field the rule is for + +## Adding Errors + +The [Validation] class will add an error for a field if any of the rules associated to it return `FALSE`. This allows many built in PHP functions to be used as rules, like `in_array`. + + $object->rule('color', 'in_array', array(':value', array('red', 'green', 'blue'))); + +Rules added to empty fields will run, but returning `FALSE` will not automatically add an error for the field. In order for a rule to affect empty fields, you must add the error manually by calling the [Validation::error] method. In order to do this, you must pass the validation object to the rule. + + $object->rule($field, 'the_rule', array(':validation', ':field')); + + public function the_rule($validation, $field) + { + if (something went wrong) + { + $validation->error($field, 'the_rule'); + } + } + +[!!] `not_empty` and `matches` are the only rules that will run on empty fields and add errors by returning `FALSE`. + +## Example + +To start our example, we will perform validation on a `$_POST` array that contains user registration information: + + $post = Validation::factory($_POST); + +Next we need to process the POST'ed information using [Validation]. To start, we need to add some rules: + + $post + ->rule('username', 'not_empty') + ->rule('username', 'regex', array(':value', '/^[a-z_.]++$/iD')) + ->rule('password', 'not_empty') + ->rule('password', 'min_length', array(':value', '6')) + ->rule('confirm', 'matches', array(':validation', 'confirm', 'password')) + ->rule('use_ssl', 'not_empty'); + +Any existing PHP function can also be used a rule. For instance, if we want to check if the user entered a proper value for the SSL question: + + $post->rule('use_ssl', 'in_array', array(':value', array('yes', 'no'))); + +Note that all array parameters must still be wrapped in an array! Without the wrapping array, `in_array` would be called as `in_array($value, 'yes', 'no')`, which would result in a PHP error. + +Any custom rules can be added using a [PHP callback](http://php.net/manual/language.pseudo-types.php#language.types.callback]: + + $post->rule('username', 'User_Model::unique_username'); + +The method `User_Model::unique_username()` would be defined similar to: + + public static function unique_username($username) + { + // Check if the username already exists in the database + return ! DB::select(array(DB::expr('COUNT(username)'), 'total')) + ->from('users') + ->where('username', '=', $username) + ->execute() + ->get('total'); + } + +[!!] Custom rules allow many additional checks to be reused for multiple purposes. These methods will almost always exist in a model, but may be defined in any class. + +# A Complete Example + +First, we need a [View] that contains the HTML form, which will be placed in `application/views/user/register.php`: + + + +

                    Some errors were encountered, please check the details you entered.

                    +