From e56d50edd6dbde50433b52e35007dfeb7048ec56 Mon Sep 17 00:00:00 2001 From: deathart Date: Tue, 31 Oct 2017 16:13:22 +0100 Subject: [PATCH] Adding the blog base --- .env | 21 + .gitignore | 14 +- application/.htaccess | 6 + application/Config/App.php | 300 + application/Config/Autoload.php | 88 + application/Config/Boot/development.php | 33 + application/Config/Boot/production.php | 54 + application/Config/Boot/testing.php | 33 + application/Config/Cache.php | 120 + application/Config/Constants.php | 77 + application/Config/ContentSecurityPolicy.php | 49 + application/Config/Database.php | 52 + application/Config/DocTypes.php | 33 + application/Config/Events.php | 32 + application/Config/Exceptions.php | 40 + application/Config/Filters.php | 33 + application/Config/ForeignCharacters.php | 5 + application/Config/Format.php | 68 + application/Config/Images.php | 31 + application/Config/Logger.php | 133 + application/Config/Migrations.php | 62 + application/Config/Mimes.php | 323 + application/Config/Pager.php | 35 + application/Config/Paths.php | 78 + application/Config/Routes.php | 208 + application/Config/Services.php | 32 + application/Config/Validation.php | 36 + application/Config/View.php | 34 + application/Controllers/Admin/Ajax/Ajax.php | 94 + .../Controllers/Admin/Ajax/Article.php | 69 + application/Controllers/Admin/Ajax/Cat.php | 65 + .../Controllers/Admin/Ajax/Comments.php | 42 + application/Controllers/Admin/Ajax/Config.php | 42 + .../Controllers/Admin/Ajax/Contact.php | 61 + application/Controllers/Admin/Ajax/Upload.php | 41 + application/Controllers/Admin/Application.php | 258 + application/Controllers/Admin/Article.php | 87 + application/Controllers/Admin/Auth.php | 98 + application/Controllers/Admin/Cat.php | 31 + application/Controllers/Admin/Comments.php | 43 + application/Controllers/Admin/Config.php | 30 + application/Controllers/Admin/Contact.php | 51 + application/Controllers/Admin/Home.php | 29 + application/Controllers/Blog/About.php | 29 + application/Controllers/Blog/Ajax/Ajax.php | 66 + .../Controllers/Blog/Ajax/Comments.php | 48 + application/Controllers/Blog/Ajax/Contact.php | 48 + application/Controllers/Blog/Application.php | 226 + application/Controllers/Blog/Article.php | 60 + application/Controllers/Blog/Cat.php | 60 + application/Controllers/Blog/Contact.php | 38 + application/Controllers/Blog/Cookies.php | 29 + application/Controllers/Blog/Errors.php | 37 + application/Controllers/Blog/Home.php | 40 + application/Controllers/Blog/Search.php | 42 + application/Database/Migrations/.gitkeep | 0 .../20171016194712_AddTableArticle.php | 42 + .../20171017105826_AddTableArticleView.php | 26 + .../20171017110026_AddTableUsers.php | 27 + .../20171017110348_AddTableMedias.php | 28 + .../20171017110517_AddTableConfig.php | 27 + .../20171017110953_AddTableComments.php | 35 + .../Migrations/20171017111219_AddTableCat.php | 35 + .../20171020172939_AddTableContact.php | 32 + application/Database/Seeds/.gitignore | 0 application/Database/Seeds/DatabaseSeeder.php | 28 + application/Filters/.gitkeep | 0 application/Filters/CSRF.php | 51 + application/Filters/DebugToolbar.php | 56 + application/Filters/Throttle.php | 45 + application/Helpers/.gitkeep | 0 application/Language/.gitkeep | 0 application/Libraries/.gitkeep | 0 application/Libraries/CSRFToken.php | 83 + application/Libraries/General.php | 90 + application/Libraries/Mailer.php | 32 + application/Libraries/ParseArticle.php | 66 + application/Libraries/Twig/Twig.php | 59 + application/Libraries/Twig/TwigExtentions.php | 109 + application/Models/.gitkeep | 0 application/Models/Admin/ArticleModel.php | 189 + application/Models/Admin/AuthModel.php | 79 + application/Models/Admin/CatModel.php | 73 + application/Models/Admin/CommentsModel.php | 64 + application/Models/Admin/ConfigModel.php | 65 + application/Models/Admin/ContactModel.php | 61 + application/Models/Admin/MediaModel.php | 29 + application/Models/Blog/ArticleModel.php | 143 + application/Models/Blog/CatModel.php | 78 + application/Models/Blog/CommentsModel.php | 79 + application/Models/Blog/ConfigModel.php | 42 + application/Models/Blog/ContactModel.php | 56 + application/Models/Blog/MediaModel.php | 26 + application/Models/Blog/SearchModel.php | 55 + application/ThirdParty/.gitkeep | 0 application/Views/Pagers/cat.php | 48 + application/Views/admin/page/article/add.twig | 84 + .../Views/admin/page/article/edit.twig | 101 + .../Views/admin/page/article/home.twig | 65 + .../Views/admin/page/article/list.twig | 34 + application/Views/admin/page/auth/login.twig | 14 + application/Views/admin/page/cat/add.twig | 4 + application/Views/admin/page/cat/edit.twig | 4 + application/Views/admin/page/cat/home.twig | 4 + application/Views/admin/page/cat/list.twig | 36 + .../Views/admin/page/comments/home.twig | 29 + application/Views/admin/page/comments/no.twig | 31 + application/Views/admin/page/comments/ok.twig | 31 + .../Views/admin/page/comments/wait.twig | 31 + application/Views/admin/page/config/home.twig | 4 + .../Views/admin/page/config/params.twig | 38 + .../Views/admin/page/contact/finish.twig | 33 + .../Views/admin/page/contact/home.twig | 4 + application/Views/admin/page/contact/new.twig | 33 + application/Views/admin/page/contact/rep.twig | 52 + application/Views/admin/page/home.twig | 35 + application/Views/admin/template.twig | 131 + application/Views/admin/template_login.twig | 20 + application/Views/blog/page/about/home.twig | 7 + application/Views/blog/page/article/view.twig | 91 + application/Views/blog/page/cat/home.twig | 25 + application/Views/blog/page/cat/view.twig | 44 + application/Views/blog/page/contact/home.twig | 30 + application/Views/blog/page/cookies/home.twig | 32 + application/Views/blog/page/errors/404.twig | 7 + application/Views/blog/page/home.twig | 81 + application/Views/blog/page/search/home.twig | 39 + application/Views/blog/template.twig | 128 + application/Views/errors/cli/error_404.php | 8 + .../Views/errors/cli/error_exception.php | 19 + application/Views/errors/cli/production.php | 5 + application/Views/errors/html/debug.css | 176 + application/Views/errors/html/debug.js | 127 + application/Views/errors/html/error_404.php | 86 + .../Views/errors/html/error_exception.php | 392 + application/Views/errors/html/production.php | 25 + application/index.html | 11 + composer.json | 37 + public/.htaccess | 97 + public/assets/css/admin/article.css | 53 + public/assets/css/admin/auth.css | 160 + public/assets/css/admin/bootstrap.css | 8839 +++++++++++++++++ .../css/admin/bootstrap/bootstrap-reboot.css | 335 + .../assets/css/admin/bootstrap/bootstrap.css | 8569 ++++++++++++++++ public/assets/css/admin/maps/article.css.map | 1 + public/assets/css/admin/maps/auth.css.map | 1 + .../assets/css/admin/maps/bootstrap.css.map | 1 + public/assets/css/admin/maps/style.css.map | 1 + public/assets/css/admin/style.css | 426 + public/assets/css/blog/maps/style.css.map | 1 + public/assets/css/blog/style.css | 1948 ++++ public/assets/css/font-awesome.css | 3046 ++++++ public/assets/css/prism.css | 223 + public/assets/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes public/assets/fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes public/assets/fonts/fontawesome-webfont.svg | 2671 +++++ public/assets/fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes public/assets/fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes public/assets/fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes public/assets/images/user.png | Bin 0 -> 1576 bytes public/assets/js/admin/app.js | 50 + public/assets/js/admin/article.js | 315 + public/assets/js/admin/auth.js | 80 + public/assets/js/admin/cat.js | 179 + public/assets/js/admin/comments.js | 78 + public/assets/js/admin/config.js | 88 + public/assets/js/admin/contact.js | 124 + public/assets/js/blog/app.js | 65 + public/assets/js/blog/article.js | 112 + public/assets/js/blog/contact.js | 69 + public/assets/js/cookie.js | 166 + public/assets/js/jquery.toast.js | 372 + public/assets/js/prism.js | 40 + public/favicon.ico | Bin 0 -> 5430 bytes public/index.php | 35 + public/robots.txt | 13 + rewrite.php | 29 + serve | 36 + spark | 58 + system/.htaccess | 6 + system/API/ResponseTrait.php | 367 + system/Autoloader/Autoloader.php | 371 + system/Autoloader/FileLocator.php | 313 + system/CLI/BaseCommand.php | 241 + system/CLI/CLI.php | 773 ++ system/CLI/CommandRunner.php | 181 + system/CLI/Console.php | 94 + system/CodeIgniter.php | 913 ++ system/Commands/Database/CreateMigration.php | 178 + system/Commands/Database/MigrateCurrent.php | 127 + system/Commands/Database/MigrateLatest.php | 136 + system/Commands/Database/MigrateRefresh.php | 108 + system/Commands/Database/MigrateRollback.php | 154 + system/Commands/Database/MigrateStatus.php | 165 + system/Commands/Database/MigrateVersion.php | 137 + system/Commands/Database/Seed.php | 128 + system/Commands/Help.php | 118 + system/Commands/ListCommands.php | 202 + system/Commands/Sessions/CreateMigration.php | 126 + .../Commands/Sessions/Views/migration.tpl.php | 88 + system/Common.php | 929 ++ system/ComposerScripts.php | 206 + system/Config/AutoloadConfig.php | 193 + system/Config/BaseConfig.php | 187 + system/Config/DotEnv.php | 299 + system/Config/ForeignCharacters.php | 141 + system/Config/Routes.php | 53 + system/Config/Services.php | 901 ++ system/Config/View.php | 79 + system/Controller.php | 193 + system/Database/BaseBuilder.php | 2849 ++++++ system/Database/BaseConnection.php | 1700 ++++ system/Database/BasePreparedQuery.php | 260 + system/Database/BaseResult.php | 617 ++ system/Database/BaseUtils.php | 443 + system/Database/Config.php | 232 + system/Database/ConnectionInterface.php | 222 + system/Database/Database.php | 138 + .../Database/Exceptions/DatabaseException.php | 48 + system/Database/Forge.php | 1195 +++ system/Database/Migration.php | 104 + system/Database/MigrationRunner.php | 724 ++ system/Database/MySQLi/Builder.php | 53 + system/Database/MySQLi/Connection.php | 613 ++ system/Database/MySQLi/Forge.php | 243 + system/Database/MySQLi/PreparedQuery.php | 130 + system/Database/MySQLi/Result.php | 162 + system/Database/Postgre/Builder.php | 361 + system/Database/Postgre/Connection.php | 570 ++ system/Database/Postgre/Forge.php | 238 + system/Database/Postgre/PreparedQuery.php | 151 + system/Database/Postgre/Result.php | 162 + system/Database/Postgre/Utils.php | 74 + system/Database/PreparedQueryInterface.php | 99 + system/Database/Query.php | 485 + system/Database/QueryInterface.php | 155 + system/Database/ResultInterface.php | 261 + system/Database/Seeder.php | 216 + system/Debug/CustomExceptions.php | 165 + system/Debug/Exceptions.php | 458 + system/Debug/Iterator.php | 170 + system/Debug/Timer.php | 181 + system/Debug/Toolbar.php | 229 + .../Toolbar/Collectors/BaseCollector.php | 281 + system/Debug/Toolbar/Collectors/Database.php | 217 + system/Debug/Toolbar/Collectors/Events.php | 171 + system/Debug/Toolbar/Collectors/Files.php | 141 + system/Debug/Toolbar/Collectors/Logs.php | 97 + system/Debug/Toolbar/Collectors/Routes.php | 150 + system/Debug/Toolbar/Collectors/Timers.php | 102 + system/Debug/Toolbar/Collectors/Views.php | 176 + system/Debug/Toolbar/Views/_database.tpl.php | 16 + system/Debug/Toolbar/Views/_events.tpl.php | 18 + system/Debug/Toolbar/Views/_files.tpl.php | 16 + system/Debug/Toolbar/Views/_logs.tpl.php | 16 + system/Debug/Toolbar/Views/_routes.tpl.php | 44 + system/Debug/Toolbar/Views/toolbar.css | 302 + system/Debug/Toolbar/Views/toolbar.js | 497 + system/Debug/Toolbar/Views/toolbar.tpl.php | 253 + system/Encryption/EncrypterInterface.php | 63 + system/Encryption/Encryption.php | 337 + system/Encryption/Handlers/BaseHandler.php | 140 + system/Encryption/Handlers/OpenSSLHandler.php | 140 + system/Entity.php | 365 + system/Events/Events.php | 326 + system/Files/Exceptions.php | 46 + system/Files/File.php | 230 + system/Filters/FilterInterface.php | 76 + system/Filters/Filters.php | 377 + system/Format/FormatterInterface.php | 49 + system/Format/JSONFormatter.php | 65 + system/Format/XMLFormatter.php | 103 + system/HTTP/CLIRequest.php | 236 + system/HTTP/CURLRequest.php | 749 ++ system/HTTP/ContentSecurityPolicy.php | 797 ++ system/HTTP/Files/FileCollection.php | 310 + system/HTTP/Files/UploadedFile.php | 368 + system/HTTP/Files/UploadedFileInterface.php | 200 + system/HTTP/Header.php | 237 + system/HTTP/IncomingRequest.php | 749 ++ system/HTTP/Message.php | 410 + system/HTTP/Negotiate.php | 433 + system/HTTP/RedirectResponse.php | 173 + system/HTTP/Request.php | 408 + system/HTTP/RequestInterface.php | 87 + system/HTTP/Response.php | 770 ++ system/HTTP/ResponseInterface.php | 253 + system/HTTP/URI.php | 1145 +++ system/Helpers/cookie_helper.php | 135 + system/Helpers/date_helper.php | 65 + system/Helpers/filesystem_helper.php | 497 + system/Helpers/form_helper.php | 990 ++ system/Helpers/html_helper.php | 828 ++ system/Helpers/inflector_helper.php | 378 + system/Helpers/number_helper.php | 325 + system/Helpers/security_helper.php | 96 + system/Helpers/text_helper.php | 876 ++ system/Helpers/url_helper.php | 624 ++ system/I18n/Time.php | 1283 +++ system/I18n/TimeDifference.php | 264 + system/Images/Exceptions/ImageException.php | 3 + system/Images/Handlers/BaseHandler.php | 703 ++ system/Images/Handlers/GDHandler.php | 563 ++ system/Images/Handlers/ImageMagickHandler.php | 410 + system/Images/Image.php | 157 + system/Images/ImageHandlerInterface.php | 136 + system/Language/Language.php | 254 + system/Language/en/CLI.php | 44 + system/Language/en/Cache.php | 43 + system/Language/en/Database.php | 5 + system/Language/en/HTTP.php | 5 + system/Language/en/Images.php | 24 + system/Language/en/Language.php | 5 + system/Language/en/Migrations.php | 78 + system/Language/en/Number.php | 50 + system/Language/en/Pager.php | 47 + system/Language/en/Time.php | 21 + system/Language/en/Validation.php | 90 + system/Log/Handlers/BaseHandler.php | 118 + system/Log/Handlers/ChromeLoggerHandler.php | 205 + system/Log/Handlers/FileHandler.php | 154 + system/Log/Handlers/HandlerInterface.php | 78 + system/Log/Logger.php | 523 + system/Model.php | 1397 +++ system/Pager/Pager.php | 450 + system/Pager/PagerInterface.php | 197 + system/Pager/PagerRenderer.php | 243 + system/Pager/Views/default_full.php | 39 + system/Pager/Views/default_simple.php | 15 + system/Router/RouteCollection.php | 1294 +++ system/Router/RouteCollectionInterface.php | 272 + system/Router/Router.php | 615 ++ system/Router/RouterInterface.php | 109 + system/Security/Security.php | 335 + system/Session/Handlers/BaseHandler.php | 201 + system/Session/Handlers/DatabaseHandler.php | 417 + system/Session/Handlers/FileHandler.php | 354 + system/Session/Handlers/MemcachedHandler.php | 391 + system/Session/Handlers/RedisHandler.php | 407 + system/Session/Session.php | 969 ++ system/Session/SessionInterface.php | 248 + system/Test/CIDatabaseTestCase.php | 338 + system/Test/CIUnitTestCase.php | 91 + system/Test/ReflectionHelper.php | 118 + system/ThirdParty/Kint/kint.php | 180 + system/ThirdParty/PSR/Log/AbstractLogger.php | 120 + .../PSR/Log/InvalidArgumentException.php | 7 + system/ThirdParty/PSR/Log/LogLevel.php | 18 + .../PSR/Log/LoggerAwareInterface.php | 17 + .../ThirdParty/PSR/Log/LoggerAwareTrait.php | 22 + system/ThirdParty/PSR/Log/LoggerInterface.php | 114 + system/ThirdParty/PSR/Log/LoggerTrait.php | 131 + system/ThirdParty/PSR/Log/NullLogger.php | 27 + system/ThirdParty/ZendEscaper/Escaper.php | 388 + .../Exception/ExceptionInterface.php | 14 + .../Exception/InvalidArgumentException.php | 18 + .../Exception/RuntimeException.php | 18 + system/Throttle/Throttler.php | 164 + system/Throttle/ThrottlerInterface.php | 70 + system/Typography/Typography.php | 406 + system/Validation/CreditCardRules.php | 205 + system/Validation/FileRules.php | 247 + system/Validation/FormatRules.php | 355 + system/Validation/Rules.php | 379 + system/Validation/Validation.php | 661 ++ system/Validation/ValidationInterface.php | 158 + system/Validation/Views/list.php | 9 + system/Validation/Views/single.php | 1 + system/View/Cell.php | 261 + system/View/Filters.php | 306 + system/View/Parser.php | 765 ++ system/View/Plugins.php | 145 + system/View/RendererInterface.php | 120 + system/View/View.php | 388 + system/bootstrap.php | 131 + system/index.html | 11 + 376 files changed, 94604 insertions(+), 4 deletions(-) create mode 100644 .env create mode 100644 application/.htaccess create mode 100644 application/Config/App.php create mode 100644 application/Config/Autoload.php create mode 100644 application/Config/Boot/development.php create mode 100644 application/Config/Boot/production.php create mode 100644 application/Config/Boot/testing.php create mode 100644 application/Config/Cache.php create mode 100644 application/Config/Constants.php create mode 100644 application/Config/ContentSecurityPolicy.php create mode 100644 application/Config/Database.php create mode 100644 application/Config/DocTypes.php create mode 100644 application/Config/Events.php create mode 100644 application/Config/Exceptions.php create mode 100644 application/Config/Filters.php create mode 100644 application/Config/ForeignCharacters.php create mode 100644 application/Config/Format.php create mode 100644 application/Config/Images.php create mode 100644 application/Config/Logger.php create mode 100644 application/Config/Migrations.php create mode 100644 application/Config/Mimes.php create mode 100644 application/Config/Pager.php create mode 100644 application/Config/Paths.php create mode 100644 application/Config/Routes.php create mode 100644 application/Config/Services.php create mode 100644 application/Config/Validation.php create mode 100644 application/Config/View.php create mode 100644 application/Controllers/Admin/Ajax/Ajax.php create mode 100644 application/Controllers/Admin/Ajax/Article.php create mode 100644 application/Controllers/Admin/Ajax/Cat.php create mode 100644 application/Controllers/Admin/Ajax/Comments.php create mode 100644 application/Controllers/Admin/Ajax/Config.php create mode 100644 application/Controllers/Admin/Ajax/Contact.php create mode 100644 application/Controllers/Admin/Ajax/Upload.php create mode 100644 application/Controllers/Admin/Application.php create mode 100644 application/Controllers/Admin/Article.php create mode 100644 application/Controllers/Admin/Auth.php create mode 100644 application/Controllers/Admin/Cat.php create mode 100644 application/Controllers/Admin/Comments.php create mode 100644 application/Controllers/Admin/Config.php create mode 100644 application/Controllers/Admin/Contact.php create mode 100644 application/Controllers/Admin/Home.php create mode 100644 application/Controllers/Blog/About.php create mode 100644 application/Controllers/Blog/Ajax/Ajax.php create mode 100644 application/Controllers/Blog/Ajax/Comments.php create mode 100644 application/Controllers/Blog/Ajax/Contact.php create mode 100644 application/Controllers/Blog/Application.php create mode 100644 application/Controllers/Blog/Article.php create mode 100644 application/Controllers/Blog/Cat.php create mode 100644 application/Controllers/Blog/Contact.php create mode 100644 application/Controllers/Blog/Cookies.php create mode 100644 application/Controllers/Blog/Errors.php create mode 100644 application/Controllers/Blog/Home.php create mode 100644 application/Controllers/Blog/Search.php create mode 100644 application/Database/Migrations/.gitkeep create mode 100644 application/Database/Migrations/20171016194712_AddTableArticle.php create mode 100644 application/Database/Migrations/20171017105826_AddTableArticleView.php create mode 100644 application/Database/Migrations/20171017110026_AddTableUsers.php create mode 100644 application/Database/Migrations/20171017110348_AddTableMedias.php create mode 100644 application/Database/Migrations/20171017110517_AddTableConfig.php create mode 100644 application/Database/Migrations/20171017110953_AddTableComments.php create mode 100644 application/Database/Migrations/20171017111219_AddTableCat.php create mode 100644 application/Database/Migrations/20171020172939_AddTableContact.php create mode 100644 application/Database/Seeds/.gitignore create mode 100644 application/Database/Seeds/DatabaseSeeder.php create mode 100644 application/Filters/.gitkeep create mode 100644 application/Filters/CSRF.php create mode 100644 application/Filters/DebugToolbar.php create mode 100644 application/Filters/Throttle.php create mode 100644 application/Helpers/.gitkeep create mode 100644 application/Language/.gitkeep create mode 100644 application/Libraries/.gitkeep create mode 100644 application/Libraries/CSRFToken.php create mode 100644 application/Libraries/General.php create mode 100644 application/Libraries/Mailer.php create mode 100644 application/Libraries/ParseArticle.php create mode 100644 application/Libraries/Twig/Twig.php create mode 100644 application/Libraries/Twig/TwigExtentions.php create mode 100644 application/Models/.gitkeep create mode 100644 application/Models/Admin/ArticleModel.php create mode 100644 application/Models/Admin/AuthModel.php create mode 100644 application/Models/Admin/CatModel.php create mode 100644 application/Models/Admin/CommentsModel.php create mode 100644 application/Models/Admin/ConfigModel.php create mode 100644 application/Models/Admin/ContactModel.php create mode 100644 application/Models/Admin/MediaModel.php create mode 100644 application/Models/Blog/ArticleModel.php create mode 100644 application/Models/Blog/CatModel.php create mode 100644 application/Models/Blog/CommentsModel.php create mode 100644 application/Models/Blog/ConfigModel.php create mode 100644 application/Models/Blog/ContactModel.php create mode 100644 application/Models/Blog/MediaModel.php create mode 100644 application/Models/Blog/SearchModel.php create mode 100644 application/ThirdParty/.gitkeep create mode 100644 application/Views/Pagers/cat.php create mode 100644 application/Views/admin/page/article/add.twig create mode 100644 application/Views/admin/page/article/edit.twig create mode 100644 application/Views/admin/page/article/home.twig create mode 100644 application/Views/admin/page/article/list.twig create mode 100644 application/Views/admin/page/auth/login.twig create mode 100644 application/Views/admin/page/cat/add.twig create mode 100644 application/Views/admin/page/cat/edit.twig create mode 100644 application/Views/admin/page/cat/home.twig create mode 100644 application/Views/admin/page/cat/list.twig create mode 100644 application/Views/admin/page/comments/home.twig create mode 100644 application/Views/admin/page/comments/no.twig create mode 100644 application/Views/admin/page/comments/ok.twig create mode 100644 application/Views/admin/page/comments/wait.twig create mode 100644 application/Views/admin/page/config/home.twig create mode 100644 application/Views/admin/page/config/params.twig create mode 100644 application/Views/admin/page/contact/finish.twig create mode 100644 application/Views/admin/page/contact/home.twig create mode 100644 application/Views/admin/page/contact/new.twig create mode 100644 application/Views/admin/page/contact/rep.twig create mode 100644 application/Views/admin/page/home.twig create mode 100644 application/Views/admin/template.twig create mode 100644 application/Views/admin/template_login.twig create mode 100644 application/Views/blog/page/about/home.twig create mode 100644 application/Views/blog/page/article/view.twig create mode 100644 application/Views/blog/page/cat/home.twig create mode 100644 application/Views/blog/page/cat/view.twig create mode 100644 application/Views/blog/page/contact/home.twig create mode 100644 application/Views/blog/page/cookies/home.twig create mode 100644 application/Views/blog/page/errors/404.twig create mode 100644 application/Views/blog/page/home.twig create mode 100644 application/Views/blog/page/search/home.twig create mode 100644 application/Views/blog/template.twig create mode 100644 application/Views/errors/cli/error_404.php create mode 100644 application/Views/errors/cli/error_exception.php create mode 100644 application/Views/errors/cli/production.php create mode 100644 application/Views/errors/html/debug.css create mode 100644 application/Views/errors/html/debug.js create mode 100644 application/Views/errors/html/error_404.php create mode 100644 application/Views/errors/html/error_exception.php create mode 100644 application/Views/errors/html/production.php create mode 100644 application/index.html create mode 100644 composer.json create mode 100644 public/.htaccess create mode 100644 public/assets/css/admin/article.css create mode 100644 public/assets/css/admin/auth.css create mode 100644 public/assets/css/admin/bootstrap.css create mode 100644 public/assets/css/admin/bootstrap/bootstrap-reboot.css create mode 100644 public/assets/css/admin/bootstrap/bootstrap.css create mode 100644 public/assets/css/admin/maps/article.css.map create mode 100644 public/assets/css/admin/maps/auth.css.map create mode 100644 public/assets/css/admin/maps/bootstrap.css.map create mode 100644 public/assets/css/admin/maps/style.css.map create mode 100644 public/assets/css/admin/style.css create mode 100644 public/assets/css/blog/maps/style.css.map create mode 100644 public/assets/css/blog/style.css create mode 100644 public/assets/css/font-awesome.css create mode 100644 public/assets/css/prism.css create mode 100644 public/assets/fonts/FontAwesome.otf create mode 100644 public/assets/fonts/fontawesome-webfont.eot create mode 100644 public/assets/fonts/fontawesome-webfont.svg create mode 100644 public/assets/fonts/fontawesome-webfont.ttf create mode 100644 public/assets/fonts/fontawesome-webfont.woff create mode 100644 public/assets/fonts/fontawesome-webfont.woff2 create mode 100644 public/assets/images/user.png create mode 100644 public/assets/js/admin/app.js create mode 100644 public/assets/js/admin/article.js create mode 100644 public/assets/js/admin/auth.js create mode 100644 public/assets/js/admin/cat.js create mode 100644 public/assets/js/admin/comments.js create mode 100644 public/assets/js/admin/config.js create mode 100644 public/assets/js/admin/contact.js create mode 100644 public/assets/js/blog/app.js create mode 100644 public/assets/js/blog/article.js create mode 100644 public/assets/js/blog/contact.js create mode 100644 public/assets/js/cookie.js create mode 100644 public/assets/js/jquery.toast.js create mode 100644 public/assets/js/prism.js create mode 100644 public/favicon.ico create mode 100644 public/index.php create mode 100644 public/robots.txt create mode 100644 rewrite.php create mode 100644 serve create mode 100644 spark create mode 100644 system/.htaccess create mode 100644 system/API/ResponseTrait.php create mode 100644 system/Autoloader/Autoloader.php create mode 100644 system/Autoloader/FileLocator.php create mode 100644 system/CLI/BaseCommand.php create mode 100644 system/CLI/CLI.php create mode 100644 system/CLI/CommandRunner.php create mode 100644 system/CLI/Console.php create mode 100644 system/CodeIgniter.php create mode 100644 system/Commands/Database/CreateMigration.php create mode 100644 system/Commands/Database/MigrateCurrent.php create mode 100644 system/Commands/Database/MigrateLatest.php create mode 100644 system/Commands/Database/MigrateRefresh.php create mode 100644 system/Commands/Database/MigrateRollback.php create mode 100644 system/Commands/Database/MigrateStatus.php create mode 100644 system/Commands/Database/MigrateVersion.php create mode 100644 system/Commands/Database/Seed.php create mode 100644 system/Commands/Help.php create mode 100644 system/Commands/ListCommands.php create mode 100644 system/Commands/Sessions/CreateMigration.php create mode 100644 system/Commands/Sessions/Views/migration.tpl.php create mode 100644 system/Common.php create mode 100644 system/ComposerScripts.php create mode 100644 system/Config/AutoloadConfig.php create mode 100644 system/Config/BaseConfig.php create mode 100644 system/Config/DotEnv.php create mode 100644 system/Config/ForeignCharacters.php create mode 100644 system/Config/Routes.php create mode 100644 system/Config/Services.php create mode 100644 system/Config/View.php create mode 100644 system/Controller.php create mode 100644 system/Database/BaseBuilder.php create mode 100644 system/Database/BaseConnection.php create mode 100644 system/Database/BasePreparedQuery.php create mode 100644 system/Database/BaseResult.php create mode 100644 system/Database/BaseUtils.php create mode 100644 system/Database/Config.php create mode 100644 system/Database/ConnectionInterface.php create mode 100644 system/Database/Database.php create mode 100644 system/Database/Exceptions/DatabaseException.php create mode 100644 system/Database/Forge.php create mode 100644 system/Database/Migration.php create mode 100644 system/Database/MigrationRunner.php create mode 100644 system/Database/MySQLi/Builder.php create mode 100644 system/Database/MySQLi/Connection.php create mode 100644 system/Database/MySQLi/Forge.php create mode 100644 system/Database/MySQLi/PreparedQuery.php create mode 100644 system/Database/MySQLi/Result.php create mode 100644 system/Database/Postgre/Builder.php create mode 100644 system/Database/Postgre/Connection.php create mode 100644 system/Database/Postgre/Forge.php create mode 100644 system/Database/Postgre/PreparedQuery.php create mode 100644 system/Database/Postgre/Result.php create mode 100644 system/Database/Postgre/Utils.php create mode 100644 system/Database/PreparedQueryInterface.php create mode 100644 system/Database/Query.php create mode 100644 system/Database/QueryInterface.php create mode 100644 system/Database/ResultInterface.php create mode 100644 system/Database/Seeder.php create mode 100644 system/Debug/CustomExceptions.php create mode 100644 system/Debug/Exceptions.php create mode 100644 system/Debug/Iterator.php create mode 100644 system/Debug/Timer.php create mode 100644 system/Debug/Toolbar.php create mode 100644 system/Debug/Toolbar/Collectors/BaseCollector.php create mode 100644 system/Debug/Toolbar/Collectors/Database.php create mode 100644 system/Debug/Toolbar/Collectors/Events.php create mode 100644 system/Debug/Toolbar/Collectors/Files.php create mode 100644 system/Debug/Toolbar/Collectors/Logs.php create mode 100644 system/Debug/Toolbar/Collectors/Routes.php create mode 100644 system/Debug/Toolbar/Collectors/Timers.php create mode 100644 system/Debug/Toolbar/Collectors/Views.php create mode 100644 system/Debug/Toolbar/Views/_database.tpl.php create mode 100644 system/Debug/Toolbar/Views/_events.tpl.php create mode 100644 system/Debug/Toolbar/Views/_files.tpl.php create mode 100644 system/Debug/Toolbar/Views/_logs.tpl.php create mode 100644 system/Debug/Toolbar/Views/_routes.tpl.php create mode 100644 system/Debug/Toolbar/Views/toolbar.css create mode 100644 system/Debug/Toolbar/Views/toolbar.js create mode 100644 system/Debug/Toolbar/Views/toolbar.tpl.php create mode 100644 system/Encryption/EncrypterInterface.php create mode 100644 system/Encryption/Encryption.php create mode 100644 system/Encryption/Handlers/BaseHandler.php create mode 100644 system/Encryption/Handlers/OpenSSLHandler.php create mode 100644 system/Entity.php create mode 100644 system/Events/Events.php create mode 100644 system/Files/Exceptions.php create mode 100644 system/Files/File.php create mode 100644 system/Filters/FilterInterface.php create mode 100644 system/Filters/Filters.php create mode 100644 system/Format/FormatterInterface.php create mode 100644 system/Format/JSONFormatter.php create mode 100644 system/Format/XMLFormatter.php create mode 100644 system/HTTP/CLIRequest.php create mode 100644 system/HTTP/CURLRequest.php create mode 100644 system/HTTP/ContentSecurityPolicy.php create mode 100644 system/HTTP/Files/FileCollection.php create mode 100644 system/HTTP/Files/UploadedFile.php create mode 100644 system/HTTP/Files/UploadedFileInterface.php create mode 100644 system/HTTP/Header.php create mode 100644 system/HTTP/IncomingRequest.php create mode 100644 system/HTTP/Message.php create mode 100644 system/HTTP/Negotiate.php create mode 100644 system/HTTP/RedirectResponse.php create mode 100644 system/HTTP/Request.php create mode 100644 system/HTTP/RequestInterface.php create mode 100644 system/HTTP/Response.php create mode 100644 system/HTTP/ResponseInterface.php create mode 100644 system/HTTP/URI.php create mode 100644 system/Helpers/cookie_helper.php create mode 100644 system/Helpers/date_helper.php create mode 100644 system/Helpers/filesystem_helper.php create mode 100644 system/Helpers/form_helper.php create mode 100644 system/Helpers/html_helper.php create mode 100644 system/Helpers/inflector_helper.php create mode 100644 system/Helpers/number_helper.php create mode 100644 system/Helpers/security_helper.php create mode 100644 system/Helpers/text_helper.php create mode 100644 system/Helpers/url_helper.php create mode 100644 system/I18n/Time.php create mode 100644 system/I18n/TimeDifference.php create mode 100644 system/Images/Exceptions/ImageException.php create mode 100644 system/Images/Handlers/BaseHandler.php create mode 100644 system/Images/Handlers/GDHandler.php create mode 100644 system/Images/Handlers/ImageMagickHandler.php create mode 100644 system/Images/Image.php create mode 100644 system/Images/ImageHandlerInterface.php create mode 100644 system/Language/Language.php create mode 100644 system/Language/en/CLI.php create mode 100644 system/Language/en/Cache.php create mode 100644 system/Language/en/Database.php create mode 100644 system/Language/en/HTTP.php create mode 100644 system/Language/en/Images.php create mode 100644 system/Language/en/Language.php create mode 100644 system/Language/en/Migrations.php create mode 100644 system/Language/en/Number.php create mode 100644 system/Language/en/Pager.php create mode 100644 system/Language/en/Time.php create mode 100644 system/Language/en/Validation.php create mode 100644 system/Log/Handlers/BaseHandler.php create mode 100644 system/Log/Handlers/ChromeLoggerHandler.php create mode 100644 system/Log/Handlers/FileHandler.php create mode 100644 system/Log/Handlers/HandlerInterface.php create mode 100644 system/Log/Logger.php create mode 100644 system/Model.php create mode 100644 system/Pager/Pager.php create mode 100644 system/Pager/PagerInterface.php create mode 100644 system/Pager/PagerRenderer.php create mode 100644 system/Pager/Views/default_full.php create mode 100644 system/Pager/Views/default_simple.php create mode 100644 system/Router/RouteCollection.php create mode 100644 system/Router/RouteCollectionInterface.php create mode 100644 system/Router/Router.php create mode 100644 system/Router/RouterInterface.php create mode 100644 system/Security/Security.php create mode 100644 system/Session/Handlers/BaseHandler.php create mode 100644 system/Session/Handlers/DatabaseHandler.php create mode 100644 system/Session/Handlers/FileHandler.php create mode 100644 system/Session/Handlers/MemcachedHandler.php create mode 100644 system/Session/Handlers/RedisHandler.php create mode 100644 system/Session/Session.php create mode 100644 system/Session/SessionInterface.php create mode 100644 system/Test/CIDatabaseTestCase.php create mode 100644 system/Test/CIUnitTestCase.php create mode 100644 system/Test/ReflectionHelper.php create mode 100644 system/ThirdParty/Kint/kint.php create mode 100644 system/ThirdParty/PSR/Log/AbstractLogger.php create mode 100644 system/ThirdParty/PSR/Log/InvalidArgumentException.php create mode 100644 system/ThirdParty/PSR/Log/LogLevel.php create mode 100644 system/ThirdParty/PSR/Log/LoggerAwareInterface.php create mode 100644 system/ThirdParty/PSR/Log/LoggerAwareTrait.php create mode 100644 system/ThirdParty/PSR/Log/LoggerInterface.php create mode 100644 system/ThirdParty/PSR/Log/LoggerTrait.php create mode 100644 system/ThirdParty/PSR/Log/NullLogger.php create mode 100644 system/ThirdParty/ZendEscaper/Escaper.php create mode 100644 system/ThirdParty/ZendEscaper/Exception/ExceptionInterface.php create mode 100644 system/ThirdParty/ZendEscaper/Exception/InvalidArgumentException.php create mode 100644 system/ThirdParty/ZendEscaper/Exception/RuntimeException.php create mode 100644 system/Throttle/Throttler.php create mode 100644 system/Throttle/ThrottlerInterface.php create mode 100644 system/Typography/Typography.php create mode 100644 system/Validation/CreditCardRules.php create mode 100644 system/Validation/FileRules.php create mode 100644 system/Validation/FormatRules.php create mode 100644 system/Validation/Rules.php create mode 100644 system/Validation/Validation.php create mode 100644 system/Validation/ValidationInterface.php create mode 100644 system/Validation/Views/list.php create mode 100644 system/Validation/Views/single.php create mode 100644 system/View/Cell.php create mode 100644 system/View/Filters.php create mode 100644 system/View/Parser.php create mode 100644 system/View/Plugins.php create mode 100644 system/View/RendererInterface.php create mode 100644 system/View/View.php create mode 100644 system/bootstrap.php create mode 100644 system/index.html diff --git a/.env b/.env new file mode 100644 index 0000000..5973360 --- /dev/null +++ b/.env @@ -0,0 +1,21 @@ +#-------------------------------------------------------------------- +# APP +#-------------------------------------------------------------------- + +app.baseURL = 'http://blog.dev/' + +#-------------------------------------------------------------------- +# DATABASE +#-------------------------------------------------------------------- + +database.default.hostname = localhost +database.default.database = blog +database.default.username = root +database.default.password = +database.default.DBDriver = MySQLi + +#-------------------------------------------------------------------- +# OTHER +#-------------------------------------------------------------------- + +CI_ENVIRONMENT = development \ No newline at end of file diff --git a/.gitignore b/.gitignore index bfea17c..439cf6c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,14 @@ user_guide_src/cilexer/build/* user_guide_src/cilexer/dist/* user_guide_src/cilexer/pycilexer.egg-info/* -#codeigniter 3 -application/logs/* -!application/logs/index.html -!application/logs/.htaccess /vendor/ +.idea +node_modules +vendor +writable/cache +writable/logs +writable/session +writable/uploads +public/uploads +composer.lock +package-lock.json \ No newline at end of file diff --git a/application/.htaccess b/application/.htaccess new file mode 100644 index 0000000..f24db0a --- /dev/null +++ b/application/.htaccess @@ -0,0 +1,6 @@ + + Require all denied + + + Deny from all + diff --git a/application/Config/App.php b/application/Config/App.php new file mode 100644 index 0000000..13d827f --- /dev/null +++ b/application/Config/App.php @@ -0,0 +1,300 @@ + SYSPATH + * `]; + */ + $psr4 = [ + 'Config' => APPPATH.'Config', + APP_NAMESPACE => APPPATH, // For custom namespace + 'App' => APPPATH, // To ensure filters, etc still found, + //'Tests\Support' => TESTPATH.'_support', // So custom migrations can run during testing + ]; + + /** + * ------------------------------------------------------------------- + * Class Map + * ------------------------------------------------------------------- + * The class map provides a map of class names and their exact + * location on the drive. Classes loaded in this manner will have + * slightly faster performance because they will not have to be + * searched for within one or more directories as they would if they + * were being autoloaded through a namespace. + * + * Prototype: + * + * $Config['classmap'] = [ + * 'MyClass' => '/path/to/class/file.php' + * ]; + */ + $classmap = []; + + //-------------------------------------------------------------------- + // Do Not Edit Below This Line + //-------------------------------------------------------------------- + + $this->psr4 = array_merge($this->psr4, $psr4); + $this->classmap = array_merge($this->classmap, $classmap); + + unset($psr4, $classmap); + } + + //-------------------------------------------------------------------- +} diff --git a/application/Config/Boot/development.php b/application/Config/Boot/development.php new file mode 100644 index 0000000..25f6151 --- /dev/null +++ b/application/Config/Boot/development.php @@ -0,0 +1,33 @@ + [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'weight' => 1 + ] + ]; + + /* + | ------------------------------------------------------------------------- + | Redis settings + | ------------------------------------------------------------------------- + | Your Redis server can be specified below, if you are using + | the Redis or Predis drivers. + | + */ + public $redis = [ + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'timeout' => 0, + ]; + + /* + |-------------------------------------------------------------------------- + | Available Cache Handlers + |-------------------------------------------------------------------------- + | + | This is an array of cache engine alias' and class names. Only engines + | that are listed here are allowed to be used. + | + */ + public $validHandlers = [ + 'dummy' => \CodeIgniter\Cache\Handlers\DummyHandler::class, + 'file' => \CodeIgniter\Cache\Handlers\FileHandler::class, + 'memcached' => \CodeIgniter\Cache\Handlers\MemcachedHandler::class, + 'predis' => \CodeIgniter\Cache\Handlers\PredisHandler::class, + 'redis' => \CodeIgniter\Cache\Handlers\RedisHandler::class, + 'wincache' => \CodeIgniter\Cache\Handlers\WincacheHandler::class, + ]; +} diff --git a/application/Config/Constants.php b/application/Config/Constants.php new file mode 100644 index 0000000..bf767a3 --- /dev/null +++ b/application/Config/Constants.php @@ -0,0 +1,77 @@ + '', + 'hostname' => 'localhost', + 'username' => 'root', + 'password' => '', + 'database' => 'blog', + 'DBDriver' => 'MySQLi', + 'DBPrefix' => '', + 'pConnect' => false, + 'DBDebug' => (ENVIRONMENT !== 'production'), + 'cacheOn' => false, + 'cacheDir' => '', + 'charset' => 'utf8', + 'DBCollat' => 'utf8_general_ci', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + ]; + + //-------------------------------------------------------------------- +} diff --git a/application/Config/DocTypes.php b/application/Config/DocTypes.php new file mode 100644 index 0000000..5364ab7 --- /dev/null +++ b/application/Config/DocTypes.php @@ -0,0 +1,33 @@ + '', + 'xhtml1-strict' => '', + 'xhtml1-trans' => '', + 'xhtml1-frame' => '', + 'xhtml-basic11' => '', + 'html5' => '', + 'html4-strict' => '', + 'html4-trans' => '', + 'html4-frame' => '', + 'mathml1' => '', + 'mathml2' => '', + 'svg10' => '', + 'svg11' => '', + 'svg11-basic' => '', + 'svg11-tiny' => '', + 'xhtml-math-svg-xh' => '', + 'xhtml-math-svg-sh' => '', + 'xhtml-rdfa-1' => '', + 'xhtml-rdfa-2' => '' + ]; +} diff --git a/application/Config/Events.php b/application/Config/Events.php new file mode 100644 index 0000000..f2fa883 --- /dev/null +++ b/application/Config/Events.php @@ -0,0 +1,32 @@ + \App\Filters\CSRF::class, + 'toolbar' => \App\Filters\DebugToolbar::class, + ]; + + // Always applied before every request + public $globals = [ + 'before' => [ + // 'csrf' + ], + 'after' => [ + 'toolbar' + ] + ]; + + // Works on all of a particular HTTP method + // (GET, POST, etc) as BEFORE filters only + // like: 'post' => ['CSRF', 'throttle'], + public $methods = []; + + // List filter aliases and any before/after uri patterns + // that they should run on, like: + // 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']], + public $filters = []; +} diff --git a/application/Config/ForeignCharacters.php b/application/Config/ForeignCharacters.php new file mode 100644 index 0000000..fb9da5c --- /dev/null +++ b/application/Config/ForeignCharacters.php @@ -0,0 +1,5 @@ + \CodeIgniter\Format\JSONFormatter::class, + 'application/xml' => \CodeIgniter\Format\XMLFormatter::class, + 'text/xml' => \CodeIgniter\Format\XMLFormatter::class, + ]; + + //-------------------------------------------------------------------- + + /** + * A Factory method to return the appropriate formatter for the given mime type. + * + * @param string $mime + * + * @return \CodeIgniter\Format\FormatterInterface + */ + public function getFormatter(string $mime) + { + if (! array_key_exists($mime, $this->formatters)) { + throw new \InvalidArgumentException('No Formatter defined for mime type: '. $mime); + } + + $class = $this->formatters[$mime]; + + if (! class_exists($class)) { + throw new \BadMethodCallException($class.' is not a valid Formatter.'); + } + + return new $class(); + } + + //-------------------------------------------------------------------- +} diff --git a/application/Config/Images.php b/application/Config/Images.php new file mode 100644 index 0000000..826d969 --- /dev/null +++ b/application/Config/Images.php @@ -0,0 +1,31 @@ + \CodeIgniter\Images\Handlers\GDHandler::class, + 'imagick' => \CodeIgniter\Images\Handlers\ImageMagickHandler::class, + ]; +} diff --git a/application/Config/Logger.php b/application/Config/Logger.php new file mode 100644 index 0000000..f13019b --- /dev/null +++ b/application/Config/Logger.php @@ -0,0 +1,133 @@ + [ + + /* + * The log levels that this handler will handle. + */ + 'handles' => ['critical', 'alert', 'emergency', 'debug', + 'error', 'info', 'notice', 'warning'], + + /* + * Leave this BLANK unless you would like to set something other than the default + * writeable/logs/ directory. Use a full getServer path with trailing slash. + */ + 'path' => WRITEPATH.'logs/', + + /* + * The default filename extension for log files. The default 'php' allows for + * protecting the log files via basic scripting, when they are to be stored + * under a publicly accessible directory. + * + * Note: Leaving it blank will default to 'php'. + */ + 'fileExtension' => 'php', + + /* + * The file system permissions to be applied on newly created log files. + * + * IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal + * integer notation (i.e. 0700, 0644, etc.) + */ + 'filePermissions' => 0644 + ], + + /** + * The ChromeLoggerHandler requires the use of the Chrome web browser + * and the ChromeLogger extension. Uncomment this block to use it. + */ +// 'CodeIgniter\Log\Handlers\ChromeLoggerHandler' => [ +// /* +// * The log levels that this handler will handle. +// */ +// 'handles' => ['critical', 'alert', 'emergency', 'debug', +// 'error', 'info', 'notice', 'warning'], +// ] + ]; +} diff --git a/application/Config/Migrations.php b/application/Config/Migrations.php new file mode 100644 index 0000000..a0cda55 --- /dev/null +++ b/application/Config/Migrations.php @@ -0,0 +1,62 @@ +migration->current() this is the version that schema will + | be upgraded / downgraded to. + | + */ + public $currentVersion = ""; +} diff --git a/application/Config/Mimes.php b/application/Config/Mimes.php new file mode 100644 index 0000000..ef0002c --- /dev/null +++ b/application/Config/Mimes.php @@ -0,0 +1,323 @@ + [ + 'application/mac-binhex40', + 'application/mac-binhex', + 'application/x-binhex40', + 'application/x-mac-binhex40', + ], + 'cpt' => 'application/mac-compactpro', + 'csv' => [ + 'text/csv', + 'text/x-comma-separated-values', + 'text/comma-separated-values', + 'application/octet-stream', + 'application/vnd.ms-excel', + 'application/x-csv', + 'text/x-csv', + 'application/csv', + 'application/excel', + 'application/vnd.msexcel', + 'text/plain', + ], + 'bin' => [ + 'application/macbinary', + 'application/mac-binary', + 'application/octet-stream', + 'application/x-binary', + 'application/x-macbinary', + ], + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'exe' => ['application/octet-stream', 'application/x-msdownload'], + 'class' => 'application/octet-stream', + 'psd' => ['application/x-photoshop', 'image/vnd.adobe.photoshop'], + 'so' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => ['application/pdf', 'application/force-download', 'application/x-download', 'binary/octet-stream'], + 'ai' => ['application/pdf', 'application/postscript'], + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => [ + 'application/vnd.ms-excel', + 'application/msexcel', + 'application/x-msexcel', + 'application/x-ms-excel', + 'application/x-excel', + 'application/x-dos_ms_excel', + 'application/xls', + 'application/x-xls', + 'application/excel', + 'application/download', + 'application/vnd.ms-office', + 'application/msword', + ], + 'ppt' => [ + 'application/vnd.ms-powerpoint', + 'application/powerpoint', + 'application/vnd.ms-office', + 'application/msword', + ], + 'pptx' => [ + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/x-zip', + 'application/zip', + ], + 'wbxml' => 'application/wbxml', + 'wmlc' => 'application/wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'php' => [ + 'application/x-php', + 'application/x-httpd-php', + 'application/php', + 'text/php', + 'text/x-php', + 'application/x-httpd-php-source', + ], + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => ['application/x-javascript', 'text/plain'], + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => ['application/x-tar', 'application/x-gzip-compressed'], + 'z' => 'application/x-compress', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'zip' => [ + 'application/x-zip', + 'application/zip', + 'application/x-zip-compressed', + 'application/s-compressed', + 'multipart/x-zip', + ], + 'rar' => ['application/x-rar', 'application/rar', 'application/x-rar-compressed'], + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'], + 'aif' => ['audio/x-aiff', 'audio/aiff'], + 'aiff' => ['audio/x-aiff', 'audio/aiff'], + 'aifc' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'rv' => 'video/vnd.rn-realvideo', + 'wav' => ['audio/x-wav', 'audio/wave', 'audio/wav'], + 'bmp' => [ + 'image/bmp', + 'image/x-bmp', + 'image/x-bitmap', + 'image/x-xbitmap', + 'image/x-win-bitmap', + 'image/x-windows-bmp', + 'image/ms-bmp', + 'image/x-ms-bmp', + 'application/bmp', + 'application/x-bmp', + 'application/x-win-bitmap', + ], + 'gif' => 'image/gif', + 'jpg' => ['image/jpeg', 'image/pjpeg'], + 'jpeg' => ['image/jpeg', 'image/pjpeg'], + 'jpe' => ['image/jpeg', 'image/pjpeg'], + 'jp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'], + 'j2k' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'], + 'jpf' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'], + 'jpg2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'], + 'jpx' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'], + 'jpm' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'], + 'mj2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'], + 'mjp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'], + 'png' => ['image/png', 'image/x-png'], + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'css' => ['text/css', 'text/plain'], + 'html' => ['text/html', 'text/plain'], + 'htm' => ['text/html', 'text/plain'], + 'shtml' => ['text/html', 'text/plain'], + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'log' => ['text/plain', 'text/x-log'], + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => ['application/xml', 'text/xml', 'text/plain'], + 'xsl' => ['application/xml', 'text/xsl', 'text/xml'], + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'avi' => ['video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'], + 'movie' => 'video/x-sgi-movie', + 'doc' => ['application/msword', 'application/vnd.ms-office'], + 'docx' => [ + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/zip', + 'application/msword', + 'application/x-zip', + ], + 'dot' => ['application/msword', 'application/vnd.ms-office'], + 'dotx' => [ + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/zip', + 'application/msword', + ], + 'xlsx' => [ + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/zip', + 'application/vnd.ms-excel', + 'application/msword', + 'application/x-zip', + ], + 'word' => ['application/msword', 'application/octet-stream'], + 'xl' => 'application/excel', + 'eml' => 'message/rfc822', + 'json' => ['application/json', 'text/json'], + 'pem' => ['application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'], + 'p10' => ['application/x-pkcs10', 'application/pkcs10'], + 'p12' => 'application/x-pkcs12', + 'p7a' => 'application/x-pkcs7-signature', + 'p7c' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'], + 'p7m' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'], + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'crt' => ['application/x-x509-ca-cert', 'application/x-x509-user-cert', 'application/pkix-cert'], + 'crl' => ['application/pkix-crl', 'application/pkcs-crl'], + 'der' => 'application/x-x509-ca-cert', + 'kdb' => 'application/octet-stream', + 'pgp' => 'application/pgp', + 'gpg' => 'application/gpg-keys', + 'sst' => 'application/octet-stream', + 'csr' => 'application/octet-stream', + 'rsa' => 'application/x-pkcs7', + 'cer' => ['application/pkix-cert', 'application/x-x509-ca-cert'], + '3g2' => 'video/3gpp2', + '3gp' => ['video/3gp', 'video/3gpp'], + 'mp4' => 'video/mp4', + 'm4a' => 'audio/x-m4a', + 'f4v' => ['video/mp4', 'video/x-f4v'], + 'flv' => 'video/x-flv', + 'webm' => 'video/webm', + 'aac' => 'audio/x-acc', + 'm4u' => 'application/vnd.mpegurl', + 'm3u' => 'text/plain', + 'xspf' => 'application/xspf+xml', + 'vlc' => 'application/videolan', + 'wmv' => ['video/x-ms-wmv', 'video/x-ms-asf'], + 'au' => 'audio/x-au', + 'ac3' => 'audio/ac3', + 'flac' => 'audio/x-flac', + 'ogg' => ['audio/ogg', 'video/ogg', 'application/ogg'], + 'kmz' => ['application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'], + 'kml' => ['application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'], + 'ics' => 'text/calendar', + 'ical' => 'text/calendar', + 'zsh' => 'text/x-scriptzsh', + '7zip' => ['application/x-compressed', 'application/x-zip-compressed', 'application/zip', 'multipart/x-zip'], + 'cdr' => [ + 'application/cdr', + 'application/coreldraw', + 'application/x-cdr', + 'application/x-coreldraw', + 'image/cdr', + 'image/x-cdr', + 'zz-application/zz-winassoc-cdr', + ], + 'wma' => ['audio/x-ms-wma', 'video/x-ms-asf'], + 'jar' => [ + 'application/java-archive', + 'application/x-java-application', + 'application/x-jar', + 'application/x-compressed', + ], + 'svg' => ['image/svg+xml', 'application/xml', 'text/xml'], + 'vcf' => 'text/x-vcard', + 'srt' => ['text/srt', 'text/plain'], + 'vtt' => ['text/vtt', 'text/plain'], + 'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'], + ]; + + //-------------------------------------------------------------------- + + /** + * Attempts to determine the best mime type for the given file extension. + * + * @param string $ext + * + * @return string|null The mime type found, or none if unable to determine. + */ + public static function guessTypeFromExtension(string $extension) + { + $extension = trim(strtolower($extension), '. '); + + if (! array_key_exists($extension, self::$mimes)) { + return null; + } + + return is_array(self::$mimes[$extension]) ? self::$mimes[$extension][0] : self::$mimes[$extension]; + } + + //-------------------------------------------------------------------- + + /** + * Attempts to determine the best file extension for a given mime type. + * + * @param string $type + * + * @return string|null The extension determined, or null if unable to match. + */ + public static function guessExtensionFromType(string $type) + { + $type = trim(strtolower($type), '. '); + + foreach (self::$mimes as $ext => $types) { + if (is_string($types) && $types == $type) { + return $ext; + } elseif (is_array($types) && in_array($type, $types)) { + return $ext; + } + } + + return null; + } + + //-------------------------------------------------------------------- +} diff --git a/application/Config/Pager.php b/application/Config/Pager.php new file mode 100644 index 0000000..02c9870 --- /dev/null +++ b/application/Config/Pager.php @@ -0,0 +1,35 @@ + 'CodeIgniter\Pager\Views\default_full', + 'default_simple' => 'CodeIgniter\Pager\Views\default_simple', + 'cat' => 'App\Views\Pagers\cat' + ]; + + /* + |-------------------------------------------------------------------------- + | Items Per Page + |-------------------------------------------------------------------------- + | + | The default number of results shown in a single page. + | + */ + public $perPage = 20; +} diff --git a/application/Config/Paths.php b/application/Config/Paths.php new file mode 100644 index 0000000..cbfe72d --- /dev/null +++ b/application/Config/Paths.php @@ -0,0 +1,78 @@ +defaultNamespace() + * + * Modifies the namespace that is added to a controller if it doesn't + * already have one. By default this is the global namespace (\). + * + * $routes->defaultController() + * + * Changes the name of the class used as a controller when the route + * points to a folder instead of a class. + * + * $routes->defaultMethod() + * + * Assigns the method inside the controller that is ran when the + * Router is unable to determine the appropriate method to run. + * + * $routes->setAutoRoute() + * + * Determines whether the Router will attempt to match URIs to + * Controllers when no specific route has been defined. If false, + * only routes that have been defined here will be available. + */ +$routes->setDefaultNamespace('App\Controllers\Blog'); +$routes->setDefaultController('Home'); +$routes->setDefaultMethod('index'); +$routes->setTranslateURIDashes(false); +$routes->set404Override('App\Controllers\Blog\Errors::show_404'); +$routes->setAutoRoute(false); +$routes->discoverLocal(false); + +/** + * -------------------------------------------------------------------- + * Route Definitions + * -------------------------------------------------------------------- + */ + +// We get a performance increase by specifying the default +// route since we don't have to scan directories. +$routes->add('/', 'Home::index', ['namespace' => 'App\Controllers\Blog']); +$routes->add('home', 'Home::index', ['namespace' => 'App\Controllers\Blog']); + +/* + * Route for catégorie + */ +$routes->add('cat', 'Cat::index', ['namespace' => 'App\Controllers\Blog']); +$routes->add('cat/(:any)', 'Cat::View/$1', ['namespace' => 'App\Controllers\Blog']); + +/* + * Route for Contact + */ +$routes->add('contact', 'Contact::index', ['namespace' => 'App\Controllers\Blog']); +$routes->add('contact/add', 'Contact::addContact', ['namespace' => 'App\Controllers\Blog\Ajax']); + +/* + * Route for about + */ +$routes->add('about', 'About::index', ['namespace' => 'App\Controllers\Blog']); + +/* + * Route for article + */ +$routes->add('article', 'Article::index', ['namespace' => 'App\Controllers\Blog']); +$routes->add('article/(:any)', 'Article::View/$1', ['namespace' => 'App\Controllers\Blog']); + +/* + * Route for comments + */ +$routes->add('comments/add', 'Comments::AddComments', ['namespace' => 'App\Controllers\Blog\Ajax']); + +/* + * Route for research + */ +$routes->group('search', function ($routes) { + $routes->add('/', 'Search::index', ['namespace' => 'App\Controllers\Blog']); +}); + +/* + * Route for cookies + */ +$routes->add('cookies', 'Cookies::index', ['namespace' => 'App\Controllers\Blog']); + +/* + * Route for Admin (group) + */ +$routes->group('admin', function ($routes) { + $routes->add('/', 'Home::index', ['namespace' => 'App\Controllers\Admin']); + $routes->add('home', 'Home::index', ['namespace' => 'App\Controllers\Admin']); + + $routes->group('auth', function ($routes) { + $routes->add('login', 'Auth::Login', ['namespace' => 'App\Controllers\Admin']); + $routes->add('logout', 'Auth::Logout', ['namespace' => 'App\Controllers\Admin']); + $routes->add('login_ajax', 'Auth::login_ajax', ['namespace' => 'App\Controllers\Admin']); + }); + + $routes->group('article', function ($routes) { + $routes->add('/', 'Article::index', ['namespace' => 'App\Controllers\Admin']); + $routes->add('add', 'Article::add', ['namespace' => 'App\Controllers\Admin']); + $routes->add('edit/(:num)/(:num)', 'Article::edit/$1/$2', ['namespace' => 'App\Controllers\Admin']); + $routes->add('list/(:num)', 'Article::list/$1', ['namespace' => 'App\Controllers\Admin']); + }); + + $routes->group('cat', function ($routes) { + $routes->add('/', 'Cat::index', ['namespace' => 'App\Controllers\Admin']); + }); + + $routes->group('comments', function ($routes) { + $routes->add('/', 'Comments::index', ['namespace' => 'App\Controllers\Admin']); + $routes->add('wait', 'Comments::wait', ['namespace' => 'App\Controllers\Admin']); + $routes->add('ok', 'Comments::ok', ['namespace' => 'App\Controllers\Admin']); + $routes->add('no', 'Comments::no', ['namespace' => 'App\Controllers\Admin']); + }); + + $routes->group('media', function ($routes) { + $routes->add('/', 'Media::index', ['namespace' => 'App\Controllers\Admin']); + $routes->add('add', 'Media::add', ['namespace' => 'App\Controllers\Admin']); + }); + + $routes->group('users', function ($routes) { + $routes->add('/', 'Users::index', ['namespace' => 'App\Controllers\Admin']); + $routes->add('liste', 'Users::liste', ['namespace' => 'App\Controllers\Admin']); + $routes->add('add', 'Users::add', ['namespace' => 'App\Controllers\Admin']); + $routes->add('log', 'Users::log', ['namespace' => 'App\Controllers\Admin']); + }); + + $routes->group('contact', function ($routes) { + $routes->add('/', 'Contact::index', ['namespace' => 'App\Controllers\Admin']); + $routes->add('new', 'Contact::new', ['namespace' => 'App\Controllers\Admin']); + $routes->add('finish', 'Contact::finish', ['namespace' => 'App\Controllers\Admin']); + $routes->add('rep/(:num)', 'Contact::rep/$1', ['namespace' => 'App\Controllers\Admin']); + }); + + $routes->group('config', function ($routes) { + $routes->add('/', 'Config::index', ['namespace' => 'App\Controllers\Admin']); + $routes->add('params', 'Config::params', ['namespace' => 'App\Controllers\Admin']); + $routes->add('task', 'Config::task', ['namespace' => 'App\Controllers\Admin']); + $routes->add('db', 'Config::db', ['namespace' => 'App\Controllers\Admin']); + $routes->add('cache', 'Config::cache', ['namespace' => 'App\Controllers\Admin']); + $routes->add('log', 'Config::log', ['namespace' => 'App\Controllers\Admin']); + }); + + $routes->group('ajax', function ($routes) { + $routes->group('article', function ($routes) { + $routes->add('add', 'Article::add', ['namespace' => 'App\Controllers\Admin\Ajax']); + $routes->add('edit', 'Article::edit', ['namespace' => 'App\Controllers\Admin\Ajax']); + }); + + $routes->group('cat', function ($routes) { + $routes->add('updatetitle', 'Cat::UpdateTitle', ['namespace' => 'App\Controllers\Admin\Ajax']); + $routes->add('updatecontent', 'Cat::UpdateContent', ['namespace' => 'App\Controllers\Admin\Ajax']); + $routes->add('add', 'Cat::Add', ['namespace' => 'App\Controllers\Admin\Ajax']); + }); + + $routes->group('comments', function ($routes) { + $routes->add('valide', 'Comments::valide', ['namespace' => 'App\Controllers\Admin\Ajax']); + $routes->add('refuse', 'Comments::refuse', ['namespace' => 'App\Controllers\Admin\Ajax']); + }); + + $routes->add('upload', 'Upload::index', ['namespace' => 'App\Controllers\Admin\Ajax']); + + $routes->group('contact', function ($routes) { + $routes->add('rep', 'Contact::rep', ['namespace' => 'App\Controllers\Admin\Ajax']); + $routes->add('markedview', 'Contact::markedview', ['namespace' => 'App\Controllers\Admin\Ajax']); + $routes->add('del', 'Contact::del', ['namespace' => 'App\Controllers\Admin\Ajax']); + }); + + $routes->group('config', function ($routes) { + $routes->add('updateparams', 'Config::UpdateParams', ['namespace' => 'App\Controllers\Admin\Ajax']); + $routes->add('delparams', 'Config::DelParams', ['namespace' => 'App\Controllers\Admin\Ajax']); + }); + }); +}); diff --git a/application/Config/Services.php b/application/Config/Services.php new file mode 100644 index 0000000..9701ea5 --- /dev/null +++ b/application/Config/Services.php @@ -0,0 +1,32 @@ + 'CodeIgniter\Validation\Views\list', + 'single' => 'CodeIgniter\Validation\Views\single' + ]; + + //-------------------------------------------------------------------- + // Rules + //-------------------------------------------------------------------- +} diff --git a/application/Config/View.php b/application/Config/View.php new file mode 100644 index 0000000..a047106 --- /dev/null +++ b/application/Config/View.php @@ -0,0 +1,34 @@ +config = new App(); + $this->session = Services::session($this->config); + $this->request = Services::request(); + $this->General = new General; + $this->csrf = new CSRFToken(); + } + + /** + * @return bool + */ + public function isConnected() + { + if (!$this->session->has('logged_in')) { + if (get_cookie("remember_me", true) != null) { + $this->session->set('Account_ip', $this->request->getIPAddress()); + $this->session->set('Account_id', get_cookie("remember_me", true)); + //$this->session->set ('Account_name', $this->general->Get_display_name(get_cookie("remember_me", TRUE))); + $this->session->set('logged_in', true); + delete_cookie('remember_me'); + set_cookie(['name' => 'remember_me', 'value' => get_cookie("remember_me", true), 'expire' => '32140800'], true); + header('Location: ' . $_SERVER['REQUEST_URI']); + exit(); + } else { + return false; + } + } else { + if (get_cookie("remember_me", true) != null) { + $this->session->set('Account_ip', $this->request->getIPAddress()); + $this->session->set('Account_id', get_cookie("remember_me", true)); + //$this->session->set ('Account_name', $this->general->Get_display_name(get_cookie("remember_me", TRUE))); + $this->session->set('logged_in', true); + delete_cookie('remember_me'); + set_cookie(['name' => 'remember_me', 'value' => get_cookie("remember_me", true), 'expire' => '32140800'], true); + } + return true; + } + } + + /** + * @param array $arr + * @param int $code + * + * @return \CodeIgniter\HTTP\Response + */ + public function responded(array $arr, int $code = 200) + { + $this->response = new Response($this->config); + + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + if ($this->isConnected()) { + $this->response->setStatusCode($code)->setContentType('application/json')->setBody(json_encode($arr))->send(); + } else { + $this->response->setStatusCode(403)->setContentType('application/json')->setBody(json_encode(["error" => "You are not loged", "error_code" => 4001]))->send(); + } + } else { + $this->response->setStatusCode(403)->setContentType('application/json')->setBody(json_encode(["error" => "Error CSRF, You are HACKER ?", "error_code" => 4010]))->send(); + return false; + } + exit(); + } +} diff --git a/application/Controllers/Admin/Ajax/Article.php b/application/Controllers/Admin/Ajax/Article.php new file mode 100644 index 0000000..99d1f02 --- /dev/null +++ b/application/Controllers/Admin/Ajax/Article.php @@ -0,0 +1,69 @@ +article_model = new ArticleModel(); + } + + /** + * @return \CodeIgniter\HTTP\Response + */ + public function add() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $picture_one = str_replace("C:\\fakepath\\", "uploads/", $_POST['pic']); + $article_id = $this->article_model->Add($_POST['title'], $_POST['link'], $_POST['content'], $_POST['wordkey'], $_POST['cat'], $picture_one, $_POST['important']); + return $this->responded(["code" => 1, "title" => "Création d'article", "message" => "Article en attente de correction, redirection en cours", "article_id" => $article_id]); + } else { + return $this->responded(["code" => 0, "message" => "Erreurs..."]); + } + } else { + return $this->responded([]); + } + } + + /** + * @return \CodeIgniter\HTTP\Response + */ + public function edit() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->article_model->Edit($_POST['postid'], $_POST['title'], $_POST['link'], $_POST['content'], $_POST['wordkey'], $_POST['cat'], $_POST['pic'], $_POST['important'], $_POST['type']); + if ($_POST['type'] == 0) { + return $this->responded(["code" => 1, "title" => "Edition d'article", "message" => "Article modifié"]); + } elseif ($_POST['type'] == 1) { + return $this->responded(["code" => 1, "title" => "Edition d'article", "message" => "Article corrigé, il est maintenant publié"]); + } elseif ($_POST['type'] == 2) { + return $this->responded(["code" => 1, "title" => "Edition d'article", "message" => "Article corrigé, il est en attente de correction"]); + } elseif ($_POST['type'] == 3) { + return $this->responded(["code" => 1, "title" => "Edition d'article", "message" => "Article corrigé, il est en attente de publication"]); + } else { + return $this->responded(["code" => 0, "message" => "Erreurs..."]); + } + } else { + return $this->responded(["code" => 0, "message" => "Erreurs..."]); + } + } else { + return $this->responded([]); + } + } +} diff --git a/application/Controllers/Admin/Ajax/Cat.php b/application/Controllers/Admin/Ajax/Cat.php new file mode 100644 index 0000000..4df9e83 --- /dev/null +++ b/application/Controllers/Admin/Ajax/Cat.php @@ -0,0 +1,65 @@ +cat_model = new CatModel(); + } + + public function UpdateTitle() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->cat_model->UpdateCat($_POST['id'], 'title', $_POST['title']); + return $this->responded(["code" => 1, "title" => "Catégorie modifier", "message" => "La catégories à bien été modifier"]); + } else { + return $this->responded(["code" => 0, "message" => "Erreurs..."]); + } + } else { + return $this->responded([]); + } + } + + public function UpdateContent() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->cat_model->UpdateCat($_POST['id'], 'description', $_POST['content']); + return $this->responded(["code" => 1, "title" => "Catégorie modifier", "message" => "La catégories à bien été modifier"]); + } else { + return $this->responded(["code" => 0, "message" => "Erreurs..."]); + } + } else { + return $this->responded([]); + } + } + + public function Add() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->cat_model->AddCat($_POST['title'], $_POST['content'], $_POST['slug'], $_POST['icon']); + return $this->responded(["code" => 1, "title" => "Ajout d'une catégorie", "message" => "La catégories à bien été ajouter, rechargemen tde la page"]); + } else { + return $this->responded(["code" => 0, "message" => "Erreurs..."]); + } + } else { + return $this->responded([]); + } + } +} diff --git a/application/Controllers/Admin/Ajax/Comments.php b/application/Controllers/Admin/Ajax/Comments.php new file mode 100644 index 0000000..499453d --- /dev/null +++ b/application/Controllers/Admin/Ajax/Comments.php @@ -0,0 +1,42 @@ +comments_model = new CommentsModel(); + } + + public function valide() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->comments_model->valideComments($_POST['id']); + return $this->responded(["code" => 1, "title" => "Commentaire", "message" => "Commentaire validé avec success"]); + } + } else { + return $this->responded([]); + } + } + + public function refuse() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->comments_model->refuseComments($_POST['id']); + return $this->responded(["code" => 1, "title" => "Commentaire", "message" => "Commentaire refusé avec success"]); + } + } else { + return $this->responded([]); + } + } +} diff --git a/application/Controllers/Admin/Ajax/Config.php b/application/Controllers/Admin/Ajax/Config.php new file mode 100644 index 0000000..26d4727 --- /dev/null +++ b/application/Controllers/Admin/Ajax/Config.php @@ -0,0 +1,42 @@ +config_model = new ConfigModel(); + } + + public function updateparams() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->config_model->UpdateConfig($_POST['id'], $_POST['key'], $_POST['data']); + return $this->responded(["code" => 1, "title" => "Paramètres", "message" => "Les paramêtres on bien été mise à jours"]); + } + } else { + return $this->responded([]); + } + } + + public function delparams() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->config_model->DelConfig($_POST['id']); + return $this->responded(["code" => 1, "title" => "Paramètres", "message" => "Paramêtre supprimé avec success"]); + } + } else { + return $this->responded([]); + } + } +} diff --git a/application/Controllers/Admin/Ajax/Contact.php b/application/Controllers/Admin/Ajax/Contact.php new file mode 100644 index 0000000..e22d3bd --- /dev/null +++ b/application/Controllers/Admin/Ajax/Contact.php @@ -0,0 +1,61 @@ +contact_model = new ContactModel(); + $this->mailer = new Mailer(); + } + + public function rep() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->contact_model->markedview($_POST['id']); + $infocontact = $this->contact_model->getContact($_POST['id']); + $this->mailer->sendmail($_POST['sujet'], $infocontact->email, $_POST['message']); + return $this->responded(["code" => 1, "title" => "Contact", "message" => 'Le message à bien été envoyé']); + } + } else { + return $this->responded([]); + } + } + + public function markedview() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->contact_model->markedview($_POST['id']); + return $this->responded(["code" => 1, "title" => "Contact", "message" => "La prise de contact à été marqué comme vue"]); + } + } else { + return $this->responded([]); + } + } + + public function del() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->contact_model->del($_POST['id']); + return $this->responded(["code" => 1, "title" => "Contact", "message" => "La prise de contact à été supprimé"]); + } + } else { + return $this->responded([]); + } + } +} diff --git a/application/Controllers/Admin/Ajax/Upload.php b/application/Controllers/Admin/Ajax/Upload.php new file mode 100644 index 0000000..9a8f313 --- /dev/null +++ b/application/Controllers/Admin/Ajax/Upload.php @@ -0,0 +1,41 @@ +image = new Image($_FILES); + $this->media_model = new MediaModel(); + } + + public function index() + { + if ($this->isConnected()) { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + $this->image->setLocation(FCPATH . 'uploads/' . date('Y') . '/' . date('n')); + if ($this->image["pictures"]) { + $name = substr($_FILES['pictures']['name'], 0, strrpos($_FILES['pictures']['name'], ".")); + $this->image->setName($name); + $upload = $this->image->upload(); + if ($upload) { + $id_pic = $this->media_model->Add('uploads/' . date('Y') . '/' . date('n') . '/', $this->image->getName() . "." . $this->image->getMime()); + return $this->responded(["code" => 1, "title" => "Uploads", "message" => 'Image sauvegarder', "id" => $id_pic, "slug" => 'uploads/' . date('Y') . '/' . date('n') . '/' . $this->image->getName() . "." . $this->image->getMime()]); + } else { + return $this->responded(["code" => 0, "message" => "Erreur : " . $this->image["error"]]); + } + } else { + return $this->responded(["code" => 0, "message" => "Erreur : " . $this->image["error"]]); + } + } else { + return $this->responded(["code" => 0]); + } + } else { + return $this->responded(["code" => 0]); + } + } +} diff --git a/application/Controllers/Admin/Application.php b/application/Controllers/Admin/Application.php new file mode 100644 index 0000000..33fbe78 --- /dev/null +++ b/application/Controllers/Admin/Application.php @@ -0,0 +1,258 @@ +config = new App(); + $this->session = Services::session($this->config); + $this->request = Services::request(); + $this->response = new Response($this->config); + $this->twig = new Twig('admin'); + $this->csrf = new CSRFToken(); + $this->config_model = new ConfigModel(); + + //Set header + $this->response->setStatusCode(Response::HTTP_OK); + $this->response->setHeader('Content-type', 'text/html'); + $this->response->noCache(); + // Prevent some security threats, per Kevin + // Turn on IE8-IE9 XSS prevention tools + $this->response->setHeader('X-XSS-Protection', '1; mode=block'); + // Don't allow any pages to be framed - Defends against CSRF + $this->response->setHeader('X-Frame-Options', 'DENY'); + // prevent mime based attacks + $this->response->setHeader('X-Content-Type-Options', 'nosniff'); + // Prevent google + $this->response->setHeader('X-robots-tag', 'noindex'); + + if (!$this->isConnected() && uri_string() != 'admin/auth/login' && uri_string() != 'admin/auth/login_ajax') { + redirect('admin/auth/login'); + } + } + + /** + * Set js & css file + * + * @return string $this + */ + private function base_template() + { + + //Set css file + $this->set_css(base_url('assets/css/font-awesome.css')); + $this->set_css('//fonts.googleapis.com/css?family=RobotoDraft:300,400,500'); + $this->set_css(base_url('assets/css/admin/bootstrap.css')); + + //Set JS + $this->set_js('//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'); + $this->set_js(base_url('assets/js/cookie.js')); + $this->set_js('//cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js'); + $this->set_js('//maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js'); + $this->set_js(base_url('assets/js/jquery.toast.js')); + + //Set by page + if ($this->request->uri->getSegment(2) == 'auth' && $this->request->uri->getSegment(3) == 'login') { + $this->set_css(base_url('assets/css/admin/auth.css')); + $this->set_js(base_url('assets/js/admin/auth.js')); + } else { + $this->set_css(base_url('assets/css/admin/style.css')); + $this->set_js(base_url('assets/js/admin/app.js')); + + if ($this->request->uri->getSegment(2) == 'article') { + $this->set_css(base_url('assets/css/admin/article.css')); + $this->set_js(base_url('assets/js/admin/article.js')); + } elseif ($this->request->uri->getSegment(2) == 'comments') { + $this->set_js(base_url('assets/js/admin/comments.js')); + } elseif ($this->request->uri->getSegment(2) == 'config') { + $this->set_js(base_url('assets/js/admin/config.js')); + } elseif ($this->request->uri->getSegment(2) == 'cat') { + $this->set_js(base_url('assets/js/admin/cat.js')); + } elseif ($this->request->uri->getSegment(2) == 'contact') { + $this->set_js(base_url('assets/js/admin/contact.js')); + } + } + + return $this; + } + /** + * @param string $file link + * + * @return string $this + */ + public function set_css(string $file) + { + $this->css[]['file'] = $file; + return $this; + } + + /** + * @param string $file link + * + * @return string $this + */ + public function set_js(string $file) + { + $this->js[]['file'] = $file; + return $this; + } + + /** + * @param string $title set title page + * @param string $ype set if type is website or article + * + * @return string $h return all meta + */ + public function meta(string $title) + { + $titre = $title . ' | Administration | ' . $this->config_model->GetConfig('site_title'); + + $h = ''; + $h .= ''; + $h .= ''; + $h .= '' . $titre . ''; + $h .= ''; + //$h .= ''; + + return $h; + } + + /** + * @param string $page set page + * @param string $title set title page + * + * @return string $this + */ + public function render(string $page, string $title = null) + { + $this->base_template(); + + $this->data['css'] = $this->css; + $this->data['js'] = $this->js; + + if ($title != null) { + $this->data['meta'] = $this->meta($title); + } else { + $this->data['meta'] = $this->meta($this->stitle); + } + + $this->data['page_title'] = $this->stitle; + $this->data['page_stitle'] = $this->tpage; + + $this->data['breadcrumb'] = $this->breadcrumb(); + + $this->response->setBody($this->twig->Rendered($page, $this->data)); + $this->response->send(); + + return $this; + } + + protected function breadcrumb() + { + $bread = '
'; + if ($this->request->uri->getTotalSegments() >= 2) { + $bread .= "Accueil"; + if ($this->request->uri->getTotalSegments() >= 3) { + $bread .= ''.$this->stitle.''; + $bread .= '' . $this->tpage . ''; + } else { + $bread .= '' . $this->stitle . ''; + } + } else { + $bread .= 'Accueil'; + } + $bread .= '
'; + return $bread; + } + + /** + * @return bool + */ + public function isConnected() + { + if (!$this->session->has('logged_in')) { + if (get_cookie('remember_me', true) != null) { + $this->session->set('Account_ip', $this->request->getIPAddress()); + $this->session->set('Account_id', get_cookie('remember_me', true)); + //$this->session->set ('Account_name', $this->general->Get_display_name(get_cookie("remember_me", TRUE))); + $this->session->set('logged_in', true); + delete_cookie('remember_me'); + set_cookie(['name' => 'remember_me', 'value' => get_cookie('remember_me', true), 'expire' => '32140800'], true); + header('Location: ' . $_SERVER['REQUEST_URI']); + exit(); + } else { + return false; + } + } else { + if (get_cookie('remember_me', true) != null) { + $this->session->set('Account_ip', $this->request->getIPAddress()); + $this->session->set('Account_id', get_cookie('remember_me', true)); + //$this->session->set ('Account_name', $this->general->Get_display_name(get_cookie("remember_me", TRUE))); + $this->session->set('logged_in', true); + delete_cookie('remember_me'); + set_cookie(['name' => 'remember_me', 'value' => get_cookie('remember_me', true), 'expire' => '32140800'], true); + } + return true; + } + } +} diff --git a/application/Controllers/Admin/Article.php b/application/Controllers/Admin/Article.php new file mode 100644 index 0000000..15c5192 --- /dev/null +++ b/application/Controllers/Admin/Article.php @@ -0,0 +1,87 @@ +article_model = new ArticleModel(); + $this->cat_model = new CatModel(); + $this->stitle = 'Articles'; + } + + /** + * @return string + */ + public function index(): self + { + $this->data['last_five'] = $this->article_model->lastFive(); + $this->data['count_article'] = [ + 'pub' => $this->article_model->count_publied(), + 'att_co' => $this->article_model->count_attCorrect(), + 'att_pub' => $this->article_model->count_attPublished(), + 'brouillon' => $this->article_model->count_brouillon() + ]; + + return $this->render('article/home'); + } + + /** + * @return string + */ + public function add(): self + { + $this->tpage = "Ajout d'un article"; + $this->data['getcat'] = $this->cat_model->getlist(); + return $this->render('article/add', 'Ajouter un article'); + } + + /** + * @param int $id id of article + * @param int $type (1 = publied, 2 = wait corrected, 3 = wait publied, 4 = brouillon) + * + * @return string + */ + public function edit(int $id, int $type): self + { + $this->tpage = "Modification d'article"; + $this->data['get_article'] = $this->article_model->GetArticle('id', $id); + $this->data['getcat'] = $this->cat_model->getlist(); + $this->data['type'] = $type; + return $this->render('article/edit', 'Modification de l\'article'); + } + + /** + * @param int $id (1 = publied, 2 = wait corrected, 3 = wait publied, 4 = brouillon) + * + * @return string + */ + public function list(int $id): self + { + $this->tpage = 'Liste des articles'; + $this->data['get_list'] = $this->article_model->getArticleListAdmin($id); + $this->data['type'] = $id; + $this->tpage = 'Liste des articles'; + return $this->render('article/list', 'Liste des articles'); + } +} diff --git a/application/Controllers/Admin/Auth.php b/application/Controllers/Admin/Auth.php new file mode 100644 index 0000000..3a1c429 --- /dev/null +++ b/application/Controllers/Admin/Auth.php @@ -0,0 +1,98 @@ +config = new App(); + $this->session = Services::session($this->config); + $this->request = Services::request(); + $this->response = new Response($this->config); + $this->auth_model = new AuthModel(); + $this->stitle = 'Auth'; + } + + /** + * @return string render + */ + public function index() + { + return $this->render('home'); + } + + /** + * @return string render + */ + public function Login(): self + { + return $this->render('auth/login', 'Connexion'); + } + + /** + * + */ + public function login_ajax() + { + $this->response->setStatusCode(Response::HTTP_OK); + $this->response->setHeader('Content-type', 'application/json'); + $this->response->noCache(); + $this->response->setHeader('X-robots-tag', 'noindex'); + $this->response->setHeader('X-XSS-Protection', '1; mode=block'); + $this->response->setHeader('X-Frame-Options', 'DENY'); + $this->response->setHeader('X-Content-Type-Options', 'nosniff'); + + header('Content-type: application/json'); + + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + if ($this->auth_model->isValidEmail($_POST['email'])) { + $account_id = $this->auth_model->isValidEmail($_POST['email']); + $account_name = $this->auth_model->GetUsername($account_id); + if ($this->auth_model->isValidPassword($_POST['password'], $account_id)) { + if ($_POST['remember'] == 1) { + $DataCookie = ['name' => 'remember_me', 'value' => $account_id, 'expire' => '32140800']; + set_cookie($DataCookie, true); + $DataCookieRelog = ['name' => 'relog', 'value' => true, 'expire' => '32140800']; + set_cookie($DataCookieRelog, true); + } + $this->session->set('Account_ip', $this->request->getIPAddress()); + $this->session->set('Account_id', $account_id); + $this->session->set('Account_name', $account_name); + $this->session->set('logged_in', true); + + $result = 1; + } else { + $result = 0; + } + } else { + $result = 0; + } + } else { + $result = 0; + } + + echo json_encode(['result' => $result]); + + exit(); + } +} diff --git a/application/Controllers/Admin/Cat.php b/application/Controllers/Admin/Cat.php new file mode 100644 index 0000000..e5b7ea4 --- /dev/null +++ b/application/Controllers/Admin/Cat.php @@ -0,0 +1,31 @@ +cat_model = new CatModel(); + $this->stitle = 'Catgories'; + } + + public function index(): self + { + $this->tpage = 'Liste des categories'; + $this->data['get_cat'] = $this->cat_model->getlist(); + return $this->render('cat/list'); + } +} diff --git a/application/Controllers/Admin/Comments.php b/application/Controllers/Admin/Comments.php new file mode 100644 index 0000000..2d3c286 --- /dev/null +++ b/application/Controllers/Admin/Comments.php @@ -0,0 +1,43 @@ +comments_model = new CommentsModel(); + $this->stitle = 'Commentaires'; + } + + public function index(): self + { + $this->data['count_wait'] = $this->comments_model->countComments('0'); + $this->data['count_ok'] = $this->comments_model->countComments('1'); + $this->data['count_no'] = $this->comments_model->countComments('-1'); + return $this->render('comments/home'); + } + + public function wait(): self + { + $this->tpage = 'Commentaires en attentes de validation'; + $this->data['comments'] = $this->comments_model->getComments('0'); + return $this->render('comments/wait'); + } + + public function ok(): self + { + $this->tpage = 'Commentaires validés'; + $this->data['comments'] = $this->comments_model->getComments('1'); + return $this->render('comments/ok'); + } + + public function no(): self + { + $this->tpage = 'Commentaires refusés'; + $this->data['comments'] = $this->comments_model->getComments('-1'); + return $this->render('comments/no'); + } +} diff --git a/application/Controllers/Admin/Config.php b/application/Controllers/Admin/Config.php new file mode 100644 index 0000000..af562fc --- /dev/null +++ b/application/Controllers/Admin/Config.php @@ -0,0 +1,30 @@ +stitle = 'Config'; + } + + public function index(): self + { + return $this->render('config/home'); + } + + public function params(): self + { + $this->tpage = 'Paramêtres'; + $this->data['getall'] = $this->config_model->GetConfigAll(); + return $this->render('config/params'); + } +} diff --git a/application/Controllers/Admin/Contact.php b/application/Controllers/Admin/Contact.php new file mode 100644 index 0000000..adb18e3 --- /dev/null +++ b/application/Controllers/Admin/Contact.php @@ -0,0 +1,51 @@ +contact_model = new ContactModel(); + $this->stitle = 'Contact'; + } + + public function index(): self + { + $this->tpage = 'Liste des contacts'; + return $this->render('contact/home'); + } + + public function new(): self + { + $this->tpage = 'Liste des contacts nouveau'; + $this->data['get_list'] = $this->contact_model->getList(0); + return $this->render('contact/new'); + } + + public function finish(): self + { + $this->tpage = 'Liste des contacts fini'; + $this->data['get_list'] = $this->contact_model->getList(1); + return $this->render('contact/finish'); + } + + public function rep(int $id): self + { + $this->tpage = 'Envoyez une réponse'; + $this->data['getContact'] = $this->contact_model->getContact($id); + return $this->render('contact/rep'); + } +} diff --git a/application/Controllers/Admin/Home.php b/application/Controllers/Admin/Home.php new file mode 100644 index 0000000..b923d84 --- /dev/null +++ b/application/Controllers/Admin/Home.php @@ -0,0 +1,29 @@ +stitle = 'Accueil'; + } + + /** + * @return string + */ + public function index(): self + { + return $this->render('home'); + } +} diff --git a/application/Controllers/Blog/About.php b/application/Controllers/Blog/About.php new file mode 100644 index 0000000..55fa210 --- /dev/null +++ b/application/Controllers/Blog/About.php @@ -0,0 +1,29 @@ +stitle = 'Informations'; + } + + /** + * @return string + */ + public function index(): self + { + return $this->render('about/home'); + } +} diff --git a/application/Controllers/Blog/Ajax/Ajax.php b/application/Controllers/Blog/Ajax/Ajax.php new file mode 100644 index 0000000..9847f60 --- /dev/null +++ b/application/Controllers/Blog/Ajax/Ajax.php @@ -0,0 +1,66 @@ +csrf = new CSRFToken(); + + $this->response->setStatusCode(Response::HTTP_OK); + $this->response->setHeader('Content-type', 'application/json'); + $this->response->noCache(); + $this->response->setHeader('X-robots-tag', 'noindex'); + $this->response->setHeader('X-XSS-Protection', '1; mode=block'); + $this->response->setHeader('X-Frame-Options', 'DENY'); + $this->response->setHeader('X-Content-Type-Options', 'nosniff'); + + header('Content-type: application/json'); + } + + /** + * + */ + + /** + * @param array $data + * @param bool $error + * + * @return \CodeIgniter\HTTP\Response + */ + public function Render(array $data, bool $error = false) + { + if (!$error) { + return $this->response->setStatusCode(Response::HTTP_OK)->setContentType('application/json')->setBody(json_encode($data))->send(); + } else { + return $this->response->setStatusCode(500)->setContentType('application/json')->setBody(json_encode($data))->send(); + } + exit(); + } +} diff --git a/application/Controllers/Blog/Ajax/Comments.php b/application/Controllers/Blog/Ajax/Comments.php new file mode 100644 index 0000000..f4cb21a --- /dev/null +++ b/application/Controllers/Blog/Ajax/Comments.php @@ -0,0 +1,48 @@ +comments_model = new CommentsModel(); + } + + /** + * @return bool + */ + public function AddComments() + { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + if ($this->request->isValidIP($this->request->getIPAddress())) { + $this->comments_model->AddComments($_POST['post_id'], $_POST['name'], $_POST['email'], $_POST['message'], $this->request->getIPAddress()); + $this->Render(["message" => "Le commentaire à été envoyez à la modération, Rechargement de la page", "code" => 1]); + return true; + } else { + $this->Render(["message" => "Error : yout IP is bizzar ?"], true); + return false; + } + } else { + $this->Render(["message" => "Error CSRF, You are HACKER ?"], true); + return false; + } + } +} diff --git a/application/Controllers/Blog/Ajax/Contact.php b/application/Controllers/Blog/Ajax/Contact.php new file mode 100644 index 0000000..49acffa --- /dev/null +++ b/application/Controllers/Blog/Ajax/Contact.php @@ -0,0 +1,48 @@ +contact_model = new ContactModel(); + } + + /** + * @return bool + */ + public function addContact() + { + if ($this->csrf->validateToken($_SERVER['HTTP_X_CSRFTOKEN'])) { + if ($this->request->isValidIP($this->request->getIPAddress())) { + $this->contact_model->Add($_POST['name'], $_POST['email'], $_POST['sujet'], $_POST['message'], $this->request->getIPAddress()); + $this->Render(["message" => "La prise de contact à bien été envoyez, vous receverez une réponse d'ici 24h à 48h", "code" => 1]); + return true; + } else { + $this->Render(["message" => "Error : yout IP is bizzar ?"], true); + return false; + } + } else { + $this->Render(["message" => "Error CSRF, You are HACKER ?"], true); + return false; + } + } +} diff --git a/application/Controllers/Blog/Application.php b/application/Controllers/Blog/Application.php new file mode 100644 index 0000000..416f202 --- /dev/null +++ b/application/Controllers/Blog/Application.php @@ -0,0 +1,226 @@ +config = new App(); + $this->session = Services::session($this->config); + $this->request = Services::request(); + $this->response = new Response($this->config); + $this->twig = new Twig('blog'); + $this->csrf = new CSRFToken(); + $this->config_model = new ConfigModel(); + + //Set header + $this->response->setStatusCode(200); + $this->response->setHeader('Content-type', 'text/html'); + $this->response->noCache(); + // Prevent some security threats, per Kevin + // Turn on IE8-IE9 XSS prevention tools + $this->response->setHeader('X-XSS-Protection', '1; mode=block'); + // Don't allow any pages to be framed - Defends against CSRF + $this->response->setHeader('X-Frame-Options', 'DENY'); + // prevent mime based attacks + $this->response->setHeader('X-Content-Type-Options', 'nosniff'); + } + + /** + * Set js & css file + * + * @return string $this + */ + private function base_template() + { + + //Set css file + $this->set_css(base_url('assets/css/blog/style.css')); + $this->set_css(base_url('assets/css/font-awesome.css')); + $this->set_css('//fonts.googleapis.com/css?family=Roboto:100,300,400|Roboto+Condensed:100,300'); + + //Set JS + $this->set_js('//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'); + $this->set_js(base_url('assets/js/cookie.js')); + $this->set_js(base_url('assets/js/blog/app.js')); + + //Set by page + if ($this->request->uri->getSegment(1) == 'article') { + $this->set_js(base_url('assets/js/blog/article.js')); + $this->set_css(base_url('assets/css/prism.css')); + $this->set_js(base_url('assets/js/prism.js')); + } elseif ($this->request->uri->getSegment(1) == 'contact') { + $this->set_js(base_url('assets/js/blog/contact.js')); + } + + return $this; + } + /** + * @param string $file link + * + * @return string $this + */ + public function set_css(string $file) + { + $this->css[]['file'] = $file; + return $this; + } + + /** + * @param string $file link + * + * @return string $this + */ + public function set_js(string $file) + { + $this->js[]['file'] = $file; + return $this; + } + + /** + * @param string $title set title page + * @param int $type + * @param string $keyword set keyword by article + * + * @return string $h return all meta + * @internal param int $ype set if type is website or article + */ + public function meta(string $title, int $type, string $keyword = null): string + { + if ($type == 1) { + $titre = $this->config_model->GetConfig('site_title') . ' | ' . $this->config_model->GetConfig('site_description'); + $oritype = 'Website'; + } elseif ($type == 2) { + $titre = $title . ' | ' . $this->config_model->GetConfig('site_title'); + $oritype = 'Article'; + } + + $h = ''; + $h .= ''; + $h .= ''; + $h .= '' . $titre . ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + if ($keyword != null) { + $h .= ''; + $h .= ''; + } else { + $h .=''; + } + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + $h .= ''; + + return $h; + } + + /** + * @param string $page set page + * @param string $title set title page + * @param string|null $keyword + * + * @return string $this + */ + public function render(string $page, string $title = null, string $keyword = null) + { + $this->base_template(); + + $this->data['css'] = $this->css; + $this->data['js'] = $this->js; + + if ($title != null) { + $this->data['meta'] = $this->meta($title, 2, $keyword); + } else { + $this->data['meta'] = $this->meta($this->stitle, 1, $keyword); + } + + $this->data['page_title'] = $this->stitle; + $this->data['page_stitle'] = $this->tpage; + + $this->response->setBody($this->twig->Rendered($page, $this->data)); + $this->response->send(); + + return $this; + } +} diff --git a/application/Controllers/Blog/Article.php b/application/Controllers/Blog/Article.php new file mode 100644 index 0000000..e9c7cb3 --- /dev/null +++ b/application/Controllers/Blog/Article.php @@ -0,0 +1,60 @@ +stitle = 'Article'; + $this->article_model = new ArticleModel(); + $this->comments_model = new CommentsModel(); + } + + /** + * @return string + */ + public function index() + { + return redirect('404'); + } + + /** + * @param string $link + * + * @return string + */ + public function View(string $link): self + { + $info = $this->article_model->GetArticle('link', $link); + + if ($this->request->isValidIP($this->request->getIPAddress())) { + $this->article_model->add_view($info->id, $this->request->getIPAddress()); + } + + $this->data['get_info_article'] = $info; + $this->data['get_coms'] = $this->comments_model->GetComs($info->id); + $this->data['PostView'] = $this->article_model->nb_PostView($info->id); + $this->data['related_article'] = $this->article_model->GetRelated($info->keyword); + + return $this->render('article/view', $info->title, $info->keyword); + } +} diff --git a/application/Controllers/Blog/Cat.php b/application/Controllers/Blog/Cat.php new file mode 100644 index 0000000..f8cc98f --- /dev/null +++ b/application/Controllers/Blog/Cat.php @@ -0,0 +1,60 @@ +stitle = 'Article'; + $this->post_model = new ArticleModel(); + $this->cat_model = new CatModel(); + } + + /** + * @return string + */ + public function index(): self + { + $this->data['get_cat'] = $this->cat_model->GetCat(); + return $this->render('cat/home'); + } + + /** + * @param string $link + * + * @return string + */ + public function View(string $link): self + { + $pager = Services::pager(); + $CatID = $this->cat_model->GetCatByLink($link); + $this->data['info_cat'] = $CatID; + + $total_row = $this->post_model->nb_articleByCat($CatID->id); + + $perPage = 8; + + $page = (!empty($_GET['page']) ? $_GET['page'] : 1); + + $this->data['get_all'] = $this->post_model->GetArticleByCat($CatID->id, $perPage, $page); + + $this->data['pager'] = $pager->makeLinks($page, $perPage, $total_row, 'cat'); + + return $this->render('cat/view'); + } +} diff --git a/application/Controllers/Blog/Contact.php b/application/Controllers/Blog/Contact.php new file mode 100644 index 0000000..39ca842 --- /dev/null +++ b/application/Controllers/Blog/Contact.php @@ -0,0 +1,38 @@ +stitle = 'Contact'; + $this->contact_model = new ContactModel(); + } + + /** + * @return string + */ + public function index(): self + { + $this->data['check'] = $this->contact_model->Check($this->request->getIPAddress()); + return $this->render('contact/home'); + } +} diff --git a/application/Controllers/Blog/Cookies.php b/application/Controllers/Blog/Cookies.php new file mode 100644 index 0000000..665d2ca --- /dev/null +++ b/application/Controllers/Blog/Cookies.php @@ -0,0 +1,29 @@ +stitle = 'Cookies'; + } + + /** + * @return string + */ + public function index(): self + { + return $this->render('cookies/home'); + } +} diff --git a/application/Controllers/Blog/Errors.php b/application/Controllers/Blog/Errors.php new file mode 100644 index 0000000..1948e6b --- /dev/null +++ b/application/Controllers/Blog/Errors.php @@ -0,0 +1,37 @@ +stitle = 'Erreurs'; + } + + /** + * @return string + */ + public function index(): self + { + return $this->render('errors/home'); + } + + /** + * @return string + */ + public function show_404(): self + { + return $this->render('errors/404', 'Page introuvable'); + } +} diff --git a/application/Controllers/Blog/Home.php b/application/Controllers/Blog/Home.php new file mode 100644 index 0000000..a5faa45 --- /dev/null +++ b/application/Controllers/Blog/Home.php @@ -0,0 +1,40 @@ +stitle = 'Accueil'; + $this->Post_model = new ArticleModel(); + } + + /** + * @return string + */ + public function index(): self + { + $this->data['last_important'] = $this->Post_model->GetLastArticle(4, true); + $this->data['last_article'] = $this->Post_model->GetLastArticle(8); + + return $this->render('home'); + } +} diff --git a/application/Controllers/Blog/Search.php b/application/Controllers/Blog/Search.php new file mode 100644 index 0000000..90ca317 --- /dev/null +++ b/application/Controllers/Blog/Search.php @@ -0,0 +1,42 @@ +stitle = 'Search'; + $this->search_model = new SearchModel(); + } + + /** + * @return string + */ + public function index(): self + { + if (isset($_POST['mc_key'])) { + $valeur = $_POST['mc_key']; + + $this->data['search_article'] = $this->search_model->bytype($valeur, 1); + $this->data['search_com'] = $this->search_model->bytype($valeur, 2); + } else { + $valeur = ''; + } + + $this->data['val_mc'] = $valeur; + return $this->render('search/home'); + } +} diff --git a/application/Database/Migrations/.gitkeep b/application/Database/Migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/application/Database/Migrations/20171016194712_AddTableArticle.php b/application/Database/Migrations/20171016194712_AddTableArticle.php new file mode 100644 index 0000000..c37eebb --- /dev/null +++ b/application/Database/Migrations/20171016194712_AddTableArticle.php @@ -0,0 +1,42 @@ +forge->addField([ + 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], + 'title' => ['type' => 'TEXT'], + 'content' => ['type' => 'LONGTEXT'], + 'author_created' => ['type' => 'INT', 'constraint' => 11], + ]); + + $this->forge->addField("date_created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP"); + + $this->forge->addField([ + 'author_update' => ['type' => 'INT', 'constraint' => 11, 'null' => true], + 'date_update' => ['type' => 'DATETIME', 'null' => true], + 'important' => ['type' => 'INT', 'constraint' => 11, 'default' => "0"], + 'link' => ['type' => 'TEXT'], + 'picture_one' => ['type' => 'TEXT'], + 'cat' => ['type' => 'CHAR', 'constraint' => 50], + 'published' => ['type' => 'INT', 'constraint' => 11, 'default' => "0"], + 'corriged' => ['type' => 'INT', 'constraint' => 11, 'default' => "0"], + 'keyword' => ['type' => 'TEXT'], + 'brouillon' => ['type' => 'INT', 'constraint' => 11, 'default' => "0"] + ]); + + $this->forge->addKey('id', true); + + $this->forge->createTable('article'); + } + + //-------------------------------------------------------------------- + + public function down() + { + // + } +} diff --git a/application/Database/Migrations/20171017105826_AddTableArticleView.php b/application/Database/Migrations/20171017105826_AddTableArticleView.php new file mode 100644 index 0000000..82fc998 --- /dev/null +++ b/application/Database/Migrations/20171017105826_AddTableArticleView.php @@ -0,0 +1,26 @@ +forge->addField([ + 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], + 'post_id' => ['type' => 'INT', 'constraint' => 11], + 'ip' => ['type' => 'VARCHAR', 'constraint' => 250] + ]); + + $this->forge->addKey('id', true); + + $this->forge->createTable('article_view'); + } + + //-------------------------------------------------------------------- + + public function down() + { + // + } +} diff --git a/application/Database/Migrations/20171017110026_AddTableUsers.php b/application/Database/Migrations/20171017110026_AddTableUsers.php new file mode 100644 index 0000000..520e2e0 --- /dev/null +++ b/application/Database/Migrations/20171017110026_AddTableUsers.php @@ -0,0 +1,27 @@ +forge->addField([ + 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], + 'username' => ['type' => 'VARCHAR', 'constraint' => 250], + 'password' => ['type' => 'VARCHAR', 'constraint' => 250], + 'email' => ['type' => 'TEXT'] + ]); + + $this->forge->addKey('id', true); + + $this->forge->createTable('users'); + } + + //-------------------------------------------------------------------- + + public function down() + { + // + } +} diff --git a/application/Database/Migrations/20171017110348_AddTableMedias.php b/application/Database/Migrations/20171017110348_AddTableMedias.php new file mode 100644 index 0000000..55c44a8 --- /dev/null +++ b/application/Database/Migrations/20171017110348_AddTableMedias.php @@ -0,0 +1,28 @@ +forge->addField([ + 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], + 'slug' => ['type' => 'TEXT'], + 'name' => ['type' => 'VARCHAR', 'constraint' => 250] + ]); + + $this->forge->addField("date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP"); + + $this->forge->addKey('id', true); + + $this->forge->createTable('medias'); + } + + //-------------------------------------------------------------------- + + public function down() + { + // + } +} diff --git a/application/Database/Migrations/20171017110517_AddTableConfig.php b/application/Database/Migrations/20171017110517_AddTableConfig.php new file mode 100644 index 0000000..bf960c8 --- /dev/null +++ b/application/Database/Migrations/20171017110517_AddTableConfig.php @@ -0,0 +1,27 @@ +forge->addField([ + 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], + 'key' => ['type' => 'VARCHAR', 'constraint' => 250], + 'data' => ['type' => 'LONGTEXT'], + 'type' => ['type' => 'VARCHAR', 'constraint' => 250, 'default' => 'simple'] + ]); + + $this->forge->addKey('id', true); + + $this->forge->createTable('config'); + } + + //-------------------------------------------------------------------- + + public function down() + { + // + } +} diff --git a/application/Database/Migrations/20171017110953_AddTableComments.php b/application/Database/Migrations/20171017110953_AddTableComments.php new file mode 100644 index 0000000..df29d94 --- /dev/null +++ b/application/Database/Migrations/20171017110953_AddTableComments.php @@ -0,0 +1,35 @@ +forge->addField([ + 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], + 'post_id' => ['type' => 'INT', 'constraint' => 11], + 'author_name' => ['type' => 'VARCHAR', 'constraint' => 250], + 'author_email' => ['type' => 'TEXT'], + 'author_ip' => ['type' => 'VARCHAR', 'constraint' => 250] + ]); + + $this->forge->addField("created_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP"); + + $this->forge->addField([ + 'verified' => ['type' => 'INT', 'constraint' => 11, 'default' => "0"], + 'content' => ['type' => 'LONGTEXT'], + ]); + + $this->forge->addKey('id', true); + + $this->forge->createTable('comments'); + } + + //-------------------------------------------------------------------- + + public function down() + { + // + } +} diff --git a/application/Database/Migrations/20171017111219_AddTableCat.php b/application/Database/Migrations/20171017111219_AddTableCat.php new file mode 100644 index 0000000..05ad6a0 --- /dev/null +++ b/application/Database/Migrations/20171017111219_AddTableCat.php @@ -0,0 +1,35 @@ +forge->addField([ + 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], + 'title' => ['type' => 'VARCHAR', 'constraint' => 250], + 'description' => ['type' => 'TEXT'], + 'slug' => ['type' => 'VARCHAR', 'constraint' => 250], + 'icon' => ['type' => 'VARCHAR', 'constraint' => 250], + 'parent' => ['type' => 'INT', 'constraint' => 11, 'default' => "0"], + ]); + + $this->forge->addKey('id', true); + + $this->forge->createTable('cat'); + + $this->db->query("INSERT INTO `cat` (`id`, `title`, `description`, `slug`, `icon`, `parent`) VALUES (1, 'PHP', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a posuere mauris, vel cursus magna. In scelerisque pharetra felis, nec faucibus ligula tincidunt tempor. Cras et sem nec erat laoreet lobortis. Donec id lorem quis orci interdum vehicula. Vivamus pretium in risus ac vestibulum.', 'php', 'http://www.leifzimmerman.com/images/icons/icon_large_php.png', 0)"); + $this->db->query("INSERT INTO `cat` (`id`, `title`, `description`, `slug`, `icon`, `parent`) VALUES (2, 'HTML/CSS', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a posuere mauris, vel cursus magna. In scelerisque pharetra felis, nec faucibus ligula tincidunt tempor. Cras et sem nec erat laoreet lobortis. Donec id lorem quis orci interdum vehicula. Vivamus pretium in risus ac vestibulum.', 'html-css', 'http://solidfoundationwebdev.com/system/categories/images/000/000/004/original/html-css.png', 0)"); + $this->db->query("INSERT INTO `cat` (`id`, `title`, `description`, `slug`, `icon`, `parent`) VALUES (3, 'Node JS', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a posuere mauris, vel cursus magna. In scelerisque pharetra felis, nec faucibus ligula tincidunt tempor. Cras et sem nec erat laoreet lobortis. Donec id lorem quis orci interdum vehicula. Vivamus pretium in risus ac vestibulum.', 'node-js', 'http://glenneggleton.com/files/2016-02/nodejs-logo.png', 0)"); + $this->db->query("INSERT INTO `cat` (`id`, `title`, `description`, `slug`, `icon`, `parent`) VALUES (4, 'Javascript', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a posuere mauris, vel cursus magna. In scelerisque pharetra felis, nec faucibus ligula tincidunt tempor. Cras et sem nec erat laoreet lobortis. Donec id lorem quis orci interdum vehicula. Vivamus pretium in risus ac vestibulum.', 'js', 'http://detechter.com/wp-content/uploads/2015/03/jquery-javascript.png', 0)"); + $this->db->query("INSERT INTO `cat` (`id`, `title`, `description`, `slug`, `icon`, `parent`) VALUES (5, 'Autres', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a posuere mauris, vel cursus magna. In scelerisque pharetra felis, nec faucibus ligula tincidunt tempor. Cras et sem nec erat laoreet lobortis. Donec id lorem quis orci interdum vehicula. Vivamus pretium in risus ac vestibulum.', 'others', 'https://cdn2.iconfinder.com/data/icons/eshop-outline-pack/100/Noun_Project_20Icon_5px_grid-13-512.png', 0)"); + } + + //-------------------------------------------------------------------- + + public function down() + { + // + } +} diff --git a/application/Database/Migrations/20171020172939_AddTableContact.php b/application/Database/Migrations/20171020172939_AddTableContact.php new file mode 100644 index 0000000..6c36174 --- /dev/null +++ b/application/Database/Migrations/20171020172939_AddTableContact.php @@ -0,0 +1,32 @@ +forge->addField([ + 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], + 'name' => ['type' => 'TEXT'], + 'email' => ['type' => 'TEXT'], + 'sujet' => ['type' => 'TEXT'], + 'message' => ['type' => 'LONGTEXT'], + 'ip' => ['type' => 'VARCHAR', 'constraint' => 250], + 'etat' => ['type' => 'INT', 'constraint' => 11, 'default' => '0'] + ]); + + $this->forge->addField("date_created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP"); + + $this->forge->addKey('id', true); + + $this->forge->createTable('contact'); + } + + //-------------------------------------------------------------------- + + public function down() + { + // + } +} diff --git a/application/Database/Seeds/.gitignore b/application/Database/Seeds/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/application/Database/Seeds/DatabaseSeeder.php b/application/Database/Seeds/DatabaseSeeder.php new file mode 100644 index 0000000..e200b64 --- /dev/null +++ b/application/Database/Seeds/DatabaseSeeder.php @@ -0,0 +1,28 @@ +db->query("INSERT INTO `config` (`key`, `data`) VALUES ('site_title', 'Mon blog')"); + $this->db->query("INSERT INTO `config` (`key`, `data`, `type`) VALUES ('site_description', 'Mon blog préféré', 'text')"); + $this->db->query("INSERT INTO `config` (`key`, `data`, `type`) VALUES ('site_keyword', 'ide, development, free, open source, articles, website, php, javascript, jquery, node.js, codeigniter, ci3, ci4, tutorials', 'text')"); + $this->db->query("INSERT INTO `config` (`key`, `data`, `type`) VALUES ('presentation', 'Lorem ipsun', 'text')"); + $this->db->query("INSERT INTO `config` (`key`, `data`, `type`) VALUES ('annonce_general', 'Annonce', 'text')"); + $this->db->query("INSERT INTO `config` (`key`, `data`, `type`) VALUES ('home_annonce_1', 'Annonce 1', 'text')"); + $this->db->query("INSERT INTO `config` (`key`, `data`, `type`) VALUES ('home_annonce_2', 'Annonce 2', 'text')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('pub_active', '0')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('analytics_active', '0')"); + $this->db->query("INSERT INTO `config` (`key`, `data`, `type`) VALUES ('analytics_code', '', 'text')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('mail_host', 'localhost')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('mail_port', '25')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('mail_security', '')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('mail_username', '')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('mail_password', '')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('mail_from_adress', '')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('mail_from_name', '')"); + $this->db->query("INSERT INTO `config` (`key`, `data`) VALUES ('cache', 'off')"); + + $this->db->query('INSERT INTO `users` (`username`, `password`, `email`) VALUES (\'admin\', \'$2y$10$YFeHHk2cC1GFPWhw2yQMSeY9DFiFNjsOu3/jGnLzyGBqVqG5sOUDO\', \'contact@blog.dev\')'); + } +} diff --git a/application/Filters/.gitkeep b/application/Filters/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/application/Filters/CSRF.php b/application/Filters/CSRF.php new file mode 100644 index 0000000..daf0f5b --- /dev/null +++ b/application/Filters/CSRF.php @@ -0,0 +1,51 @@ +isCLI()) { + return; + } + + $security = Services::security(); + + $security->CSRFVerify($request); + } + + //-------------------------------------------------------------------- + + /** + * We don't have anything to do here. + * + * @param \CodeIgniter\HTTP\RequestInterface $request + * @param \CodeIgniter\HTTP\ResponseInterface $response + * + * @return mixed + */ + public function after(RequestInterface $request, ResponseInterface $response) + { + } + + //-------------------------------------------------------------------- +} diff --git a/application/Filters/DebugToolbar.php b/application/Filters/DebugToolbar.php new file mode 100644 index 0000000..97c6a1a --- /dev/null +++ b/application/Filters/DebugToolbar.php @@ -0,0 +1,56 @@ +getHeaderLine('content-type'); + + if (! is_cli() && CI_DEBUG && strpos($format, 'html') !== false) { + global $app; + + $toolbar = Services::toolbar(new App()); + $stats = $app->getPerformanceStats(); + + return $response->appendBody( + $toolbar->run( + $stats['startTime'], + $stats['totalTime'], + $stats['startMemory'], + $request, + $response + ) + ); + } + } + + //-------------------------------------------------------------------- +} diff --git a/application/Filters/Throttle.php b/application/Filters/Throttle.php new file mode 100644 index 0000000..6dc7a16 --- /dev/null +++ b/application/Filters/Throttle.php @@ -0,0 +1,45 @@ +check($request->getIPAddress(), 60, MINUTE) === false) { + return Services::response()->setStatusCode(429); + } + } + + //-------------------------------------------------------------------- + + /** + * We don't have anything to do here. + * + * @param \CodeIgniter\HTTP\RequestInterface $request + * @param \CodeIgniter\HTTP\ResponseInterface $response + * + * @return mixed + */ + public function after(RequestInterface $request, ResponseInterface $response) + { + } + + //-------------------------------------------------------------------- +} diff --git a/application/Helpers/.gitkeep b/application/Helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/application/Language/.gitkeep b/application/Language/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/application/Libraries/.gitkeep b/application/Libraries/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/application/Libraries/CSRFToken.php b/application/Libraries/CSRFToken.php new file mode 100644 index 0000000..5a0a144 --- /dev/null +++ b/application/Libraries/CSRFToken.php @@ -0,0 +1,83 @@ +config = new App(); + $this->session = Services::session($this->config); + $this->session_name = $session_name; + $this->session->start(); + } + /** + * Getting a token + * + * @return string Return result + */ + public function getToken() + { + if ($this->session->has($this->session_name)) { + return $this->session->get($this->session_name); + } + + $token = uniqid($this->randomString()); + $this->session->set($this->session_name, $token); + + return $token; + } + /** + * Checking the token + * + * @param string $token The token to check + * + * @return boolean Return result + */ + public function validateToken($token) + { + if ($this->session->has($this->session_name)) { + if ($this->session->get($this->session_name) === $token) { + return true; + } + + return '1'; + } else { + return '0'; + } + } + /** + * Generate a random string + * + * @return string Return result + */ + private function randomString(): string + { + $chars = 'qazxswedcvfrtgbnhyujmkiolp1234567890QAZXSWEDCVFRTGBNHYUJMKIOLP'; + $max = $this->length_random_string; + $size = strlen($chars) - 1; + $string = ''; + while ($max--) { + $string .= $chars[random_int(0, $size)]; + } + return $string; + } +} diff --git a/application/Libraries/General.php b/application/Libraries/General.php new file mode 100644 index 0000000..f2a6bcd --- /dev/null +++ b/application/Libraries/General.php @@ -0,0 +1,90 @@ +cat_model = new CatModel(); + $this->article_model = new ArticleModel(); + $this->comments_model = new CommentsModel(); + } + + /** + * @param string $json + * + * @return string + */ + public function TradCat(string $json): string + { + $arr = ''; + $piece = explode(';', $json); + foreach ($piece as $data) { + $tt = $this->cat_model->GetCatNameAndLink($data); + $arr .= "slug)."'>".$tt->title. ' '; + } + + return $arr; + } + + public function GetCat() + { + return $this->cat_model->GetCat(); + } + + /** + * @param int $id post ID + * + * @return int + */ + public function CountCom(int $id): int + { + return $this->comments_model->CountComs($id); + } + + /** + * @param int $cat car id + * + * @return int + */ + public function CountArticle(int $cat): int + { + return $this->article_model->nb_articleByCat($cat); + } + + /** + * @param int $post post ID + * + * @return int + */ + public function CountView(int $post): int + { + return $this->article_model->nb_PostView($post); + } +} diff --git a/application/Libraries/Mailer.php b/application/Libraries/Mailer.php new file mode 100644 index 0000000..9459bdb --- /dev/null +++ b/application/Libraries/Mailer.php @@ -0,0 +1,32 @@ +config_model = new ConfigModel(); + $transport = (new Swift_SmtpTransport($this->config_model->GetConfig('mail_host'), $this->config_model->GetConfig('mail_port'), $this->config_model->GetConfig('mail_security'))) + ->setUsername($this->config_model->GetConfig('mail_username')) + ->setPassword($this->config_model->GetConfig('mail_password')); + $this->mailp = new Swift_Mailer($transport); + } + + public function sendmail(string $sujet, string $to, string $body) + { + $message = (new Swift_Message($sujet)) + ->setFrom([$this->config_model->GetConfig('mail_from_adress') => $this->config_model->GetConfig('mail_from_name')]) + ->setTo([$to]) + ->setBody($body); + + $this->mailp->send($message); + return true; + } +} diff --git a/application/Libraries/ParseArticle.php b/application/Libraries/ParseArticle.php new file mode 100644 index 0000000..923c3a9 --- /dev/null +++ b/application/Libraries/ParseArticle.php @@ -0,0 +1,66 @@ +media = new MediaModel(); + } + + /** + * @param string $content + * + * @return mixed|string + */ + public function rendered(string $content) + { + $content = $this->parse_img($content); + $content = $this->parse_href($content); + return $content; + } + + /** + * @param string $content + * + * @return mixed + */ + protected function parse_img(string $content) + { + $content = preg_replace_callback( + '`\[img id="(.+)"\]`isU', + function ($matches) { + $get_info = $this->media->get_link($matches[1]); + return ''; + }, + $content + ); + return $content; + } + + /** + * @param string $content + * + * @return mixed + */ + protected function parse_href(string $content) + { + return preg_replace('//', '', $content); + } +} diff --git a/application/Libraries/Twig/Twig.php b/application/Libraries/Twig/Twig.php new file mode 100644 index 0000000..0f5ecef --- /dev/null +++ b/application/Libraries/Twig/Twig.php @@ -0,0 +1,59 @@ +GetConfig('cache') == "on") { + $dataconfig = [ + 'cache' => WRITEPATH . 'cache', + 'auto_reload' => true, + 'autoescape' => false + ]; + } else { + $dataconfig = [ + 'autoescape' => false + ]; + } + + $this->environment = new Twig_Environment($loader, $dataconfig); + + $this->environment->addExtension(new TwigExtentions()); + + $this->templateFolder = $templateFolder; + } + + /** + * @param $file + * @param $array + * @param string $ext + * + * @return string + */ + public function Rendered($file, $array, $ext = '.twig'): string + { + $template = $this->environment->load('page/' . $file . $ext); + + return $template->render($array); + } +} diff --git a/application/Libraries/Twig/TwigExtentions.php b/application/Libraries/Twig/TwigExtentions.php new file mode 100644 index 0000000..fd7a803 --- /dev/null +++ b/application/Libraries/Twig/TwigExtentions.php @@ -0,0 +1,109 @@ +config = new App(); + $this->session = Services::session($this->config); + $this->request = Services::request(); + $this->response = new Response($this->config); + $this->Config_model = new ConfigModel(); + $this->general = new General(); + } + + /** + * @return array + */ + public function getFilters():array + { + return [ + 'base_url' => new Twig_Filter('base_url', 'base_url'), + 'config' => new Twig_Filter('config', [$this, 'filterConfig']), + 'uri_segment' => new Twig_Filter('uri_segment', [$this, 'filterSegment']) + ]; + } + + /** + * @return array + */ + public function getFunctions():array + { + return [ + 'general' => new Twig_Function('general', [$this, 'functionGeneral'], ['is_safe' => ['html']]), + 'parse' => new Twig_Function('parse', [$this, 'parseBbcode'], ['is_safe' => ['html']]) + ]; + } + + /** + * @param $string + * + * @return mixed + */ + public function filterConfig($string) + { + return $this->Config_model->GetConfig($string); + } + + /** + * @param $string + * + * @return string + */ + public function filterSegment($string):string + { + return $this->request->uri->getSegment($string); + } + + /** + * @param $func + * @param $data + * + * @return mixed + */ + public function functionGeneral($func, $data = null) + { + if ($data != null) { + return $this->general->$func($data); + } + + return $this->general->$func(); + } + + public function parseBbcode($content) + { + $parse = new ParseArticle(); + return $parse->rendered($content); + } +} diff --git a/application/Models/.gitkeep b/application/Models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/application/Models/Admin/ArticleModel.php b/application/Models/Admin/ArticleModel.php new file mode 100644 index 0000000..2b98f78 --- /dev/null +++ b/application/Models/Admin/ArticleModel.php @@ -0,0 +1,189 @@ +db = Database::connect(); + $this->article_table = $this->db->table('article'); + } + + public function GetArticle(string $column, string $data) + { + $this->article_table->select("*, DATE_FORMAT(`date_created`,'Le %d-%m-%Y à %H:%i:%s') AS `date_created`, DATE_FORMAT(`date_update`,'Le %d-%m-%Y à %H:%i:%s') AS `date_update`"); + $this->article_table->where($column, $data); + return $this->article_table->get()->getRow(); + } + + /** + * @return array|mixed + */ + public function lastFive():array + { + $this->article_table->select("*, DATE_FORMAT(`date_created`,'%d-%m-%Y à %H:%i:%s') AS `date_created`, DATE_FORMAT(`date_update`,'%d-%m-%Y à %H:%i:%s') AS `date_update`"); + $this->article_table->limit('5'); + $this->article_table->orderBy('id', 'DESC'); + return $this->article_table->get()->getResult(); + } + + /** + * @return int|void + */ + public function count_publied():int + { + $this->article_table->select('COUNT(id) as id'); + $this->article_table->where('published', 1); + return $this->article_table->get()->getRow()->id; + } + + /** + * @return int|void + */ + public function count_attCorrect():int + { + $this->article_table->select('COUNT(id) as id'); + $this->article_table->where('corriged', 0); + $this->article_table->where('published', 0); + $this->article_table->where('brouillon', 0); + return $this->article_table->get()->getRow()->id; + } + + /** + * @return int|void + */ + public function count_attPublished():int + { + $this->article_table->select('COUNT(id) as id'); + $this->article_table->where('corriged', 1); + $this->article_table->where('published', 0); + return $this->article_table->get()->getRow()->id; + } + + /** + * @return int|void + */ + public function count_brouillon():int + { + $this->article_table->select('COUNT(id) as id'); + $this->article_table->where('brouillon', 1); + return $this->article_table->get()->getRow()->id; + } + + /** + * @param string $title + * @param string $link + * @param string $content + * @param string $wordkey + * @param string $cat + * @param string $pic + * @param int $important + * + * @return int (Return id) + */ + public function Add(string $title, string $link, string $content, string $wordkey, string $cat, string $pic, int $important):int + { + $data = [ + 'title' => $title, + 'content' => $content, + 'author_created' => 1, + 'important' => $important, + 'link' => $link, + 'picture_one' => $pic, + 'cat' => $cat, + 'keyword' => $wordkey + ]; + $this->article_table->insert($data); + return $this->db->insertID(); + } + + /** + * @param int $id + * @param string $title + * @param string $link + * @param string $content + * @param string $wordkey + * @param string $cat + * @param string $pic + * @param int $important + * @param int $type + * + * @return bool + */ + public function Edit(int $id, string $title, string $link, string $content, string $wordkey, string $cat, string $pic, int $important, int $type): bool + { + $data = [ + 'title' => $title, + 'content' => $content, + 'author_created' => 1, + 'author_update' => 1, + 'important' => $important, + 'link' => $link, + 'picture_one' => $pic, + 'cat' => $cat, + 'keyword' => $wordkey + ]; + + if ($type == 1) { + $data['published'] = 1; + $data['corriged'] = 1; + } elseif ($type == 2) { + $data['published'] = 0; + $data['corriged'] = 0; + $data['brouillon'] = 0; + } elseif ($type == 3) { + $data['published'] = 0; + $data['corriged'] = 1; + $data['brouillon'] = 0; + } + + $this->article_table->where('id', $id); + $this->article_table->set('date_update', 'NOW()', false); + $this->article_table->update($data); + return true; + } + + /** + * @param int $type (1 = publied, 2 = wait corrected, 3 = wait publied, 4 = brouillon) + * + * @return string + */ + public function getArticleListAdmin(int $type) + { + $this->article_table->select("*, DATE_FORMAT(`date_created`,'%d-%m-%Y à %H:%i:%s') AS `date_created`, DATE_FORMAT(`date_update`,'%d-%m-%Y à %H:%i:%s') AS `date_update`"); + + if ($type == 1) { + $this->article_table->where('published', 1); + } elseif ($type == 2) { + $this->article_table->where('corriged', 0); + $this->article_table->where('published', 0); + $this->article_table->where('brouillon', 0); + } elseif ($type == 3) { + $this->article_table->where('corriged', 1); + $this->article_table->where('published', 0); + } elseif ($type == 4) { + $this->article_table->where('brouillon', 1); + } + + return $this->article_table->get()->getResult(); + } +} diff --git a/application/Models/Admin/AuthModel.php b/application/Models/Admin/AuthModel.php new file mode 100644 index 0000000..7a66cd5 --- /dev/null +++ b/application/Models/Admin/AuthModel.php @@ -0,0 +1,79 @@ +db = Database::connect(); + $this->user_table = $this->db->table('users'); + } + + /** + * @param $email + * + * @return bool + */ + public function isValidEmail($email):bool + { + $this->user_table->select('id'); + $this->user_table->where('email', $email); + $this->user_table->limit('1'); + $this->account_id_query = $this->user_table->get()->getRow(); + if (count($this->account_id_query) > 0) { + return $this->account_id_query->id; + } + + return false; + } + + /** + * @param $pass + * @param $account + * + * @return bool + */ + public function isValidPassword($pass, $account):bool + { + $this->user_table->select('password'); + $this->user_table->where('id', $account); + $pass_db = $this->user_table->get()->getRow()->password; + + if (password_verify($pass, $pass_db)) { + return true; + } + + return false; + } + + /** + * @param $account + * + * @return mixed + */ + public function GetUsername($account) + { + $this->user_table->select('username'); + $this->user_table->where('id', $account); + return $this->user_table->get()->getRow()->username; + } +} diff --git a/application/Models/Admin/CatModel.php b/application/Models/Admin/CatModel.php new file mode 100644 index 0000000..e9acfbb --- /dev/null +++ b/application/Models/Admin/CatModel.php @@ -0,0 +1,73 @@ +db = Database::connect(); + $this->cat_table = $this->db->table('cat'); + } + + /** + * @return array + */ + public function getlist():array + { + $this->cat_table->select('*'); + $this->cat_table->where('parent', '0'); + $this->cat_table->orderBy('id', 'ASC'); + return $this->cat_table->get()->getResult('array'); + } + + /** + * @param int $id + * @param string $column + * @param string $data + * + * @return bool + */ + public function UpdateCat(int $id, string $column, string $data):bool + { + $this->cat_table->set($column, $data); + $this->cat_table->where('id', $id); + $this->cat_table->update(); + return true; + } + + /** + * @param string $title + * @param string $content + * @param string $slug + * @param string $icon + */ + public function AddCat(string $title, string $content, string $slug, string $icon) + { + $data = [ + 'title' => $title, + 'description' => $content, + 'slug' => $slug, + 'icon' => $icon + ]; + $this->cat_table->insert($data); + } +} diff --git a/application/Models/Admin/CommentsModel.php b/application/Models/Admin/CommentsModel.php new file mode 100644 index 0000000..b3d226f --- /dev/null +++ b/application/Models/Admin/CommentsModel.php @@ -0,0 +1,64 @@ +db = Database::connect(); + $this->comments_table = $this->db->table('comments'); + } + + public function countComments(string $type) + { + $this->comments_table->select('COUNT(id) as id'); + $this->comments_table->where('verified', $type); + return $this->comments_table->get()->getRow()->id; + } + + public function GetComments(string $type) + { + $this->comments_table->select("comments.*, post.title AS post_title, post.link AS post_slug, DATE_FORMAT(`created_date`,'%d-%m-%Y à %H:%i:%s') AS `created_date`"); + $this->comments_table->where('verified', $type); + $this->comments_table->join('post', 'post.id = comments.post_id', 'left'); + return $this->comments_table->get()->getResult(); + } + + public function valideComments(int $id) + { + $data = [ + 'verified' => 1 + ]; + $this->comments_table->where('id', $id); + $this->comments_table->update($data); + return true; + } + public function refuseComments(int $id) + { + $data = [ + 'verified' => '-1' + ]; + $this->comments_table->where('id', $id); + $this->comments_table->update($data); + return true; + } +} diff --git a/application/Models/Admin/ConfigModel.php b/application/Models/Admin/ConfigModel.php new file mode 100644 index 0000000..219bb55 --- /dev/null +++ b/application/Models/Admin/ConfigModel.php @@ -0,0 +1,65 @@ +db = Database::connect(); + $this->config_table = $this->db->table('config'); + } + + public function GetConfigAll() + { + $this->config_table->select('*'); + return $this->config_table->get()->getResult(); + } + + public function UpdateConfig(int $id, string $key, string $data) + { + $this->config_table->where('id', $id); + $this->config_table->update([ + 'key' => $key, + 'data' => $data + ]); + return true; + } + + public function DelConfig(int $id) + { + $this->config_table->where('id', $id); + $this->config_table->delete(); + return true; + } + + /** + * @param string $key + * + * @return mixed + */ + public function GetConfig(string $key) + { + $this->config_table->select('data'); + $this->config_table->where('key', $key); + return $this->config_table->get()->getRow()->data; + } +} diff --git a/application/Models/Admin/ContactModel.php b/application/Models/Admin/ContactModel.php new file mode 100644 index 0000000..0260748 --- /dev/null +++ b/application/Models/Admin/ContactModel.php @@ -0,0 +1,61 @@ +db = Database::connect(); + $this->contact_table = $this->db->table('contact'); + } + + public function getList(int $etat) + { + $this->contact_table->select("*, DATE_FORMAT(`date_created`,'%d-%m-%Y à %H:%i:%s') AS `date_created`"); + $this->contact_table->where('etat', $etat); + $this->contact_table->orderBy('id', 'DESC'); + return $this->contact_table->get()->getResult(); + } + + public function markedview(int $id) + { + $data = [ + 'etat' => 1 + ]; + $this->contact_table->where('id', $id); + $this->contact_table->update($data); + return true; + } + + public function del(int $id) + { + $this->contact_table->delete(['id' => $id]); + return true; + } + + public function getContact(int $id) + { + $this->contact_table->select("*, DATE_FORMAT(`date_created`,'%d-%m-%Y à %H:%i:%s') AS `date_created`"); + $this->contact_table->where('id', $id); + return $this->contact_table->get()->getRow(); + } +} diff --git a/application/Models/Admin/MediaModel.php b/application/Models/Admin/MediaModel.php new file mode 100644 index 0000000..593cd75 --- /dev/null +++ b/application/Models/Admin/MediaModel.php @@ -0,0 +1,29 @@ +db = Database::connect(); + $this->medias_table = $this->db->table('medias'); + } + + public function Add(string $slug, string $name) + { + $data = [ + 'slug' => $slug, + 'name' => $name + ]; + $this->medias_table->insert($data); + return $this->db->insertID(); + } +} diff --git a/application/Models/Blog/ArticleModel.php b/application/Models/Blog/ArticleModel.php new file mode 100644 index 0000000..d88bbc4 --- /dev/null +++ b/application/Models/Blog/ArticleModel.php @@ -0,0 +1,143 @@ +db = Database::connect(); + $this->article_table = $this->db->table('article'); + $this->article_view_table = $this->db->table('article_view'); + } + + public function GetArticle(string $column, string $data) + { + $this->article_table->select("*, DATE_FORMAT(`date_created`,'Le %d-%m-%Y à %H:%i:%s') AS `date_created`, DATE_FORMAT(`date_update`,'Le %d-%m-%Y à %H:%i:%s') AS `date_update`"); + $this->article_table->where($column, $data); + return $this->article_table->get()->getRow(); + } + + public function GetLastArticle(int $limit = null, bool $important = false): array + { + $this->article_table->select("*, DATE_FORMAT(`date_created`,'Le %d-%m-%Y à %H:%i:%s') AS `date_created`, DATE_FORMAT(`date_update`,'Le %d-%m-%Y à %H:%i:%s') AS `date_update`"); + if ($important) { + $this->article_table->where('important', '1'); + } + $this->article_table->where('published', '1'); + if ($limit != null) { + $this->article_table->limit($limit); + } + $this->article_table->orderBy('id', 'DESC'); + return $this->article_table->get()->getResult('array'); + } + + public function GetArticleByCat(int $cat_id, int $per_page, int $page): array + { + $offset = ($page-1)*$per_page; + + $this->article_table->select("*, DATE_FORMAT(`date_created`,'Le %d-%m-%Y à %H:%i:%s') AS `date_created`, DATE_FORMAT(`date_update`,'Le %d-%m-%Y à %H:%i:%s') AS `date_update`"); + $this->article_table->where('published', '1'); + $this->article_table->like('cat', $cat_id); + $this->article_table->orderBy('id', 'DESC'); + $this->article_table->limit($per_page, $offset); + + return $this->article_table->get()->getResult('array'); + } + + /** + * @param int $cat + * + * @return int + */ + public function nb_articleByCat(int $cat):int + { + $this->article_table->select('COUNT(id) as id'); + $this->article_table->where('published', '1'); + $this->article_table->where('corriged', '1'); + $this->article_table->like('cat', $cat); + return $this->article_table->get()->getRow()->id; + } + + /** + * @param int $id + * @param string $ip + * + * @return bool + */ + public function add_view(int $id, string $ip):bool + { + $this->article_view_table->select('*'); + $this->article_view_table->where('post_id', $id); + $this->article_view_table->where('ip', $ip); + $verify = $this->article_view_table->get()->getRow(); + if (!isset($verify->id)) { + $data = [ + 'post_id' => $id, + 'ip' => $ip + ]; + $this->article_view_table->insert($data); + return true; + } + + return false; + } + + /** + * @param int $id + * + * @return int|void + */ + public function nb_PostView(int $id):int + { + $this->article_view_table->select('COUNT(id) as id'); + $this->article_view_table->where('post_id', $id); + return $this->article_view_table->get()->getRow()->id; + } + + /** + * @param string $keyword + * + * @return array|mixed + */ + public function GetRelated(string $keyword):array + { + $keys = explode(',', $keyword); + + $this->article_table->select('*'); + $this->article_table->like('keyword', $keys[0]); + + foreach ($keys as $key=>$key_data) { + if ($key != 0) { + $this->article_table->orLike('keyword', $key_data); + } + } + + $this->article_table->limit('5'); + $this->article_table->orderBy('id', 'DESC'); + + return $this->article_table->get()->getResult('array'); + } +} diff --git a/application/Models/Blog/CatModel.php b/application/Models/Blog/CatModel.php new file mode 100644 index 0000000..1ea1165 --- /dev/null +++ b/application/Models/Blog/CatModel.php @@ -0,0 +1,78 @@ +db = Database::connect(); + $this->cat_table = $this->db->table('cat'); + } + + /** + * @param int $id + * + * @return mixed + */ + public function GetCatNameAndLink(int $id) + { + $this->cat_table->select('*'); + $this->cat_table->where('id', $id); + return $this->cat_table->get()->getRow(); + } + + /** + * @param int $id + * + * @return mixed + */ + public function GetCatByID(int $id) + { + $this->cat_table->select('*'); + $this->cat_table->where('id', $id); + return $this->cat_table->get()->getRow(); + } + + /** + * @param string $slug + * + * @return mixed + * @internal param string $slug + */ + public function GetCatByLink(string $slug) + { + $this->cat_table->select('*'); + $this->cat_table->where('slug', $slug); + return $this->cat_table->get()->getRow(); + } + + /** + * @return array|mixed + */ + public function GetCat():array + { + $this->cat_table->select('*'); + $this->cat_table->where('parent', '0'); + $this->cat_table->orderBy('id', 'DESC'); + return $this->cat_table->get()->getResult('array'); + } +} diff --git a/application/Models/Blog/CommentsModel.php b/application/Models/Blog/CommentsModel.php new file mode 100644 index 0000000..4e5e7f6 --- /dev/null +++ b/application/Models/Blog/CommentsModel.php @@ -0,0 +1,79 @@ +db = Database::connect(); + $this->comments_table = $this->db->table('comments'); + } + + /** + * @param int $id + * + * @return array|mixed + */ + public function GetComs(int $id):array + { + $this->comments_table->select("*, DATE_FORMAT(`created_date`,'Le %d-%m-%Y à %H:%i:%s') AS `created_date`"); + $this->comments_table->where('post_id', $id); + $this->comments_table->where('verified', 1); + $this->comments_table->orderBy('id', 'DESC'); + return $this->comments_table->get()->getResult('array'); + } + + /** + * @param int $id + * + * @return int + */ + public function CountComs(int $id):int + { + $this->comments_table->select('COUNT(id) as id'); + $this->comments_table->where('post_id', $id); + $this->comments_table->where('verified', 1); + return $this->comments_table->get()->getRow()->id; + } + + /** + * @param int $id + * @param string $name + * @param string $email + * @param string $content + * @param string $ip + * + * @return array|mixed + */ + public function AddComments(int $id, string $name, string $email, string $content, string $ip) + { + $data = array( + 'post_id' => $id, + 'author_name' => $name, + 'author_email' => $email, + 'author_ip' => $ip, + 'content' => $content + ); + + return $this->comments_table->insert($data); + } +} diff --git a/application/Models/Blog/ConfigModel.php b/application/Models/Blog/ConfigModel.php new file mode 100644 index 0000000..2c5496c --- /dev/null +++ b/application/Models/Blog/ConfigModel.php @@ -0,0 +1,42 @@ +db = Database::connect(); + $this->config_table = $this->db->table('config'); + } + + /** + * @param string $key + * + * @return mixed + */ + public function GetConfig(string $key) + { + $this->config_table->select('data'); + $this->config_table->where('key', $key); + return $this->config_table->get()->getRow()->data; + } +} diff --git a/application/Models/Blog/ContactModel.php b/application/Models/Blog/ContactModel.php new file mode 100644 index 0000000..dc0b957 --- /dev/null +++ b/application/Models/Blog/ContactModel.php @@ -0,0 +1,56 @@ +db = Database::connect(); + $this->contact_table = $this->db->table('contact'); + } + + public function Check(string $ip) + { + $this->contact_table->select('COUNT(id) as id'); + $this->contact_table->where('ip', $ip); + $this->contact_table->where('date_created >', date('Y-m-d H:i:s', strtotime('-1 day'))); + $result = $this->contact_table->get()->getRow()->id; + if ($result == 0) { + return false; + } else { + return true; + } + } + + public function Add(string $name, string $email, string $sujet, string $message, string $ip) + { + $data = [ + 'name' => $name, + 'email' => $email, + 'sujet' => $sujet, + 'message' => $message, + 'ip' => $ip, + ]; + + return $this->contact_table->insert($data); + } +} diff --git a/application/Models/Blog/MediaModel.php b/application/Models/Blog/MediaModel.php new file mode 100644 index 0000000..944418b --- /dev/null +++ b/application/Models/Blog/MediaModel.php @@ -0,0 +1,26 @@ +db = Database::connect(); + $this->medias_table = $this->db->table('medias'); + } + + public function get_link(int $id) + { + $this->medias_table->select('*'); + $this->medias_table->where('id', $id); + return $this->medias_table->get()->getRow(); + } +} diff --git a/application/Models/Blog/SearchModel.php b/application/Models/Blog/SearchModel.php new file mode 100644 index 0000000..69ce572 --- /dev/null +++ b/application/Models/Blog/SearchModel.php @@ -0,0 +1,55 @@ +db = Database::connect(); + $this->post_table = $this->db->table('post'); + $this->comments_table = $this->db->table('comments'); + } + + public function bytype(string $valeur, int $type):array + { + $key_delimiter = explode(',', $valeur); + + if ($type == 1) { + $this->post_table->select('*'); + $this->post_table->like('content', $key_delimiter[0]); + + foreach ($key_delimiter as $key=>$key_data) { + if ($key != 0) { + $this->post_table->orLike('content', $key_data); + } + } + $this->post_table->orderBy('id', 'DESC'); + return $this->post_table->get()->getResult('array'); + } elseif ($type == 2) { + $this->comments_table->select('*'); + $this->comments_table->like('content', $key_delimiter[0]); + + foreach ($key_delimiter as $key=>$key_data) { + if ($key != 0) { + $this->comments_table->orLike('content', $key_data); + } + } + $this->comments_table->orderBy('id', 'DESC'); + return $this->comments_table->get()->getResult('array'); + } + } +} diff --git a/application/ThirdParty/.gitkeep b/application/ThirdParty/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/application/Views/Pagers/cat.php b/application/Views/Pagers/cat.php new file mode 100644 index 0000000..73885d7 --- /dev/null +++ b/application/Views/Pagers/cat.php @@ -0,0 +1,48 @@ +setSurroundCount(2); +if ($pager->hasPrevious() || $pager->hasNext()) : +?> +
+

Pages

+
+
+ diff --git a/application/Views/admin/page/article/add.twig b/application/Views/admin/page/article/add.twig new file mode 100644 index 0000000..cbc9a89 --- /dev/null +++ b/application/Views/admin/page/article/add.twig @@ -0,0 +1,84 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+
+

Information principal

+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+
+

Publication

+
+
+ +
+
+
+
+

Information Secondaire

+
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/article/edit.twig b/application/Views/admin/page/article/edit.twig new file mode 100644 index 0000000..6dcd2cb --- /dev/null +++ b/application/Views/admin/page/article/edit.twig @@ -0,0 +1,101 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+
+

Information principal

+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+
+

Publication

+
+
+
+ {% if type == 1 %} +
+ + {% elseif type == 2 %} +
+ + {% elseif type == 3 %} +
+ + {% elseif type == 4 %} +
+ + {% endif %} +
+
+
+
+
+

Information Secondaire

+
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ {% if get_article.picture_one %} + + {% endif %} + +
+
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/article/home.twig b/application/Views/admin/page/article/home.twig new file mode 100644 index 0000000..f7d2527 --- /dev/null +++ b/application/Views/admin/page/article/home.twig @@ -0,0 +1,65 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
Article publié
+
+

Nombre d'article : {{ count_article.pub }}

+
+
+
+
+
+
Article en attente de correction
+
+

Nombre d'article : {{ count_article.att_co }}

+
+
+
+
+
+
Article en attente de publication
+
+

Nombre d'article : {{ count_article.att_pub }}

+
+
+
+
+
+
Article en brouillons
+
+

Nombre d'article : {{ count_article.brouillon }}

+
+
+
+
+
+
+
+
+

Les 5 derniès articles (Ajouter un article)

+
+
+
    + {% for data in last_five %} +
  • + +
    {{ data.title }}
    +
    +
    + Catégories : {{ general('TradCat', data.cat) }} +
    + Date de publication : {{ data.date_created }} {% if data.date_update %} + | Mise à jours : {{ data.date_update }} + {% endif %} +
    + Vues : {{ general('CountView', data.id) }} / Commentaires : {{ general('CountCom', data.id) }} +
  • + {% endfor %} +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/article/list.twig b/application/Views/admin/page/article/list.twig new file mode 100644 index 0000000..d4ecb1c --- /dev/null +++ b/application/Views/admin/page/article/list.twig @@ -0,0 +1,34 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+

Liste des articles {% if type == 1 %}publiés{% elseif type == 2 %}en attente de correction{% elseif type == 3 %}en attente de publication{% elseif type == 4 %}brouillons{% endif %}

+
+
+ {% if get_list %} +
    + {% for data in get_list %} +
  • + +
    {{ data.title }}
    +
    +
    + Catégories : {{ general('TradCat', data.cat) }} +
    + Date de publication : {{ data.date_created }} {% if data.date_update %} + | Mise à jours : {{ data.date_update }} + {% endif %} +
    + Vues : {{ general('CountView', data.id) }} / Commentaires : {{ general('CountCom', data.id) }}
  • + {% endfor %} +
+ {% else %} + Aucun article + {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/auth/login.twig b/application/Views/admin/page/auth/login.twig new file mode 100644 index 0000000..72c0c0f --- /dev/null +++ b/application/Views/admin/page/auth/login.twig @@ -0,0 +1,14 @@ +{% extends "template_login.twig" %} +{% block content %} +
+
Connexion
+ +
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/cat/add.twig b/application/Views/admin/page/cat/add.twig new file mode 100644 index 0000000..d99a417 --- /dev/null +++ b/application/Views/admin/page/cat/add.twig @@ -0,0 +1,4 @@ +{% extends "template.twig" %} +{% block content %} + +{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/cat/edit.twig b/application/Views/admin/page/cat/edit.twig new file mode 100644 index 0000000..d99a417 --- /dev/null +++ b/application/Views/admin/page/cat/edit.twig @@ -0,0 +1,4 @@ +{% extends "template.twig" %} +{% block content %} + +{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/cat/home.twig b/application/Views/admin/page/cat/home.twig new file mode 100644 index 0000000..d99a417 --- /dev/null +++ b/application/Views/admin/page/cat/home.twig @@ -0,0 +1,4 @@ +{% extends "template.twig" %} +{% block content %} + +{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/cat/list.twig b/application/Views/admin/page/cat/list.twig new file mode 100644 index 0000000..9917a20 --- /dev/null +++ b/application/Views/admin/page/cat/list.twig @@ -0,0 +1,36 @@ +{% extends "template.twig" %} +{% block content %} +
+ {% for data in get_cat %} +
+
+
+ +
+
+

+ +

+
+ +
+
+ {% endfor %} +
+
+
+ Ajouter une catégorie +
+
+

+ +

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/comments/home.twig b/application/Views/admin/page/comments/home.twig new file mode 100644 index 0000000..4ece5b1 --- /dev/null +++ b/application/Views/admin/page/comments/home.twig @@ -0,0 +1,29 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
Commentaire en attente de validation
+
+

Nombre de commentaire : {{ count_wait }}

+
+
+
+
+
+
Commentaires validés
+
+

Nombre de commentaire : {{ count_ok }}

+
+
+
+
+
+
Commentaire non validés
+
+

Nombre de commentaire : {{ count_no }}

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/comments/no.twig b/application/Views/admin/page/comments/no.twig new file mode 100644 index 0000000..6193ed3 --- /dev/null +++ b/application/Views/admin/page/comments/no.twig @@ -0,0 +1,31 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+

Liste des commentaires

+
+
+ {% if comments %} +
    + {% for data in comments %} +
  • + Commentaire de {{ data.author_name }} (Email : {{ data.author_email }}) (IP : {{ data.author_ip }}) +
    + {{ data.content }} +
    + Posté le {{ data.created_date }} | Article : {{ data.post_title }} +
    + Validés +
  • + {% endfor %} +
+ {% else %} +
Aucun commentaire non validé
+ {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/comments/ok.twig b/application/Views/admin/page/comments/ok.twig new file mode 100644 index 0000000..f676995 --- /dev/null +++ b/application/Views/admin/page/comments/ok.twig @@ -0,0 +1,31 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+

Liste des commentaires

+
+
+ {% if comments %} +
    + {% for data in comments %} +
  • + Commentaire de {{ data.author_name }} (Email : {{ data.author_email }}) (IP : {{ data.author_ip }}) +
    + {{ data.content }} +
    + Posté le {{ data.created_date }} | Article : {{ data.post_title }} +
    + Refusé +
  • + {% endfor %} +
+ {% else %} +
Aucun commentaire validé
+ {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/comments/wait.twig b/application/Views/admin/page/comments/wait.twig new file mode 100644 index 0000000..25a27d8 --- /dev/null +++ b/application/Views/admin/page/comments/wait.twig @@ -0,0 +1,31 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+

Liste des commentaires

+
+
+ {% if comments %} +
    + {% for data in comments %} +
  • + Commentaire de {{ data.author_name }} (Email : {{ data.author_email }}) (IP : {{ data.author_ip }}) +
    + {{ data.content }} +
    + Posté le {{ data.created_date }} | Article : {{ data.post_title }} +
    + ValidésRefusé +
  • + {% endfor %} +
+ {% else %} +
Aucun commentaire en attente de validation
+ {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/config/home.twig b/application/Views/admin/page/config/home.twig new file mode 100644 index 0000000..d99a417 --- /dev/null +++ b/application/Views/admin/page/config/home.twig @@ -0,0 +1,4 @@ +{% extends "template.twig" %} +{% block content %} + +{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/config/params.twig b/application/Views/admin/page/config/params.twig new file mode 100644 index 0000000..002ad28 --- /dev/null +++ b/application/Views/admin/page/config/params.twig @@ -0,0 +1,38 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+

Liste des paramêtres

+
+
+ {% for data in getall %} +
+
+
+
Clef
+ +
+
+
+
+
Data
+ {% if data.type == 'simple' %} + + {% else %} + + {% endif %} +
+
+ +
+ {% endfor %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/contact/finish.twig b/application/Views/admin/page/contact/finish.twig new file mode 100644 index 0000000..a0bd7f2 --- /dev/null +++ b/application/Views/admin/page/contact/finish.twig @@ -0,0 +1,33 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+

Liste des contact

+
+
+ {% if get_list %} +
    + {% for data in get_list %} +
  • + Commentaire de {{ data.name }} (Email : {{ data.email }}) (IP : {{ data.ip }}) +
    + Sujet : {{ data.sujet }} +
    + {{ data.message }} +
    + Envoyé le {{ data.date_created }} +
    + supprimer +
  • + {% endfor %} +
+ {% else %} +
Aucun ancien contact
+ {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/contact/home.twig b/application/Views/admin/page/contact/home.twig new file mode 100644 index 0000000..d99a417 --- /dev/null +++ b/application/Views/admin/page/contact/home.twig @@ -0,0 +1,4 @@ +{% extends "template.twig" %} +{% block content %} + +{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/contact/new.twig b/application/Views/admin/page/contact/new.twig new file mode 100644 index 0000000..4fdf4f5 --- /dev/null +++ b/application/Views/admin/page/contact/new.twig @@ -0,0 +1,33 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+

Liste des contact

+
+
+ {% if get_list %} +
    + {% for data in get_list %} +
  • + Contact de {{ data.name }} (Email : {{ data.email }}) (IP : {{ data.ip }}) +
    + Sujet : {{ data.sujet }} +
    + {{ data.message }} +
    + Envoyé le {{ data.date_created }} +
    + RépondreMarquer comme lutsupprimer +
  • + {% endfor %} +
+ {% else %} +
Aucune nouvel prise de contact
+ {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/contact/rep.twig b/application/Views/admin/page/contact/rep.twig new file mode 100644 index 0000000..8131669 --- /dev/null +++ b/application/Views/admin/page/contact/rep.twig @@ -0,0 +1,52 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+

Contact

+
+
+ Contact de {{ getContact.name }} (Email : {{ getContact.email }}) (IP : {{ getContact.ip }}) +
+ Sujet : {{ getContact.sujet }} +
+ {{ getContact.message }} +
+ Envoyé le {{ getContact.date_created }} +
+
+
+
+
+
+
+
+

Répondre

+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/page/home.twig b/application/Views/admin/page/home.twig new file mode 100644 index 0000000..2e9f7c8 --- /dev/null +++ b/application/Views/admin/page/home.twig @@ -0,0 +1,35 @@ +{% extends "template.twig" %} +{% block content %} +
+
+
+
+

Accueil du dashboard

+
+
+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/admin/template.twig b/application/Views/admin/template.twig new file mode 100644 index 0000000..7bc069f --- /dev/null +++ b/application/Views/admin/template.twig @@ -0,0 +1,131 @@ + + + + {{ meta }} + {% for data in css %} + + {% endfor %} + + +
+ +
+ {% for data in js %} + + {% endfor %} + + diff --git a/application/Views/admin/template_login.twig b/application/Views/admin/template_login.twig new file mode 100644 index 0000000..258a5a1 --- /dev/null +++ b/application/Views/admin/template_login.twig @@ -0,0 +1,20 @@ + + + + {{ meta }} + {% for data in css %} + + {% endfor %} + + + {% block content %}{% endblock %} + {% for data in js %} + + {% endfor %} + + + diff --git a/application/Views/blog/page/about/home.twig b/application/Views/blog/page/about/home.twig new file mode 100644 index 0000000..78f4833 --- /dev/null +++ b/application/Views/blog/page/about/home.twig @@ -0,0 +1,7 @@ +{% extends "template.twig" %} +{% block content %} +
+

Informations

+

A venir...

+
+{% endblock %} diff --git a/application/Views/blog/page/article/view.twig b/application/Views/blog/page/article/view.twig new file mode 100644 index 0000000..931bb41 --- /dev/null +++ b/application/Views/blog/page/article/view.twig @@ -0,0 +1,91 @@ +{% extends "template.twig" %} +{% block content %} + +
+
+

{{ get_info_article.title }}

+
    +
  • Publié : {{ get_info_article.date_created }}
  • + {% if get_info_article.date_update %} +
  • Mise à jours :{{ get_info_article.date_update }}
  • + {% endif %} +
  • Catégories : {{ general('TradCat', get_info_article.cat) }}
  • +
  • vues {{ PostView }}
  • +
+
+
+ +
+
+ {{ parse(get_info_article.content) }} +
+
+ +
+
+
+

Commentaire(s)

+ {% for data in get_coms %} +
+
+ {{ data.author_name }} + {{ data.created_date }} +
+
+ {{ data.content }} +
+
+ {% endfor %} +

Ajouter un commentaire

+
+
+
+ + +
+
+ + +
+
+ + +
+ {{ csrf_field }} + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/application/Views/blog/page/cat/home.twig b/application/Views/blog/page/cat/home.twig new file mode 100644 index 0000000..eeba960 --- /dev/null +++ b/application/Views/blog/page/cat/home.twig @@ -0,0 +1,25 @@ +{% extends "template.twig" %} +{% block content %} +
+ {% for data in get_cat %} + + {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/application/Views/blog/page/cat/view.twig b/application/Views/blog/page/cat/view.twig new file mode 100644 index 0000000..2fdfc02 --- /dev/null +++ b/application/Views/blog/page/cat/view.twig @@ -0,0 +1,44 @@ +{% extends "template.twig" %} +{% block content %} + +
+ {% for data in get_all %} +
+
+ +
+ {{ general('TradCat', data.cat) }} +
+
+
+
+ {{ data.date_created }} +
+
+ {{ general('CountCom', data.id) }} +
+
+

{{ data.title }}

+
+ {{ data.content|slice(0, 500) ~ '...' }} +
+ +
+ {% else %} +
+ Aucun article +
+ {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/application/Views/blog/page/contact/home.twig b/application/Views/blog/page/contact/home.twig new file mode 100644 index 0000000..f63b39d --- /dev/null +++ b/application/Views/blog/page/contact/home.twig @@ -0,0 +1,30 @@ +{% extends "template.twig" %} +{% block content %} +
+

Contact

+
+ {% if check %} + Vous avez déjà réssament fait une prise de contact + {% else %} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ {% endif %} +
+{% endblock %} diff --git a/application/Views/blog/page/cookies/home.twig b/application/Views/blog/page/cookies/home.twig new file mode 100644 index 0000000..545f06f --- /dev/null +++ b/application/Views/blog/page/cookies/home.twig @@ -0,0 +1,32 @@ +{% extends "template.twig" %} +{% block content %} +
+

Politique sur les cookies pour {{ 'site_title'|config }}

+

Quels sont les cookies?

+

Comme c'est pratique habituelle avec presque tous les sites Web professionnels, ce site utilise des cookies, qui sont de minuscules fichiers téléchargés sur votre ordinateur, afin d'améliorer votre expérience. Cette page décrit les informations recueillies, la façon dont nous l'utilisons et pourquoi nous devons parfois stocker ces cookies. Nous partagerons également la façon dont vous pouvez éviter que ces cookies ne soient stockés, mais cela risque de dégrader ou de «briser» certains éléments de la fonctionnalité des sites.

+

Pour plus d'informations générales sur les cookies, consultez l'article de Wikipedia sur les cookies HTTP ...

+

Comment utilisons des cookies

+

Nous utilisons des cookies pour diverses raisons détaillées ci-dessous. Malheureusement, dans la plupart des cas, il n'existe aucune option standard de l'industrie pour désactiver les cookies sans désactiver complètement les fonctionnalités et les fonctionnalités qu'ils ajoutent à ce site. Il est recommandé de laisser sur tous les cookies si vous ne savez pas si vous en avez besoin ou non au cas où ils sont utilisés pour fournir un service que vous utilisez.

+

Désactivation des cookies

+

Vous pouvez empêcher le réglage des cookies en ajustant les paramètres de votre navigateur (voir l'aide de votre navigateur pour savoir comment le faire). Soyez conscient que la désactivation des cookies affectera la fonctionnalité de ce site et de nombreux autres sites que vous visitez. La désactivation des cookies entraînera généralement la désactivation de certaines fonctionnalités et fonctionnalités de ce site. Par conséquent, il est recommandé de ne pas désactiver les cookies.

+

Les cookies que nous fixons

+

 

+

Afin de vous fournir une excellente expérience sur ce site, nous fournissons la fonctionnalité pour définir vos préférences quant à la façon dont ce site fonctionne lorsque vous l'utilisez. Pour se souvenir de vos préférences, nous devons définir les cookies afin que ces informations puissent être appelées chaque fois que vous interagissez avec une page est affectée par vos préférences.

+

 

+

Cookies tiers

+

Dans certains cas particuliers, nous utilisons également des cookies fournis par des tiers approuvés. La section suivante détaille les cookies de tiers que vous pourriez rencontrer sur ce site.

+

Ce site utilise Google Analytics qui est l'une des solutions analytiques les plus répandues et les plus fiables sur le Web pour nous aider à comprendre comment vous utilisez le site et les moyens d'améliorer votre expérience. Ces cookies peuvent suivre des choses telles que combien de temps vous dépensez sur le site et les pages que vous visitez afin que nous puissions continuer à produire du contenu attrayant.

+

Pour plus d'informations sur les cookies Google Analytics, consultez la page officielle Google Analytics.

+

De temps en temps, nous testons de nouvelles fonctionnalités et apportons des modifications subtiles à la manière dont le site est livré. Lorsque nous testons encore de nouvelles fonctionnalités, ces cookies peuvent être utilisés pour vous assurer que vous recevez une expérience constante sur le site tout en veillant à ce que nous comprenions quelles optimisations nos utilisateurs apprécient le plus.

+

Le service Google AdSense que nous utilisons pour diffuser de la publicité utilise un cookie DoubleClick pour diffuser des annonces plus pertinentes sur le Web et limiter le nombre de fois qu'une annonce est affichée.

+

Pour plus d'informations sur Google AdSense, consultez la FAQ de confidentialité Google AdSense officielle.

+

Nous utilisons des publicités pour compenser les coûts de fonctionnement de ce site et fournir des fonds pour un développement ultérieur. Les cookies de publicité comportementale utilisés par ce site sont conçus pour vous assurer que nous vous fournissons les publicités les plus pertinentes si possible en surveillant anonymement vos intérêts et en présentant des éléments similaires qui pourraient être intéressants.

+

Dans certains cas, nous pouvons vous fournir un contenu personnalisé en fonction de ce que vous nous dites sur vous directement ou indirectement en liant un compte de médias sociaux. Ces types de cookies nous permettent simplement de vous fournir un contenu qui, selon nous, pourrait vous intéresser.

+

Plusieurs partenaires annoncent en notre nom et les cookies de suivi des affiliés nous permettent simplement de voir si nos clients sont venus sur le site par l'intermédiaire de l'un de nos sites partenaires afin que nous puissions les créditer de manière appropriée et, le cas échéant, permettre à nos partenaires affiliés de fournir un bonus qu'ils pourraient Vous procurer un achat.

+

Nous utilisons également des boutons de médias sociaux et / ou des plugins sur ce site qui vous permettent de vous connecter à votre réseau social de diverses façons. Pour que ceux-ci fonctionnent, les sites de médias sociaux suivants incluent: Facebook, Twitter, établira des cookies via notre site qui peut être utilisé pour améliorer votre profil sur leur site ou contribuer aux données qu'ils détiennent à diverses fins décrites dans leurs politiques de confidentialité respectives.

+

 

+

Plus d'information

+

J'espère que cela vous a clarifié les choses et, comme cela a été mentionné précédemment, si vous ne savez pas si vous avez besoin ou pas, il est généralement plus sûr de laisser les cookies activés au cas où il interagirait avec l'une des fonctionnalités que vous utilisez sur notre site. Toutefois, si vous cherchez encore plus d'informations, vous pouvez nous contacter à travers une de nos méthodes de contact préférées.

+

Email: {{ 'mail_from_adress'|config }}

+
+{% endblock %} diff --git a/application/Views/blog/page/errors/404.twig b/application/Views/blog/page/errors/404.twig new file mode 100644 index 0000000..7da8bd3 --- /dev/null +++ b/application/Views/blog/page/errors/404.twig @@ -0,0 +1,7 @@ +{% extends "template.twig" %} +{% block content %} +
+

Erreur 404

+
Page introuvable
+
+{% endblock %} diff --git a/application/Views/blog/page/home.twig b/application/Views/blog/page/home.twig new file mode 100644 index 0000000..48d641e --- /dev/null +++ b/application/Views/blog/page/home.twig @@ -0,0 +1,81 @@ +{% extends "template.twig" %} +{% block content %} + +
+

Articles les plus luts

+ {% for data in last_important %} +
+
+ +
+ {{ general('TradCat', data.cat) }} +
+
+
+
+ {{ data.date_created }} +
+
+ {{ general('CountCom', data.id) }} +
+
+

{{ data.title }}

+ + +
+ {% endfor %} +
+ {% if 'home_annonce_1'|config %} +
{{ 'home_annonce_1'|config }}
+ {% endif %} +
+

Les derniers articles

+ {% for data in last_article %} +
+
+ +
+ {{ general('TradCat', data.cat) }} +
+
+
+
+ {{ data.date_created }} +
+
+ {{ general('CountCom', data.id) }} +
+
+

{{ data.title }}

+
+ {{ data.content|slice(0, 500) ~ '...' }} +
+ +
+ {% endfor %} +
+ {% if 'home_annonce_2'|config %} +
{{ 'home_annonce_2'|config }}
+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/application/Views/blog/page/search/home.twig b/application/Views/blog/page/search/home.twig new file mode 100644 index 0000000..96032da --- /dev/null +++ b/application/Views/blog/page/search/home.twig @@ -0,0 +1,39 @@ +{% extends "template.twig" %} +{% block content %} +
+

Recherche

+
+
+ + + +
+ {% if val_mc %} +
+
+ + +
+
+
+ {% if search_article %} + {% for data in search_article %} + {{ data.title }} + {% endfor %} + {% else %} + Aucun article correspondant à votre recherche à été trouvé + {% endif %} +
+
+ {% if search_com %} + {% for data in search_com %} + {{ data.content }} + {% endfor %} + {% else %} + Aucun commentaires correspondant à votre recherche à été trouvé + {% endif %} +
+
+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/application/Views/blog/template.twig b/application/Views/blog/template.twig new file mode 100644 index 0000000..9a1544f --- /dev/null +++ b/application/Views/blog/template.twig @@ -0,0 +1,128 @@ + + + + {% autoescape false %} + {{ meta }} + {% endautoescape %} + {% for data in css %} + + {% endfor %} + {% if 'analytics_active'|config == 1 %} + {{ 'analytics_code'|config }} + {% endif %} + + +
+ +
+
+
+ + +
+
+
+ {{ 'annonce_general'|config }} +
+ {% block content %}{% endblock %} +
+
+ Copyright © 2017 {{ 'site_title'|config }}, All Rights Reserved. Page chargée en 0.7196 secondes. Version : 1.0.0 / CI : 4.0-dev +
+
+ + + +
+
+ + {% for data in js %} + + {% endfor %} + + + \ No newline at end of file diff --git a/application/Views/errors/cli/error_404.php b/application/Views/errors/cli/error_404.php new file mode 100644 index 0000000..eab984d --- /dev/null +++ b/application/Views/errors/cli/error_404.php @@ -0,0 +1,8 @@ + + +An uncaught Exception was encountered + +Type: +Message: +Filename: getFile(), "\n"; ?> +Line Number: getLine(); ?> + + + + Backtrace: + getTrace() as $error): ?> + + + + + + diff --git a/application/Views/errors/cli/production.php b/application/Views/errors/cli/production.php new file mode 100644 index 0000000..f04d517 --- /dev/null +++ b/application/Views/errors/cli/production.php @@ -0,0 +1,5 @@ + + + + + 404 Page Not Found + + + + +
+

404 - File Not Found

+ +

+ + + + Sorry! Cannot seem to find the page you were looking for. + +

+
+ + diff --git a/application/Views/errors/html/error_exception.php b/application/Views/errors/html/error_exception.php new file mode 100644 index 0000000..aa22569 --- /dev/null +++ b/application/Views/errors/html/error_exception.php @@ -0,0 +1,392 @@ + + + + + + + + <?= htmlspecialchars($title, ENT_SUBSTITUTE, 'UTF-8') ?> + + + + + + + +
+
+

getCode() ? ' #'.$exception->getCode() : '') ?>

+

+ getMessage() ?> + getMessage())) ?>" + rel="noreferrer" target="_blank">search → +

+
+
+ + +
+

at line

+ + +
+ +
+ +
+ +
+ + + +
+ + +
+ +
    + $row) : ?> + +
  1. +

    + + + + + {PHP internal code} + + + + +   —   + + + ( arguments ) +

    + + + getParameters(); + } + foreach ($row['args'] as $key => $value) : ?> + + + + + + +
    name : "#$key", ENT_SUBSTITUTE, 'UTF-8') ?>
    +
    + + () + + + + +   —   () + +

    + + + +
    + +
    + +
  2. + + +
+ +
+ + +
+ + + +

$

+ + + + + + + + + + $value) : ?> + + + + + + +
KeyValue
+ + + + '.print_r($value, true) ?> + +
+ + + + + + +

Constants

+ + + + + + + + + + $value) : ?> + + + + + + +
KeyValue
+ + + + '.print_r($value, true) ?> + +
+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Pathuri ?>
HTTP MethodgetMethod(true) ?>
IP AddressgetIPAddress() ?>
Is AJAX Request?isAJAX() ? 'yes' : 'no' ?>
Is CLI Request?isCLI() ? 'yes' : 'no' ?>
Is Secure Request?isSecure() ? 'yes' : 'no' ?>
User AgentgetUserAgent() ?>
+ + + + + + + + +

$

+ + + + + + + + + + $value) : ?> + + + + + + +
KeyValue
+ + + + '.print_r($value, true) ?> + +
+ + + + + +
+ No $_GET, $_POST, or $_COOKIE Information to show. +
+ + + + getHeaders(); ?> + + +

Headers

+ + + + + + + + + + $value) : ?> + + + + + + + + + + +
HeaderValue
getName(), 'html') ?>getValueLine(), 'html') ?>
+ + +
+ + + setStatusCode(http_response_code()); + ?> +
+ + + + + +
Response StatusgetStatusCode().' - '.$response->getReason() ?>
+ + getHeaders(); ?> + + + +

Headers

+ + + + + + + + + + $value) : ?> + + + + + + +
HeaderValue
getHeaderLine($name), 'html') ?>
+ + +
+ + +
+ + +
    + +
  1. + +
+
+ + +
+ + + + + + + + + + + + + + + + +
Memory Usage
Peak Memory Usage:
Memory Limit:
+ +
+ +
+ +
+ + + + + diff --git a/application/Views/errors/html/production.php b/application/Views/errors/html/production.php new file mode 100644 index 0000000..b912a01 --- /dev/null +++ b/application/Views/errors/html/production.php @@ -0,0 +1,25 @@ + + + + + + + Whoops! + + + + + +
+ +

Whoops!

+ +

We seem to have hit a snag. Please try again later...

+ +
+ + + + diff --git a/application/index.html b/application/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/application/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..54df39a --- /dev/null +++ b/composer.json @@ -0,0 +1,37 @@ +{ + "description": "The CodeIgniter framework v4", + "name": "codeigniter4/framework", + "type": "project", + "homepage": "https://codeigniter.com", + "license": "MIT", + "support": { + "forum": "http://forum.codeigniter.com/", + "wiki": "https://github.com/bcit-ci/CodeIgniter4/wiki", + "slack": "https://codeigniterchat.slack.com", + "source": "https://github.com/bcit-ci/CodeIgniter4" + }, + "autoload": { + "psr-4": { + "CodeIgniter\\": "system/" + } + }, + "require": { + "php": ">=7.0", + "zendframework/zend-escaper": "^2.5", + "paragonie/sodium_compat": "^1.1", + "kint-php/kint": "^2.1", + "twig/twig": "^2.4", + "samayo/bulletproof": "3.2.0", + "swiftmailer/swiftmailer": "v6.0.2" + }, + "require-dev": { + "phpunit/phpunit": "^6.0", + "mikey179/vfsStream": "1.6.5" + }, + "scripts": { + "post-update-cmd": [ + "composer dump-autoload", + "CodeIgniter\\ComposerScripts::postUpdate" + ] + } +} diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..de88a80 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,97 @@ +# ---------------------------------------------------------------------- +# Environment Name +# ---------------------------------------------------------------------- + +# Sets the environment that CodeIgniter runs under. +SetEnv CI_ENVIRONMENT development + +# ---------------------------------------------------------------------- +# UTF-8 encoding +# ---------------------------------------------------------------------- + +# Use UTF-8 encoding for anything served text/plain or text/html +AddDefaultCharset utf-8 + +# Force UTF-8 for a number of file formats + + AddCharset utf-8 .atom .css .js .json .rss .vtt .xml + + +# ---------------------------------------------------------------------- +# Rewrite engine +# ---------------------------------------------------------------------- + +# Turning on the rewrite engine is necessary for the following rules and features. +# FollowSymLinks must be enabled for this to work. + + Options +FollowSymlinks + RewriteEngine On + + # If you installed CodeIgniter in a subfolder, you will need to + # change the following line to match the subfolder you need. + # http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase + RewriteBase / + + # Redirect Trailing Slashes... + RewriteRule ^(.*)/$ /$1 [L,R=301] + + # Rewrite "www.example.com -> example.com" + RewriteCond %{HTTPS} !=on + RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] + RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] + + # Checks to see if the user is attempting to access a valid file, + # such as an image or css document, if this isn't true it sends the + # request to the front controller, index.php + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^(.*)$ index.php/$1 [L] + + # Ensure Authorization header is passed along + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + + + # If we don't have mod_rewrite installed, all 404's + # can be sent to index.php, and everything works as normal. + ErrorDocument 404 index.php + + +# ---------------------------------------------------------------------- +# Gzip compression +# ---------------------------------------------------------------------- + + + + # Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/ + + + SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding + RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding + + + + # Compress all output labeled with one of the following MIME-types + # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` + # and can remove the `` and `` lines as + # `AddOutputFilterByType` is still in the core directives) + + AddOutputFilterByType DEFLATE application/atom+xml \ + application/javascript \ + application/json \ + application/rss+xml \ + application/vnd.ms-fontobject \ + application/x-font-ttf \ + application/xhtml+xml \ + application/xml \ + font/opentype \ + image/svg+xml \ + image/x-icon \ + text/css \ + text/html \ + text/plain \ + text/x-component \ + text/xml + + \ No newline at end of file diff --git a/public/assets/css/admin/article.css b/public/assets/css/admin/article.css new file mode 100644 index 0000000..790711c --- /dev/null +++ b/public/assets/css/admin/article.css @@ -0,0 +1,53 @@ +.edit_article { + cursor: pointer; + background: orange; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + padding: 5px; +} + +.edit_article:hover { + -webkit-box-shadow: 0 0 3px black; + box-shadow: 0 0 3px black; +} + +.desactive_article { + cursor: pointer; + background: #d9ff00; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + padding: 5px; + margin-left: 4px; +} + +.desactive_article:hover { + -webkit-box-shadow: 0 0 3px black; + box-shadow: 0 0 3px black; +} + +.delete_article { + cursor: pointer; + background: red; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + padding: 5px; + margin-left: 4px; +} + +.delete_article:hover { + -webkit-box-shadow: 0 0 3px black; + box-shadow: 0 0 3px black; +} + +.add_article { + text-decoration: none; +} + +#art_home { + margin: 15px; +} + +/*# sourceMappingURL=../maps/admin/article.css.map */ diff --git a/public/assets/css/admin/auth.css b/public/assets/css/admin/auth.css new file mode 100644 index 0000000..19f386c --- /dev/null +++ b/public/assets/css/admin/auth.css @@ -0,0 +1,160 @@ +@import url(https://fonts.googleapis.com/css?family=Dosis:300|Lato:300,400,600,700|Roboto+Condensed:300,700|Open+Sans+Condensed:300,600|Open+Sans:400,300,600,700|Maven+Pro:400,700); +html { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +*, *:before, *:after { + -webkit-box-sizing: inherit; + box-sizing: inherit; +} + +html { + background: black; +} + +body { + font-family: "Open Sans"; + font-size: 16px; + color: White; + font-weight: 600; +} + +a { + color: #BBB; +} + +.content:before { + content: ""; + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: -1; + display: block; + background-color: black; + background-image: url("http://ultraimg.com/images/Ho6hQWs.jpg"); + width: 100%; + height: 100%; + background-size: cover; + -webkit-filter: blur(2px); + -moz-filter: blur(2px); + -o-filter: blur(2px); + -ms-filter: blur(2px); + filter: blur(2px); +} + +.content { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 540px; + height: 400px; + background-color: rgba(10, 10, 10, 0.5); + margin: auto auto; + padding: 40px; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -webkit-box-shadow: 0 0 10px black; + box-shadow: 0 0 10px black; +} + +.content .title { + text-align: center; + font-size: 2.0rem; + font-weight: 600; + padding-bottom: 30px; +} + +.content .login_form .message { + margin: 10px 0px; + padding: 10px; + width: 100%; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; +} + +.content .login_form .message.error { + background: red; +} + +.content .login_form .message.success { + background: green; +} + +.content .login_form input { + width: 100%; + font-size: 1.2rem; + font-family: "Open Sans"; + margin: 10px 0px; + border: none; + padding: 10px; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; +} + +.content .login_form input[type=checkbox] { + display: none; +} + +.content .login_form label { + display: inline-block; + width: 20px; + height: 20px; + cursor: pointer; + position: relative; + margin-left: 5px; + margin-right: 10px; + top: 13px; +} + +.content .login_form label:before { + content: ""; + display: inline-block; + width: 20px; + height: 20px; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + position: absolute; + left: 0; + bottom: 1px; + background-color: #aaa; + -webkit-box-shadow: inset 0px 2px 3px 0px rgba(0, 0, 0, 0.3), 0px 1px 0px 0px rgba(255, 255, 255, 0.8); + box-shadow: inset 0px 2px 3px 0px rgba(0, 0, 0, 0.3), 0px 1px 0px 0px rgba(255, 255, 255, 0.8); +} + +.content .login_form input[type=checkbox]:checked + label:before { + font-family: FontAwesome; + content: "\f00c"; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2) 2px 5px rgba(0, 0, 0, 0.4); + font-size: 16px; + color: Black; + text-align: center; + line-height: 20px; +} + +.content .login_form span { + font-size: 0.9rem; +} + +.content .login_form button { + width: 100%; + font-size: 1.1rem; + padding: 10px; + margin: 20px 0px; + background-color: #66A756; + color: White; + border: none; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; +} + +/*# sourceMappingURL=../maps/admin/auth.css.map */ diff --git a/public/assets/css/admin/bootstrap.css b/public/assets/css/admin/bootstrap.css new file mode 100644 index 0000000..f6d09ed --- /dev/null +++ b/public/assets/css/admin/bootstrap.css @@ -0,0 +1,8839 @@ +/*! + * Bootstrap v4.0.0-beta (https://getbootstrap.com) + * Copyright 2011-2017 The Bootstrap Authors + * Copyright 2011-2017 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +:root { + --blue: #007bff; + --indigo: #6610f2; + --purple: #6f42c1; + --pink: #e83e8c; + --red: #dc3545; + --orange: #fd7e14; + --yellow: #ffc107; + --green: #28a745; + --teal: #20c997; + --cyan: #17a2b8; + --white: #fff; + --gray: #868e96; + --gray-dark: #343a40; + --primary: #007bff; + --secondary: #868e96; + --success: #28a745; + --info: #17a2b8; + --warning: #ffc107; + --danger: #dc3545; + --light: #f8f9fa; + --dark: #343a40; + --breakpoint-xs: 0; + --breakpoint-sm: 576px; + --breakpoint-md: 768px; + --breakpoint-lg: 992px; + --breakpoint-xl: 1200px; + --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --font-family-monospace: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +@media print { + *, + *::before, + *::after { + text-shadow: none !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + abbr[title]::after { + content: " (" attr(title) ")"; + } + pre { + white-space: pre-wrap !important; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .badge { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} + +*, +*::before, +*::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: transparent; +} + +@-ms-viewport { + width: device-width; +} + +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; +} + +[tabindex="-1"]:focus { + outline: none !important; +} + +hr { + -webkit-box-sizing: content-box; + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +dfn { + font-style: italic; +} + +b, +strong { + font-weight: bolder; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg:not(:root) { + overflow: hidden; +} + +a, +area, +button, +[role="button"], +input:not([type="range"]), +label, +select, +summary, +textarea { + -ms-touch-action: manipulation; + touch-action: manipulation; +} + +table { + border-collapse: collapse; +} + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #868e96; + text-align: left; + caption-side: bottom; +} + +th { + text-align: inherit; +} + +label { + display: inline-block; + margin-bottom: .5rem; +} + +button { + border-radius: 0; +} + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +button, +html [type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + padding: 0; + border-style: none; +} + +input[type="radio"], +input[type="checkbox"] { + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; +} + +textarea { + overflow: auto; + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} + +progress { + vertical-align: baseline; +} + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; +} + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +summary { + display: list-item; +} + +template { + display: none; +} + +[hidden] { + display: none !important; +} + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + margin-bottom: 0.5rem; + font-family: inherit; + font-weight: 500; + line-height: 1.2; + color: inherit; +} + +h1, .h1 { + font-size: 2.5rem; +} + +h2, .h2 { + font-size: 2rem; +} + +h3, .h3 { + font-size: 1.75rem; +} + +h4, .h4 { + font-size: 1.5rem; +} + +h5, .h5 { + font-size: 1.25rem; +} + +h6, .h6 { + font-size: 1rem; +} + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.2; +} + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.2; +} + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.2; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.2; +} + +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +small, +.small { + font-size: 80%; + font-weight: 400; +} + +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline-item { + display: inline-block; +} + +.list-inline-item:not(:last-child) { + margin-right: 5px; +} + +.initialism { + font-size: 90%; + text-transform: uppercase; +} + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; +} + +.blockquote-footer { + display: block; + font-size: 80%; + color: #868e96; +} + +.blockquote-footer::before { + content: "\2014 \00A0"; +} + +.img-fluid { + max-width: 100%; + height: auto; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 0.25rem; + max-width: 100%; + height: auto; +} + +.figure { + display: inline-block; +} + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} + +.figure-caption { + font-size: 90%; + color: #868e96; +} + +code, +kbd, +pre, +samp { + font-family: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +code { + padding: 0.2rem 0.4rem; + font-size: 90%; + color: #bd4147; + background-color: #f8f9fa; + border-radius: 0.25rem; +} + +a > code { + padding: 0; + color: inherit; + background-color: inherit; +} + +kbd { + padding: 0.2rem 0.4rem; + font-size: 90%; + color: #fff; + background-color: #212529; + border-radius: 0.2rem; +} + +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; +} + +pre { + display: block; + font-size: 90%; + color: #212529; +} + +pre code { + padding: 0; + font-size: inherit; + color: inherit; + background-color: transparent; + border-radius: 0; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +.container { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } +} + +.container-fluid { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +.row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; +} + +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; +} + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} + +.col-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; +} + +.col-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; +} + +.col-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; +} + +.col-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.col-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; +} + +.col-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; +} + +.col-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.col-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; +} + +.col-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; +} + +.col-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} + +.col-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; +} + +.col-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; +} + +.col-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.order-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; +} + +.order-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; +} + +.order-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; +} + +.order-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; +} + +.order-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; +} + +.order-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; +} + +.order-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; +} + +.order-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; +} + +.order-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; +} + +.order-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; +} + +.order-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; +} + +.order-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; +} + +.order-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; +} + +.offset-1 { + margin-left: 8.33333%; +} + +.offset-2 { + margin-left: 16.66667%; +} + +.offset-3 { + margin-left: 25%; +} + +.offset-4 { + margin-left: 33.33333%; +} + +.offset-5 { + margin-left: 41.66667%; +} + +.offset-6 { + margin-left: 50%; +} + +.offset-7 { + margin-left: 58.33333%; +} + +.offset-8 { + margin-left: 66.66667%; +} + +.offset-9 { + margin-left: 75%; +} + +.offset-10 { + margin-left: 83.33333%; +} + +.offset-11 { + margin-left: 91.66667%; +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-sm-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-sm-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; + } + .col-sm-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; + } + .col-sm-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-sm-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; + } + .col-sm-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; + } + .col-sm-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-sm-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; + } + .col-sm-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; + } + .col-sm-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-sm-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; + } + .col-sm-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; + } + .col-sm-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-sm-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-sm-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-sm-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-sm-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-sm-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-sm-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-sm-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-sm-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-sm-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-sm-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-sm-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-sm-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-sm-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.33333%; + } + .offset-sm-2 { + margin-left: 16.66667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.33333%; + } + .offset-sm-5 { + margin-left: 41.66667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.33333%; + } + .offset-sm-8 { + margin-left: 66.66667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.33333%; + } + .offset-sm-11 { + margin-left: 91.66667%; + } +} + +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-md-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-md-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; + } + .col-md-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; + } + .col-md-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-md-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; + } + .col-md-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; + } + .col-md-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-md-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; + } + .col-md-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; + } + .col-md-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-md-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; + } + .col-md-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; + } + .col-md-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-md-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-md-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-md-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-md-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-md-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-md-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-md-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-md-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-md-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-md-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-md-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-md-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-md-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.33333%; + } + .offset-md-2 { + margin-left: 16.66667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.33333%; + } + .offset-md-5 { + margin-left: 41.66667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.33333%; + } + .offset-md-8 { + margin-left: 66.66667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.33333%; + } + .offset-md-11 { + margin-left: 91.66667%; + } +} + +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-lg-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-lg-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; + } + .col-lg-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; + } + .col-lg-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-lg-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; + } + .col-lg-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; + } + .col-lg-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-lg-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; + } + .col-lg-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; + } + .col-lg-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-lg-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; + } + .col-lg-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; + } + .col-lg-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-lg-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-lg-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-lg-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-lg-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-lg-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-lg-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-lg-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-lg-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-lg-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-lg-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-lg-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-lg-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-lg-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.33333%; + } + .offset-lg-2 { + margin-left: 16.66667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.33333%; + } + .offset-lg-5 { + margin-left: 41.66667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.33333%; + } + .offset-lg-8 { + margin-left: 66.66667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.33333%; + } + .offset-lg-11 { + margin-left: 91.66667%; + } +} + +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-xl-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-xl-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; + } + .col-xl-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; + } + .col-xl-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-xl-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; + } + .col-xl-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; + } + .col-xl-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-xl-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; + } + .col-xl-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; + } + .col-xl-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-xl-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; + } + .col-xl-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; + } + .col-xl-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-xl-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-xl-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-xl-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-xl-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-xl-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-xl-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-xl-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-xl-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-xl-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-xl-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-xl-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-xl-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-xl-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.33333%; + } + .offset-xl-2 { + margin-left: 16.66667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.33333%; + } + .offset-xl-5 { + margin-left: 41.66667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.33333%; + } + .offset-xl-8 { + margin-left: 66.66667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.33333%; + } + .offset-xl-11 { + margin-left: 91.66667%; + } +} + +.table { + width: 100%; + max-width: 100%; + margin-bottom: 1rem; + background-color: transparent; +} + +.table th, +.table td { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #e9ecef; +} + +.table thead th { + vertical-align: bottom; + border-bottom: 2px solid #e9ecef; +} + +.table tbody + tbody { + border-top: 2px solid #e9ecef; +} + +.table .table { + background-color: #fff; +} + +.table-sm th, +.table-sm td { + padding: 0.3rem; +} + +.table-bordered { + border: 1px solid #e9ecef; +} + +.table-bordered th, +.table-bordered td { + border: 1px solid #e9ecef; +} + +.table-bordered thead th, +.table-bordered thead td { + border-bottom-width: 2px; +} + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); +} + +.table-hover tbody tr:hover { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-primary, +.table-primary > th, +.table-primary > td { + background-color: #b8daff; +} + +.table-hover .table-primary:hover { + background-color: #9fcdff; +} + +.table-hover .table-primary:hover > td, +.table-hover .table-primary:hover > th { + background-color: #9fcdff; +} + +.table-secondary, +.table-secondary > th, +.table-secondary > td { + background-color: #dddfe2; +} + +.table-hover .table-secondary:hover { + background-color: #cfd2d6; +} + +.table-hover .table-secondary:hover > td, +.table-hover .table-secondary:hover > th { + background-color: #cfd2d6; +} + +.table-success, +.table-success > th, +.table-success > td { + background-color: #c3e6cb; +} + +.table-hover .table-success:hover { + background-color: #b1dfbb; +} + +.table-hover .table-success:hover > td, +.table-hover .table-success:hover > th { + background-color: #b1dfbb; +} + +.table-info, +.table-info > th, +.table-info > td { + background-color: #bee5eb; +} + +.table-hover .table-info:hover { + background-color: #abdde5; +} + +.table-hover .table-info:hover > td, +.table-hover .table-info:hover > th { + background-color: #abdde5; +} + +.table-warning, +.table-warning > th, +.table-warning > td { + background-color: #ffeeba; +} + +.table-hover .table-warning:hover { + background-color: #ffe8a1; +} + +.table-hover .table-warning:hover > td, +.table-hover .table-warning:hover > th { + background-color: #ffe8a1; +} + +.table-danger, +.table-danger > th, +.table-danger > td { + background-color: #f5c6cb; +} + +.table-hover .table-danger:hover { + background-color: #f1b0b7; +} + +.table-hover .table-danger:hover > td, +.table-hover .table-danger:hover > th { + background-color: #f1b0b7; +} + +.table-light, +.table-light > th, +.table-light > td { + background-color: #fdfdfe; +} + +.table-hover .table-light:hover { + background-color: #ececf6; +} + +.table-hover .table-light:hover > td, +.table-hover .table-light:hover > th { + background-color: #ececf6; +} + +.table-dark, +.table-dark > th, +.table-dark > td { + background-color: #c6c8ca; +} + +.table-hover .table-dark:hover { + background-color: #b9bbbe; +} + +.table-hover .table-dark:hover > td, +.table-hover .table-dark:hover > th { + background-color: #b9bbbe; +} + +.table-active, +.table-active > th, +.table-active > td { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-hover .table-active:hover > td, +.table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); +} + +.table .thead-dark th { + color: #fff; + background-color: #212529; + border-color: #32383e; +} + +.table .thead-light th { + color: #495057; + background-color: #e9ecef; + border-color: #e9ecef; +} + +.table-dark { + color: #fff; + background-color: #212529; +} + +.table-dark th, +.table-dark td, +.table-dark thead th { + border-color: #32383e; +} + +.table-dark.table-bordered { + border: 0; +} + +.table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); +} + +.table-dark.table-hover tbody tr:hover { + background-color: rgba(255, 255, 255, 0.075); +} + +@media (max-width: 575px) { + .table-responsive-sm { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-sm.table-bordered { + border: 0; + } +} + +@media (max-width: 767px) { + .table-responsive-md { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-md.table-bordered { + border: 0; + } +} + +@media (max-width: 991px) { + .table-responsive-lg { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-lg.table-bordered { + border: 0; + } +} + +@media (max-width: 1199px) { + .table-responsive-xl { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-xl.table-bordered { + border: 0; + } +} + +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; +} + +.table-responsive.table-bordered { + border: 0; +} + +.form-control { + display: block; + width: 100%; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + -webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; +} + +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} + +.form-control:focus { + color: #495057; + background-color: #fff; + border-color: #80bdff; + outline: none; + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.form-control::-webkit-input-placeholder { + color: #868e96; + opacity: 1; +} + +.form-control:-ms-input-placeholder { + color: #868e96; + opacity: 1; +} + +.form-control::-ms-input-placeholder { + color: #868e96; + opacity: 1; +} + +.form-control::placeholder { + color: #868e96; + opacity: 1; +} + +.form-control:disabled, .form-control[readonly] { + background-color: #e9ecef; + opacity: 1; +} + +select.form-control:not([size]):not([multiple]) { + height: calc(2.25rem + 2px); +} + +select.form-control:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.form-control-file, +.form-control-range { + display: block; +} + +.col-form-label { + padding-top: calc(0.375rem + 1px); + padding-bottom: calc(0.375rem + 1px); + margin-bottom: 0; + line-height: 1.5; +} + +.col-form-label-lg { + padding-top: calc(0.5rem + 1px); + padding-bottom: calc(0.5rem + 1px); + font-size: 1.25rem; + line-height: 1.5; +} + +.col-form-label-sm { + padding-top: calc(0.25rem + 1px); + padding-bottom: calc(0.25rem + 1px); + font-size: 0.875rem; + line-height: 1.5; +} + +.col-form-legend { + padding-top: 0.375rem; + padding-bottom: 0.375rem; + margin-bottom: 0; + font-size: 1rem; +} + +.form-control-plaintext { + display: block; + width: 100%; + padding-top: 0.375rem; + padding-bottom: 0.375rem; + margin-bottom: 0; + line-height: 1.5; + background-color: transparent; + border: solid transparent; + border-width: 1px 0; +} + +.form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control, +.input-group-sm > .form-control-plaintext.input-group-addon, +.input-group-sm > .input-group-btn > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control, +.input-group-lg > .form-control-plaintext.input-group-addon, +.input-group-lg > .input-group-btn > .form-control-plaintext.btn { + padding-right: 0; + padding-left: 0; +} + +.form-control-sm, .input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +select.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]), +.input-group-sm > select.input-group-addon:not([size]):not([multiple]), +.input-group-sm > .input-group-btn > select.btn:not([size]):not([multiple]) { + height: calc(1.8125rem + 2px); +} + +.form-control-lg, .input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]), +.input-group-lg > select.input-group-addon:not([size]):not([multiple]), +.input-group-lg > .input-group-btn > select.btn:not([size]):not([multiple]) { + height: calc(2.875rem + 2px); +} + +.form-group { + margin-bottom: 1rem; +} + +.form-text { + display: block; + margin-top: 0.25rem; +} + +.form-row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -5px; + margin-left: -5px; +} + +.form-row > .col, +.form-row > [class*="col-"] { + padding-right: 5px; + padding-left: 5px; +} + +.form-check { + position: relative; + display: block; + margin-bottom: 0.5rem; +} + +.form-check.disabled .form-check-label { + color: #868e96; +} + +.form-check-label { + padding-left: 1.25rem; + margin-bottom: 0; +} + +.form-check-input { + position: absolute; + margin-top: 0.25rem; + margin-left: -1.25rem; +} + +.form-check-inline { + display: inline-block; + margin-right: 0.75rem; +} + +.form-check-inline .form-check-label { + vertical-align: middle; +} + +.valid-feedback { + display: none; + margin-top: .25rem; + font-size: .875rem; + color: #28a745; +} + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + width: 250px; + padding: .5rem; + margin-top: .1rem; + font-size: .875rem; + line-height: 1; + color: #fff; + background-color: rgba(40, 167, 69, 0.8); + border-radius: .2rem; +} + +.was-validated .form-control:valid, .form-control.is-valid, .was-validated +.custom-select:valid, +.custom-select.is-valid { + border-color: #28a745; +} + +.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated +.custom-select:valid:focus, +.custom-select.is-valid:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.was-validated .form-control:valid ~ .valid-feedback, +.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback, +.form-control.is-valid ~ .valid-tooltip, .was-validated +.custom-select:valid ~ .valid-feedback, +.was-validated +.custom-select:valid ~ .valid-tooltip, +.custom-select.is-valid ~ .valid-feedback, +.custom-select.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .form-check-input:valid + .form-check-label, .form-check-input.is-valid + .form-check-label { + color: #28a745; +} + +.was-validated .custom-control-input:valid ~ .custom-control-indicator, .custom-control-input.is-valid ~ .custom-control-indicator { + background-color: rgba(40, 167, 69, 0.4); +} + +.was-validated .custom-control-input:valid ~ .custom-control-description, .custom-control-input.is-valid ~ .custom-control-description { + color: #28a745; +} + +.was-validated .custom-control-input:valid:focus ~ .custom-control-indicator, .custom-control-input.is-valid:focus ~ .custom-control-indicator { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(40, 167, 69, 0.25); + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.was-validated .custom-file-input:valid ~ .custom-file-control, .custom-file-input.is-valid ~ .custom-file-control { + border-color: #28a745; +} + +.was-validated .custom-file-input:valid ~ .custom-file-control::before, .custom-file-input.is-valid ~ .custom-file-control::before { + border-color: inherit; +} + +.was-validated .custom-file-input:valid:focus ~ .custom-file-control, .custom-file-input.is-valid:focus ~ .custom-file-control { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.invalid-feedback { + display: none; + margin-top: .25rem; + font-size: .875rem; + color: #dc3545; +} + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + width: 250px; + padding: .5rem; + margin-top: .1rem; + font-size: .875rem; + line-height: 1; + color: #fff; + background-color: rgba(220, 53, 69, 0.8); + border-radius: .2rem; +} + +.was-validated .form-control:invalid, .form-control.is-invalid, .was-validated +.custom-select:invalid, +.custom-select.is-invalid { + border-color: #dc3545; +} + +.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated +.custom-select:invalid:focus, +.custom-select.is-invalid:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.was-validated .form-control:invalid ~ .invalid-feedback, +.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, +.form-control.is-invalid ~ .invalid-tooltip, .was-validated +.custom-select:invalid ~ .invalid-feedback, +.was-validated +.custom-select:invalid ~ .invalid-tooltip, +.custom-select.is-invalid ~ .invalid-feedback, +.custom-select.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .form-check-input:invalid + .form-check-label, .form-check-input.is-invalid + .form-check-label { + color: #dc3545; +} + +.was-validated .custom-control-input:invalid ~ .custom-control-indicator, .custom-control-input.is-invalid ~ .custom-control-indicator { + background-color: rgba(220, 53, 69, 0.4); +} + +.was-validated .custom-control-input:invalid ~ .custom-control-description, .custom-control-input.is-invalid ~ .custom-control-description { + color: #dc3545; +} + +.was-validated .custom-control-input:invalid:focus ~ .custom-control-indicator, .custom-control-input.is-invalid:focus ~ .custom-control-indicator { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(220, 53, 69, 0.25); + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.was-validated .custom-file-input:invalid ~ .custom-file-control, .custom-file-input.is-invalid ~ .custom-file-control { + border-color: #dc3545; +} + +.was-validated .custom-file-input:invalid ~ .custom-file-control::before, .custom-file-input.is-invalid ~ .custom-file-control::before { + border-color: inherit; +} + +.was-validated .custom-file-input:invalid:focus ~ .custom-file-control, .custom-file-input.is-invalid:focus ~ .custom-file-control { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.form-inline { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.form-inline .form-check { + width: 100%; +} + +@media (min-width: 576px) { + .form-inline label { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + margin-bottom: 0; + } + .form-inline .form-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-bottom: 0; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-plaintext { + display: inline-block; + } + .form-inline .input-group { + width: auto; + } + .form-inline .form-check { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + width: auto; + margin-top: 0; + margin-bottom: 0; + } + .form-inline .form-check-label { + padding-left: 0; + } + .form-inline .form-check-input { + position: relative; + margin-top: 0; + margin-right: 0.25rem; + margin-left: 0; + } + .form-inline .custom-control { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding-left: 0; + } + .form-inline .custom-control-indicator { + position: static; + display: inline-block; + margin-right: 0.25rem; + vertical-align: text-bottom; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} + +.btn { + display: inline-block; + font-weight: 400; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; +} + +.btn:focus, .btn:hover { + text-decoration: none; +} + +.btn:focus, .btn.focus { + outline: 0; + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.btn.disabled, .btn:disabled { + opacity: .65; +} + +.btn:not([disabled]):not(.disabled):active, .btn:not([disabled]):not(.disabled).active { + background-image: none; +} + +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} + +.btn-primary { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.btn-primary:hover { + color: #fff; + background-color: #0069d9; + border-color: #0062cc; +} + +.btn-primary:focus, .btn-primary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.btn-primary.disabled, .btn-primary:disabled { + background-color: #007bff; + border-color: #007bff; +} + +.btn-primary:not([disabled]):not(.disabled):active, .btn-primary:not([disabled]):not(.disabled).active, +.show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #0062cc; + border-color: #005cbf; + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.btn-secondary { + color: #fff; + background-color: #868e96; + border-color: #868e96; +} + +.btn-secondary:hover { + color: #fff; + background-color: #727b84; + border-color: #6c757d; +} + +.btn-secondary:focus, .btn-secondary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); +} + +.btn-secondary.disabled, .btn-secondary:disabled { + background-color: #868e96; + border-color: #868e96; +} + +.btn-secondary:not([disabled]):not(.disabled):active, .btn-secondary:not([disabled]):not(.disabled).active, +.show > .btn-secondary.dropdown-toggle { + color: #fff; + background-color: #6c757d; + border-color: #666e76; + -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); +} + +.btn-success { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-success:hover { + color: #fff; + background-color: #218838; + border-color: #1e7e34; +} + +.btn-success:focus, .btn-success.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-success.disabled, .btn-success:disabled { + background-color: #28a745; + border-color: #28a745; +} + +.btn-success:not([disabled]):not(.disabled):active, .btn-success:not([disabled]):not(.disabled).active, +.show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #1e7e34; + border-color: #1c7430; + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-info { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-info:hover { + color: #fff; + background-color: #138496; + border-color: #117a8b; +} + +.btn-info:focus, .btn-info.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-info.disabled, .btn-info:disabled { + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-info:not([disabled]):not(.disabled):active, .btn-info:not([disabled]):not(.disabled).active, +.show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #117a8b; + border-color: #10707f; + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-warning { + color: #111; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-warning:hover { + color: #111; + background-color: #e0a800; + border-color: #d39e00; +} + +.btn-warning:focus, .btn-warning.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-warning.disabled, .btn-warning:disabled { + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-warning:not([disabled]):not(.disabled):active, .btn-warning:not([disabled]):not(.disabled).active, +.show > .btn-warning.dropdown-toggle { + color: #111; + background-color: #d39e00; + border-color: #c69500; + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-danger:hover { + color: #fff; + background-color: #c82333; + border-color: #bd2130; +} + +.btn-danger:focus, .btn-danger.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-danger.disabled, .btn-danger:disabled { + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-danger:not([disabled]):not(.disabled):active, .btn-danger:not([disabled]):not(.disabled).active, +.show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #bd2130; + border-color: #b21f2d; + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-light { + color: #111; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:hover { + color: #111; + background-color: #e2e6ea; + border-color: #dae0e5; +} + +.btn-light:focus, .btn-light.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-light.disabled, .btn-light:disabled { + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:not([disabled]):not(.disabled):active, .btn-light:not([disabled]):not(.disabled).active, +.show > .btn-light.dropdown-toggle { + color: #111; + background-color: #dae0e5; + border-color: #d3d9df; + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124; +} + +.btn-dark:focus, .btn-dark.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-dark.disabled, .btn-dark:disabled { + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:not([disabled]):not(.disabled):active, .btn-dark:not([disabled]):not(.disabled).active, +.show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #1d2124; + border-color: #171a1d; + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-outline-primary { + color: #007bff; + background-color: transparent; + background-image: none; + border-color: #007bff; +} + +.btn-outline-primary:hover { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.btn-outline-primary:focus, .btn-outline-primary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #007bff; + background-color: transparent; +} + +.btn-outline-primary:not([disabled]):not(.disabled):active, .btn-outline-primary:not([disabled]):not(.disabled).active, +.show > .btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #007bff; + border-color: #007bff; + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.btn-outline-secondary { + color: #868e96; + background-color: transparent; + background-image: none; + border-color: #868e96; +} + +.btn-outline-secondary:hover { + color: #fff; + background-color: #868e96; + border-color: #868e96; +} + +.btn-outline-secondary:focus, .btn-outline-secondary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); +} + +.btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #868e96; + background-color: transparent; +} + +.btn-outline-secondary:not([disabled]):not(.disabled):active, .btn-outline-secondary:not([disabled]):not(.disabled).active, +.show > .btn-outline-secondary.dropdown-toggle { + color: #fff; + background-color: #868e96; + border-color: #868e96; + -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); +} + +.btn-outline-success { + color: #28a745; + background-color: transparent; + background-image: none; + border-color: #28a745; +} + +.btn-outline-success:hover { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-outline-success:focus, .btn-outline-success.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-outline-success.disabled, .btn-outline-success:disabled { + color: #28a745; + background-color: transparent; +} + +.btn-outline-success:not([disabled]):not(.disabled):active, .btn-outline-success:not([disabled]):not(.disabled).active, +.show > .btn-outline-success.dropdown-toggle { + color: #fff; + background-color: #28a745; + border-color: #28a745; + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-outline-info { + color: #17a2b8; + background-color: transparent; + background-image: none; + border-color: #17a2b8; +} + +.btn-outline-info:hover { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-outline-info:focus, .btn-outline-info.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-outline-info.disabled, .btn-outline-info:disabled { + color: #17a2b8; + background-color: transparent; +} + +.btn-outline-info:not([disabled]):not(.disabled):active, .btn-outline-info:not([disabled]):not(.disabled).active, +.show > .btn-outline-info.dropdown-toggle { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-outline-warning { + color: #ffc107; + background-color: transparent; + background-image: none; + border-color: #ffc107; +} + +.btn-outline-warning:hover { + color: #fff; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-outline-warning:focus, .btn-outline-warning.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #ffc107; + background-color: transparent; +} + +.btn-outline-warning:not([disabled]):not(.disabled):active, .btn-outline-warning:not([disabled]):not(.disabled).active, +.show > .btn-outline-warning.dropdown-toggle { + color: #fff; + background-color: #ffc107; + border-color: #ffc107; + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-outline-danger { + color: #dc3545; + background-color: transparent; + background-image: none; + border-color: #dc3545; +} + +.btn-outline-danger:hover { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-outline-danger:focus, .btn-outline-danger.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #dc3545; + background-color: transparent; +} + +.btn-outline-danger:not([disabled]):not(.disabled):active, .btn-outline-danger:not([disabled]):not(.disabled).active, +.show > .btn-outline-danger.dropdown-toggle { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-outline-light { + color: #f8f9fa; + background-color: transparent; + background-image: none; + border-color: #f8f9fa; +} + +.btn-outline-light:hover { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-outline-light:focus, .btn-outline-light.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-outline-light.disabled, .btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent; +} + +.btn-outline-light:not([disabled]):not(.disabled):active, .btn-outline-light:not([disabled]):not(.disabled).active, +.show > .btn-outline-light.dropdown-toggle { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-outline-dark { + color: #343a40; + background-color: transparent; + background-image: none; + border-color: #343a40; +} + +.btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-outline-dark:focus, .btn-outline-dark.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-outline-dark.disabled, .btn-outline-dark:disabled { + color: #343a40; + background-color: transparent; +} + +.btn-outline-dark:not([disabled]):not(.disabled):active, .btn-outline-dark:not([disabled]):not(.disabled).active, +.show > .btn-outline-dark.dropdown-toggle { + color: #fff; + background-color: #343a40; + border-color: #343a40; + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-link { + font-weight: 400; + color: #007bff; + background-color: transparent; +} + +.btn-link:hover { + color: #0056b3; + text-decoration: underline; + background-color: transparent; + border-color: transparent; +} + +.btn-link:focus, .btn-link.focus { + border-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-link:disabled, .btn-link.disabled { + color: #868e96; +} + +.btn-lg, .btn-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +.btn-block { + display: block; + width: 100%; +} + +.btn-block + .btn-block { + margin-top: 0.5rem; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} + +.fade.show { + opacity: 1; +} + +.collapse { + display: none; +} + +.collapse.show { + display: block; +} + +tr.collapse.show { + display: table-row; +} + +tbody.collapse.show { + display: table-row-group; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + transition: height 0.35s ease; +} + +.dropup, +.dropdown { + position: relative; +} + +.dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} + +.dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + font-size: 1rem; + color: #212529; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; +} + +.dropup .dropdown-menu { + margin-top: 0; + margin-bottom: 0.125rem; +} + +.dropup .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; +} + +.dropup .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropdown-divider { + height: 0; + margin: 0.5rem 0; + overflow: hidden; + border-top: 1px solid #e9ecef; +} + +.dropdown-item { + display: block; + width: 100%; + padding: 0.25rem 1.5rem; + clear: both; + font-weight: 400; + color: #212529; + text-align: inherit; + white-space: nowrap; + background: none; + border: 0; +} + +.dropdown-item:focus, .dropdown-item:hover { + color: #16181b; + text-decoration: none; + background-color: #f8f9fa; +} + +.dropdown-item.active, .dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #007bff; +} + +.dropdown-item.disabled, .dropdown-item:disabled { + color: #868e96; + background-color: transparent; +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.875rem; + color: #868e96; + white-space: nowrap; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + vertical-align: middle; +} + +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; +} + +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover { + z-index: 1; +} + +.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, +.btn-group-vertical > .btn:focus, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn.active { + z-index: 1; +} + +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group, +.btn-group-vertical .btn + .btn, +.btn-group-vertical .btn + .btn-group, +.btn-group-vertical .btn-group + .btn, +.btn-group-vertical .btn-group + .btn-group { + margin-left: -1px; +} + +.btn-toolbar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.btn-toolbar .input-group { + width: auto; +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +.btn-group > .btn:first-child { + margin-left: 0; +} + +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group > .btn-group { + float: left; +} + +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.btn + .dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; +} + +.btn + .dropdown-toggle-split::after { + margin-left: 0; +} + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} + +.btn-group-vertical { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + +.btn-group-vertical .btn, +.btn-group-vertical .btn-group { + width: 100%; +} + +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} + +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +[data-toggle="buttons"] > .btn, +[data-toggle="buttons"] > .btn-group > .btn { + margin-bottom: 0; +} + +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} + +.input-group { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + width: 100%; +} + +.input-group .form-control { + position: relative; + z-index: 1; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + width: 1%; + margin-bottom: 0; +} + +.input-group .form-control:focus, .input-group .form-control:active, .input-group .form-control:hover { + z-index: 2; +} + +.input-group-addon, +.input-group-btn, +.input-group .form-control, +.input-group .custom-select, +.input-group .custom-file { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child), +.input-group .custom-select:not(:first-child):not(:last-child), +.input-group .custom-file:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.input-group .custom-file { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.input-group .custom-select, +.input-group .custom-file { + width: 100%; +} + +.input-group-addon, +.input-group-btn { + white-space: nowrap; +} + +.input-group-addon { + padding: 0.375rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} + +.input-group-addon.form-control-sm, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .input-group-addon.btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: 0.2rem; +} + +.input-group-addon.form-control-lg, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .input-group-addon.btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + border-radius: 0.3rem; +} + +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} + +.input-group .form-control:not(:last-child), +.input-group .custom-select:not(:last-child), +.input-group .custom-file:not(:last-child) .custom-file-control::before, +.input-group-addon:not(:last-child), +.input-group-btn:not(:last-child) > .btn, +.input-group-btn:not(:last-child) > .btn-group > .btn, +.input-group-btn:not(:last-child) > .dropdown-toggle, +.input-group-btn:not(:first-child) > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:not(:first-child) > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group-addon:not(:last-child) { + border-right: 0; +} + +.input-group .form-control:not(:first-child), +.input-group .custom-select:not(:first-child), +.input-group .custom-file:not(:first-child) .custom-file-control, +.input-group-addon:not(:first-child), +.input-group-btn:not(:first-child) > .btn, +.input-group-btn:not(:first-child) > .btn-group > .btn, +.input-group-btn:not(:first-child) > .dropdown-toggle, +.input-group-btn:not(:last-child) > .btn:not(:first-child), +.input-group-btn:not(:last-child) > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.form-control + .input-group-addon:not(:first-child), +.custom-select + .input-group-addon:not(:first-child), +.custom-file + .input-group-addon:not(:first-child) { + border-left: 0; +} + +.input-group-btn { + position: relative; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + font-size: 0; + white-space: nowrap; +} + +.input-group-btn > .btn { + position: relative; +} + +.input-group-btn > .btn + .btn { + margin-left: -1px; +} + +.input-group-btn > .btn:focus, .input-group-btn > .btn:active, .input-group-btn > .btn:hover { + z-index: 2; +} + +.input-group-btn:first-child > .btn + .btn { + margin-left: 0; +} + +.input-group-btn:not(:last-child) > .btn, +.input-group-btn:not(:last-child) > .btn-group { + margin-right: -1px; +} + +.input-group-btn:not(:first-child) > .btn, +.input-group-btn:not(:first-child) > .btn-group { + z-index: 1; + margin-left: 0; +} + +.input-group-btn:not(:first-child) > .btn:first-child, +.input-group-btn:not(:first-child) > .btn-group:first-child { + margin-left: -1px; +} + +.input-group-btn:not(:first-child) > .btn:focus, .input-group-btn:not(:first-child) > .btn:active, .input-group-btn:not(:first-child) > .btn:hover, +.input-group-btn:not(:first-child) > .btn-group:focus, +.input-group-btn:not(:first-child) > .btn-group:active, +.input-group-btn:not(:first-child) > .btn-group:hover { + z-index: 2; +} + +.custom-control { + position: relative; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + min-height: 1.5rem; + padding-left: 1.5rem; + margin-right: 1rem; +} + +.custom-control-input { + position: absolute; + z-index: -1; + opacity: 0; +} + +.custom-control-input:checked ~ .custom-control-indicator { + color: #fff; + background-color: #007bff; +} + +.custom-control-input:focus ~ .custom-control-indicator { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.custom-control-input:active ~ .custom-control-indicator { + color: #fff; + background-color: #b3d7ff; +} + +.custom-control-input:disabled ~ .custom-control-indicator { + background-color: #e9ecef; +} + +.custom-control-input:disabled ~ .custom-control-description { + color: #868e96; +} + +.custom-control-indicator { + position: absolute; + top: 0.25rem; + left: 0; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: #ddd; + background-repeat: no-repeat; + background-position: center center; + background-size: 50% 50%; +} + +.custom-checkbox .custom-control-indicator { + border-radius: 0.25rem; +} + +.custom-checkbox .custom-control-input:checked ~ .custom-control-indicator { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); +} + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-indicator { + background-color: #007bff; + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); +} + +.custom-radio .custom-control-indicator { + border-radius: 50%; +} + +.custom-radio .custom-control-input:checked ~ .custom-control-indicator { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); +} + +.custom-controls-stacked { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.custom-controls-stacked .custom-control { + margin-bottom: 0.25rem; +} + +.custom-controls-stacked .custom-control + .custom-control { + margin-left: 0; +} + +.custom-select { + display: inline-block; + max-width: 100%; + height: calc(2.25rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + vertical-align: middle; + background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23333' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center; + background-size: 8px 10px; + border: 1px solid #ced4da; + border-radius: 0.25rem; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.custom-select:focus { + border-color: #80bdff; + outline: none; +} + +.custom-select:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.custom-select[multiple] { + height: auto; + background-image: none; +} + +.custom-select:disabled { + color: #868e96; + background-color: #e9ecef; +} + +.custom-select::-ms-expand { + opacity: 0; +} + +.custom-select-sm { + height: calc(1.8125rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 75%; +} + +.custom-file { + position: relative; + display: inline-block; + max-width: 100%; + height: calc(2.25rem + 2px); + margin-bottom: 0; +} + +.custom-file-input { + min-width: 14rem; + max-width: 100%; + height: calc(2.25rem + 2px); + margin: 0; + opacity: 0; +} + +.custom-file-input:focus ~ .custom-file-control { + -webkit-box-shadow: 0 0 0 0.075rem #fff, 0 0 0 0.2rem #007bff; + box-shadow: 0 0 0 0.075rem #fff, 0 0 0 0.2rem #007bff; +} + +.custom-file-control { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 5; + height: calc(2.25rem + 2px); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} + +.custom-file-control:lang(en):empty::after { + content: "Choose file..."; +} + +.custom-file-control::before { + position: absolute; + top: -1px; + right: -1px; + bottom: -1px; + z-index: 6; + display: block; + height: calc(2.25rem + 2px); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: 0 0.25rem 0.25rem 0; +} + +.custom-file-control:lang(en)::before { + content: "Browse"; +} + +.nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: 0.5rem 1rem; +} + +.nav-link:focus, .nav-link:hover { + text-decoration: none; +} + +.nav-link.disabled { + color: #868e96; +} + +.nav-tabs { + border-bottom: 1px solid #ddd; +} + +.nav-tabs .nav-item { + margin-bottom: -1px; +} + +.nav-tabs .nav-link { + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover { + border-color: #e9ecef #e9ecef #ddd; +} + +.nav-tabs .nav-link.disabled { + color: #868e96; + background-color: transparent; + border-color: transparent; +} + +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; + border-color: #ddd #ddd #fff; +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav-pills .nav-link { + border-radius: 0.25rem; +} + +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #007bff; +} + +.nav-fill .nav-item { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + text-align: center; +} + +.nav-justified .nav-item { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + text-align: center; +} + +.tab-content > .tab-pane { + display: none; +} + +.tab-content > .active { + display: block; +} + +.navbar { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 0.5rem 1rem; +} + +.navbar > .container, +.navbar > .container-fluid { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.navbar-brand { + display: inline-block; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap; +} + +.navbar-brand:focus, .navbar-brand:hover { + text-decoration: none; +} + +.navbar-nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; +} + +.navbar-nav .dropdown-menu { + position: static; + float: none; +} + +.navbar-text { + display: inline-block; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.navbar-collapse { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + background: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.navbar-toggler:focus, .navbar-toggler:hover { + text-decoration: none; +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100%; +} + +@media (max-width: 575px) { + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 576px) { + .navbar-expand-sm { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-sm .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } + .navbar-expand-sm .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} + +@media (max-width: 767px) { + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 768px) { + .navbar-expand-md { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-md .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-md .navbar-toggler { + display: none; + } + .navbar-expand-md .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} + +@media (max-width: 991px) { + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 992px) { + .navbar-expand-lg { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-lg .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } + .navbar-expand-lg .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} + +@media (max-width: 1199px) { + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 1200px) { + .navbar-expand-xl { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-xl .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } + .navbar-expand-xl .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} + +.navbar-expand { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.navbar-expand > .container, +.navbar-expand > .container-fluid { + padding-right: 0; + padding-left: 0; +} + +.navbar-expand .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; +} + +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} + +.navbar-expand .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; +} + +.navbar-expand .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; +} + +.navbar-expand > .container, +.navbar-expand > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; +} + +.navbar-expand .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; +} + +.navbar-expand .navbar-toggler { + display: none; +} + +.navbar-expand .dropup .dropdown-menu { + top: auto; + bottom: 100%; +} + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover { + color: rgba(0, 0, 0, 0.7); +} + +.navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); +} + +.navbar-light .navbar-nav .show > .nav-link, +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.show, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.1); +} + +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} + +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-light .navbar-text a { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-text a:focus, .navbar-light .navbar-text a:hover { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-dark .navbar-brand { + color: #fff; +} + +.navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover { + color: #fff; +} + +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.5); +} + +.navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover { + color: rgba(255, 255, 255, 0.75); +} + +.navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); +} + +.navbar-dark .navbar-nav .show > .nav-link, +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .nav-link.active { + color: #fff; +} + +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.5); + border-color: rgba(255, 255, 255, 0.1); +} + +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} + +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.5); +} + +.navbar-dark .navbar-text a { + color: #fff; +} + +.navbar-dark .navbar-text a:focus, .navbar-dark .navbar-text a:hover { + color: #fff; +} + +.card { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; +} + +.card > hr { + margin-right: 0; + margin-left: 0; +} + +.card > .list-group:first-child .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.card > .list-group:last-child .list-group-item:last-child { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.card-body { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1.25rem; +} + +.card-title { + margin-bottom: 0.75rem; +} + +.card-subtitle { + margin-top: -0.375rem; + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link:hover { + text-decoration: none; +} + +.card-link + .card-link { + margin-left: 1.25rem; +} + +.card-header { + padding: 0.75rem 1.25rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} + +.card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; +} + +.card-header + .list-group .list-group-item:first-child { + border-top: 0; +} + +.card-footer { + padding: 0.75rem 1.25rem; + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 0, 0, 0.125); +} + +.card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); +} + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -0.75rem; + margin-left: -0.625rem; + border-bottom: 0; +} + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; +} + +.card-img { + width: 100%; + border-radius: calc(0.25rem - 1px); +} + +.card-img-top { + width: 100%; + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); +} + +.card-img-bottom { + width: 100%; + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); +} + +.card-deck { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.card-deck .card { + margin-bottom: 15px; +} + +@media (min-width: 576px) { + .card-deck { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + margin-right: -15px; + margin-left: -15px; + } + .card-deck .card { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + margin-right: 15px; + margin-bottom: 0; + margin-left: 15px; + } +} + +.card-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.card-group .card { + margin-bottom: 15px; +} + +@media (min-width: 576px) { + .card-group { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + } + .card-group .card { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group .card:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group .card:first-child .card-img-top, + .card-group .card:first-child .card-header { + border-top-right-radius: 0; + } + .card-group .card:first-child .card-img-bottom, + .card-group .card:first-child .card-footer { + border-bottom-right-radius: 0; + } + .card-group .card:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group .card:last-child .card-img-top, + .card-group .card:last-child .card-header { + border-top-left-radius: 0; + } + .card-group .card:last-child .card-img-bottom, + .card-group .card:last-child .card-footer { + border-bottom-left-radius: 0; + } + .card-group .card:only-child { + border-radius: 0.25rem; + } + .card-group .card:only-child .card-img-top, + .card-group .card:only-child .card-header { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + } + .card-group .card:only-child .card-img-bottom, + .card-group .card:only-child .card-footer { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + .card-group .card:not(:first-child):not(:last-child):not(:only-child) { + border-radius: 0; + } + .card-group .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top, + .card-group .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom, + .card-group .card:not(:first-child):not(:last-child):not(:only-child) .card-header, + .card-group .card:not(:first-child):not(:last-child):not(:only-child) .card-footer { + border-radius: 0; + } +} + +.card-columns .card { + margin-bottom: 0.75rem; +} + +@media (min-width: 576px) { + .card-columns { + -webkit-column-count: 3; + column-count: 3; + -webkit-column-gap: 1.25rem; + column-gap: 1.25rem; + } + .card-columns .card { + display: inline-block; + width: 100%; + } +} + +.breadcrumb { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding: 0.75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: 0.5rem; + padding-left: 0.5rem; + color: #868e96; + content: "/"; +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline; +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none; +} + +.breadcrumb-item.active { + color: #868e96; +} + +.pagination { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding-left: 0; + list-style: none; + border-radius: 0.25rem; +} + +.page-link { + position: relative; + display: block; + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #007bff; + background-color: #fff; + border: 1px solid #ddd; +} + +.page-link:focus, .page-link:hover { + color: #0056b3; + text-decoration: none; + background-color: #e9ecef; + border-color: #ddd; +} + +.page-item:first-child .page-link { + margin-left: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} + +.page-item.active .page-link { + z-index: 1; + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.page-item.disabled .page-link { + color: #868e96; + pointer-events: none; + background-color: #fff; + border-color: #ddd; +} + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5; +} + +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; +} + +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.3rem; + border-bottom-right-radius: 0.3rem; +} + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; +} + +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; +} + +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; +} + +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; +} + +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -1px; +} + +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; +} + +.badge-primary { + color: #fff; + background-color: #007bff; +} + +.badge-primary[href]:focus, .badge-primary[href]:hover { + color: #fff; + text-decoration: none; + background-color: #0062cc; +} + +.badge-secondary { + color: #fff; + background-color: #868e96; +} + +.badge-secondary[href]:focus, .badge-secondary[href]:hover { + color: #fff; + text-decoration: none; + background-color: #6c757d; +} + +.badge-success { + color: #fff; + background-color: #28a745; +} + +.badge-success[href]:focus, .badge-success[href]:hover { + color: #fff; + text-decoration: none; + background-color: #1e7e34; +} + +.badge-info { + color: #fff; + background-color: #17a2b8; +} + +.badge-info[href]:focus, .badge-info[href]:hover { + color: #fff; + text-decoration: none; + background-color: #117a8b; +} + +.badge-warning { + color: #111; + background-color: #ffc107; +} + +.badge-warning[href]:focus, .badge-warning[href]:hover { + color: #111; + text-decoration: none; + background-color: #d39e00; +} + +.badge-danger { + color: #fff; + background-color: #dc3545; +} + +.badge-danger[href]:focus, .badge-danger[href]:hover { + color: #fff; + text-decoration: none; + background-color: #bd2130; +} + +.badge-light { + color: #111; + background-color: #f8f9fa; +} + +.badge-light[href]:focus, .badge-light[href]:hover { + color: #111; + text-decoration: none; + background-color: #dae0e5; +} + +.badge-dark { + color: #fff; + background-color: #343a40; +} + +.badge-dark[href]:focus, .badge-dark[href]:hover { + color: #fff; + text-decoration: none; + background-color: #1d2124; +} + +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #e9ecef; + border-radius: 0.3rem; +} + +@media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem; + } +} + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0; +} + +.alert { + position: relative; + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.alert-heading { + color: inherit; +} + +.alert-link { + font-weight: 700; +} + +.alert-dismissible { + padding-right: 4rem; +} + +.alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + padding: 0.75rem 1.25rem; + color: inherit; +} + +.alert-primary { + color: #004085; + background-color: #cce5ff; + border-color: #b8daff; +} + +.alert-primary hr { + border-top-color: #9fcdff; +} + +.alert-primary .alert-link { + color: #002752; +} + +.alert-secondary { + color: #464a4e; + background-color: #e7e8ea; + border-color: #dddfe2; +} + +.alert-secondary hr { + border-top-color: #cfd2d6; +} + +.alert-secondary .alert-link { + color: #2e3133; +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} + +.alert-success hr { + border-top-color: #b1dfbb; +} + +.alert-success .alert-link { + color: #0b2e13; +} + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; +} + +.alert-info hr { + border-top-color: #abdde5; +} + +.alert-info .alert-link { + color: #062c33; +} + +.alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeeba; +} + +.alert-warning hr { + border-top-color: #ffe8a1; +} + +.alert-warning .alert-link { + color: #533f03; +} + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} + +.alert-danger hr { + border-top-color: #f1b0b7; +} + +.alert-danger .alert-link { + color: #491217; +} + +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe; +} + +.alert-light hr { + border-top-color: #ececf6; +} + +.alert-light .alert-link { + color: #686868; +} + +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca; +} + +.alert-dark hr { + border-top-color: #b9bbbe; +} + +.alert-dark .alert-link { + color: #040505; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} + +.progress { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + height: 1rem; + overflow: hidden; + font-size: 0.75rem; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.progress-bar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + color: #fff; + background-color: #007bff; +} + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 1rem 1rem; +} + +.progress-bar-animated { + -webkit-animation: progress-bar-stripes 1s linear infinite; + animation: progress-bar-stripes 1s linear infinite; +} + +.media { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; +} + +.media-body { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.list-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; +} + +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit; +} + +.list-group-item-action:focus, .list-group-item-action:hover { + color: #495057; + text-decoration: none; + background-color: #f8f9fa; +} + +.list-group-item-action:active { + color: #212529; + background-color: #e9ecef; +} + +.list-group-item { + position: relative; + display: block; + padding: 0.75rem 1.25rem; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.125); +} + +.list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.list-group-item:focus, .list-group-item:hover { + text-decoration: none; +} + +.list-group-item.disabled, .list-group-item:disabled { + color: #868e96; + background-color: #fff; +} + +.list-group-item.active { + z-index: 1; + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.list-group-flush .list-group-item { + border-right: 0; + border-left: 0; + border-radius: 0; +} + +.list-group-flush:first-child .list-group-item:first-child { + border-top: 0; +} + +.list-group-flush:last-child .list-group-item:last-child { + border-bottom: 0; +} + +.list-group-item-primary { + color: #004085; + background-color: #b8daff; +} + +a.list-group-item-primary, +button.list-group-item-primary { + color: #004085; +} + +a.list-group-item-primary:focus, a.list-group-item-primary:hover, +button.list-group-item-primary:focus, +button.list-group-item-primary:hover { + color: #004085; + background-color: #9fcdff; +} + +a.list-group-item-primary.active, +button.list-group-item-primary.active { + color: #fff; + background-color: #004085; + border-color: #004085; +} + +.list-group-item-secondary { + color: #464a4e; + background-color: #dddfe2; +} + +a.list-group-item-secondary, +button.list-group-item-secondary { + color: #464a4e; +} + +a.list-group-item-secondary:focus, a.list-group-item-secondary:hover, +button.list-group-item-secondary:focus, +button.list-group-item-secondary:hover { + color: #464a4e; + background-color: #cfd2d6; +} + +a.list-group-item-secondary.active, +button.list-group-item-secondary.active { + color: #fff; + background-color: #464a4e; + border-color: #464a4e; +} + +.list-group-item-success { + color: #155724; + background-color: #c3e6cb; +} + +a.list-group-item-success, +button.list-group-item-success { + color: #155724; +} + +a.list-group-item-success:focus, a.list-group-item-success:hover, +button.list-group-item-success:focus, +button.list-group-item-success:hover { + color: #155724; + background-color: #b1dfbb; +} + +a.list-group-item-success.active, +button.list-group-item-success.active { + color: #fff; + background-color: #155724; + border-color: #155724; +} + +.list-group-item-info { + color: #0c5460; + background-color: #bee5eb; +} + +a.list-group-item-info, +button.list-group-item-info { + color: #0c5460; +} + +a.list-group-item-info:focus, a.list-group-item-info:hover, +button.list-group-item-info:focus, +button.list-group-item-info:hover { + color: #0c5460; + background-color: #abdde5; +} + +a.list-group-item-info.active, +button.list-group-item-info.active { + color: #fff; + background-color: #0c5460; + border-color: #0c5460; +} + +.list-group-item-warning { + color: #856404; + background-color: #ffeeba; +} + +a.list-group-item-warning, +button.list-group-item-warning { + color: #856404; +} + +a.list-group-item-warning:focus, a.list-group-item-warning:hover, +button.list-group-item-warning:focus, +button.list-group-item-warning:hover { + color: #856404; + background-color: #ffe8a1; +} + +a.list-group-item-warning.active, +button.list-group-item-warning.active { + color: #fff; + background-color: #856404; + border-color: #856404; +} + +.list-group-item-danger { + color: #721c24; + background-color: #f5c6cb; +} + +a.list-group-item-danger, +button.list-group-item-danger { + color: #721c24; +} + +a.list-group-item-danger:focus, a.list-group-item-danger:hover, +button.list-group-item-danger:focus, +button.list-group-item-danger:hover { + color: #721c24; + background-color: #f1b0b7; +} + +a.list-group-item-danger.active, +button.list-group-item-danger.active { + color: #fff; + background-color: #721c24; + border-color: #721c24; +} + +.list-group-item-light { + color: #818182; + background-color: #fdfdfe; +} + +a.list-group-item-light, +button.list-group-item-light { + color: #818182; +} + +a.list-group-item-light:focus, a.list-group-item-light:hover, +button.list-group-item-light:focus, +button.list-group-item-light:hover { + color: #818182; + background-color: #ececf6; +} + +a.list-group-item-light.active, +button.list-group-item-light.active { + color: #fff; + background-color: #818182; + border-color: #818182; +} + +.list-group-item-dark { + color: #1b1e21; + background-color: #c6c8ca; +} + +a.list-group-item-dark, +button.list-group-item-dark { + color: #1b1e21; +} + +a.list-group-item-dark:focus, a.list-group-item-dark:hover, +button.list-group-item-dark:focus, +button.list-group-item-dark:hover { + color: #1b1e21; + background-color: #b9bbbe; +} + +a.list-group-item-dark.active, +button.list-group-item-dark.active { + color: #fff; + background-color: #1b1e21; + border-color: #1b1e21; +} + +.close { + float: right; + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: .5; +} + +.close:focus, .close:hover { + color: #000; + text-decoration: none; + opacity: .75; +} + +button.close { + padding: 0; + background: transparent; + border: 0; + -webkit-appearance: none; +} + +.modal-open { + overflow: hidden; +} + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + outline: 0; +} + +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +.modal-dialog { + position: relative; + width: auto; + margin: 10px; + pointer-events: none; +} + +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform 0.3s ease-out; + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; + -webkit-transform: translate(0, -25%); + transform: translate(0, -25%); +} + +.modal.show .modal-dialog { + -webkit-transform: translate(0, 0); + transform: translate(0, 0); +} + +.modal-content { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + pointer-events: auto; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + outline: 0; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} + +.modal-backdrop.fade { + opacity: 0; +} + +.modal-backdrop.show { + opacity: 0.5; +} + +.modal-header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 15px; + border-bottom: 1px solid #e9ecef; + border-top-left-radius: 0.3rem; + border-top-right-radius: 0.3rem; +} + +.modal-header .close { + padding: 15px; + margin: -15px -15px -15px auto; +} + +.modal-title { + margin-bottom: 0; + line-height: 1.5; +} + +.modal-body { + position: relative; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 15px; +} + +.modal-footer { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + padding: 15px; + border-top: 1px solid #e9ecef; +} + +.modal-footer > :not(:first-child) { + margin-left: .25rem; +} + +.modal-footer > :not(:last-child) { + margin-right: .25rem; +} + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 30px auto; + } + .modal-sm { + max-width: 300px; + } +} + +@media (min-width: 992px) { + .modal-lg { + max-width: 800px; + } +} + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + opacity: 0; +} + +.tooltip.show { + opacity: 0.9; +} + +.tooltip .arrow { + position: absolute; + display: block; + width: 5px; + height: 5px; +} + +.tooltip .arrow::before { + position: absolute; + border-color: transparent; + border-style: solid; +} + +.tooltip.bs-tooltip-top, .tooltip.bs-tooltip-auto[x-placement^="top"] { + padding: 5px 0; +} + +.tooltip.bs-tooltip-top .arrow, .tooltip.bs-tooltip-auto[x-placement^="top"] .arrow { + bottom: 0; +} + +.tooltip.bs-tooltip-top .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="top"] .arrow::before { + margin-left: -3px; + content: ""; + border-width: 5px 5px 0; + border-top-color: #000; +} + +.tooltip.bs-tooltip-right, .tooltip.bs-tooltip-auto[x-placement^="right"] { + padding: 0 5px; +} + +.tooltip.bs-tooltip-right .arrow, .tooltip.bs-tooltip-auto[x-placement^="right"] .arrow { + left: 0; +} + +.tooltip.bs-tooltip-right .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="right"] .arrow::before { + margin-top: -3px; + content: ""; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} + +.tooltip.bs-tooltip-bottom, .tooltip.bs-tooltip-auto[x-placement^="bottom"] { + padding: 5px 0; +} + +.tooltip.bs-tooltip-bottom .arrow, .tooltip.bs-tooltip-auto[x-placement^="bottom"] .arrow { + top: 0; +} + +.tooltip.bs-tooltip-bottom .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="bottom"] .arrow::before { + margin-left: -3px; + content: ""; + border-width: 0 5px 5px; + border-bottom-color: #000; +} + +.tooltip.bs-tooltip-left, .tooltip.bs-tooltip-auto[x-placement^="left"] { + padding: 0 5px; +} + +.tooltip.bs-tooltip-left .arrow, .tooltip.bs-tooltip-auto[x-placement^="left"] .arrow { + right: 0; +} + +.tooltip.bs-tooltip-left .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="left"] .arrow::before { + right: 0; + margin-top: -3px; + content: ""; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} + +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; +} + +.popover .arrow { + position: absolute; + display: block; + width: 0.8rem; + height: 0.4rem; +} + +.popover .arrow::before, +.popover .arrow::after { + position: absolute; + display: block; + border-color: transparent; + border-style: solid; +} + +.popover .arrow::before { + content: ""; + border-width: 0.8rem; +} + +.popover .arrow::after { + content: ""; + border-width: 0.8rem; +} + +.popover.bs-popover-top, .popover.bs-popover-auto[x-placement^="top"] { + margin-bottom: 0.8rem; +} + +.popover.bs-popover-top .arrow, .popover.bs-popover-auto[x-placement^="top"] .arrow { + bottom: 0; +} + +.popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^="top"] .arrow::before, +.popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^="top"] .arrow::after { + border-bottom-width: 0; +} + +.popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^="top"] .arrow::before { + bottom: -0.8rem; + margin-left: -0.8rem; + border-top-color: rgba(0, 0, 0, 0.25); +} + +.popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^="top"] .arrow::after { + bottom: calc((0.8rem - 1px) * -1); + margin-left: -0.8rem; + border-top-color: #fff; +} + +.popover.bs-popover-right, .popover.bs-popover-auto[x-placement^="right"] { + margin-left: 0.8rem; +} + +.popover.bs-popover-right .arrow, .popover.bs-popover-auto[x-placement^="right"] .arrow { + left: 0; +} + +.popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^="right"] .arrow::before, +.popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^="right"] .arrow::after { + margin-top: -0.8rem; + border-left-width: 0; +} + +.popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^="right"] .arrow::before { + left: -0.8rem; + border-right-color: rgba(0, 0, 0, 0.25); +} + +.popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^="right"] .arrow::after { + left: calc((0.8rem - 1px) * -1); + border-right-color: #fff; +} + +.popover.bs-popover-bottom, .popover.bs-popover-auto[x-placement^="bottom"] { + margin-top: 0.8rem; +} + +.popover.bs-popover-bottom .arrow, .popover.bs-popover-auto[x-placement^="bottom"] .arrow { + top: 0; +} + +.popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::before, +.popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::after { + margin-left: -0.8rem; + border-top-width: 0; +} + +.popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::before { + top: -0.8rem; + border-bottom-color: rgba(0, 0, 0, 0.25); +} + +.popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::after { + top: calc((0.8rem - 1px) * -1); + border-bottom-color: #fff; +} + +.popover.bs-popover-bottom .popover-header::before, .popover.bs-popover-auto[x-placement^="bottom"] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 20px; + margin-left: -10px; + content: ""; + border-bottom: 1px solid #f7f7f7; +} + +.popover.bs-popover-left, .popover.bs-popover-auto[x-placement^="left"] { + margin-right: 0.8rem; +} + +.popover.bs-popover-left .arrow, .popover.bs-popover-auto[x-placement^="left"] .arrow { + right: 0; +} + +.popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^="left"] .arrow::before, +.popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^="left"] .arrow::after { + margin-top: -0.8rem; + border-right-width: 0; +} + +.popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^="left"] .arrow::before { + right: -0.8rem; + border-left-color: rgba(0, 0, 0, 0.25); +} + +.popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^="left"] .arrow::after { + right: calc((0.8rem - 1px) * -1); + border-left-color: #fff; +} + +.popover-header { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + color: inherit; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); +} + +.popover-header:empty { + display: none; +} + +.popover-body { + padding: 0.5rem 0.75rem; + color: #212529; +} + +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-item { + position: relative; + display: none; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + width: 100%; + -webkit-transition: -webkit-transform 0.6s ease; + transition: -webkit-transform 0.6s ease; + transition: transform 0.6s ease; + transition: transform 0.6s ease, -webkit-transform 0.6s ease; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next, +.carousel-item-prev { + position: absolute; + top: 0; +} + +.carousel-item-next.carousel-item-left, +.carousel-item-prev.carousel-item-right { + -webkit-transform: translateX(0); + transform: translateX(0); +} + +@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { + .carousel-item-next.carousel-item-left, + .carousel-item-prev.carousel-item-right { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.carousel-item-next, +.active.carousel-item-right { + -webkit-transform: translateX(100%); + transform: translateX(100%); +} + +@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { + .carousel-item-next, + .active.carousel-item-right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.carousel-item-prev, +.active.carousel-item-left { + -webkit-transform: translateX(-100%); + transform: translateX(-100%); +} + +@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { + .carousel-item-prev, + .active.carousel-item-left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + width: 15%; + color: #fff; + text-align: center; + opacity: 0.5; +} + +.carousel-control-prev:focus, .carousel-control-prev:hover, +.carousel-control-next:focus, +.carousel-control-next:hover { + color: #fff; + text-decoration: none; + outline: 0; + opacity: .9; +} + +.carousel-control-prev { + left: 0; +} + +.carousel-control-next { + right: 0; +} + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 20px; + height: 20px; + background: transparent no-repeat center center; + background-size: 100% 100%; +} + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"); +} + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"); +} + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 10px; + left: 0; + z-index: 15; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none; +} + +.carousel-indicators li { + position: relative; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + background-color: rgba(255, 255, 255, 0.5); +} + +.carousel-indicators li::before { + position: absolute; + top: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; +} + +.carousel-indicators li::after { + position: absolute; + bottom: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; +} + +.carousel-indicators .active { + background-color: #fff; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; +} + +.align-baseline { + vertical-align: baseline !important; +} + +.align-top { + vertical-align: top !important; +} + +.align-middle { + vertical-align: middle !important; +} + +.align-bottom { + vertical-align: bottom !important; +} + +.align-text-bottom { + vertical-align: text-bottom !important; +} + +.align-text-top { + vertical-align: text-top !important; +} + +.bg-primary { + background-color: #007bff !important; +} + +a.bg-primary:focus, a.bg-primary:hover { + background-color: #0062cc !important; +} + +.bg-secondary { + background-color: #868e96 !important; +} + +a.bg-secondary:focus, a.bg-secondary:hover { + background-color: #6c757d !important; +} + +.bg-success { + background-color: #28a745 !important; +} + +a.bg-success:focus, a.bg-success:hover { + background-color: #1e7e34 !important; +} + +.bg-info { + background-color: #17a2b8 !important; +} + +a.bg-info:focus, a.bg-info:hover { + background-color: #117a8b !important; +} + +.bg-warning { + background-color: #ffc107 !important; +} + +a.bg-warning:focus, a.bg-warning:hover { + background-color: #d39e00 !important; +} + +.bg-danger { + background-color: #dc3545 !important; +} + +a.bg-danger:focus, a.bg-danger:hover { + background-color: #bd2130 !important; +} + +.bg-light { + background-color: #f8f9fa !important; +} + +a.bg-light:focus, a.bg-light:hover { + background-color: #dae0e5 !important; +} + +.bg-dark { + background-color: #343a40 !important; +} + +a.bg-dark:focus, a.bg-dark:hover { + background-color: #1d2124 !important; +} + +.bg-white { + background-color: #fff !important; +} + +.bg-transparent { + background-color: transparent !important; +} + +.border { + border: 1px solid #e9ecef !important; +} + +.border-0 { + border: 0 !important; +} + +.border-top-0 { + border-top: 0 !important; +} + +.border-right-0 { + border-right: 0 !important; +} + +.border-bottom-0 { + border-bottom: 0 !important; +} + +.border-left-0 { + border-left: 0 !important; +} + +.border-primary { + border-color: #007bff !important; +} + +.border-secondary { + border-color: #868e96 !important; +} + +.border-success { + border-color: #28a745 !important; +} + +.border-info { + border-color: #17a2b8 !important; +} + +.border-warning { + border-color: #ffc107 !important; +} + +.border-danger { + border-color: #dc3545 !important; +} + +.border-light { + border-color: #f8f9fa !important; +} + +.border-dark { + border-color: #343a40 !important; +} + +.border-white { + border-color: #fff !important; +} + +.rounded { + border-radius: 0.25rem !important; +} + +.rounded-top { + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; +} + +.rounded-right { + border-top-right-radius: 0.25rem !important; + border-bottom-right-radius: 0.25rem !important; +} + +.rounded-bottom { + border-bottom-right-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-left { + border-top-left-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.rounded-0 { + border-radius: 0 !important; +} + +.clearfix::after { + display: block; + clear: both; + content: ""; +} + +.d-none { + display: none !important; +} + +.d-inline { + display: inline !important; +} + +.d-inline-block { + display: inline-block !important; +} + +.d-block { + display: block !important; +} + +.d-table { + display: table !important; +} + +.d-table-row { + display: table-row !important; +} + +.d-table-cell { + display: table-cell !important; +} + +.d-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; +} + +.d-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; +} + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-row { + display: table-row !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-sm-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 768px) { + .d-md-none { + display: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-row { + display: table-row !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-md-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 992px) { + .d-lg-none { + display: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-row { + display: table-row !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-lg-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-row { + display: table-row !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-xl-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +.d-print-block { + display: none !important; +} + +@media print { + .d-print-block { + display: block !important; + } +} + +.d-print-inline { + display: none !important; +} + +@media print { + .d-print-inline { + display: inline !important; + } +} + +.d-print-inline-block { + display: none !important; +} + +@media print { + .d-print-inline-block { + display: inline-block !important; + } +} + +@media print { + .d-print-none { + display: none !important; + } +} + +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; +} + +.embed-responsive::before { + display: block; + content: ""; +} + +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} + +.embed-responsive-21by9::before { + padding-top: 42.85714%; +} + +.embed-responsive-16by9::before { + padding-top: 56.25%; +} + +.embed-responsive-4by3::before { + padding-top: 75%; +} + +.embed-responsive-1by1::before { + padding-top: 100%; +} + +.flex-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; +} + +.flex-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; +} + +.flex-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; +} + +.flex-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; +} + +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; +} + +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; +} + +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; +} + +.justify-content-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; +} + +.justify-content-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} + +.justify-content-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; +} + +.justify-content-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; +} + +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; +} + +.align-items-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; +} + +.align-items-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; +} + +.align-items-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; +} + +.align-items-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; +} + +.align-items-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; +} + +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; +} + +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; +} + +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; +} + +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; +} + +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; +} + +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; +} + +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; +} + +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; +} + +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; +} + +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; +} + +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; +} + +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; +} + +@media (min-width: 576px) { + .flex-sm-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-sm-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-sm-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-sm-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-sm-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-sm-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-sm-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-sm-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-sm-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-sm-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-sm-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-sm-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 768px) { + .flex-md-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-md-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-md-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-md-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-md-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-md-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-md-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-md-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-md-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-md-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-md-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-md-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 992px) { + .flex-lg-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-lg-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-lg-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-lg-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-lg-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-lg-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-lg-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-lg-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-lg-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-lg-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-lg-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-lg-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 1200px) { + .flex-xl-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-xl-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-xl-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-xl-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-xl-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-xl-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-xl-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-xl-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-xl-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-xl-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-xl-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-xl-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +.float-left { + float: left !important; +} + +.float-right { + float: right !important; +} + +.float-none { + float: none !important; +} + +@media (min-width: 576px) { + .float-sm-left { + float: left !important; + } + .float-sm-right { + float: right !important; + } + .float-sm-none { + float: none !important; + } +} + +@media (min-width: 768px) { + .float-md-left { + float: left !important; + } + .float-md-right { + float: right !important; + } + .float-md-none { + float: none !important; + } +} + +@media (min-width: 992px) { + .float-lg-left { + float: left !important; + } + .float-lg-right { + float: right !important; + } + .float-lg-none { + float: none !important; + } +} + +@media (min-width: 1200px) { + .float-xl-left { + float: left !important; + } + .float-xl-right { + float: right !important; + } + .float-xl-none { + float: none !important; + } +} + +.position-static { + position: static !important; +} + +.position-relative { + position: relative !important; +} + +.position-absolute { + position: absolute !important; +} + +.position-fixed { + position: fixed !important; +} + +.position-sticky { + position: -webkit-sticky !important; + position: sticky !important; +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; +} + +@supports ((position: -webkit-sticky) or (position: sticky)) { + .sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + border: 0; +} + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; + -webkit-clip-path: none; + clip-path: none; +} + +.w-25 { + width: 25% !important; +} + +.w-50 { + width: 50% !important; +} + +.w-75 { + width: 75% !important; +} + +.w-100 { + width: 100% !important; +} + +.h-25 { + height: 25% !important; +} + +.h-50 { + height: 50% !important; +} + +.h-75 { + height: 75% !important; +} + +.h-100 { + height: 100% !important; +} + +.mw-100 { + max-width: 100% !important; +} + +.mh-100 { + max-height: 100% !important; +} + +.m-0 { + margin: 0 !important; +} + +.mt-0, +.my-0 { + margin-top: 0 !important; +} + +.mr-0, +.mx-0 { + margin-right: 0 !important; +} + +.mb-0, +.my-0 { + margin-bottom: 0 !important; +} + +.ml-0, +.mx-0 { + margin-left: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.mt-1, +.my-1 { + margin-top: 0.25rem !important; +} + +.mr-1, +.mx-1 { + margin-right: 0.25rem !important; +} + +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; +} + +.ml-1, +.mx-1 { + margin-left: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.mt-2, +.my-2 { + margin-top: 0.5rem !important; +} + +.mr-2, +.mx-2 { + margin-right: 0.5rem !important; +} + +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; +} + +.ml-2, +.mx-2 { + margin-left: 0.5rem !important; +} + +.m-3 { + margin: 1rem !important; +} + +.mt-3, +.my-3 { + margin-top: 1rem !important; +} + +.mr-3, +.mx-3 { + margin-right: 1rem !important; +} + +.mb-3, +.my-3 { + margin-bottom: 1rem !important; +} + +.ml-3, +.mx-3 { + margin-left: 1rem !important; +} + +.m-4 { + margin: 1.5rem !important; +} + +.mt-4, +.my-4 { + margin-top: 1.5rem !important; +} + +.mr-4, +.mx-4 { + margin-right: 1.5rem !important; +} + +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; +} + +.ml-4, +.mx-4 { + margin-left: 1.5rem !important; +} + +.m-5 { + margin: 3rem !important; +} + +.mt-5, +.my-5 { + margin-top: 3rem !important; +} + +.mr-5, +.mx-5 { + margin-right: 3rem !important; +} + +.mb-5, +.my-5 { + margin-bottom: 3rem !important; +} + +.ml-5, +.mx-5 { + margin-left: 3rem !important; +} + +.p-0 { + padding: 0 !important; +} + +.pt-0, +.py-0 { + padding-top: 0 !important; +} + +.pr-0, +.px-0 { + padding-right: 0 !important; +} + +.pb-0, +.py-0 { + padding-bottom: 0 !important; +} + +.pl-0, +.px-0 { + padding-left: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.pt-1, +.py-1 { + padding-top: 0.25rem !important; +} + +.pr-1, +.px-1 { + padding-right: 0.25rem !important; +} + +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; +} + +.pl-1, +.px-1 { + padding-left: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.pt-2, +.py-2 { + padding-top: 0.5rem !important; +} + +.pr-2, +.px-2 { + padding-right: 0.5rem !important; +} + +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; +} + +.pl-2, +.px-2 { + padding-left: 0.5rem !important; +} + +.p-3 { + padding: 1rem !important; +} + +.pt-3, +.py-3 { + padding-top: 1rem !important; +} + +.pr-3, +.px-3 { + padding-right: 1rem !important; +} + +.pb-3, +.py-3 { + padding-bottom: 1rem !important; +} + +.pl-3, +.px-3 { + padding-left: 1rem !important; +} + +.p-4 { + padding: 1.5rem !important; +} + +.pt-4, +.py-4 { + padding-top: 1.5rem !important; +} + +.pr-4, +.px-4 { + padding-right: 1.5rem !important; +} + +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; +} + +.pl-4, +.px-4 { + padding-left: 1.5rem !important; +} + +.p-5 { + padding: 3rem !important; +} + +.pt-5, +.py-5 { + padding-top: 3rem !important; +} + +.pr-5, +.px-5 { + padding-right: 3rem !important; +} + +.pb-5, +.py-5 { + padding-bottom: 3rem !important; +} + +.pl-5, +.px-5 { + padding-left: 3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mt-auto, +.my-auto { + margin-top: auto !important; +} + +.mr-auto, +.mx-auto { + margin-right: auto !important; +} + +.mb-auto, +.my-auto { + margin-bottom: auto !important; +} + +.ml-auto, +.mx-auto { + margin-left: auto !important; +} + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; + } + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; + } + .mr-sm-0, + .mx-sm-0 { + margin-right: 0 !important; + } + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; + } + .ml-sm-0, + .mx-sm-0 { + margin-left: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; + } + .mr-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; + } + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; + } + .ml-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; + } + .mr-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; + } + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; + } + .ml-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; + } + .mr-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; + } + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; + } + .ml-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; + } + .mr-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; + } + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; + } + .ml-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; + } + .mr-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; + } + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; + } + .ml-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; + } + .p-sm-0 { + padding: 0 !important; + } + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; + } + .pr-sm-0, + .px-sm-0 { + padding-right: 0 !important; + } + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; + } + .pl-sm-0, + .px-sm-0 { + padding-left: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; + } + .pr-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; + } + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; + } + .pl-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; + } + .pr-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; + } + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; + } + .pl-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; + } + .pr-sm-3, + .px-sm-3 { + padding-right: 1rem !important; + } + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; + } + .pl-sm-3, + .px-sm-3 { + padding-left: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; + } + .pr-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; + } + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; + } + .pl-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; + } + .pr-sm-5, + .px-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; + } + .pl-sm-5, + .px-sm-5 { + padding-left: 3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; + } + .mr-sm-auto, + .mx-sm-auto { + margin-right: auto !important; + } + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; + } + .ml-sm-auto, + .mx-sm-auto { + margin-left: auto !important; + } +} + +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; + } + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; + } + .mr-md-0, + .mx-md-0 { + margin-right: 0 !important; + } + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; + } + .ml-md-0, + .mx-md-0 { + margin-left: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; + } + .mr-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; + } + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; + } + .ml-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; + } + .mr-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; + } + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; + } + .ml-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; + } + .mr-md-3, + .mx-md-3 { + margin-right: 1rem !important; + } + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; + } + .ml-md-3, + .mx-md-3 { + margin-left: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; + } + .mr-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; + } + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; + } + .ml-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; + } + .mr-md-5, + .mx-md-5 { + margin-right: 3rem !important; + } + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; + } + .ml-md-5, + .mx-md-5 { + margin-left: 3rem !important; + } + .p-md-0 { + padding: 0 !important; + } + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; + } + .pr-md-0, + .px-md-0 { + padding-right: 0 !important; + } + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; + } + .pl-md-0, + .px-md-0 { + padding-left: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; + } + .pr-md-1, + .px-md-1 { + padding-right: 0.25rem !important; + } + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; + } + .pl-md-1, + .px-md-1 { + padding-left: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; + } + .pr-md-2, + .px-md-2 { + padding-right: 0.5rem !important; + } + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; + } + .pl-md-2, + .px-md-2 { + padding-left: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; + } + .pr-md-3, + .px-md-3 { + padding-right: 1rem !important; + } + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; + } + .pl-md-3, + .px-md-3 { + padding-left: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; + } + .pr-md-4, + .px-md-4 { + padding-right: 1.5rem !important; + } + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; + } + .pl-md-4, + .px-md-4 { + padding-left: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; + } + .pr-md-5, + .px-md-5 { + padding-right: 3rem !important; + } + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; + } + .pl-md-5, + .px-md-5 { + padding-left: 3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; + } + .mr-md-auto, + .mx-md-auto { + margin-right: auto !important; + } + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; + } + .ml-md-auto, + .mx-md-auto { + margin-left: auto !important; + } +} + +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; + } + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; + } + .mr-lg-0, + .mx-lg-0 { + margin-right: 0 !important; + } + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; + } + .ml-lg-0, + .mx-lg-0 { + margin-left: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; + } + .mr-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; + } + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; + } + .ml-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; + } + .mr-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; + } + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; + } + .ml-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; + } + .mr-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; + } + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; + } + .ml-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; + } + .mr-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; + } + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; + } + .ml-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; + } + .mr-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; + } + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; + } + .ml-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; + } + .p-lg-0 { + padding: 0 !important; + } + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; + } + .pr-lg-0, + .px-lg-0 { + padding-right: 0 !important; + } + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; + } + .pl-lg-0, + .px-lg-0 { + padding-left: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; + } + .pr-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; + } + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; + } + .pl-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; + } + .pr-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; + } + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; + } + .pl-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; + } + .pr-lg-3, + .px-lg-3 { + padding-right: 1rem !important; + } + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; + } + .pl-lg-3, + .px-lg-3 { + padding-left: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; + } + .pr-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; + } + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; + } + .pl-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; + } + .pr-lg-5, + .px-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; + } + .pl-lg-5, + .px-lg-5 { + padding-left: 3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; + } + .mr-lg-auto, + .mx-lg-auto { + margin-right: auto !important; + } + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; + } + .ml-lg-auto, + .mx-lg-auto { + margin-left: auto !important; + } +} + +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; + } + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; + } + .mr-xl-0, + .mx-xl-0 { + margin-right: 0 !important; + } + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; + } + .ml-xl-0, + .mx-xl-0 { + margin-left: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; + } + .mr-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; + } + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; + } + .ml-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; + } + .mr-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; + } + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; + } + .ml-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; + } + .mr-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; + } + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; + } + .ml-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; + } + .mr-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; + } + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; + } + .ml-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; + } + .mr-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; + } + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; + } + .ml-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; + } + .p-xl-0 { + padding: 0 !important; + } + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; + } + .pr-xl-0, + .px-xl-0 { + padding-right: 0 !important; + } + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; + } + .pl-xl-0, + .px-xl-0 { + padding-left: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; + } + .pr-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; + } + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; + } + .pl-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; + } + .pr-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; + } + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; + } + .pl-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; + } + .pr-xl-3, + .px-xl-3 { + padding-right: 1rem !important; + } + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; + } + .pl-xl-3, + .px-xl-3 { + padding-left: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; + } + .pr-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; + } + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; + } + .pl-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; + } + .pr-xl-5, + .px-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; + } + .pl-xl-5, + .px-xl-5 { + padding-left: 3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; + } + .mr-xl-auto, + .mx-xl-auto { + margin-right: auto !important; + } + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; + } + .ml-xl-auto, + .mx-xl-auto { + margin-left: auto !important; + } +} + +.text-justify { + text-align: justify !important; +} + +.text-nowrap { + white-space: nowrap !important; +} + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-left { + text-align: left !important; +} + +.text-right { + text-align: right !important; +} + +.text-center { + text-align: center !important; +} + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; + } + .text-sm-right { + text-align: right !important; + } + .text-sm-center { + text-align: center !important; + } +} + +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; + } + .text-md-right { + text-align: right !important; + } + .text-md-center { + text-align: center !important; + } +} + +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; + } + .text-lg-right { + text-align: right !important; + } + .text-lg-center { + text-align: center !important; + } +} + +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; + } + .text-xl-right { + text-align: right !important; + } + .text-xl-center { + text-align: center !important; + } +} + +.text-lowercase { + text-transform: lowercase !important; +} + +.text-uppercase { + text-transform: uppercase !important; +} + +.text-capitalize { + text-transform: capitalize !important; +} + +.font-weight-light { + font-weight: 300 !important; +} + +.font-weight-normal { + font-weight: 400 !important; +} + +.font-weight-bold { + font-weight: 700 !important; +} + +.font-italic { + font-style: italic !important; +} + +.text-white { + color: #fff !important; +} + +.text-primary { + color: #007bff !important; +} + +a.text-primary:focus, a.text-primary:hover { + color: #0062cc !important; +} + +.text-secondary { + color: #868e96 !important; +} + +a.text-secondary:focus, a.text-secondary:hover { + color: #6c757d !important; +} + +.text-success { + color: #28a745 !important; +} + +a.text-success:focus, a.text-success:hover { + color: #1e7e34 !important; +} + +.text-info { + color: #17a2b8 !important; +} + +a.text-info:focus, a.text-info:hover { + color: #117a8b !important; +} + +.text-warning { + color: #ffc107 !important; +} + +a.text-warning:focus, a.text-warning:hover { + color: #d39e00 !important; +} + +.text-danger { + color: #dc3545 !important; +} + +a.text-danger:focus, a.text-danger:hover { + color: #bd2130 !important; +} + +.text-light { + color: #f8f9fa !important; +} + +a.text-light:focus, a.text-light:hover { + color: #dae0e5 !important; +} + +.text-dark { + color: #343a40 !important; +} + +a.text-dark:focus, a.text-dark:hover { + color: #1d2124 !important; +} + +.text-muted { + color: #868e96 !important; +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.visible { + visibility: visible !important; +} + +.invisible { + visibility: hidden !important; +} + +/*# sourceMappingURL=../maps/admin/bootstrap.css.map */ diff --git a/public/assets/css/admin/bootstrap/bootstrap-reboot.css b/public/assets/css/admin/bootstrap/bootstrap-reboot.css new file mode 100644 index 0000000..7c59476 --- /dev/null +++ b/public/assets/css/admin/bootstrap/bootstrap-reboot.css @@ -0,0 +1,335 @@ +html { + -webkit-box-sizing: border-box; + box-sizing: border-box; + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: transparent; +} + +*, +*::before, +*::after { + -webkit-box-sizing: inherit; + box-sizing: inherit; +} + +@-ms-viewport { + width: device-width; +} + +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-size: 1rem; + font-weight: normal; + line-height: 1.5; + color: #212529; + background-color: #fff; +} + +[tabindex="-1"]:focus { + outline: none !important; +} + +hr { + -webkit-box-sizing: content-box; + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: .5rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: bold; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +dfn { + font-style: italic; +} + +b, +strong { + font-weight: bolder; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg:not(:root) { + overflow: hidden; +} + +a, +area, +button, +[role="button"], +input, +label, +select, +summary, +textarea { + -ms-touch-action: manipulation; + touch-action: manipulation; +} + +table { + border-collapse: collapse; +} + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #868e96; + text-align: left; + caption-side: bottom; +} + +th { + text-align: left; +} + +label { + display: inline-block; + margin-bottom: .5rem; +} + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +button, +html [type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + padding: 0; + border-style: none; +} + +input[type="radio"], +input[type="checkbox"] { + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; +} + +textarea { + overflow: auto; + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} + +progress { + vertical-align: baseline; +} + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; +} + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +summary { + display: list-item; +} + +template { + display: none; +} + +[hidden] { + display: none !important; +} + +/*# sourceMappingURL=../../maps/admin/bootstrap/bootstrap-reboot.css.map */ diff --git a/public/assets/css/admin/bootstrap/bootstrap.css b/public/assets/css/admin/bootstrap/bootstrap.css new file mode 100644 index 0000000..123fbcf --- /dev/null +++ b/public/assets/css/admin/bootstrap/bootstrap.css @@ -0,0 +1,8569 @@ +/*! + * Bootstrap v4.0.0-beta (https://getbootstrap.com) + * Copyright 2011-2017 The Bootstrap Authors + * Copyright 2011-2017 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +@media print { + *, + *::before, + *::after { + text-shadow: none !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + abbr[title]::after { + content: " (" attr(title) ")"; + } + pre { + white-space: pre-wrap !important; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .badge { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} + +html { + -webkit-box-sizing: border-box; + box-sizing: border-box; + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: transparent; +} + +*, +*::before, +*::after { + -webkit-box-sizing: inherit; + box-sizing: inherit; +} + +@-ms-viewport { + width: device-width; +} + +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-size: 1rem; + font-weight: normal; + line-height: 1.5; + color: #212529; + background-color: #fff; +} + +[tabindex="-1"]:focus { + outline: none !important; +} + +hr { + -webkit-box-sizing: content-box; + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: .5rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: bold; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +dfn { + font-style: italic; +} + +b, +strong { + font-weight: bolder; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg:not(:root) { + overflow: hidden; +} + +a, +area, +button, +[role="button"], +input, +label, +select, +summary, +textarea { + -ms-touch-action: manipulation; + touch-action: manipulation; +} + +table { + border-collapse: collapse; +} + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #868e96; + text-align: left; + caption-side: bottom; +} + +th { + text-align: left; +} + +label { + display: inline-block; + margin-bottom: .5rem; +} + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +button, +html [type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + padding: 0; + border-style: none; +} + +input[type="radio"], +input[type="checkbox"] { + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; +} + +textarea { + overflow: auto; + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} + +progress { + vertical-align: baseline; +} + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; +} + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +summary { + display: list-item; +} + +template { + display: none; +} + +[hidden] { + display: none !important; +} + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + margin-bottom: 0.5rem; + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +h1, .h1 { + font-size: 2.5rem; +} + +h2, .h2 { + font-size: 2rem; +} + +h3, .h3 { + font-size: 1.75rem; +} + +h4, .h4 { + font-size: 1.5rem; +} + +h5, .h5 { + font-size: 1.25rem; +} + +h6, .h6 { + font-size: 1rem; +} + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.1; +} + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.1; +} + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.1; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.1; +} + +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +small, +.small { + font-size: 80%; + font-weight: normal; +} + +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline-item { + display: inline-block; +} + +.list-inline-item:not(:last-child) { + margin-right: 5px; +} + +.initialism { + font-size: 90%; + text-transform: uppercase; +} + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; +} + +.blockquote-footer { + display: block; + font-size: 80%; + color: #868e96; +} + +.blockquote-footer::before { + content: "\2014 \00A0"; +} + +.img-fluid { + max-width: 100%; + height: auto; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 0.25rem; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + max-width: 100%; + height: auto; +} + +.figure { + display: inline-block; +} + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} + +.figure-caption { + font-size: 90%; + color: #868e96; +} + +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +code { + padding: 0.2rem 0.4rem; + font-size: 90%; + color: #bd4147; + background-color: #f8f9fa; + border-radius: 0.25rem; +} + +a > code { + padding: 0; + color: inherit; + background-color: inherit; +} + +kbd { + padding: 0.2rem 0.4rem; + font-size: 90%; + color: #fff; + background-color: #212529; + border-radius: 0.2rem; +} + +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; +} + +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + font-size: 90%; + color: #212529; +} + +pre code { + padding: 0; + font-size: inherit; + color: inherit; + background-color: transparent; + border-radius: 0; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +.container { + margin-right: auto; + margin-left: auto; + padding-right: 15px; + padding-left: 15px; + width: 100%; +} + +@media (min-width: 576px) { + .container { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } +} + +.container-fluid { + width: 100%; + margin-right: auto; + margin-left: auto; + padding-right: 15px; + padding-left: 15px; + width: 100%; +} + +.row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; +} + +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; +} + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} + +.col-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; +} + +.col-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; +} + +.col-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; +} + +.col-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.col-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; +} + +.col-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; +} + +.col-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.col-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; +} + +.col-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; +} + +.col-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} + +.col-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; +} + +.col-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; +} + +.col-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.order-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; +} + +.order-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; +} + +.order-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; +} + +.order-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; +} + +.order-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; +} + +.order-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; +} + +.order-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; +} + +.order-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; +} + +.order-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; +} + +.order-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; +} + +.order-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; +} + +.order-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-sm-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-sm-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; + } + .col-sm-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; + } + .col-sm-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-sm-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; + } + .col-sm-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; + } + .col-sm-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-sm-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; + } + .col-sm-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; + } + .col-sm-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-sm-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; + } + .col-sm-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; + } + .col-sm-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-sm-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-sm-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-sm-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-sm-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-sm-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-sm-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-sm-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-sm-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-sm-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-sm-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-sm-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-sm-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } +} + +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-md-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-md-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; + } + .col-md-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; + } + .col-md-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-md-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; + } + .col-md-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; + } + .col-md-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-md-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; + } + .col-md-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; + } + .col-md-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-md-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; + } + .col-md-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; + } + .col-md-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-md-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-md-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-md-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-md-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-md-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-md-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-md-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-md-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-md-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-md-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-md-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-md-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } +} + +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-lg-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-lg-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; + } + .col-lg-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; + } + .col-lg-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-lg-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; + } + .col-lg-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; + } + .col-lg-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-lg-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; + } + .col-lg-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; + } + .col-lg-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-lg-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; + } + .col-lg-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; + } + .col-lg-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-lg-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-lg-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-lg-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-lg-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-lg-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-lg-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-lg-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-lg-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-lg-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-lg-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-lg-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-lg-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } +} + +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-xl-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-xl-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.33333%; + flex: 0 0 8.33333%; + max-width: 8.33333%; + } + .col-xl-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.66667%; + flex: 0 0 16.66667%; + max-width: 16.66667%; + } + .col-xl-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-xl-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.33333%; + flex: 0 0 33.33333%; + max-width: 33.33333%; + } + .col-xl-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.66667%; + flex: 0 0 41.66667%; + max-width: 41.66667%; + } + .col-xl-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-xl-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.33333%; + flex: 0 0 58.33333%; + max-width: 58.33333%; + } + .col-xl-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.66667%; + flex: 0 0 66.66667%; + max-width: 66.66667%; + } + .col-xl-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-xl-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.33333%; + flex: 0 0 83.33333%; + max-width: 83.33333%; + } + .col-xl-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.66667%; + flex: 0 0 91.66667%; + max-width: 91.66667%; + } + .col-xl-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-xl-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-xl-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-xl-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-xl-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-xl-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-xl-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-xl-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-xl-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-xl-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-xl-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-xl-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-xl-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } +} + +.table { + width: 100%; + max-width: 100%; + margin-bottom: 1rem; + background-color: transparent; +} + +.table th, +.table td { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #e9ecef; +} + +.table thead th { + vertical-align: bottom; + border-bottom: 2px solid #e9ecef; +} + +.table tbody + tbody { + border-top: 2px solid #e9ecef; +} + +.table .table { + background-color: #fff; +} + +.table-sm th, +.table-sm td { + padding: 0.3rem; +} + +.table-bordered { + border: 1px solid #e9ecef; +} + +.table-bordered th, +.table-bordered td { + border: 1px solid #e9ecef; +} + +.table-bordered thead th, +.table-bordered thead td { + border-bottom-width: 2px; +} + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); +} + +.table-hover tbody tr:hover { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-primary, +.table-primary > th, +.table-primary > td { + background-color: #b8daff; +} + +.table-hover .table-primary:hover { + background-color: #9fcdff; +} + +.table-hover .table-primary:hover > td, +.table-hover .table-primary:hover > th { + background-color: #9fcdff; +} + +.table-secondary, +.table-secondary > th, +.table-secondary > td { + background-color: #dddfe2; +} + +.table-hover .table-secondary:hover { + background-color: #cfd2d6; +} + +.table-hover .table-secondary:hover > td, +.table-hover .table-secondary:hover > th { + background-color: #cfd2d6; +} + +.table-success, +.table-success > th, +.table-success > td { + background-color: #c3e6cb; +} + +.table-hover .table-success:hover { + background-color: #b1dfbb; +} + +.table-hover .table-success:hover > td, +.table-hover .table-success:hover > th { + background-color: #b1dfbb; +} + +.table-info, +.table-info > th, +.table-info > td { + background-color: #bee5eb; +} + +.table-hover .table-info:hover { + background-color: #abdde5; +} + +.table-hover .table-info:hover > td, +.table-hover .table-info:hover > th { + background-color: #abdde5; +} + +.table-warning, +.table-warning > th, +.table-warning > td { + background-color: #ffeeba; +} + +.table-hover .table-warning:hover { + background-color: #ffe8a1; +} + +.table-hover .table-warning:hover > td, +.table-hover .table-warning:hover > th { + background-color: #ffe8a1; +} + +.table-danger, +.table-danger > th, +.table-danger > td { + background-color: #f5c6cb; +} + +.table-hover .table-danger:hover { + background-color: #f1b0b7; +} + +.table-hover .table-danger:hover > td, +.table-hover .table-danger:hover > th { + background-color: #f1b0b7; +} + +.table-light, +.table-light > th, +.table-light > td { + background-color: #fdfdfe; +} + +.table-hover .table-light:hover { + background-color: #ececf6; +} + +.table-hover .table-light:hover > td, +.table-hover .table-light:hover > th { + background-color: #ececf6; +} + +.table-dark, +.table-dark > th, +.table-dark > td { + background-color: #c6c8ca; +} + +.table-hover .table-dark:hover { + background-color: #b9bbbe; +} + +.table-hover .table-dark:hover > td, +.table-hover .table-dark:hover > th { + background-color: #b9bbbe; +} + +.table-active, +.table-active > th, +.table-active > td { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-hover .table-active:hover > td, +.table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); +} + +.thead-inverse th { + color: #fff; + background-color: #212529; +} + +.thead-default th { + color: #495057; + background-color: #e9ecef; +} + +.table-inverse { + color: #fff; + background-color: #212529; +} + +.table-inverse th, +.table-inverse td, +.table-inverse thead th { + border-color: #32383e; +} + +.table-inverse.table-bordered { + border: 0; +} + +.table-inverse.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); +} + +.table-inverse.table-hover tbody tr:hover { + background-color: rgba(255, 255, 255, 0.075); +} + +@media (max-width: 991px) { + .table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive.table-bordered { + border: 0; + } +} + +.form-control { + display: block; + width: 100%; + padding: 0.5rem 0.75rem; + font-size: 1rem; + line-height: 1.25; + color: #495057; + background-color: #fff; + background-image: none; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; + -webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; +} + +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} + +.form-control:focus { + color: #495057; + background-color: #fff; + border-color: #80bdff; + outline: none; +} + +.form-control::-webkit-input-placeholder { + color: #868e96; + opacity: 1; +} + +.form-control:-ms-input-placeholder { + color: #868e96; + opacity: 1; +} + +.form-control::placeholder { + color: #868e96; + opacity: 1; +} + +.form-control:disabled, .form-control[readonly] { + background-color: #e9ecef; + opacity: 1; +} + +select.form-control:not([size]):not([multiple]) { + height: calc(2.25rem + 2px); +} + +select.form-control:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.form-control-file, +.form-control-range { + display: block; +} + +.col-form-label { + padding-top: calc(0.5rem - 1px * 2); + padding-bottom: calc(0.5rem - 1px * 2); + margin-bottom: 0; +} + +.col-form-label-lg { + padding-top: calc(0.5rem - 1px * 2); + padding-bottom: calc(0.5rem - 1px * 2); + font-size: 1.25rem; +} + +.col-form-label-sm { + padding-top: calc(0.25rem - 1px * 2); + padding-bottom: calc(0.25rem - 1px * 2); + font-size: 0.875rem; +} + +.col-form-legend { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + margin-bottom: 0; + font-size: 1rem; +} + +.form-control-plaintext { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + margin-bottom: 0; + line-height: 1.25; + border: solid transparent; + border-width: 1px 0; +} + +.form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control, +.input-group-sm > .form-control-plaintext.input-group-addon, +.input-group-sm > .input-group-btn > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control, +.input-group-lg > .form-control-plaintext.input-group-addon, +.input-group-lg > .input-group-btn > .form-control-plaintext.btn { + padding-right: 0; + padding-left: 0; +} + +.form-control-sm, .input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +select.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]), +.input-group-sm > select.input-group-addon:not([size]):not([multiple]), +.input-group-sm > .input-group-btn > select.btn:not([size]):not([multiple]) { + height: calc(1.8125rem + 2px); +} + +.form-control-lg, .input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]), +.input-group-lg > select.input-group-addon:not([size]):not([multiple]), +.input-group-lg > .input-group-btn > select.btn:not([size]):not([multiple]) { + height: calc(2.3125rem + 2px); +} + +.form-group { + margin-bottom: 1rem; +} + +.form-text { + display: block; + margin-top: 0.25rem; +} + +.form-row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -5px; + margin-left: -5px; +} + +.form-row > .col, +.form-row > [class*="col-"] { + padding-right: 5px; + padding-left: 5px; +} + +.form-check { + position: relative; + display: block; + margin-bottom: 0.5rem; +} + +.form-check.disabled .form-check-label { + color: #868e96; +} + +.form-check-label { + padding-left: 1.25rem; + margin-bottom: 0; +} + +.form-check-input { + position: absolute; + margin-top: 0.25rem; + margin-left: -1.25rem; +} + +.form-check-input:only-child { + position: static; +} + +.form-check-inline { + display: inline-block; +} + +.form-check-inline .form-check-label { + vertical-align: middle; +} + +.form-check-inline + .form-check-inline { + margin-left: 0.75rem; +} + +.invalid-feedback { + display: none; + margin-top: .25rem; + font-size: .875rem; + color: #dc3545; +} + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + width: 250px; + padding: .5rem; + margin-top: .1rem; + font-size: .875rem; + line-height: 1; + color: #fff; + background-color: rgba(220, 53, 69, 0.8); + border-radius: .2rem; +} + +.was-validated .form-control:valid, .form-control.is-valid, .was-validated +.custom-select:valid, +.custom-select.is-valid { + border-color: #28a745; +} + +.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated +.custom-select:valid:focus, +.custom-select.is-valid:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.was-validated .form-control:valid ~ .invalid-feedback, +.was-validated .form-control:valid ~ .invalid-tooltip, .form-control.is-valid ~ .invalid-feedback, +.form-control.is-valid ~ .invalid-tooltip, .was-validated +.custom-select:valid ~ .invalid-feedback, +.was-validated +.custom-select:valid ~ .invalid-tooltip, +.custom-select.is-valid ~ .invalid-feedback, +.custom-select.is-valid ~ .invalid-tooltip { + display: block; +} + +.was-validated .form-check-input:valid + .form-check-label, .form-check-input.is-valid + .form-check-label { + color: #28a745; +} + +.was-validated .custom-control-input:valid ~ .custom-control-indicator, .custom-control-input.is-valid ~ .custom-control-indicator { + background-color: rgba(40, 167, 69, 0.25); +} + +.was-validated .custom-control-input:valid ~ .custom-control-description, .custom-control-input.is-valid ~ .custom-control-description { + color: #28a745; +} + +.was-validated .custom-file-input:valid ~ .custom-file-control, .custom-file-input.is-valid ~ .custom-file-control { + border-color: #28a745; +} + +.was-validated .custom-file-input:valid ~ .custom-file-control::before, .custom-file-input.is-valid ~ .custom-file-control::before { + border-color: inherit; +} + +.was-validated .custom-file-input:valid:focus, .custom-file-input.is-valid:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.was-validated .form-control:invalid, .form-control.is-invalid, .was-validated +.custom-select:invalid, +.custom-select.is-invalid { + border-color: #dc3545; +} + +.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated +.custom-select:invalid:focus, +.custom-select.is-invalid:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.was-validated .form-control:invalid ~ .invalid-feedback, +.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, +.form-control.is-invalid ~ .invalid-tooltip, .was-validated +.custom-select:invalid ~ .invalid-feedback, +.was-validated +.custom-select:invalid ~ .invalid-tooltip, +.custom-select.is-invalid ~ .invalid-feedback, +.custom-select.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .form-check-input:invalid + .form-check-label, .form-check-input.is-invalid + .form-check-label { + color: #dc3545; +} + +.was-validated .custom-control-input:invalid ~ .custom-control-indicator, .custom-control-input.is-invalid ~ .custom-control-indicator { + background-color: rgba(220, 53, 69, 0.25); +} + +.was-validated .custom-control-input:invalid ~ .custom-control-description, .custom-control-input.is-invalid ~ .custom-control-description { + color: #dc3545; +} + +.was-validated .custom-file-input:invalid ~ .custom-file-control, .custom-file-input.is-invalid ~ .custom-file-control { + border-color: #dc3545; +} + +.was-validated .custom-file-input:invalid ~ .custom-file-control::before, .custom-file-input.is-invalid ~ .custom-file-control::before { + border-color: inherit; +} + +.was-validated .custom-file-input:invalid:focus, .custom-file-input.is-invalid:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.form-inline { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.form-inline .form-check { + width: 100%; +} + +@media (min-width: 576px) { + .form-inline label { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + margin-bottom: 0; + } + .form-inline .form-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-bottom: 0; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-plaintext { + display: inline-block; + } + .form-inline .input-group { + width: auto; + } + .form-inline .form-control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-check { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + width: auto; + margin-top: 0; + margin-bottom: 0; + } + .form-inline .form-check-label { + padding-left: 0; + } + .form-inline .form-check-input { + position: relative; + margin-top: 0; + margin-right: 0.25rem; + margin-left: 0; + } + .form-inline .custom-control { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding-left: 0; + } + .form-inline .custom-control-indicator { + position: static; + display: inline-block; + margin-right: 0.25rem; + vertical-align: text-bottom; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} + +.btn { + display: inline-block; + font-weight: normal; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border: 1px solid transparent; + padding: 0.5rem 0.75rem; + font-size: 1rem; + line-height: 1.25; + border-radius: 0.25rem; + -webkit-transition: all 0.15s ease-in-out; + transition: all 0.15s ease-in-out; +} + +.btn:focus, .btn:hover { + text-decoration: none; +} + +.btn:focus, .btn.focus { + outline: 0; + -webkit-box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); +} + +.btn.disabled, .btn:disabled { + opacity: .65; +} + +.btn:active, .btn.active { + background-image: none; +} + +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} + +.btn-primary { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.btn-primary:hover { + color: #fff; + background-color: #0069d9; + border-color: #0062cc; +} + +.btn-primary:focus, .btn-primary.focus { + -webkit-box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5); +} + +.btn-primary.disabled, .btn-primary:disabled { + background-color: #007bff; + border-color: #007bff; +} + +.btn-primary:active, .btn-primary.active, +.show > .btn-primary.dropdown-toggle { + background-color: #0069d9; + background-image: none; + border-color: #0062cc; +} + +.btn-secondary { + color: #fff; + background-color: #868e96; + border-color: #868e96; +} + +.btn-secondary:hover { + color: #fff; + background-color: #727b84; + border-color: #6c757d; +} + +.btn-secondary:focus, .btn-secondary.focus { + -webkit-box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); +} + +.btn-secondary.disabled, .btn-secondary:disabled { + background-color: #868e96; + border-color: #868e96; +} + +.btn-secondary:active, .btn-secondary.active, +.show > .btn-secondary.dropdown-toggle { + background-color: #727b84; + background-image: none; + border-color: #6c757d; +} + +.btn-success { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-success:hover { + color: #fff; + background-color: #218838; + border-color: #1e7e34; +} + +.btn-success:focus, .btn-success.focus { + -webkit-box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); +} + +.btn-success.disabled, .btn-success:disabled { + background-color: #28a745; + border-color: #28a745; +} + +.btn-success:active, .btn-success.active, +.show > .btn-success.dropdown-toggle { + background-color: #218838; + background-image: none; + border-color: #1e7e34; +} + +.btn-info { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-info:hover { + color: #fff; + background-color: #138496; + border-color: #117a8b; +} + +.btn-info:focus, .btn-info.focus { + -webkit-box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); +} + +.btn-info.disabled, .btn-info:disabled { + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-info:active, .btn-info.active, +.show > .btn-info.dropdown-toggle { + background-color: #138496; + background-image: none; + border-color: #117a8b; +} + +.btn-warning { + color: #111; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-warning:hover { + color: #111; + background-color: #e0a800; + border-color: #d39e00; +} + +.btn-warning:focus, .btn-warning.focus { + -webkit-box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); +} + +.btn-warning.disabled, .btn-warning:disabled { + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-warning:active, .btn-warning.active, +.show > .btn-warning.dropdown-toggle { + background-color: #e0a800; + background-image: none; + border-color: #d39e00; +} + +.btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-danger:hover { + color: #fff; + background-color: #c82333; + border-color: #bd2130; +} + +.btn-danger:focus, .btn-danger.focus { + -webkit-box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); +} + +.btn-danger.disabled, .btn-danger:disabled { + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-danger:active, .btn-danger.active, +.show > .btn-danger.dropdown-toggle { + background-color: #c82333; + background-image: none; + border-color: #bd2130; +} + +.btn-light { + color: #111; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:hover { + color: #111; + background-color: #e2e6ea; + border-color: #dae0e5; +} + +.btn-light:focus, .btn-light.focus { + -webkit-box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); +} + +.btn-light.disabled, .btn-light:disabled { + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:active, .btn-light.active, +.show > .btn-light.dropdown-toggle { + background-color: #e2e6ea; + background-image: none; + border-color: #dae0e5; +} + +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124; +} + +.btn-dark:focus, .btn-dark.focus { + -webkit-box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); +} + +.btn-dark.disabled, .btn-dark:disabled { + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:active, .btn-dark.active, +.show > .btn-dark.dropdown-toggle { + background-color: #23272b; + background-image: none; + border-color: #1d2124; +} + +.btn-outline-primary { + color: #007bff; + background-color: transparent; + background-image: none; + border-color: #007bff; +} + +.btn-outline-primary:hover { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.btn-outline-primary:focus, .btn-outline-primary.focus { + -webkit-box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5); +} + +.btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #007bff; + background-color: transparent; +} + +.btn-outline-primary:active, .btn-outline-primary.active, +.show > .btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.btn-outline-secondary { + color: #868e96; + background-color: transparent; + background-image: none; + border-color: #868e96; +} + +.btn-outline-secondary:hover { + color: #fff; + background-color: #868e96; + border-color: #868e96; +} + +.btn-outline-secondary:focus, .btn-outline-secondary.focus { + -webkit-box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); +} + +.btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #868e96; + background-color: transparent; +} + +.btn-outline-secondary:active, .btn-outline-secondary.active, +.show > .btn-outline-secondary.dropdown-toggle { + color: #fff; + background-color: #868e96; + border-color: #868e96; +} + +.btn-outline-success { + color: #28a745; + background-color: transparent; + background-image: none; + border-color: #28a745; +} + +.btn-outline-success:hover { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-outline-success:focus, .btn-outline-success.focus { + -webkit-box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); +} + +.btn-outline-success.disabled, .btn-outline-success:disabled { + color: #28a745; + background-color: transparent; +} + +.btn-outline-success:active, .btn-outline-success.active, +.show > .btn-outline-success.dropdown-toggle { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-outline-info { + color: #17a2b8; + background-color: transparent; + background-image: none; + border-color: #17a2b8; +} + +.btn-outline-info:hover { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-outline-info:focus, .btn-outline-info.focus { + -webkit-box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); +} + +.btn-outline-info.disabled, .btn-outline-info:disabled { + color: #17a2b8; + background-color: transparent; +} + +.btn-outline-info:active, .btn-outline-info.active, +.show > .btn-outline-info.dropdown-toggle { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-outline-warning { + color: #ffc107; + background-color: transparent; + background-image: none; + border-color: #ffc107; +} + +.btn-outline-warning:hover { + color: #fff; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-outline-warning:focus, .btn-outline-warning.focus { + -webkit-box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); +} + +.btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #ffc107; + background-color: transparent; +} + +.btn-outline-warning:active, .btn-outline-warning.active, +.show > .btn-outline-warning.dropdown-toggle { + color: #fff; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-outline-danger { + color: #dc3545; + background-color: transparent; + background-image: none; + border-color: #dc3545; +} + +.btn-outline-danger:hover { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-outline-danger:focus, .btn-outline-danger.focus { + -webkit-box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); +} + +.btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #dc3545; + background-color: transparent; +} + +.btn-outline-danger:active, .btn-outline-danger.active, +.show > .btn-outline-danger.dropdown-toggle { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-outline-light { + color: #f8f9fa; + background-color: transparent; + background-image: none; + border-color: #f8f9fa; +} + +.btn-outline-light:hover { + color: #fff; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-outline-light:focus, .btn-outline-light.focus { + -webkit-box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); +} + +.btn-outline-light.disabled, .btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent; +} + +.btn-outline-light:active, .btn-outline-light.active, +.show > .btn-outline-light.dropdown-toggle { + color: #fff; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-outline-dark { + color: #343a40; + background-color: transparent; + background-image: none; + border-color: #343a40; +} + +.btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-outline-dark:focus, .btn-outline-dark.focus { + -webkit-box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); +} + +.btn-outline-dark.disabled, .btn-outline-dark:disabled { + color: #343a40; + background-color: transparent; +} + +.btn-outline-dark:active, .btn-outline-dark.active, +.show > .btn-outline-dark.dropdown-toggle { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-link { + font-weight: normal; + color: #007bff; + border-radius: 0; +} + +.btn-link, .btn-link:active, .btn-link.active, .btn-link:disabled { + background-color: transparent; +} + +.btn-link, .btn-link:focus, .btn-link:active { + border-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-link:hover { + border-color: transparent; +} + +.btn-link:focus, .btn-link:hover { + color: #0056b3; + text-decoration: underline; + background-color: transparent; +} + +.btn-link:disabled { + color: #868e96; +} + +.btn-link:disabled:focus, .btn-link:disabled:hover { + text-decoration: none; +} + +.btn-lg, .btn-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +.btn-block { + display: block; + width: 100%; +} + +.btn-block + .btn-block { + margin-top: 0.5rem; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} + +.fade.show { + opacity: 1; +} + +.collapse { + display: none; +} + +.collapse.show { + display: block; +} + +tr.collapse.show { + display: table-row; +} + +tbody.collapse.show { + display: table-row-group; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + transition: height 0.35s ease; +} + +.dropup, +.dropdown { + position: relative; +} + +.dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-left: 0.3em solid transparent; +} + +.dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropup .dropdown-menu { + margin-top: 0; + margin-bottom: 0.125rem; +} + +.dropup .dropdown-toggle::after { + border-top: 0; + border-bottom: 0.3em solid; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + font-size: 1rem; + color: #212529; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; +} + +.dropdown-divider { + height: 0; + margin: 0.5rem 0; + overflow: hidden; + border-top: 1px solid #e9ecef; +} + +.dropdown-item { + display: block; + width: 100%; + padding: 0.25rem 1.5rem; + clear: both; + font-weight: normal; + color: #212529; + text-align: inherit; + white-space: nowrap; + background: none; + border: 0; +} + +.dropdown-item:focus, .dropdown-item:hover { + color: #16181b; + text-decoration: none; + background-color: #f8f9fa; +} + +.dropdown-item.active, .dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #007bff; +} + +.dropdown-item.disabled, .dropdown-item:disabled { + color: #868e96; + background-color: transparent; +} + +.show > a { + outline: 0; +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.875rem; + color: #868e96; + white-space: nowrap; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + vertical-align: middle; +} + +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + margin-bottom: 0; +} + +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover { + z-index: 2; +} + +.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, +.btn-group-vertical > .btn:focus, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn.active { + z-index: 2; +} + +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group, +.btn-group-vertical .btn + .btn, +.btn-group-vertical .btn + .btn-group, +.btn-group-vertical .btn-group + .btn, +.btn-group-vertical .btn-group + .btn-group { + margin-left: -1px; +} + +.btn-toolbar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.btn-toolbar .input-group { + width: auto; +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +.btn-group > .btn:first-child { + margin-left: 0; +} + +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group > .btn-group { + float: left; +} + +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.btn + .dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; +} + +.btn + .dropdown-toggle-split::after { + margin-left: 0; +} + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} + +.btn-group-vertical { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + +.btn-group-vertical .btn, +.btn-group-vertical .btn-group { + width: 100%; +} + +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} + +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} + +.input-group { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + width: 100%; +} + +.input-group .form-control { + position: relative; + z-index: 2; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + width: 1%; + margin-bottom: 0; +} + +.input-group .form-control:focus, .input-group .form-control:active, .input-group .form-control:hover { + z-index: 3; +} + +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.input-group-addon, +.input-group-btn { + white-space: nowrap; + vertical-align: middle; +} + +.input-group-addon { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: normal; + line-height: 1.25; + color: #495057; + text-align: center; + background-color: #e9ecef; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; +} + +.input-group-addon.form-control-sm, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .input-group-addon.btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: 0.2rem; +} + +.input-group-addon.form-control-lg, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .input-group-addon.btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + border-radius: 0.3rem; +} + +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} + +.input-group .form-control:not(:last-child), +.input-group-addon:not(:last-child), +.input-group-btn:not(:last-child) > .btn, +.input-group-btn:not(:last-child) > .btn-group > .btn, +.input-group-btn:not(:last-child) > .dropdown-toggle, +.input-group-btn:not(:first-child) > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:not(:first-child) > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group-addon:not(:last-child) { + border-right: 0; +} + +.input-group .form-control:not(:first-child), +.input-group-addon:not(:first-child), +.input-group-btn:not(:first-child) > .btn, +.input-group-btn:not(:first-child) > .btn-group > .btn, +.input-group-btn:not(:first-child) > .dropdown-toggle, +.input-group-btn:not(:last-child) > .btn:not(:first-child), +.input-group-btn:not(:last-child) > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.form-control + .input-group-addon:not(:first-child) { + border-left: 0; +} + +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} + +.input-group-btn > .btn { + position: relative; +} + +.input-group-btn > .btn + .btn { + margin-left: -1px; +} + +.input-group-btn > .btn:focus, .input-group-btn > .btn:active, .input-group-btn > .btn:hover { + z-index: 3; +} + +.input-group-btn:not(:last-child) > .btn, +.input-group-btn:not(:last-child) > .btn-group { + margin-right: -1px; +} + +.input-group-btn:not(:first-child) > .btn, +.input-group-btn:not(:first-child) > .btn-group { + z-index: 2; + margin-left: -1px; +} + +.input-group-btn:not(:first-child) > .btn:focus, .input-group-btn:not(:first-child) > .btn:active, .input-group-btn:not(:first-child) > .btn:hover, +.input-group-btn:not(:first-child) > .btn-group:focus, +.input-group-btn:not(:first-child) > .btn-group:active, +.input-group-btn:not(:first-child) > .btn-group:hover { + z-index: 3; +} + +.custom-control { + position: relative; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + min-height: 1.5rem; + padding-left: 1.5rem; + margin-right: 1rem; +} + +.custom-control-input { + position: absolute; + z-index: -1; + opacity: 0; +} + +.custom-control-input:checked ~ .custom-control-indicator { + color: #fff; + background-color: #007bff; +} + +.custom-control-input:focus ~ .custom-control-indicator { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 3px #007bff; + box-shadow: 0 0 0 1px #fff, 0 0 0 3px #007bff; +} + +.custom-control-input:active ~ .custom-control-indicator { + color: #fff; + background-color: #b3d7ff; +} + +.custom-control-input:disabled ~ .custom-control-indicator { + background-color: #e9ecef; +} + +.custom-control-input:disabled ~ .custom-control-description { + color: #868e96; +} + +.custom-control-indicator { + position: absolute; + top: 0.25rem; + left: 0; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: #ddd; + background-repeat: no-repeat; + background-position: center center; + background-size: 50% 50%; +} + +.custom-checkbox .custom-control-indicator { + border-radius: 0.25rem; +} + +.custom-checkbox .custom-control-input:checked ~ .custom-control-indicator { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); +} + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-indicator { + background-color: #007bff; + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); +} + +.custom-radio .custom-control-indicator { + border-radius: 50%; +} + +.custom-radio .custom-control-input:checked ~ .custom-control-indicator { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); +} + +.custom-controls-stacked { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.custom-controls-stacked .custom-control { + margin-bottom: 0.25rem; +} + +.custom-controls-stacked .custom-control + .custom-control { + margin-left: 0; +} + +.custom-select { + display: inline-block; + max-width: 100%; + height: calc(2.25rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + line-height: 1.25; + color: #495057; + vertical-align: middle; + background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23333' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center; + background-size: 8px 10px; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.custom-select:focus { + border-color: #80bdff; + outline: none; +} + +.custom-select:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.custom-select:disabled { + color: #868e96; + background-color: #e9ecef; +} + +.custom-select::-ms-expand { + opacity: 0; +} + +.custom-select-sm { + height: calc(1.8125rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 75%; +} + +.custom-file { + position: relative; + display: inline-block; + max-width: 100%; + height: 2.5rem; + margin-bottom: 0; +} + +.custom-file-input { + min-width: 14rem; + max-width: 100%; + height: 2.5rem; + margin: 0; + opacity: 0; +} + +.custom-file-control { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 5; + height: 2.5rem; + padding: 0.5rem 1rem; + line-height: 1.5; + color: #495057; + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; +} + +.custom-file-control:lang(en):empty::after { + content: "Choose file..."; +} + +.custom-file-control::before { + position: absolute; + top: -1px; + right: -1px; + bottom: -1px; + z-index: 6; + display: block; + height: 2.5rem; + padding: 0.5rem 1rem; + line-height: 1.5; + color: #495057; + background-color: #e9ecef; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0 0.25rem 0.25rem 0; +} + +.custom-file-control:lang(en)::before { + content: "Browse"; +} + +.nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: 0.5rem 1rem; +} + +.nav-link:focus, .nav-link:hover { + text-decoration: none; +} + +.nav-link.disabled { + color: #868e96; +} + +.nav-tabs { + border-bottom: 1px solid #ddd; +} + +.nav-tabs .nav-item { + margin-bottom: -1px; +} + +.nav-tabs .nav-link { + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover { + border-color: #e9ecef #e9ecef #ddd; +} + +.nav-tabs .nav-link.disabled { + color: #868e96; + background-color: transparent; + border-color: transparent; +} + +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; + border-color: #ddd #ddd #fff; +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav-pills .nav-link { + border-radius: 0.25rem; +} + +.nav-pills .nav-link.active, +.show > .nav-pills .nav-link { + color: #fff; + background-color: #007bff; +} + +.nav-fill .nav-item { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + text-align: center; +} + +.nav-justified .nav-item { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + text-align: center; +} + +.tab-content > .tab-pane { + display: none; +} + +.tab-content > .active { + display: block; +} + +.navbar { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 0.5rem 1rem; +} + +.navbar > .container, +.navbar > .container-fluid { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.navbar-brand { + display: inline-block; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap; +} + +.navbar-brand:focus, .navbar-brand:hover { + text-decoration: none; +} + +.navbar-nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; +} + +.navbar-nav .dropdown-menu { + position: static; + float: none; +} + +.navbar-text { + display: inline-block; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.navbar-collapse { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + background: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.navbar-toggler:focus, .navbar-toggler:hover { + text-decoration: none; +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100%; +} + +@media (max-width: 575px) { + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 576px) { + .navbar-expand-sm { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-sm .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } +} + +@media (max-width: 767px) { + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 768px) { + .navbar-expand-md { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-md .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .navbar-expand-md .navbar-toggler { + display: none; + } +} + +@media (max-width: 991px) { + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 992px) { + .navbar-expand-lg { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-lg .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } +} + +@media (max-width: 1199px) { + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 1200px) { + .navbar-expand-xl { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-xl .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } +} + +.navbar-expand { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.navbar-expand > .container, +.navbar-expand > .container-fluid { + padding-right: 0; + padding-left: 0; +} + +.navbar-expand .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; +} + +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} + +.navbar-expand .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; +} + +.navbar-expand .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; +} + +.navbar-expand > .container, +.navbar-expand > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; +} + +.navbar-expand .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; +} + +.navbar-expand .navbar-toggler { + display: none; +} + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover { + color: rgba(0, 0, 0, 0.7); +} + +.navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); +} + +.navbar-light .navbar-nav .show > .nav-link, +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.show, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.1); +} + +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} + +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-dark .navbar-brand { + color: white; +} + +.navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover { + color: white; +} + +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.5); +} + +.navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover { + color: rgba(255, 255, 255, 0.75); +} + +.navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); +} + +.navbar-dark .navbar-nav .show > .nav-link, +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .nav-link.active { + color: white; +} + +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.5); + border-color: rgba(255, 255, 255, 0.1); +} + +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} + +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.5); +} + +.card { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; +} + +.card-body { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1.25rem; +} + +.card-title { + margin-bottom: 0.75rem; +} + +.card-subtitle { + margin-top: -0.375rem; + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link:hover { + text-decoration: none; +} + +.card-link + .card-link { + margin-left: 1.25rem; +} + +.card > .list-group:first-child .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.card > .list-group:last-child .list-group-item:last-child { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.card-header { + padding: 0.75rem 1.25rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} + +.card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; +} + +.card-footer { + padding: 0.75rem 1.25rem; + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 0, 0, 0.125); +} + +.card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); +} + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -0.75rem; + margin-left: -0.625rem; + border-bottom: 0; +} + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; +} + +.card-img { + width: 100%; + border-radius: calc(0.25rem - 1px); +} + +.card-img-top { + width: 100%; + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); +} + +.card-img-bottom { + width: 100%; + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); +} + +@media (min-width: 576px) { + .card-deck { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + margin-right: -15px; + margin-left: -15px; + } + .card-deck .card { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + margin-right: 15px; + margin-left: 15px; + } +} + +@media (min-width: 576px) { + .card-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + } + .card-group .card { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; + } + .card-group .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group .card:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group .card:first-child .card-img-top { + border-top-right-radius: 0; + } + .card-group .card:first-child .card-img-bottom { + border-bottom-right-radius: 0; + } + .card-group .card:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group .card:last-child .card-img-top { + border-top-left-radius: 0; + } + .card-group .card:last-child .card-img-bottom { + border-bottom-left-radius: 0; + } + .card-group .card:not(:first-child):not(:last-child) { + border-radius: 0; + } + .card-group .card:not(:first-child):not(:last-child) .card-img-top, + .card-group .card:not(:first-child):not(:last-child) .card-img-bottom { + border-radius: 0; + } +} + +.card-columns .card { + margin-bottom: 0.75rem; +} + +@media (min-width: 576px) { + .card-columns { + -webkit-column-count: 3; + column-count: 3; + -webkit-column-gap: 1.25rem; + column-gap: 1.25rem; + } + .card-columns .card { + display: inline-block; + width: 100%; + } +} + +.breadcrumb { + padding: 0.75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.breadcrumb::after { + display: block; + clear: both; + content: ""; +} + +.breadcrumb-item { + float: left; +} + +.breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: 0.5rem; + padding-left: 0.5rem; + color: #868e96; + content: "/"; +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline; +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none; +} + +.breadcrumb-item.active { + color: #868e96; +} + +.pagination { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding-left: 0; + list-style: none; + border-radius: 0.25rem; +} + +.page-item:first-child .page-link { + margin-left: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} + +.page-item.active .page-link { + z-index: 2; + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.page-item.disabled .page-link { + color: #868e96; + pointer-events: none; + background-color: #fff; + border-color: #ddd; +} + +.page-link { + position: relative; + display: block; + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #007bff; + background-color: #fff; + border: 1px solid #ddd; +} + +.page-link:focus, .page-link:hover { + color: #0056b3; + text-decoration: none; + background-color: #e9ecef; + border-color: #ddd; +} + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5; +} + +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; +} + +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.3rem; + border-bottom-right-radius: 0.3rem; +} + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; +} + +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; +} + +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; +} + +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; +} + +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -1px; +} + +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; +} + +.badge-primary { + color: #fff; + background-color: #007bff; +} + +.badge-primary[href]:focus, .badge-primary[href]:hover { + color: #fff; + text-decoration: none; + background-color: #0062cc; +} + +.badge-secondary { + color: #fff; + background-color: #868e96; +} + +.badge-secondary[href]:focus, .badge-secondary[href]:hover { + color: #fff; + text-decoration: none; + background-color: #6c757d; +} + +.badge-success { + color: #fff; + background-color: #28a745; +} + +.badge-success[href]:focus, .badge-success[href]:hover { + color: #fff; + text-decoration: none; + background-color: #1e7e34; +} + +.badge-info { + color: #fff; + background-color: #17a2b8; +} + +.badge-info[href]:focus, .badge-info[href]:hover { + color: #fff; + text-decoration: none; + background-color: #117a8b; +} + +.badge-warning { + color: #111; + background-color: #ffc107; +} + +.badge-warning[href]:focus, .badge-warning[href]:hover { + color: #111; + text-decoration: none; + background-color: #d39e00; +} + +.badge-danger { + color: #fff; + background-color: #dc3545; +} + +.badge-danger[href]:focus, .badge-danger[href]:hover { + color: #fff; + text-decoration: none; + background-color: #bd2130; +} + +.badge-light { + color: #111; + background-color: #f8f9fa; +} + +.badge-light[href]:focus, .badge-light[href]:hover { + color: #111; + text-decoration: none; + background-color: #dae0e5; +} + +.badge-dark { + color: #fff; + background-color: #343a40; +} + +.badge-dark[href]:focus, .badge-dark[href]:hover { + color: #fff; + text-decoration: none; + background-color: #1d2124; +} + +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #e9ecef; + border-radius: 0.3rem; +} + +@media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem; + } +} + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0; +} + +.alert { + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.alert-heading { + color: inherit; +} + +.alert-link { + font-weight: bold; +} + +.alert-dismissible .close { + position: relative; + top: -0.75rem; + right: -1.25rem; + padding: 0.75rem 1.25rem; + color: inherit; +} + +.alert-primary { + color: #004085; + background-color: #cce5ff; + border-color: #b8daff; +} + +.alert-primary hr { + border-top-color: #9fcdff; +} + +.alert-primary .alert-link { + color: #002752; +} + +.alert-secondary { + color: #464a4e; + background-color: #e7e8ea; + border-color: #dddfe2; +} + +.alert-secondary hr { + border-top-color: #cfd2d6; +} + +.alert-secondary .alert-link { + color: #2e3133; +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} + +.alert-success hr { + border-top-color: #b1dfbb; +} + +.alert-success .alert-link { + color: #0b2e13; +} + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; +} + +.alert-info hr { + border-top-color: #abdde5; +} + +.alert-info .alert-link { + color: #062c33; +} + +.alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeeba; +} + +.alert-warning hr { + border-top-color: #ffe8a1; +} + +.alert-warning .alert-link { + color: #533f03; +} + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} + +.alert-danger hr { + border-top-color: #f1b0b7; +} + +.alert-danger .alert-link { + color: #491217; +} + +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe; +} + +.alert-light hr { + border-top-color: #ececf6; +} + +.alert-light .alert-link { + color: #686868; +} + +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca; +} + +.alert-dark hr { + border-top-color: #b9bbbe; +} + +.alert-dark .alert-link { + color: #040505; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} + +.progress { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + overflow: hidden; + font-size: 0.75rem; + line-height: 1rem; + text-align: center; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.progress-bar { + height: 1rem; + line-height: 1rem; + color: #fff; + background-color: #007bff; + -webkit-transition: width 0.6s ease; + transition: width 0.6s ease; +} + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 1rem 1rem; +} + +.progress-bar-animated { + -webkit-animation: progress-bar-stripes 1s linear infinite; + animation: progress-bar-stripes 1s linear infinite; +} + +.media { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; +} + +.media-body { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.list-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; +} + +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit; +} + +.list-group-item-action:focus, .list-group-item-action:hover { + color: #495057; + text-decoration: none; + background-color: #f8f9fa; +} + +.list-group-item-action:active { + color: #212529; + background-color: #e9ecef; +} + +.list-group-item { + position: relative; + display: block; + padding: 0.75rem 1.25rem; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.125); +} + +.list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.list-group-item:focus, .list-group-item:hover { + text-decoration: none; +} + +.list-group-item.disabled, .list-group-item:disabled { + color: #868e96; + background-color: #fff; +} + +.list-group-item.active { + z-index: 2; + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.list-group-flush .list-group-item { + border-right: 0; + border-left: 0; + border-radius: 0; +} + +.list-group-flush:first-child .list-group-item:first-child { + border-top: 0; +} + +.list-group-flush:last-child .list-group-item:last-child { + border-bottom: 0; +} + +.list-group-item-primary { + color: #004085; + background-color: #b8daff; +} + +a.list-group-item-primary, +button.list-group-item-primary { + color: #004085; +} + +a.list-group-item-primary:focus, a.list-group-item-primary:hover, +button.list-group-item-primary:focus, +button.list-group-item-primary:hover { + color: #004085; + background-color: #9fcdff; +} + +a.list-group-item-primary.active, +button.list-group-item-primary.active { + color: #fff; + background-color: #004085; + border-color: #004085; +} + +.list-group-item-secondary { + color: #464a4e; + background-color: #dddfe2; +} + +a.list-group-item-secondary, +button.list-group-item-secondary { + color: #464a4e; +} + +a.list-group-item-secondary:focus, a.list-group-item-secondary:hover, +button.list-group-item-secondary:focus, +button.list-group-item-secondary:hover { + color: #464a4e; + background-color: #cfd2d6; +} + +a.list-group-item-secondary.active, +button.list-group-item-secondary.active { + color: #fff; + background-color: #464a4e; + border-color: #464a4e; +} + +.list-group-item-success { + color: #155724; + background-color: #c3e6cb; +} + +a.list-group-item-success, +button.list-group-item-success { + color: #155724; +} + +a.list-group-item-success:focus, a.list-group-item-success:hover, +button.list-group-item-success:focus, +button.list-group-item-success:hover { + color: #155724; + background-color: #b1dfbb; +} + +a.list-group-item-success.active, +button.list-group-item-success.active { + color: #fff; + background-color: #155724; + border-color: #155724; +} + +.list-group-item-info { + color: #0c5460; + background-color: #bee5eb; +} + +a.list-group-item-info, +button.list-group-item-info { + color: #0c5460; +} + +a.list-group-item-info:focus, a.list-group-item-info:hover, +button.list-group-item-info:focus, +button.list-group-item-info:hover { + color: #0c5460; + background-color: #abdde5; +} + +a.list-group-item-info.active, +button.list-group-item-info.active { + color: #fff; + background-color: #0c5460; + border-color: #0c5460; +} + +.list-group-item-warning { + color: #856404; + background-color: #ffeeba; +} + +a.list-group-item-warning, +button.list-group-item-warning { + color: #856404; +} + +a.list-group-item-warning:focus, a.list-group-item-warning:hover, +button.list-group-item-warning:focus, +button.list-group-item-warning:hover { + color: #856404; + background-color: #ffe8a1; +} + +a.list-group-item-warning.active, +button.list-group-item-warning.active { + color: #fff; + background-color: #856404; + border-color: #856404; +} + +.list-group-item-danger { + color: #721c24; + background-color: #f5c6cb; +} + +a.list-group-item-danger, +button.list-group-item-danger { + color: #721c24; +} + +a.list-group-item-danger:focus, a.list-group-item-danger:hover, +button.list-group-item-danger:focus, +button.list-group-item-danger:hover { + color: #721c24; + background-color: #f1b0b7; +} + +a.list-group-item-danger.active, +button.list-group-item-danger.active { + color: #fff; + background-color: #721c24; + border-color: #721c24; +} + +.list-group-item-light { + color: #818182; + background-color: #fdfdfe; +} + +a.list-group-item-light, +button.list-group-item-light { + color: #818182; +} + +a.list-group-item-light:focus, a.list-group-item-light:hover, +button.list-group-item-light:focus, +button.list-group-item-light:hover { + color: #818182; + background-color: #ececf6; +} + +a.list-group-item-light.active, +button.list-group-item-light.active { + color: #fff; + background-color: #818182; + border-color: #818182; +} + +.list-group-item-dark { + color: #1b1e21; + background-color: #c6c8ca; +} + +a.list-group-item-dark, +button.list-group-item-dark { + color: #1b1e21; +} + +a.list-group-item-dark:focus, a.list-group-item-dark:hover, +button.list-group-item-dark:focus, +button.list-group-item-dark:hover { + color: #1b1e21; + background-color: #b9bbbe; +} + +a.list-group-item-dark.active, +button.list-group-item-dark.active { + color: #fff; + background-color: #1b1e21; + border-color: #1b1e21; +} + +.close { + float: right; + font-size: 1.5rem; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: .5; +} + +.close:focus, .close:hover { + color: #000; + text-decoration: none; + opacity: .75; +} + +button.close { + padding: 0; + background: transparent; + border: 0; + -webkit-appearance: none; +} + +.modal-open { + overflow: hidden; +} + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + outline: 0; +} + +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform 0.3s ease-out; + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; + -webkit-transform: translate(0, -25%); + transform: translate(0, -25%); +} + +.modal.show .modal-dialog { + -webkit-transform: translate(0, 0); + transform: translate(0, 0); +} + +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} + +.modal-content { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + outline: 0; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} + +.modal-backdrop.fade { + opacity: 0; +} + +.modal-backdrop.show { + opacity: 0.5; +} + +.modal-header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 15px; + border-bottom: 1px solid #e9ecef; +} + +.modal-title { + margin-bottom: 0; + line-height: 1.5; +} + +.modal-body { + position: relative; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 15px; +} + +.modal-footer { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + padding: 15px; + border-top: 1px solid #e9ecef; +} + +.modal-footer > :not(:first-child) { + margin-left: .25rem; +} + +.modal-footer > :not(:last-child) { + margin-right: .25rem; +} + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 30px auto; + } + .modal-sm { + max-width: 300px; + } +} + +@media (min-width: 992px) { + .modal-lg { + max-width: 800px; + } +} + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-style: normal; + font-weight: normal; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + opacity: 0; +} + +.tooltip.show { + opacity: 0.9; +} + +.tooltip .arrow { + position: absolute; + display: block; + width: 5px; + height: 5px; +} + +.tooltip.bs-tooltip-top, .tooltip.bs-tooltip-auto[x-placement^="top"] { + padding: 5px 0; +} + +.tooltip.bs-tooltip-top .arrow, .tooltip.bs-tooltip-auto[x-placement^="top"] .arrow { + bottom: 0; +} + +.tooltip.bs-tooltip-top .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="top"] .arrow::before { + margin-left: -3px; + content: ""; + border-width: 5px 5px 0; + border-top-color: #000; +} + +.tooltip.bs-tooltip-right, .tooltip.bs-tooltip-auto[x-placement^="right"] { + padding: 0 5px; +} + +.tooltip.bs-tooltip-right .arrow, .tooltip.bs-tooltip-auto[x-placement^="right"] .arrow { + left: 0; +} + +.tooltip.bs-tooltip-right .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="right"] .arrow::before { + margin-top: -3px; + content: ""; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} + +.tooltip.bs-tooltip-bottom, .tooltip.bs-tooltip-auto[x-placement^="bottom"] { + padding: 5px 0; +} + +.tooltip.bs-tooltip-bottom .arrow, .tooltip.bs-tooltip-auto[x-placement^="bottom"] .arrow { + top: 0; +} + +.tooltip.bs-tooltip-bottom .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="bottom"] .arrow::before { + margin-left: -3px; + content: ""; + border-width: 0 5px 5px; + border-bottom-color: #000; +} + +.tooltip.bs-tooltip-left, .tooltip.bs-tooltip-auto[x-placement^="left"] { + padding: 0 5px; +} + +.tooltip.bs-tooltip-left .arrow, .tooltip.bs-tooltip-auto[x-placement^="left"] .arrow { + right: 0; +} + +.tooltip.bs-tooltip-left .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="left"] .arrow::before { + right: 0; + margin-top: -3px; + content: ""; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} + +.tooltip .arrow::before { + position: absolute; + border-color: transparent; + border-style: solid; +} + +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + padding: 1px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-style: normal; + font-weight: normal; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; +} + +.popover .arrow { + position: absolute; + display: block; + width: 10px; + height: 5px; +} + +.popover .arrow::before, +.popover .arrow::after { + position: absolute; + display: block; + border-color: transparent; + border-style: solid; +} + +.popover .arrow::before { + content: ""; + border-width: 11px; +} + +.popover .arrow::after { + content: ""; + border-width: 11px; +} + +.popover.bs-popover-top, .popover.bs-popover-auto[x-placement^="top"] { + margin-bottom: 10px; +} + +.popover.bs-popover-top .arrow, .popover.bs-popover-auto[x-placement^="top"] .arrow { + bottom: 0; +} + +.popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^="top"] .arrow::before, +.popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^="top"] .arrow::after { + border-bottom-width: 0; +} + +.popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^="top"] .arrow::before { + bottom: -11px; + margin-left: -6px; + border-top-color: rgba(0, 0, 0, 0.25); +} + +.popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^="top"] .arrow::after { + bottom: -10px; + margin-left: -6px; + border-top-color: #fff; +} + +.popover.bs-popover-right, .popover.bs-popover-auto[x-placement^="right"] { + margin-left: 10px; +} + +.popover.bs-popover-right .arrow, .popover.bs-popover-auto[x-placement^="right"] .arrow { + left: 0; +} + +.popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^="right"] .arrow::before, +.popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^="right"] .arrow::after { + margin-top: -8px; + border-left-width: 0; +} + +.popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^="right"] .arrow::before { + left: -11px; + border-right-color: rgba(0, 0, 0, 0.25); +} + +.popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^="right"] .arrow::after { + left: -10px; + border-right-color: #fff; +} + +.popover.bs-popover-bottom, .popover.bs-popover-auto[x-placement^="bottom"] { + margin-top: 10px; +} + +.popover.bs-popover-bottom .arrow, .popover.bs-popover-auto[x-placement^="bottom"] .arrow { + top: 0; +} + +.popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::before, +.popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::after { + margin-left: -7px; + border-top-width: 0; +} + +.popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::before { + top: -11px; + border-bottom-color: rgba(0, 0, 0, 0.25); +} + +.popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::after { + top: -10px; + border-bottom-color: #fff; +} + +.popover.bs-popover-bottom .popover-header::before, .popover.bs-popover-auto[x-placement^="bottom"] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 20px; + margin-left: -10px; + content: ""; + border-bottom: 1px solid #f7f7f7; +} + +.popover.bs-popover-left, .popover.bs-popover-auto[x-placement^="left"] { + margin-right: 10px; +} + +.popover.bs-popover-left .arrow, .popover.bs-popover-auto[x-placement^="left"] .arrow { + right: 0; +} + +.popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^="left"] .arrow::before, +.popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^="left"] .arrow::after { + margin-top: -8px; + border-right-width: 0; +} + +.popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^="left"] .arrow::before { + right: -11px; + border-left-color: rgba(0, 0, 0, 0.25); +} + +.popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^="left"] .arrow::after { + right: -10px; + border-left-color: #fff; +} + +.popover-header { + padding: 8px 14px; + margin-bottom: 0; + font-size: 1rem; + color: inherit; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); +} + +.popover-header:empty { + display: none; +} + +.popover-body { + padding: 9px 14px; + color: #212529; +} + +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-item { + position: relative; + display: none; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + width: 100%; + -webkit-transition: -webkit-transform 0.6s ease; + transition: -webkit-transform 0.6s ease; + transition: transform 0.6s ease; + transition: transform 0.6s ease, -webkit-transform 0.6s ease; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next, +.carousel-item-prev { + position: absolute; + top: 0; +} + +.carousel-item-next.carousel-item-left, +.carousel-item-prev.carousel-item-right { + -webkit-transform: translateX(0); + transform: translateX(0); +} + +@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { + .carousel-item-next.carousel-item-left, + .carousel-item-prev.carousel-item-right { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.carousel-item-next, +.active.carousel-item-right { + -webkit-transform: translateX(100%); + transform: translateX(100%); +} + +@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { + .carousel-item-next, + .active.carousel-item-right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.carousel-item-prev, +.active.carousel-item-left { + -webkit-transform: translateX(-100%); + transform: translateX(-100%); +} + +@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { + .carousel-item-prev, + .active.carousel-item-left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + width: 15%; + color: #fff; + text-align: center; + opacity: 0.5; +} + +.carousel-control-prev:focus, .carousel-control-prev:hover, +.carousel-control-next:focus, +.carousel-control-next:hover { + color: #fff; + text-decoration: none; + outline: 0; + opacity: .9; +} + +.carousel-control-prev { + left: 0; +} + +.carousel-control-next { + right: 0; +} + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 20px; + height: 20px; + background: transparent no-repeat center center; + background-size: 100% 100%; +} + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M4 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"); +} + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M1.5 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"); +} + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 10px; + left: 0; + z-index: 15; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none; +} + +.carousel-indicators li { + position: relative; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + background-color: rgba(255, 255, 255, 0.5); +} + +.carousel-indicators li::before { + position: absolute; + top: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; +} + +.carousel-indicators li::after { + position: absolute; + bottom: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; +} + +.carousel-indicators .active { + background-color: #fff; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; +} + +.align-baseline { + vertical-align: baseline !important; +} + +.align-top { + vertical-align: top !important; +} + +.align-middle { + vertical-align: middle !important; +} + +.align-bottom { + vertical-align: bottom !important; +} + +.align-text-bottom { + vertical-align: text-bottom !important; +} + +.align-text-top { + vertical-align: text-top !important; +} + +.bg-primary { + background-color: #007bff !important; +} + +a.bg-primary:focus, a.bg-primary:hover { + background-color: #0062cc !important; +} + +.bg-secondary { + background-color: #868e96 !important; +} + +a.bg-secondary:focus, a.bg-secondary:hover { + background-color: #6c757d !important; +} + +.bg-success { + background-color: #28a745 !important; +} + +a.bg-success:focus, a.bg-success:hover { + background-color: #1e7e34 !important; +} + +.bg-info { + background-color: #17a2b8 !important; +} + +a.bg-info:focus, a.bg-info:hover { + background-color: #117a8b !important; +} + +.bg-warning { + background-color: #ffc107 !important; +} + +a.bg-warning:focus, a.bg-warning:hover { + background-color: #d39e00 !important; +} + +.bg-danger { + background-color: #dc3545 !important; +} + +a.bg-danger:focus, a.bg-danger:hover { + background-color: #bd2130 !important; +} + +.bg-light { + background-color: #f8f9fa !important; +} + +a.bg-light:focus, a.bg-light:hover { + background-color: #dae0e5 !important; +} + +.bg-dark { + background-color: #343a40 !important; +} + +a.bg-dark:focus, a.bg-dark:hover { + background-color: #1d2124 !important; +} + +.bg-white { + background-color: #fff !important; +} + +.bg-transparent { + background-color: transparent !important; +} + +.border { + border: 1px solid #e9ecef !important; +} + +.border-0 { + border: 0 !important; +} + +.border-top-0 { + border-top: 0 !important; +} + +.border-right-0 { + border-right: 0 !important; +} + +.border-bottom-0 { + border-bottom: 0 !important; +} + +.border-left-0 { + border-left: 0 !important; +} + +.border-primary { + border-color: #007bff !important; +} + +.border-secondary { + border-color: #868e96 !important; +} + +.border-success { + border-color: #28a745 !important; +} + +.border-info { + border-color: #17a2b8 !important; +} + +.border-warning { + border-color: #ffc107 !important; +} + +.border-danger { + border-color: #dc3545 !important; +} + +.border-light { + border-color: #f8f9fa !important; +} + +.border-dark { + border-color: #343a40 !important; +} + +.border-white { + border-color: #fff !important; +} + +.rounded { + border-radius: 0.25rem !important; +} + +.rounded-top { + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; +} + +.rounded-right { + border-top-right-radius: 0.25rem !important; + border-bottom-right-radius: 0.25rem !important; +} + +.rounded-bottom { + border-bottom-right-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-left { + border-top-left-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-circle { + border-radius: 50%; +} + +.rounded-0 { + border-radius: 0; +} + +.clearfix::after { + display: block; + clear: both; + content: ""; +} + +.d-none { + display: none !important; +} + +.d-inline { + display: inline !important; +} + +.d-inline-block { + display: inline-block !important; +} + +.d-block { + display: block !important; +} + +.d-table { + display: table !important; +} + +.d-table-cell { + display: table-cell !important; +} + +.d-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; +} + +.d-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; +} + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-sm-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 768px) { + .d-md-none { + display: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-md-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 992px) { + .d-lg-none { + display: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-lg-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-xl-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +.d-print-block { + display: none !important; +} + +@media print { + .d-print-block { + display: block !important; + } +} + +.d-print-inline { + display: none !important; +} + +@media print { + .d-print-inline { + display: inline !important; + } +} + +.d-print-inline-block { + display: none !important; +} + +@media print { + .d-print-inline-block { + display: inline-block !important; + } +} + +@media print { + .d-print-none { + display: none !important; + } +} + +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; +} + +.embed-responsive::before { + display: block; + content: ""; +} + +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} + +.embed-responsive-21by9::before { + padding-top: 42.85714%; +} + +.embed-responsive-16by9::before { + padding-top: 56.25%; +} + +.embed-responsive-4by3::before { + padding-top: 75%; +} + +.embed-responsive-1by1::before { + padding-top: 100%; +} + +.flex-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; +} + +.flex-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; +} + +.flex-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; +} + +.flex-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; +} + +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; +} + +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; +} + +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; +} + +.justify-content-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; +} + +.justify-content-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} + +.justify-content-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; +} + +.justify-content-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; +} + +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; +} + +.align-items-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; +} + +.align-items-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; +} + +.align-items-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; +} + +.align-items-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; +} + +.align-items-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; +} + +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; +} + +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; +} + +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; +} + +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; +} + +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; +} + +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; +} + +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; +} + +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; +} + +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; +} + +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; +} + +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; +} + +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; +} + +@media (min-width: 576px) { + .flex-sm-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-sm-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-sm-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-sm-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-sm-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-sm-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-sm-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-sm-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-sm-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-sm-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-sm-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-sm-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 768px) { + .flex-md-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-md-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-md-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-md-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-md-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-md-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-md-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-md-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-md-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-md-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-md-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-md-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 992px) { + .flex-lg-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-lg-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-lg-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-lg-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-lg-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-lg-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-lg-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-lg-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-lg-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-lg-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-lg-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-lg-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 1200px) { + .flex-xl-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-xl-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-xl-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-xl-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-xl-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-xl-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-xl-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-xl-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-xl-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-xl-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-xl-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-xl-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +.float-left { + float: left !important; +} + +.float-right { + float: right !important; +} + +.float-none { + float: none !important; +} + +@media (min-width: 576px) { + .float-sm-left { + float: left !important; + } + .float-sm-right { + float: right !important; + } + .float-sm-none { + float: none !important; + } +} + +@media (min-width: 768px) { + .float-md-left { + float: left !important; + } + .float-md-right { + float: right !important; + } + .float-md-none { + float: none !important; + } +} + +@media (min-width: 992px) { + .float-lg-left { + float: left !important; + } + .float-lg-right { + float: right !important; + } + .float-lg-none { + float: none !important; + } +} + +@media (min-width: 1200px) { + .float-xl-left { + float: left !important; + } + .float-xl-right { + float: right !important; + } + .float-xl-none { + float: none !important; + } +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; +} + +@supports ((position: -webkit-sticky) or (position: sticky)) { + .sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + border: 0; +} + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; + -webkit-clip-path: none; + clip-path: none; +} + +.w-25 { + width: 25% !important; +} + +.w-50 { + width: 50% !important; +} + +.w-75 { + width: 75% !important; +} + +.w-100 { + width: 100% !important; +} + +.h-25 { + height: 25% !important; +} + +.h-50 { + height: 50% !important; +} + +.h-75 { + height: 75% !important; +} + +.h-100 { + height: 100% !important; +} + +.mw-100 { + max-width: 100% !important; +} + +.mh-100 { + max-height: 100% !important; +} + +.m-0 { + margin: 0 !important; +} + +.mt-0 { + margin-top: 0 !important; +} + +.mr-0 { + margin-right: 0 !important; +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.ml-0 { + margin-left: 0 !important; +} + +.mx-0 { + margin-right: 0 !important; + margin-left: 0 !important; +} + +.my-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.mt-1 { + margin-top: 0.25rem !important; +} + +.mr-1 { + margin-right: 0.25rem !important; +} + +.mb-1 { + margin-bottom: 0.25rem !important; +} + +.ml-1 { + margin-left: 0.25rem !important; +} + +.mx-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; +} + +.my-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.mt-2 { + margin-top: 0.5rem !important; +} + +.mr-2 { + margin-right: 0.5rem !important; +} + +.mb-2 { + margin-bottom: 0.5rem !important; +} + +.ml-2 { + margin-left: 0.5rem !important; +} + +.mx-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; +} + +.my-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; +} + +.m-3 { + margin: 1rem !important; +} + +.mt-3 { + margin-top: 1rem !important; +} + +.mr-3 { + margin-right: 1rem !important; +} + +.mb-3 { + margin-bottom: 1rem !important; +} + +.ml-3 { + margin-left: 1rem !important; +} + +.mx-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; +} + +.my-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; +} + +.m-4 { + margin: 1.5rem !important; +} + +.mt-4 { + margin-top: 1.5rem !important; +} + +.mr-4 { + margin-right: 1.5rem !important; +} + +.mb-4 { + margin-bottom: 1.5rem !important; +} + +.ml-4 { + margin-left: 1.5rem !important; +} + +.mx-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; +} + +.my-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} + +.m-5 { + margin: 3rem !important; +} + +.mt-5 { + margin-top: 3rem !important; +} + +.mr-5 { + margin-right: 3rem !important; +} + +.mb-5 { + margin-bottom: 3rem !important; +} + +.ml-5 { + margin-left: 3rem !important; +} + +.mx-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; +} + +.my-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; +} + +.p-0 { + padding: 0 !important; +} + +.pt-0 { + padding-top: 0 !important; +} + +.pr-0 { + padding-right: 0 !important; +} + +.pb-0 { + padding-bottom: 0 !important; +} + +.pl-0 { + padding-left: 0 !important; +} + +.px-0 { + padding-right: 0 !important; + padding-left: 0 !important; +} + +.py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.pt-1 { + padding-top: 0.25rem !important; +} + +.pr-1 { + padding-right: 0.25rem !important; +} + +.pb-1 { + padding-bottom: 0.25rem !important; +} + +.pl-1 { + padding-left: 0.25rem !important; +} + +.px-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; +} + +.py-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.pt-2 { + padding-top: 0.5rem !important; +} + +.pr-2 { + padding-right: 0.5rem !important; +} + +.pb-2 { + padding-bottom: 0.5rem !important; +} + +.pl-2 { + padding-left: 0.5rem !important; +} + +.px-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; +} + +.py-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; +} + +.p-3 { + padding: 1rem !important; +} + +.pt-3 { + padding-top: 1rem !important; +} + +.pr-3 { + padding-right: 1rem !important; +} + +.pb-3 { + padding-bottom: 1rem !important; +} + +.pl-3 { + padding-left: 1rem !important; +} + +.px-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; +} + +.py-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; +} + +.p-4 { + padding: 1.5rem !important; +} + +.pt-4 { + padding-top: 1.5rem !important; +} + +.pr-4 { + padding-right: 1.5rem !important; +} + +.pb-4 { + padding-bottom: 1.5rem !important; +} + +.pl-4 { + padding-left: 1.5rem !important; +} + +.px-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; +} + +.py-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; +} + +.p-5 { + padding: 3rem !important; +} + +.pt-5 { + padding-top: 3rem !important; +} + +.pr-5 { + padding-right: 3rem !important; +} + +.pb-5 { + padding-bottom: 3rem !important; +} + +.pl-5 { + padding-left: 3rem !important; +} + +.px-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; +} + +.py-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mt-auto { + margin-top: auto !important; +} + +.mr-auto { + margin-right: auto !important; +} + +.mb-auto { + margin-bottom: auto !important; +} + +.ml-auto { + margin-left: auto !important; +} + +.mx-auto { + margin-right: auto !important; + margin-left: auto !important; +} + +.my-auto { + margin-top: auto !important; + margin-bottom: auto !important; +} + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; + } + .mt-sm-0 { + margin-top: 0 !important; + } + .mr-sm-0 { + margin-right: 0 !important; + } + .mb-sm-0 { + margin-bottom: 0 !important; + } + .ml-sm-0 { + margin-left: 0 !important; + } + .mx-sm-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .my-sm-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .mt-sm-1 { + margin-top: 0.25rem !important; + } + .mr-sm-1 { + margin-right: 0.25rem !important; + } + .mb-sm-1 { + margin-bottom: 0.25rem !important; + } + .ml-sm-1 { + margin-left: 0.25rem !important; + } + .mx-sm-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .my-sm-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .mt-sm-2 { + margin-top: 0.5rem !important; + } + .mr-sm-2 { + margin-right: 0.5rem !important; + } + .mb-sm-2 { + margin-bottom: 0.5rem !important; + } + .ml-sm-2 { + margin-left: 0.5rem !important; + } + .mx-sm-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .my-sm-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .mt-sm-3 { + margin-top: 1rem !important; + } + .mr-sm-3 { + margin-right: 1rem !important; + } + .mb-sm-3 { + margin-bottom: 1rem !important; + } + .ml-sm-3 { + margin-left: 1rem !important; + } + .mx-sm-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .my-sm-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .mt-sm-4 { + margin-top: 1.5rem !important; + } + .mr-sm-4 { + margin-right: 1.5rem !important; + } + .mb-sm-4 { + margin-bottom: 1.5rem !important; + } + .ml-sm-4 { + margin-left: 1.5rem !important; + } + .mx-sm-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .my-sm-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .mt-sm-5 { + margin-top: 3rem !important; + } + .mr-sm-5 { + margin-right: 3rem !important; + } + .mb-sm-5 { + margin-bottom: 3rem !important; + } + .ml-sm-5 { + margin-left: 3rem !important; + } + .mx-sm-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .my-sm-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .p-sm-0 { + padding: 0 !important; + } + .pt-sm-0 { + padding-top: 0 !important; + } + .pr-sm-0 { + padding-right: 0 !important; + } + .pb-sm-0 { + padding-bottom: 0 !important; + } + .pl-sm-0 { + padding-left: 0 !important; + } + .px-sm-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .py-sm-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .pt-sm-1 { + padding-top: 0.25rem !important; + } + .pr-sm-1 { + padding-right: 0.25rem !important; + } + .pb-sm-1 { + padding-bottom: 0.25rem !important; + } + .pl-sm-1 { + padding-left: 0.25rem !important; + } + .px-sm-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .py-sm-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .pt-sm-2 { + padding-top: 0.5rem !important; + } + .pr-sm-2 { + padding-right: 0.5rem !important; + } + .pb-sm-2 { + padding-bottom: 0.5rem !important; + } + .pl-sm-2 { + padding-left: 0.5rem !important; + } + .px-sm-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .py-sm-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .pt-sm-3 { + padding-top: 1rem !important; + } + .pr-sm-3 { + padding-right: 1rem !important; + } + .pb-sm-3 { + padding-bottom: 1rem !important; + } + .pl-sm-3 { + padding-left: 1rem !important; + } + .px-sm-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .py-sm-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .pt-sm-4 { + padding-top: 1.5rem !important; + } + .pr-sm-4 { + padding-right: 1.5rem !important; + } + .pb-sm-4 { + padding-bottom: 1.5rem !important; + } + .pl-sm-4 { + padding-left: 1.5rem !important; + } + .px-sm-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .py-sm-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .pt-sm-5 { + padding-top: 3rem !important; + } + .pr-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-5 { + padding-bottom: 3rem !important; + } + .pl-sm-5 { + padding-left: 3rem !important; + } + .px-sm-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-sm-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mt-sm-auto { + margin-top: auto !important; + } + .mr-sm-auto { + margin-right: auto !important; + } + .mb-sm-auto { + margin-bottom: auto !important; + } + .ml-sm-auto { + margin-left: auto !important; + } + .mx-sm-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-sm-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } +} + +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; + } + .mt-md-0 { + margin-top: 0 !important; + } + .mr-md-0 { + margin-right: 0 !important; + } + .mb-md-0 { + margin-bottom: 0 !important; + } + .ml-md-0 { + margin-left: 0 !important; + } + .mx-md-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .my-md-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .mt-md-1 { + margin-top: 0.25rem !important; + } + .mr-md-1 { + margin-right: 0.25rem !important; + } + .mb-md-1 { + margin-bottom: 0.25rem !important; + } + .ml-md-1 { + margin-left: 0.25rem !important; + } + .mx-md-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .my-md-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .mt-md-2 { + margin-top: 0.5rem !important; + } + .mr-md-2 { + margin-right: 0.5rem !important; + } + .mb-md-2 { + margin-bottom: 0.5rem !important; + } + .ml-md-2 { + margin-left: 0.5rem !important; + } + .mx-md-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .my-md-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .mt-md-3 { + margin-top: 1rem !important; + } + .mr-md-3 { + margin-right: 1rem !important; + } + .mb-md-3 { + margin-bottom: 1rem !important; + } + .ml-md-3 { + margin-left: 1rem !important; + } + .mx-md-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .my-md-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .mt-md-4 { + margin-top: 1.5rem !important; + } + .mr-md-4 { + margin-right: 1.5rem !important; + } + .mb-md-4 { + margin-bottom: 1.5rem !important; + } + .ml-md-4 { + margin-left: 1.5rem !important; + } + .mx-md-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .my-md-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .mt-md-5 { + margin-top: 3rem !important; + } + .mr-md-5 { + margin-right: 3rem !important; + } + .mb-md-5 { + margin-bottom: 3rem !important; + } + .ml-md-5 { + margin-left: 3rem !important; + } + .mx-md-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .my-md-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .p-md-0 { + padding: 0 !important; + } + .pt-md-0 { + padding-top: 0 !important; + } + .pr-md-0 { + padding-right: 0 !important; + } + .pb-md-0 { + padding-bottom: 0 !important; + } + .pl-md-0 { + padding-left: 0 !important; + } + .px-md-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .py-md-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .pt-md-1 { + padding-top: 0.25rem !important; + } + .pr-md-1 { + padding-right: 0.25rem !important; + } + .pb-md-1 { + padding-bottom: 0.25rem !important; + } + .pl-md-1 { + padding-left: 0.25rem !important; + } + .px-md-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .py-md-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .pt-md-2 { + padding-top: 0.5rem !important; + } + .pr-md-2 { + padding-right: 0.5rem !important; + } + .pb-md-2 { + padding-bottom: 0.5rem !important; + } + .pl-md-2 { + padding-left: 0.5rem !important; + } + .px-md-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .py-md-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .pt-md-3 { + padding-top: 1rem !important; + } + .pr-md-3 { + padding-right: 1rem !important; + } + .pb-md-3 { + padding-bottom: 1rem !important; + } + .pl-md-3 { + padding-left: 1rem !important; + } + .px-md-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .py-md-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .pt-md-4 { + padding-top: 1.5rem !important; + } + .pr-md-4 { + padding-right: 1.5rem !important; + } + .pb-md-4 { + padding-bottom: 1.5rem !important; + } + .pl-md-4 { + padding-left: 1.5rem !important; + } + .px-md-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .py-md-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .pt-md-5 { + padding-top: 3rem !important; + } + .pr-md-5 { + padding-right: 3rem !important; + } + .pb-md-5 { + padding-bottom: 3rem !important; + } + .pl-md-5 { + padding-left: 3rem !important; + } + .px-md-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-md-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mt-md-auto { + margin-top: auto !important; + } + .mr-md-auto { + margin-right: auto !important; + } + .mb-md-auto { + margin-bottom: auto !important; + } + .ml-md-auto { + margin-left: auto !important; + } + .mx-md-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-md-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } +} + +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; + } + .mt-lg-0 { + margin-top: 0 !important; + } + .mr-lg-0 { + margin-right: 0 !important; + } + .mb-lg-0 { + margin-bottom: 0 !important; + } + .ml-lg-0 { + margin-left: 0 !important; + } + .mx-lg-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .my-lg-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .mt-lg-1 { + margin-top: 0.25rem !important; + } + .mr-lg-1 { + margin-right: 0.25rem !important; + } + .mb-lg-1 { + margin-bottom: 0.25rem !important; + } + .ml-lg-1 { + margin-left: 0.25rem !important; + } + .mx-lg-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .my-lg-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .mt-lg-2 { + margin-top: 0.5rem !important; + } + .mr-lg-2 { + margin-right: 0.5rem !important; + } + .mb-lg-2 { + margin-bottom: 0.5rem !important; + } + .ml-lg-2 { + margin-left: 0.5rem !important; + } + .mx-lg-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .my-lg-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .mt-lg-3 { + margin-top: 1rem !important; + } + .mr-lg-3 { + margin-right: 1rem !important; + } + .mb-lg-3 { + margin-bottom: 1rem !important; + } + .ml-lg-3 { + margin-left: 1rem !important; + } + .mx-lg-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .my-lg-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .mt-lg-4 { + margin-top: 1.5rem !important; + } + .mr-lg-4 { + margin-right: 1.5rem !important; + } + .mb-lg-4 { + margin-bottom: 1.5rem !important; + } + .ml-lg-4 { + margin-left: 1.5rem !important; + } + .mx-lg-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .my-lg-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .mt-lg-5 { + margin-top: 3rem !important; + } + .mr-lg-5 { + margin-right: 3rem !important; + } + .mb-lg-5 { + margin-bottom: 3rem !important; + } + .ml-lg-5 { + margin-left: 3rem !important; + } + .mx-lg-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .my-lg-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .p-lg-0 { + padding: 0 !important; + } + .pt-lg-0 { + padding-top: 0 !important; + } + .pr-lg-0 { + padding-right: 0 !important; + } + .pb-lg-0 { + padding-bottom: 0 !important; + } + .pl-lg-0 { + padding-left: 0 !important; + } + .px-lg-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .py-lg-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .pt-lg-1 { + padding-top: 0.25rem !important; + } + .pr-lg-1 { + padding-right: 0.25rem !important; + } + .pb-lg-1 { + padding-bottom: 0.25rem !important; + } + .pl-lg-1 { + padding-left: 0.25rem !important; + } + .px-lg-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .py-lg-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .pt-lg-2 { + padding-top: 0.5rem !important; + } + .pr-lg-2 { + padding-right: 0.5rem !important; + } + .pb-lg-2 { + padding-bottom: 0.5rem !important; + } + .pl-lg-2 { + padding-left: 0.5rem !important; + } + .px-lg-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .py-lg-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .pt-lg-3 { + padding-top: 1rem !important; + } + .pr-lg-3 { + padding-right: 1rem !important; + } + .pb-lg-3 { + padding-bottom: 1rem !important; + } + .pl-lg-3 { + padding-left: 1rem !important; + } + .px-lg-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .py-lg-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .pt-lg-4 { + padding-top: 1.5rem !important; + } + .pr-lg-4 { + padding-right: 1.5rem !important; + } + .pb-lg-4 { + padding-bottom: 1.5rem !important; + } + .pl-lg-4 { + padding-left: 1.5rem !important; + } + .px-lg-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .py-lg-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .pt-lg-5 { + padding-top: 3rem !important; + } + .pr-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-5 { + padding-bottom: 3rem !important; + } + .pl-lg-5 { + padding-left: 3rem !important; + } + .px-lg-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-lg-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mt-lg-auto { + margin-top: auto !important; + } + .mr-lg-auto { + margin-right: auto !important; + } + .mb-lg-auto { + margin-bottom: auto !important; + } + .ml-lg-auto { + margin-left: auto !important; + } + .mx-lg-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-lg-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } +} + +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; + } + .mt-xl-0 { + margin-top: 0 !important; + } + .mr-xl-0 { + margin-right: 0 !important; + } + .mb-xl-0 { + margin-bottom: 0 !important; + } + .ml-xl-0 { + margin-left: 0 !important; + } + .mx-xl-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .my-xl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .mt-xl-1 { + margin-top: 0.25rem !important; + } + .mr-xl-1 { + margin-right: 0.25rem !important; + } + .mb-xl-1 { + margin-bottom: 0.25rem !important; + } + .ml-xl-1 { + margin-left: 0.25rem !important; + } + .mx-xl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .my-xl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .mt-xl-2 { + margin-top: 0.5rem !important; + } + .mr-xl-2 { + margin-right: 0.5rem !important; + } + .mb-xl-2 { + margin-bottom: 0.5rem !important; + } + .ml-xl-2 { + margin-left: 0.5rem !important; + } + .mx-xl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .my-xl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .mt-xl-3 { + margin-top: 1rem !important; + } + .mr-xl-3 { + margin-right: 1rem !important; + } + .mb-xl-3 { + margin-bottom: 1rem !important; + } + .ml-xl-3 { + margin-left: 1rem !important; + } + .mx-xl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .my-xl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .mt-xl-4 { + margin-top: 1.5rem !important; + } + .mr-xl-4 { + margin-right: 1.5rem !important; + } + .mb-xl-4 { + margin-bottom: 1.5rem !important; + } + .ml-xl-4 { + margin-left: 1.5rem !important; + } + .mx-xl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .my-xl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .mt-xl-5 { + margin-top: 3rem !important; + } + .mr-xl-5 { + margin-right: 3rem !important; + } + .mb-xl-5 { + margin-bottom: 3rem !important; + } + .ml-xl-5 { + margin-left: 3rem !important; + } + .mx-xl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .my-xl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .p-xl-0 { + padding: 0 !important; + } + .pt-xl-0 { + padding-top: 0 !important; + } + .pr-xl-0 { + padding-right: 0 !important; + } + .pb-xl-0 { + padding-bottom: 0 !important; + } + .pl-xl-0 { + padding-left: 0 !important; + } + .px-xl-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .py-xl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .pt-xl-1 { + padding-top: 0.25rem !important; + } + .pr-xl-1 { + padding-right: 0.25rem !important; + } + .pb-xl-1 { + padding-bottom: 0.25rem !important; + } + .pl-xl-1 { + padding-left: 0.25rem !important; + } + .px-xl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .py-xl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .pt-xl-2 { + padding-top: 0.5rem !important; + } + .pr-xl-2 { + padding-right: 0.5rem !important; + } + .pb-xl-2 { + padding-bottom: 0.5rem !important; + } + .pl-xl-2 { + padding-left: 0.5rem !important; + } + .px-xl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .py-xl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .pt-xl-3 { + padding-top: 1rem !important; + } + .pr-xl-3 { + padding-right: 1rem !important; + } + .pb-xl-3 { + padding-bottom: 1rem !important; + } + .pl-xl-3 { + padding-left: 1rem !important; + } + .px-xl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .py-xl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .pt-xl-4 { + padding-top: 1.5rem !important; + } + .pr-xl-4 { + padding-right: 1.5rem !important; + } + .pb-xl-4 { + padding-bottom: 1.5rem !important; + } + .pl-xl-4 { + padding-left: 1.5rem !important; + } + .px-xl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .py-xl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .pt-xl-5 { + padding-top: 3rem !important; + } + .pr-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-5 { + padding-bottom: 3rem !important; + } + .pl-xl-5 { + padding-left: 3rem !important; + } + .px-xl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-xl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mt-xl-auto { + margin-top: auto !important; + } + .mr-xl-auto { + margin-right: auto !important; + } + .mb-xl-auto { + margin-bottom: auto !important; + } + .ml-xl-auto { + margin-left: auto !important; + } + .mx-xl-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-xl-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } +} + +.text-justify { + text-align: justify !important; +} + +.text-nowrap { + white-space: nowrap !important; +} + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-left { + text-align: left !important; +} + +.text-right { + text-align: right !important; +} + +.text-center { + text-align: center !important; +} + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; + } + .text-sm-right { + text-align: right !important; + } + .text-sm-center { + text-align: center !important; + } +} + +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; + } + .text-md-right { + text-align: right !important; + } + .text-md-center { + text-align: center !important; + } +} + +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; + } + .text-lg-right { + text-align: right !important; + } + .text-lg-center { + text-align: center !important; + } +} + +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; + } + .text-xl-right { + text-align: right !important; + } + .text-xl-center { + text-align: center !important; + } +} + +.text-lowercase { + text-transform: lowercase !important; +} + +.text-uppercase { + text-transform: uppercase !important; +} + +.text-capitalize { + text-transform: capitalize !important; +} + +.font-weight-normal { + font-weight: normal; +} + +.font-weight-bold { + font-weight: bold; +} + +.font-italic { + font-style: italic; +} + +.text-white { + color: #fff !important; +} + +.text-primary { + color: #007bff !important; +} + +a.text-primary:focus, a.text-primary:hover { + color: #0062cc !important; +} + +.text-secondary { + color: #868e96 !important; +} + +a.text-secondary:focus, a.text-secondary:hover { + color: #6c757d !important; +} + +.text-success { + color: #28a745 !important; +} + +a.text-success:focus, a.text-success:hover { + color: #1e7e34 !important; +} + +.text-info { + color: #17a2b8 !important; +} + +a.text-info:focus, a.text-info:hover { + color: #117a8b !important; +} + +.text-warning { + color: #ffc107 !important; +} + +a.text-warning:focus, a.text-warning:hover { + color: #d39e00 !important; +} + +.text-danger { + color: #dc3545 !important; +} + +a.text-danger:focus, a.text-danger:hover { + color: #bd2130 !important; +} + +.text-light { + color: #f8f9fa !important; +} + +a.text-light:focus, a.text-light:hover { + color: #dae0e5 !important; +} + +.text-dark { + color: #343a40 !important; +} + +a.text-dark:focus, a.text-dark:hover { + color: #1d2124 !important; +} + +.text-muted { + color: #868e96 !important; +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.visible { + visibility: visible !important; +} + +.invisible { + visibility: hidden !important; +} + +/*# sourceMappingURL=../../maps/admin/bootstrap/bootstrap.css.map */ diff --git a/public/assets/css/admin/maps/article.css.map b/public/assets/css/admin/maps/article.css.map new file mode 100644 index 0000000..7a1e4a8 --- /dev/null +++ b/public/assets/css/admin/maps/article.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["article.scss","../mixins/_border.scss","../mixins/_shadow.scss"],"names":[],"mappings":"AAGA;EACI,gBAAe;EACf,mBAA4B;ECJ/B,mBDK6B;ECJ7B,2BDI6B;ECH7B,wBDG6B;EAC1B,aAAY;CAIf;;AARD;EEYI,kCFN6C;EEQrC,0BFRqC;CAC5C;;AAGL;EACI,gBAAe;EACf,oBAA4B;ECd/B,mBDe6B;ECd7B,2BDc6B;ECb7B,wBDa6B;EAC1B,aAAY;EACZ,iBAAgB;CAInB;;AATD;EEEI,kCFK6C;EEHrC,0BFGqC;CAC5C;;AAGL;EACI,gBAAe;EACf,gBAA0B;ECzB7B,mBD0B6B;ECzB7B,2BDyB6B;ECxB7B,wBDwB6B;EAC1B,aAAY;EACZ,iBAAgB;CAInB;;AATD;EETI,kCFgB6C;EEdrC,0BFcqC;CAC5C;;AAGL;EACI,sBAAqB;CACxB;;AAMD;EACI,aAAY;CACf","file":"../article.css","sourcesContent":["@import \"../mixins\";\r\n@import \"../susy\";\r\n\r\n.edit_article {\r\n cursor: pointer;\r\n background: rgb(255, 165, 0);\r\n @include border-radius(4px);\r\n padding: 5px;\r\n &:hover {\r\n @include box-shadow(0 0 3px rgba(0,0,0,1));\r\n }\r\n}\r\n\r\n.desactive_article {\r\n cursor: pointer;\r\n background: rgb(217, 255, 0);\r\n @include border-radius(4px);\r\n padding: 5px;\r\n margin-left: 4px;\r\n &:hover {\r\n @include box-shadow(0 0 3px rgba(0,0,0,1));\r\n }\r\n}\r\n\r\n.delete_article {\r\n cursor: pointer;\r\n background: rgb(255, 0, 0);\r\n @include border-radius(4px);\r\n padding: 5px;\r\n margin-left: 4px;\r\n &:hover {\r\n @include box-shadow(0 0 3px rgba(0,0,0,1));\r\n }\r\n}\r\n\r\n.add_article {\r\n text-decoration: none;\r\n}\r\n\r\n.add_article_form {\r\n \r\n}\r\n\r\n#art_home {\r\n margin: 15px;\r\n}","@mixin border-radius($radius) {\r\n\tborder-radius: $radius;\r\n\t-webkit-border-radius: $radius;\r\n\t-moz-border-radius: $radius;\r\n}\r\n\r\n@mixin border-radii($topleft, $topright, $bottomright, $bottomleft) {\r\n\r\n @if ($topleft > 0px) {\r\n border-top-left-radius: $topleft;\r\n -webkit-border-top-left-radius: $topleft;\r\n -moz-border-radius-topleft: $topleft;\r\n }\r\n\r\n @if ($topright > 0px) {\r\n border-top-right-radius: $topright;\r\n -webkit-border-top-right-radius: $topright;\r\n -moz-border-radius-topright: $topright;\r\n }\r\n\r\n @if ($bottomright > 0px) {\r\n border-bottom-right-radius: $bottomright;\r\n -webkit-border-bottom-right-radius: $bottomright;\r\n -moz-border-radius-bottomright: $bottomright;\r\n }\r\n\r\n @if ($bottomleft > 0px) {\r\n border-bottom-left-radius: $bottomleft;\r\n -moz-border-radius-bottomright: $bottomright;\r\n -moz-border-radius-bottomleft: $bottomleft;\r\n }\r\n\r\n}","@mixin text-shadow($x: 2px, $y: 2px, $blur: 5px, $color: rgba(0,0,0,.4)) {\r\n text-shadow: $x $y $blur $color;\r\n}\r\n\r\n@mixin box-shadow( $shadow1, $shadow2:false, $shadow3:false, $shadow4:false, $shadow5:false, $shadow6:false, $shadow7:false, $shadow8:false, $shadow9:false ) {\r\n $params: $shadow1;\r\n @if $shadow2 { $params: $shadow1, $shadow2; }\r\n @if $shadow3 != false { $params: $shadow1, $shadow2, $shadow3; }\r\n @if $shadow4 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4; }\r\n @if $shadow5 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5; }\r\n @if $shadow6 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5, $shadow6; }\r\n @if $shadow7 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5, $shadow6, $shadow7}\r\n @if $shadow8 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5, $shadow6, $shadow7, $shadow8 }\r\n @if $shadow9 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5, $shadow6, $shadow7, $shadow8, $shadow9 }\r\n \r\n -webkit-box-shadow: $params;\r\n -moz-box-shadow: $params;\r\n box-shadow: $params;\r\n }\r\n"]} \ No newline at end of file diff --git a/public/assets/css/admin/maps/auth.css.map b/public/assets/css/admin/maps/auth.css.map new file mode 100644 index 0000000..b353def --- /dev/null +++ b/public/assets/css/admin/maps/auth.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["auth.scss","../susy/language/susy/_box-sizing.scss","../susy/output/support/_rem.scss","../mixins/_border.scss","../mixins/_shadow.scss"],"names":[],"mappings":"AAGA,qLAAY;ACaL;ECGH,+BDemC;ECfnC,uBDemC;CAlBc;;AAC9C;EAAuB,4BAAmB;UAAnB,oBAAmB;CAAK;;ADNtD;EACI,kBAAiB;CACpB;;AACD;EACI,yBAAwB;EACxB,gBAAe;EACf,aAZa;EAab,iBAAgB;CACnB;;AACD;EACI,YAfY;CAgBf;;AACD;EACI,YAAW;EACX,gBAAe;EACf,QAAO;EACP,SAAQ;EACR,OAAM;EAAG,UAAS;EAClB,YAAW;EACX,eAAc;EACd,wBAAuB;EACvB,gEAA+D;EAC/D,YAAW;EACX,aAAY;EACZ,uBAAsB;EACtB,0BAAyB;EACzB,uBAAsB;EACtB,qBAAoB;EACpB,sBAAqB;EACrB,kBAAiB;CACpB;;AAED;EACI,mBAAkB;EAClB,OAAM;EAAG,UAAS;EAAG,QAAO;EAAG,SAAQ;EACvC,aAAY;EACZ,cAAa;EACb,wCAAuC;EACvC,kBAAiB;EACjB,cAAa;EGjDhB,mBHkD6B;EGjD7B,2BHiD6B;EGhD7B,wBHgD6B;EIpC1B,mCJqC0C;EInClC,2BJmCkC;CA8E7C;;AAvFD;EAWQ,mBAAkB;EAClB,kBAAiB;EACjB,iBAAgB;EAChB,qBAAoB;CAEvB;;AAhBL;EAmBY,iBAAgB;EAChB,cAAa;EACb,YAAW;EG/DtB,mBHgEqC;EG/DrC,2BH+DqC;EG9DrC,wBH8DqC;CAO7B;;AA7BT;EAwBgB,gBAAe;CAClB;;AAzBb;EA2BgB,kBAAiB;CACpB;;AA5Bb;EA+BY,YAAW;EACX,kBAAiB;EACjB,yBAAwB;EACxB,iBAAgB;EAChB,aAAY;EACZ,cAAa;EG9ExB,mBH+EqC;EG9ErC,2BH8EqC;EG7ErC,wBH6EqC;CAC7B;;AAtCT;EAwCY,cAAa;CAChB;;AAzCT;EA2CY,sBAAqB;EACrB,YAAW;EACX,aAAY;EACZ,gBAAe;EACf,mBAAkB;EAClB,iBAAgB;EAChB,mBAAkB;EAClB,UAAS;CAaZ;;AA/DT;EAoDgB,YAAW;EACX,sBAAqB;EACrB,YAAW;EACX,aAAY;EGjG3B,mBHkGyC;EGjGzC,2BHiGyC;EGhGzC,wBHgGyC;EAC1B,mBAAkB;EAClB,QAAO;EACP,YAAW;EACX,uBAAsB;EIxFlC,uGJyFgH;EIvFxG,+FJuFwG;CACvG;;AA9Db;EAiEY,yBAAwB;EACxB,iBAAgB;EI5GxB,uEADmE;EJ+G3D,gBAAe;EACf,aAAY;EACZ,mBAAkB;EAClB,kBAAiB;CACpB;;AAxET;EA0EY,kBAAiB;CACpB;;AA3ET;EA6EY,YAAW;EACX,kBAAiB;EACjB,cAAa;EACb,iBAAgB;EAChB,0BAAyB;EACzB,aAxHK;EAyHL,aAAY;EG7HvB,mBH8HqC;EG7HrC,2BH6HqC;EG5HrC,wBH4HqC;CAC7B","file":"../auth.css","sourcesContent":["@import \"../mixins\";\n@import \"../susy\";\n\n@import url(https://fonts.googleapis.com/css?family=Dosis:300|Lato:300,400,600,700|Roboto+Condensed:300,700|Open+Sans+Condensed:300,600|Open+Sans:400,300,600,700|Maven+Pro:400,700);\n\n$textColor: White;\n$greyColor: #BBB;\n\n* {\n @include border-box-sizing(border-box);\n}\nhtml {\n background: black;\n}\nbody {\n font-family: \"Open Sans\";\n font-size: 16px;\n color: $textColor;\n font-weight: 600;\n}\na {\n color: $greyColor;\n}\n.content:before {\n content: \"\";\n position: fixed;\n left: 0;\n right: 0;\n top: 0; bottom: 0;\n z-index: -1;\n display: block;\n background-color: black;\n background-image: url('http://ultraimg.com/images/Ho6hQWs.jpg');\n width: 100%;\n height: 100%;\n background-size: cover;\n -webkit-filter: blur(2px);\n -moz-filter: blur(2px);\n -o-filter: blur(2px);\n -ms-filter: blur(2px);\n filter: blur(2px);\n}\n\n.content {\n position: absolute;\n top: 0; bottom: 0; left: 0; right: 0;\n width: 540px;\n height: 400px;\n background-color: rgba(10, 10, 10, 0.5);\n margin: auto auto; \n padding: 40px;\n @include border-radius(4px);\n @include box-shadow(0 0 10px rgba(0,0,0,1));\n .title {\n text-align: center;\n font-size: 2.0rem;\n font-weight: 600;\n padding-bottom: 30px;\n \n } // .title\n .login_form {\n .message {\n margin: 10px 0px;\n padding: 10px;\n width: 100%;\n @include border-radius(4px);\n &.error {\n background: red;\n }\n &.success {\n background: green;\n }\n }\n input {\n width: 100%;\n font-size: 1.2rem;\n font-family: \"Open Sans\";\n margin: 10px 0px;\n border: none;\n padding: 10px;\n @include border-radius(4px);\n }\n input[type=checkbox] {\n display: none;\n }\n label {\n display: inline-block;\n width: 20px;\n height: 20px;\n cursor: pointer;\n position: relative;\n margin-left: 5px;\n margin-right: 10px;\n top: 13px;\n &:before {\n content: \"\";\n display: inline-block;\n width: 20px;\n height: 20px;\n @include border-radius(4px);\n position: absolute;\n left: 0;\n bottom: 1px;\n background-color: #aaa;\n @include box-shadow(inset 0px 2px 3px 0px rgba(0, 0, 0, .3), 0px 1px 0px 0px rgba(255, 255, 255, .8));\n }\n }\n input[type=checkbox]:checked + label:before {\n font-family: FontAwesome;\n content: \"\\f00c\";\n @include text-shadow(1px 1px 1px rgba(0, 0, 0, .2));\n font-size: 16px;\n color: Black;\n text-align: center;\n line-height: 20px;\n }\n span {\n font-size: 0.9rem;\n }\n button {\n width: 100%;\n font-size: 1.1rem;\n padding: 10px;\n margin: 20px 0px;\n background-color: #66A756;\n color: $textColor;\n border: none;\n @include border-radius(4px);\n }\n }\n}","// Susy Box Sizing\n// =================\n\n// Global Box Sizing\n// -----------------\n// Set a box model globally on all elements.\n// - [$box]: border-box | content-box\n// - [$inherit]: true | false\n@mixin global-box-sizing(\n $box: susy-get(global-box-sizing),\n $inherit: false\n) {\n $inspect: $box;\n\n @if $inherit {\n @at-root {\n html { @include output((box-sizing: $box)); }\n *, *:before, *:after { box-sizing: inherit; }\n }\n } @else {\n *, *:before, *:after { @include output((box-sizing: $box)); }\n }\n\n @include susy-inspect(global-box-sizing, $inspect);\n @include update-box-model($box);\n}\n\n// Border Box Sizing\n// -----------------\n// A legacy shortcut...\n// - [$inherit]: true | false\n@mixin border-box-sizing(\n $inherit: false\n) {\n @include global-box-sizing(border-box, $inherit);\n}\n\n// Update Box Model\n// ----------------\n// PRIVATE: Updates global box model setting\n@mixin update-box-model(\n $box\n) {\n @if $box != susy-get(global-box-sizing) {\n @include susy-set(global-box-sizing, $box);\n }\n}\n","// rem Support\n// ===========\n\n// rem\n// ---\n// Check for an existing support mixin, or output directly.\n// - $prop : \n// - $val : \n@mixin susy-rem(\n $prop,\n $val\n) {\n $_reqs: (\n variable: rhythm-unit rem-with-px-fallback,\n mixin: rem,\n );\n @if susy-support(rem, $_reqs, $warn: false) and $rhythm-unit == rem {\n @include rem($prop, $val);\n } @else {\n #{$prop}: $val;\n }\n}\n","@mixin border-radius($radius) {\r\n\tborder-radius: $radius;\r\n\t-webkit-border-radius: $radius;\r\n\t-moz-border-radius: $radius;\r\n}\r\n\r\n@mixin border-radii($topleft, $topright, $bottomright, $bottomleft) {\r\n\r\n @if ($topleft > 0px) {\r\n border-top-left-radius: $topleft;\r\n -webkit-border-top-left-radius: $topleft;\r\n -moz-border-radius-topleft: $topleft;\r\n }\r\n\r\n @if ($topright > 0px) {\r\n border-top-right-radius: $topright;\r\n -webkit-border-top-right-radius: $topright;\r\n -moz-border-radius-topright: $topright;\r\n }\r\n\r\n @if ($bottomright > 0px) {\r\n border-bottom-right-radius: $bottomright;\r\n -webkit-border-bottom-right-radius: $bottomright;\r\n -moz-border-radius-bottomright: $bottomright;\r\n }\r\n\r\n @if ($bottomleft > 0px) {\r\n border-bottom-left-radius: $bottomleft;\r\n -moz-border-radius-bottomright: $bottomright;\r\n -moz-border-radius-bottomleft: $bottomleft;\r\n }\r\n\r\n}","@mixin text-shadow($x: 2px, $y: 2px, $blur: 5px, $color: rgba(0,0,0,.4)) {\r\n text-shadow: $x $y $blur $color;\r\n}\r\n\r\n@mixin box-shadow( $shadow1, $shadow2:false, $shadow3:false, $shadow4:false, $shadow5:false, $shadow6:false, $shadow7:false, $shadow8:false, $shadow9:false ) {\r\n $params: $shadow1;\r\n @if $shadow2 { $params: $shadow1, $shadow2; }\r\n @if $shadow3 != false { $params: $shadow1, $shadow2, $shadow3; }\r\n @if $shadow4 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4; }\r\n @if $shadow5 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5; }\r\n @if $shadow6 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5, $shadow6; }\r\n @if $shadow7 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5, $shadow6, $shadow7}\r\n @if $shadow8 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5, $shadow6, $shadow7, $shadow8 }\r\n @if $shadow9 != false { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5, $shadow6, $shadow7, $shadow8, $shadow9 }\r\n \r\n -webkit-box-shadow: $params;\r\n -moz-box-shadow: $params;\r\n box-shadow: $params;\r\n }\r\n"]} \ No newline at end of file diff --git a/public/assets/css/admin/maps/bootstrap.css.map b/public/assets/css/admin/maps/bootstrap.css.map new file mode 100644 index 0000000..07dcb98 --- /dev/null +++ b/public/assets/css/admin/maps/bootstrap.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap.scss","bootstrap/_print.scss","bootstrap.css","bootstrap/_reboot.scss","bootstrap/_variables.scss","bootstrap/mixins/_hover.scss","bootstrap/_type.scss","bootstrap/mixins/_lists.scss","bootstrap/_images.scss","bootstrap/mixins/_image.scss","bootstrap/mixins/_border-radius.scss","bootstrap/mixins/_transition.scss","bootstrap/_code.scss","bootstrap/_grid.scss","bootstrap/mixins/_grid.scss","bootstrap/mixins/_breakpoints.scss","bootstrap/mixins/_grid-framework.scss","bootstrap/_tables.scss","bootstrap/mixins/_table-row.scss","bootstrap/_functions.scss","bootstrap/_forms.scss","bootstrap/mixins/_forms.scss","bootstrap/_buttons.scss","bootstrap/mixins/_buttons.scss","bootstrap/_transitions.scss","bootstrap/_dropdown.scss","bootstrap/mixins/_nav-divider.scss","bootstrap/_button-group.scss","bootstrap/_input-group.scss","bootstrap/_custom-forms.scss","bootstrap/_nav.scss","bootstrap/_navbar.scss","bootstrap/_card.scss","bootstrap/_breadcrumb.scss","bootstrap/mixins/_clearfix.scss","bootstrap/_pagination.scss","bootstrap/mixins/_pagination.scss","bootstrap/_badge.scss","bootstrap/mixins/_badge.scss","bootstrap/_jumbotron.scss","bootstrap/_alert.scss","bootstrap/mixins/_alert.scss","bootstrap/_progress.scss","bootstrap/mixins/_gradients.scss","bootstrap/_media.scss","bootstrap/_list-group.scss","bootstrap/mixins/_list-group.scss","bootstrap/_close.scss","bootstrap/_modal.scss","bootstrap/_tooltip.scss","bootstrap/mixins/_reset-text.scss","bootstrap/_popover.scss","bootstrap/_carousel.scss","bootstrap/utilities/_align.scss","bootstrap/mixins/_background-variant.scss","bootstrap/utilities/_background.scss","bootstrap/utilities/_borders.scss","bootstrap/utilities/_display.scss","bootstrap/utilities/_embed.scss","bootstrap/utilities/_flex.scss","bootstrap/utilities/_float.scss","bootstrap/mixins/_float.scss","bootstrap/utilities/_position.scss","bootstrap/utilities/_screenreaders.scss","bootstrap/mixins/_screen-reader.scss","bootstrap/utilities/_sizing.scss","bootstrap/utilities/_spacing.scss","bootstrap/utilities/_text.scss","bootstrap/mixins/_text-truncate.scss","bootstrap/mixins/_text-emphasis.scss","bootstrap/mixins/_text-hide.scss","bootstrap/utilities/_visibility.scss","bootstrap/mixins/_visibility.scss"],"names":[],"mappings":"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EMnU5B,uBNmN2B;EOlNzB,0CP0V+C;EO1V/C,kCP0V+C;CkBxTpD;;AjBjBG;EiBHA,sBAAqB;CjBMpB;;AiBnBL;EAiBI,WAAU;EACV,sDlBkDa;UkBlDb,8ClBkDa;CkBjDd;;AAnBH;EAwBI,aAAY;CAEb;;AA1BH;EA8BI,uBAAsB;CAEvB;;AAIH;;EAEE,qBAAoB;CACrB;;AAQC;EHQE,YAAW;EItDb,0BnBmEe;EmBlEf,sBnBkEe;CkBnBd;;AC5CD;EJkDE,YAAW;EIhDX,0BARqF;EASrF,sBAT2H;CAU5H;;AAED;EAMI,qDnBmDW;UmBnDX,6CnBmDW;CmBjDd;;AAGD;EAEE,0BnB4Ca;EmB3Cb,sBnB2Ca;CmB1Cd;;AAED;;EAGE,0BAhCqF;EAiCrF,uBAAsB;EACtB,sBAlC2H;CAoC5H;;ADYD;EHQE,YAAW;EItDb,0BnBiDgB;EmBhDhB,sBnBgDgB;CkBDf;;AC5CD;EJkDE,YAAW;EIhDX,0BARqF;EASrF,sBAT2H;CAU5H;;AAED;EAMI,uDnBiCY;UmBjCZ,+CnBiCY;CmB/Bf;;AAGD;EAEE,0BnB0Bc;EmBzBd,sBnByBc;CmBxBf;;AAED;;EAGE,0BAhCqF;EAiCrF,uBAAsB;EACtB,sBAlC2H;CAoC5H;;ADYD;EHQE,YAAW;EItDb,0BnB0Ee;EmBzEf,sBnByEe;CkB1Bd;;AC5CD;EJkDE,YAAW;EIhDX,0BARqF;EASrF,sBAT2H;CAU5H;;AAED;EAMI,qDnB0DW;UmB1DX,6CnB0DW;CmBxDd;;AAGD;EAEE,0BnBmDa;EmBlDb,sBnBkDa;CmBjDd;;AAED;;EAGE,0BAhCqF;EAiCrF,uBAAsB;EACtB,sBAlC2H;CAoC5H;;ADYD;EHQE,YAAW;EItDb,0BnB4Ee;EmB3Ef,sBnB2Ee;CkB5Bd;;AC5CD;EJkDE,YAAW;EIhDX,0BARqF;EASrF,sBAT2H;CAU5H;;AAED;EAMI,sDnB4DW;UmB5DX,8CnB4DW;CmB1Dd;;AAGD;EAEE,0BnBqDa;EmBpDb,sBnBoDa;CmBnDd;;AAED;;EAGE,0BAhCqF;EAiCrF,uBAAsB;EACtB,sBAlC2H;CAoC5H;;ADYD;EHME,YAAW;EIpDb,0BnByEe;EmBxEf,sBnBwEe;CkBzBd;;AC5CD;EJgDE,YAAW;EI9CX,0BARqF;EASrF,sBAT2H;CAU5H;;AAED;EAMI,qDnByDW;UmBzDX,6CnByDW;CmBvDd;;AAGD;EAEE,0BnBkDa;EmBjDb,sBnBiDa;CmBhDd;;AAED;;EAGE,0BAhCqF;EAiCrF,uBAAsB;EACtB,sBAlC2H;CAoC5H;;ADYD;EHQE,YAAW;EItDb,0BnBuEe;EmBtEf,sBnBsEe;CkBvBd;;AC5CD;EJkDE,YAAW;EIhDX,0BARqF;EASrF,sBAT2H;CAU5H;;AAED;EAMI,qDnBuDW;UmBvDX,6CnBuDW;CmBrDd;;AAGD;EAEE,0BnBgDa;EmB/Cb,sBnB+Ca;CmB9Cd;;AAED;;EAGE,0BAhCqF;EAiCrF,uBAAsB;EACtB,sBAlC2H;CAoC5H;;ADYD;EHME,YAAW;EIpDb,0BnB4CgB;EmB3ChB,sBnB2CgB;CkBIf;;AC5CD;EJgDE,YAAW;EI9CX,0BARqF;EASrF,sBAT2H;CAU5H;;AAED;EAMI,uDnB4BY;UmB5BZ,+CnB4BY;CmB1Bf;;AAGD;EAEE,0BnBqBc;EmBpBd,sBnBoBc;CmBnBf;;AAED;;EAGE,0BAhCqF;EAiCrF,uBAAsB;EACtB,sBAlC2H;CAoC5H;;ADYD;EHQE,YAAW;EItDb,0BnBmDgB;EmBlDhB,sBnBkDgB;CkBHf;;AC5CD;EJkDE,YAAW;EIhDX,0BARqF;EASrF,sBAT2H;CAU5H;;AAED;EAMI,oDnBmCY;UmBnCZ,4CnBmCY;CmBjCf;;AAGD;EAEE,0BnB4Bc;EmB3Bd,sBnB2Bc;CmB1Bf;;AAED;;EAGE,0BAhCqF;EAiCrF,uBAAsB;EACtB,sBAlC2H;CAoC5H;;ADkBD;ECdA,enB6Be;EmB5Bf,8BAA6B;EAC7B,uBAAsB;EACtB,sBnB0Be;CkBbd;;AjBlDC;EkBwCA,YDS4C;ECR5C,0BnBsBa;EmBrBb,sBnBqBa;CC/DQ;;AkB6CvB;EAEE,qDnBgBa;UmBhBb,6CnBgBa;CmBfd;;AAED;EAEE,enBWa;EmBVb,8BAA6B;CAC9B;;AAED;;EAGE,YDV4C;ECW5C,0BnBGa;EmBFb,sBnBEa;CmBDd;;ADdD;ECdA,enBWgB;EmBVhB,8BAA6B;EAC7B,uBAAsB;EACtB,sBnBQgB;CkBKf;;AjBlDC;EkBwCA,YDS4C;ECR5C,0BnBIc;EmBHd,sBnBGc;CC7CO;;AkB6CvB;EAEE,uDnBFc;UmBEd,+CnBFc;CmBGf;;AAED;EAEE,enBPc;EmBQd,8BAA6B;CAC9B;;AAED;;EAGE,YDV4C;ECW5C,0BnBfc;EmBgBd,sBnBhBc;CmBiBf;;ADdD;ECdA,enBoCe;EmBnCf,8BAA6B;EAC7B,uBAAsB;EACtB,sBnBiCe;CkBpBd;;AjBlDC;EkBwCA,YDS4C;ECR5C,0BnB6Ba;EmB5Bb,sBnB4Ba;CCtEQ;;AkB6CvB;EAEE,qDnBuBa;UmBvBb,6CnBuBa;CmBtBd;;AAED;EAEE,enBkBa;EmBjBb,8BAA6B;CAC9B;;AAED;;EAGE,YDV4C;ECW5C,0BnBUa;EmBTb,sBnBSa;CmBRd;;ADdD;ECdA,enBsCe;EmBrCf,8BAA6B;EAC7B,uBAAsB;EACtB,sBnBmCe;CkBtBd;;AjBlDC;EkBwCA,YDS4C;ECR5C,0BnB+Ba;EmB9Bb,sBnB8Ba;CCxEQ;;AkB6CvB;EAEE,sDnByBa;UmBzBb,8CnByBa;CmBxBd;;AAED;EAEE,enBoBa;EmBnBb,8BAA6B;CAC9B;;AAED;;EAGE,YDV4C;ECW5C,0BnBYa;EmBXb,sBnBWa;CmBVd;;ADdD;ECdA,enBmCe;EmBlCf,8BAA6B;EAC7B,uBAAsB;EACtB,sBnBgCe;CkBnBd;;AjBlDC;EkBwCA,YDS4C;ECR5C,0BnB4Ba;EmB3Bb,sBnB2Ba;CCrEQ;;AkB6CvB;EAEE,qDnBsBa;UmBtBb,6CnBsBa;CmBrBd;;AAED;EAEE,enBiBa;EmBhBb,8BAA6B;CAC9B;;AAED;;EAGE,YDV4C;ECW5C,0BnBSa;EmBRb,sBnBQa;CmBPd;;ADdD;ECdA,enBiCe;EmBhCf,8BAA6B;EAC7B,uBAAsB;EACtB,sBnB8Be;CkBjBd;;AjBlDC;EkBwCA,YDS4C;ECR5C,0BnB0Ba;EmBzBb,sBnByBa;CCnEQ;;AkB6CvB;EAEE,qDnBoBa;UmBpBb,6CnBoBa;CmBnBd;;AAED;EAEE,enBea;EmBdb,8BAA6B;CAC9B;;AAED;;EAGE,YDV4C;ECW5C,0BnBOa;EmBNb,sBnBMa;CmBLd;;ADdD;ECdA,enBMgB;EmBLhB,8BAA6B;EAC7B,uBAAsB;EACtB,sBnBGgB;CkBUf;;AjBlDC;EkBwCA,YDS4C;ECR5C,0BnBDc;EmBEd,sBnBFc;CCxCO;;AkB6CvB;EAEE,uDnBPc;UmBOd,+CnBPc;CmBQf;;AAED;EAEE,enBZc;EmBad,8BAA6B;CAC9B;;AAED;;EAGE,YDV4C;ECW5C,0BnBpBc;EmBqBd,sBnBrBc;CmBsBf;;ADdD;ECdA,enBagB;EmBZhB,8BAA6B;EAC7B,uBAAsB;EACtB,sBnBUgB;CkBGf;;AjBlDC;EkBwCA,YDS4C;ECR5C,0BnBMc;EmBLd,sBnBKc;CC/CO;;AkB6CvB;EAEE,oDnBAc;UmBAd,4CnBAc;CmBCf;;AAED;EAEE,enBLc;EmBMd,8BAA6B;CAC9B;;AAED;;EAGE,YDV4C;ECW5C,0BnBbc;EmBcd,sBnBdc;CmBef;;ADHH;EACE,oBlB0KyB;EkBzKzB,elBEe;EkBDf,iBAAgB;CA8BjB;;AAjCD;EASI,8BAA6B;CAE9B;;AAXH;EAeI,0BAAyB;EACzB,yBAAgB;UAAhB,iBAAgB;CACjB;;AjB5EC;EiB8EA,0BAAyB;CjB9EJ;;AAWrB;EiBsEA,elB0E4C;EkBzE5C,2BlB0E6B;EkBzE7B,8BAA6B;CjBrE5B;;AiB6CL;EA2BI,elBzCc;CkB8Cf;;AjBhFC;EiB8EE,sBAAqB;CjB3EtB;;AiBqFL;EChCE,qBnBgQ8B;EmB/P9B,mBnB+JsB;EmB9JtB,iBnBkI0B;EM9MxB,sBNoN0B;CkBxG7B;;AAED;ECpCE,wBnB4P+B;EmB3P/B,oBnBgKsB;EmB/JtB,iBnBmI0B;EM/MxB,sBNqN0B;CkBrG7B;;AAOD;EACE,eAAc;EACd,YAAW;CACZ;;AAGD;EACE,mBlBsNoC;CkBrNrC;;AAGD;;;EAII,YAAW;CACZ;;AE3IH;EACE,WAAU;EbIN,yCP4NsC;EO5NtC,iCP4NsC;CoB1N3C;;AAPD;EAKI,WAAU;CACX;;AAGH;EACE,cAAa;CAId;;AALD;EAGI,eAAc;CACf;;AAGH;EAEI,mBAAkB;CACnB;;AAGH;EAEI,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A5BD;EAKI,sBAAqB;EACrB,sB7BowBiC;E6BnwBjC,qB7BmwBiC;E6BlwBjC,e7BuCc;E6BtCd,aAAiC;CAClC;;AAVH;EAmBI,2BAA0B;CAC3B;;AApBH;EAsBI,sBAAqB;CACtB;;AAvBH;EA0BI,e7BqBc;C6BpBf;;AEpCH;EACE,qBAAa;EAAb,qBAAa;EAAb,cAAa;EAEb,gBAAe;EACf,iBAAgB;EzBAd,uBNmN2B;C+BjN9B;;AAED;EAGM,eAAc;EzBoBhB,gCNwL2B;EMvL3B,mCNuL2B;C+B1M1B;;AALL;EzBSI,iCNsM2B;EMrM3B,oCNqM2B;C+BrM1B;;AAVL;EAcI,WAAU;EACV,Y/B2BS;E+B1BT,0B/BkDa;E+BjDb,sB/BiDa;C+BhDd;;AAlBH;EAqBI,e/B2Bc;E+B1Bd,qBAAoB;EACpB,uB/BmBS;E+BlBT,mB/ByjBuC;C+BxjBxC;;AAGH;EACE,mBAAkB;EAClB,eAAc;EACd,wB/B4hB0C;E+B3hB1C,kBAAiB;EACjB,kB/B+hBwC;E+B9hBxC,e/BgCe;E+B/Bf,uB/BOW;E+BNX,uB/BiiByC;C+BzhB1C;;A9B9BG;E8ByBA,e/BuH4C;E+BtH5C,sBAAqB;EACrB,0B/BGc;E+BFd,mB/B+hBuC;CCxjBtC;;A+BtBH;EACE,wBhCmkBwC;EgClkBxC,mBhCyOoB;EgCxOpB,iBhC4MwB;CgC3MzB;;AAIG;E1BoBF,+BNyL0B;EMxL1B,kCNwL0B;CgC3MvB;;AAGD;E1BCF,gCNuM0B;EMtM1B,mCNsM0B;CgCtMvB;;AAfL;EACE,wBhCikBuC;EgChkBvC,oBhC0OoB;EgCzOpB,iBhC6MwB;CgC5MzB;;AAIG;E1BoBF,+BN0L0B;EMzL1B,kCNyL0B;CgC5MvB;;AAGD;E1BCF,gCNwM0B;EMvM1B,mCNuM0B;CgCvMvB;;ACbP;EACE,sBAAqB;EACrB,sBjC+pBgC;EiC9pBhC,ejC2pB+B;EiC1pB/B,kBjCyOqB;EiCxOrB,eAAc;EACd,YjCuCW;EiCtCX,mBAAkB;EAClB,oBAAmB;EACnB,yBAAwB;E3BVtB,uBNmN2B;CiClM9B;;AAhBD;EAcI,cAAa;CACd;;AAIH;EACE,mBAAkB;EAClB,UAAS;CACV;;AAMD;EACE,qBjCsoBgC;EiCroBhC,oBjCqoBgC;EMpqB9B,qBNuqB+B;CiCtoBlC;;AAOC;ElBiBE,YAAW;EmB3Db,0BlCwEe;CiC5Bd;;AhCxBC;EcuCA,YAAW;EmBtDT,sBAAqB;EACrB,0BAAkC;CjCiBnC;;AgCmBH;ElBiBE,YAAW;EmB3Db,0BlCsDgB;CiCVf;;AhCxBC;EcuCA,YAAW;EmBtDT,sBAAqB;EACrB,0BAAkC;CjCiBnC;;AgCmBH;ElBiBE,YAAW;EmB3Db,0BlC+Ee;CiCnCd;;AhCxBC;EcuCA,YAAW;EmBtDT,sBAAqB;EACrB,0BAAkC;CjCiBnC;;AgCmBH;ElBiBE,YAAW;EmB3Db,0BlCiFe;CiCrCd;;AhCxBC;EcuCA,YAAW;EmBtDT,sBAAqB;EACrB,0BAAkC;CjCiBnC;;AgCmBH;ElBeE,YAAW;EmBzDb,0BlC8Ee;CiClCd;;AhCxBC;EcqCA,YAAW;EmBpDT,sBAAqB;EACrB,0BAAkC;CjCiBnC;;AgCmBH;ElBiBE,YAAW;EmB3Db,0BlC4Ee;CiChCd;;AhCxBC;EcuCA,YAAW;EmBtDT,sBAAqB;EACrB,0BAAkC;CjCiBnC;;AgCmBH;ElBeE,YAAW;EmBzDb,0BlCiDgB;CiCLf;;AhCxBC;EcqCA,YAAW;EmBpDT,sBAAqB;EACrB,0BAAkC;CjCiBnC;;AgCmBH;ElBiBE,YAAW;EmB3Db,0BlCwDgB;CiCZf;;AhCxBC;EcuCA,YAAW;EmBtDT,sBAAqB;EACrB,0BAAkC;CjCiBnC;;AkCzBL;EACE,mBAAoD;EACpD,oBnC4lBmC;EmC3lBnC,0BnCiDgB;EMhDd,sBNoN0B;CmC/M7B;;AxB+CG;EwBxDJ;IAOI,mBnCulBiC;GmCrlBpC;CrCkvHA;;AqChvHD;EACE,iBAAgB;EAChB,gBAAe;E7BTb,iB6BUsB;CACzB;;ACXD;EACE,yBpC6sBmC;EoC5sBnC,oBpC6sBgC;EoC5sBhC,8BAA6C;E9BH3C,uBNmN2B;CoC9M9B;;AAGD;EAEE,eAAc;CACf;;AAGD;EACE,kBpC+NqB;CoC9NtB;;AAOD;EAGI,mBAAkB;EAClB,cpCkrBgC;EoCjrBhC,gBpCkrBiC;EoCjrBjC,yBpCirBiC;EoChrBjC,eAAc;CACf;;AASD;EC3CA,etBsFkE;EsBrFlE,0BtBmFuE;EsBlFvE,sBtBkFuE;CqBvCtE;;ACzCD;EACE,0BAAqC;CACtC;;AAED;EACE,eAA0B;CAC3B;;ADiCD;EC3CA,etBsFkE;EsBrFlE,0BtBmFuE;EsBlFvE,sBtBkFuE;CqBvCtE;;ACzCD;EACE,0BAAqC;CACtC;;AAED;EACE,eAA0B;CAC3B;;ADiCD;EC3CA,etBsFkE;EsBrFlE,0BtBmFuE;EsBlFvE,sBtBkFuE;CqBvCtE;;ACzCD;EACE,0BAAqC;CACtC;;AAED;EACE,eAA0B;CAC3B;;ADiCD;EC3CA,etBsFkE;EsBrFlE,0BtBmFuE;EsBlFvE,sBtBkFuE;CqBvCtE;;ACzCD;EACE,0BAAqC;CACtC;;AAED;EACE,eAA0B;CAC3B;;ADiCD;EC3CA,etBsFkE;EsBrFlE,0BtBmFuE;EsBlFvE,sBtBkFuE;CqBvCtE;;ACzCD;EACE,0BAAqC;CACtC;;AAED;EACE,eAA0B;CAC3B;;ADiCD;EC3CA,etBsFkE;EsBrFlE,0BtBmFuE;EsBlFvE,sBtBkFuE;CqBvCtE;;ACzCD;EACE,0BAAqC;CACtC;;AAED;EACE,eAA0B;CAC3B;;ADiCD;EC3CA,etBsFkE;EsBrFlE,0BtBmFuE;EsBlFvE,sBtBkFuE;CqBvCtE;;ACzCD;EACE,0BAAqC;CACtC;;AAED;EACE,eAA0B;CAC3B;;ADiCD;EC3CA,etBsFkE;EsBrFlE,0BtBmFuE;EsBlFvE,sBtBkFuE;CqBvCtE;;ACzCD;EACE,0BAAqC;CACtC;;AAED;EACE,eAA0B;CAC3B;;ACXH;EACE;IAAO,4BAAuC;GxC44H7C;EwC34HD;IAAK,yBAAwB;GxC84H5B;CACF;;AwCj5HD;EACE;IAAO,4BAAuC;GxC44H7C;EwC34HD;IAAK,yBAAwB;GxC84H5B;CACF;;AwC54HD;EACE,qBAAa;EAAb,qBAAa;EAAb,cAAa;EACb,iBAAgB;EAChB,mBtCotBoC;EsCntBpC,kBtCktBkC;EsCjtBlC,mBAAkB;EAClB,0BtCyCgB;EMhDd,uBNmN2B;CsCzM9B;;AAED;EACE,atC0sBkC;EsCzsBlC,kBtCysBkC;EsCxsBlC,YtC+BW;EsC9BX,0BtCsDe;EOrEX,oCP8tBwC;EO9tBxC,4BP8tBwC;CsC7sB7C;;AAED;ECWE,sMAA6I;EDT7I,2BtCisBkC;CsChsBnC;;AAED;EACE,2DtCosBgD;UsCpsBhD,mDtCosBgD;CsCnsBjD;;AE/BD;EACE,qBAAa;EAAb,qBAAa;EAAb,cAAa;EACb,yBAAuB;MAAvB,sBAAuB;UAAvB,wBAAuB;CACxB;;AAED;EACE,oBAAO;MAAP,YAAO;UAAP,QAAO;CACR;;ACHD;EACE,qBAAa;EAAb,qBAAa;EAAb,cAAa;EACb,6BAAsB;EAAtB,8BAAsB;MAAtB,2BAAsB;UAAtB,uBAAsB;EAGtB,gBAAe;EACf,iBAAgB;CACjB;;AAQD;EACE,YAAW;EACX,ezCoCgB;EyCnChB,oBAAmB;CAapB;;AxCbG;EwCIA,ezC+Bc;EyC9Bd,sBAAqB;EACrB,0BzCuBc;CC1Bb;;AwCNL;EAaI,ezC2Bc;EyC1Bd,0BzCmBc;CyClBf;;AAQH;EACE,mBAAkB;EAClB,eAAc;EACd,yBzCgsBsC;EyC9rBtC,oBzCsKgB;EyCrKhB,uBzCEW;EyCDX,uCzCWW;CyCiBZ;;AAnCD;EnChCI,gCN6M2B;EM5M3B,iCN4M2B;CyClK5B;;AAXH;EAcI,iBAAgB;EnChChB,oCN+L2B;EM9L3B,mCN8L2B;CyC7J5B;;AxCpCC;EwCuCA,sBAAqB;CxCpCpB;;AwCiBL;EAwBI,ezCVc;EyCWd,uBzCjBS;CyCkBV;;AA1BH;EA8BI,WAAU;EACV,YzCvBS;EyCwBT,0BzCAa;EyCCb,sBzCDa;CyCEd;;AASH;EAEI,gBAAe;EACf,eAAc;EACd,iBAAgB;CACjB;;AALH;EASM,cAAa;CACd;;AAVL;EAeM,iBAAgB;CACjB;;AClGH;EACE,e3BmFgE;E2BlFhE,0B3BgFqE;C2B/EtE;;AAGD;;EAEE,e3B4EgE;C2BhEjE;;AzCDC;;;EyCRE,e3ByE8D;E2BxE9D,0BAAyC;CzCU1C;;AyChBH;;EAUI,YAAW;EACX,0B3BmE8D;E2BlE9D,sB3BkE8D;C2BjE/D;;AAnBH;EACE,e3BmFgE;E2BlFhE,0B3BgFqE;C2B/EtE;;AAGD;;EAEE,e3B4EgE;C2BhEjE;;AzCDC;;;EyCRE,e3ByE8D;E2BxE9D,0BAAyC;CzCU1C;;AyChBH;;EAUI,YAAW;EACX,0B3BmE8D;E2BlE9D,sB3BkE8D;C2BjE/D;;AAnBH;EACE,e3BmFgE;E2BlFhE,0B3BgFqE;C2B/EtE;;AAGD;;EAEE,e3B4EgE;C2BhEjE;;AzCDC;;;EyCRE,e3ByE8D;E2BxE9D,0BAAyC;CzCU1C;;AyChBH;;EAUI,YAAW;EACX,0B3BmE8D;E2BlE9D,sB3BkE8D;C2BjE/D;;AAnBH;EACE,e3BmFgE;E2BlFhE,0B3BgFqE;C2B/EtE;;AAGD;;EAEE,e3B4EgE;C2BhEjE;;AzCDC;;;EyCRE,e3ByE8D;E2BxE9D,0BAAyC;CzCU1C;;AyChBH;;EAUI,YAAW;EACX,0B3BmE8D;E2BlE9D,sB3BkE8D;C2BjE/D;;AAnBH;EACE,e3BmFgE;E2BlFhE,0B3BgFqE;C2B/EtE;;AAGD;;EAEE,e3B4EgE;C2BhEjE;;AzCDC;;;EyCRE,e3ByE8D;E2BxE9D,0BAAyC;CzCU1C;;AyChBH;;EAUI,YAAW;EACX,0B3BmE8D;E2BlE9D,sB3BkE8D;C2BjE/D;;AAnBH;EACE,e3BmFgE;E2BlFhE,0B3BgFqE;C2B/EtE;;AAGD;;EAEE,e3B4EgE;C2BhEjE;;AzCDC;;;EyCRE,e3ByE8D;E2BxE9D,0BAAyC;CzCU1C;;AyChBH;;EAUI,YAAW;EACX,0B3BmE8D;E2BlE9D,sB3BkE8D;C2BjE/D;;AAnBH;EACE,e3BmFgE;E2BlFhE,0B3BgFqE;C2B/EtE;;AAGD;;EAEE,e3B4EgE;C2BhEjE;;AzCDC;;;EyCRE,e3ByE8D;E2BxE9D,0BAAyC;CzCU1C;;AyChBH;;EAUI,YAAW;EACX,0B3BmE8D;E2BlE9D,sB3BkE8D;C2BjE/D;;AAnBH;EACE,e3BmFgE;E2BlFhE,0B3BgFqE;C2B/EtE;;AAGD;;EAEE,e3B4EgE;C2BhEjE;;AzCDC;;;EyCRE,e3ByE8D;E2BxE9D,0BAAyC;CzCU1C;;AyChBH;;EAUI,YAAW;EACX,0B3BmE8D;E2BlE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","file":"../bootstrap.css","sourcesContent":["/*!\n * Bootstrap v4.0.0-beta (https://getbootstrap.com)\n * Copyright 2011-2017 The Bootstrap Authors\n * Copyright 2011-2017 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n@import \"bootstrap/functions\";\n@import \"bootstrap/variables\";\n@import \"bootstrap/mixins\";\n@import \"bootstrap/print\";\n@import \"bootstrap/reboot\";\n@import \"bootstrap/type\";\n@import \"bootstrap/images\";\n@import \"bootstrap/code\";\n@import \"bootstrap/grid\";\n@import \"bootstrap/tables\";\n@import \"bootstrap/forms\";\n@import \"bootstrap/buttons\";\n@import \"bootstrap/transitions\";\n@import \"bootstrap/dropdown\";\n@import \"bootstrap/button-group\";\n@import \"bootstrap/input-group\";\n@import \"bootstrap/custom-forms\";\n@import \"bootstrap/nav\";\n@import \"bootstrap/navbar\";\n@import \"bootstrap/card\";\n@import \"bootstrap/breadcrumb\";\n@import \"bootstrap/pagination\";\n@import \"bootstrap/badge\";\n@import \"bootstrap/jumbotron\";\n@import \"bootstrap/alert\";\n@import \"bootstrap/progress\";\n@import \"bootstrap/media\";\n@import \"bootstrap/list-group\";\n@import \"bootstrap/close\";\n@import \"bootstrap/modal\";\n@import \"bootstrap/tooltip\";\n@import \"bootstrap/popover\";\n@import \"bootstrap/carousel\";\n@import \"bootstrap/utilities\";\n","// scss-lint:disable QualifyingElement\n\n// Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request:\n// http://www.phpied.com/delay-loading-your-print-css/\n// ==========================================================================\n\n@if $enable-print-styles {\n @media print {\n *,\n *::before,\n *::after {\n // Bootstrap specific; comment out `color` and `background`\n //color: #000 !important; // Black prints faster:\n // http://www.sanbeiji.com/archives/953\n text-shadow: none !important;\n //background: transparent !important;\n box-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n // Bootstrap specific; comment the following selector out\n //a[href]::after {\n // content: \" (\" attr(href) \")\";\n //}\n\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n\n // Bootstrap specific; comment the following selector out\n //\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n //\n\n //a[href^=\"#\"]::after,\n //a[href^=\"javascript:\"]::after {\n // content: \"\";\n //}\n\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: $border-width solid #999; // Bootstrap custom code; using `$border-width` instead of 1px\n page-break-inside: avoid;\n }\n\n //\n // Printing Tables:\n // http://css-discuss.incutio.com/wiki/Printing_Tables\n //\n\n thead {\n display: table-header-group;\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .badge {\n border: $border-width solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n }\n}\n","/*!\n * Bootstrap v4.0.0-beta (https://getbootstrap.com)\n * Copyright 2011-2017 The Bootstrap Authors\n * Copyright 2011-2017 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n@media print {\n *,\n *::before,\n *::after {\n text-shadow: none !important;\n box-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n .navbar {\n display: none;\n }\n .badge {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n\nhtml {\n box-sizing: border-box;\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -ms-text-size-adjust: 100%;\n -ms-overflow-style: scrollbar;\n -webkit-tap-highlight-color: transparent;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: inherit;\n}\n\n@-ms-viewport {\n width: device-width;\n}\n\narticle, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n font-size: 1rem;\n font-weight: normal;\n line-height: 1.5;\n color: #212529;\n background-color: #fff;\n}\n\n[tabindex=\"-1\"]:focus {\n outline: none !important;\n}\n\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\n\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: .5rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: 0;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: bold;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\ndfn {\n font-style: italic;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #007bff;\n text-decoration: none;\n background-color: transparent;\n -webkit-text-decoration-skip: objects;\n}\n\na:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):focus {\n outline: 0;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\npre {\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n border-style: none;\n}\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\na,\narea,\nbutton,\n[role=\"button\"],\ninput,\nlabel,\nselect,\nsummary,\ntextarea {\n touch-action: manipulation;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n color: #868e96;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: left;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: .5rem;\n}\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\nbutton,\nhtml [type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box;\n padding: 0;\n}\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n\n[type=\"search\"]::-webkit-search-cancel-button,\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n}\n\ntemplate {\n display: none;\n}\n\n[hidden] {\n display: none !important;\n}\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: 0.5rem;\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.1;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.1;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.1;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.1;\n}\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\nsmall,\n.small {\n font-size: 80%;\n font-weight: normal;\n}\n\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 5px;\n}\n\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%;\n color: #868e96;\n}\n\n.blockquote-footer::before {\n content: \"\\2014 \\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 0.25rem;\n transition: all 0.2s ease-in-out;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 90%;\n color: #868e96;\n}\n\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\ncode {\n padding: 0.2rem 0.4rem;\n font-size: 90%;\n color: #bd4147;\n background-color: #f8f9fa;\n border-radius: 0.25rem;\n}\n\na > code {\n padding: 0;\n color: inherit;\n background-color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 90%;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n}\n\npre {\n display: block;\n margin-top: 0;\n margin-bottom: 1rem;\n font-size: 90%;\n color: #212529;\n}\n\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n background-color: transparent;\n border-radius: 0;\n}\n\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-right: 15px;\n padding-left: 15px;\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container {\n max-width: 1140px;\n }\n}\n\n.container-fluid {\n width: 100%;\n margin-right: auto;\n margin-left: auto;\n padding-right: 15px;\n padding-left: 15px;\n width: 100%;\n}\n\n.row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n min-height: 1px;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n}\n\n.col-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n}\n\n.col-1 {\n flex: 0 0 8.33333%;\n max-width: 8.33333%;\n}\n\n.col-2 {\n flex: 0 0 16.66667%;\n max-width: 16.66667%;\n}\n\n.col-3 {\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n flex: 0 0 33.33333%;\n max-width: 33.33333%;\n}\n\n.col-5 {\n flex: 0 0 41.66667%;\n max-width: 41.66667%;\n}\n\n.col-6 {\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n flex: 0 0 58.33333%;\n max-width: 58.33333%;\n}\n\n.col-8 {\n flex: 0 0 66.66667%;\n max-width: 66.66667%;\n}\n\n.col-9 {\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n flex: 0 0 83.33333%;\n max-width: 83.33333%;\n}\n\n.col-11 {\n flex: 0 0 91.66667%;\n max-width: 91.66667%;\n}\n\n.col-12 {\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.order-1 {\n order: 1;\n}\n\n.order-2 {\n order: 2;\n}\n\n.order-3 {\n order: 3;\n}\n\n.order-4 {\n order: 4;\n}\n\n.order-5 {\n order: 5;\n}\n\n.order-6 {\n order: 6;\n}\n\n.order-7 {\n order: 7;\n}\n\n.order-8 {\n order: 8;\n}\n\n.order-9 {\n order: 9;\n}\n\n.order-10 {\n order: 10;\n}\n\n.order-11 {\n order: 11;\n}\n\n.order-12 {\n order: 12;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-sm-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-sm-1 {\n flex: 0 0 8.33333%;\n max-width: 8.33333%;\n }\n .col-sm-2 {\n flex: 0 0 16.66667%;\n max-width: 16.66667%;\n }\n .col-sm-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n flex: 0 0 33.33333%;\n max-width: 33.33333%;\n }\n .col-sm-5 {\n flex: 0 0 41.66667%;\n max-width: 41.66667%;\n }\n .col-sm-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n flex: 0 0 58.33333%;\n max-width: 58.33333%;\n }\n .col-sm-8 {\n flex: 0 0 66.66667%;\n max-width: 66.66667%;\n }\n .col-sm-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n flex: 0 0 83.33333%;\n max-width: 83.33333%;\n }\n .col-sm-11 {\n flex: 0 0 91.66667%;\n max-width: 91.66667%;\n }\n .col-sm-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-sm-1 {\n order: 1;\n }\n .order-sm-2 {\n order: 2;\n }\n .order-sm-3 {\n order: 3;\n }\n .order-sm-4 {\n order: 4;\n }\n .order-sm-5 {\n order: 5;\n }\n .order-sm-6 {\n order: 6;\n }\n .order-sm-7 {\n order: 7;\n }\n .order-sm-8 {\n order: 8;\n }\n .order-sm-9 {\n order: 9;\n }\n .order-sm-10 {\n order: 10;\n }\n .order-sm-11 {\n order: 11;\n }\n .order-sm-12 {\n order: 12;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-md-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-md-1 {\n flex: 0 0 8.33333%;\n max-width: 8.33333%;\n }\n .col-md-2 {\n flex: 0 0 16.66667%;\n max-width: 16.66667%;\n }\n .col-md-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n flex: 0 0 33.33333%;\n max-width: 33.33333%;\n }\n .col-md-5 {\n flex: 0 0 41.66667%;\n max-width: 41.66667%;\n }\n .col-md-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n flex: 0 0 58.33333%;\n max-width: 58.33333%;\n }\n .col-md-8 {\n flex: 0 0 66.66667%;\n max-width: 66.66667%;\n }\n .col-md-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n flex: 0 0 83.33333%;\n max-width: 83.33333%;\n }\n .col-md-11 {\n flex: 0 0 91.66667%;\n max-width: 91.66667%;\n }\n .col-md-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-md-1 {\n order: 1;\n }\n .order-md-2 {\n order: 2;\n }\n .order-md-3 {\n order: 3;\n }\n .order-md-4 {\n order: 4;\n }\n .order-md-5 {\n order: 5;\n }\n .order-md-6 {\n order: 6;\n }\n .order-md-7 {\n order: 7;\n }\n .order-md-8 {\n order: 8;\n }\n .order-md-9 {\n order: 9;\n }\n .order-md-10 {\n order: 10;\n }\n .order-md-11 {\n order: 11;\n }\n .order-md-12 {\n order: 12;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-lg-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-lg-1 {\n flex: 0 0 8.33333%;\n max-width: 8.33333%;\n }\n .col-lg-2 {\n flex: 0 0 16.66667%;\n max-width: 16.66667%;\n }\n .col-lg-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n flex: 0 0 33.33333%;\n max-width: 33.33333%;\n }\n .col-lg-5 {\n flex: 0 0 41.66667%;\n max-width: 41.66667%;\n }\n .col-lg-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n flex: 0 0 58.33333%;\n max-width: 58.33333%;\n }\n .col-lg-8 {\n flex: 0 0 66.66667%;\n max-width: 66.66667%;\n }\n .col-lg-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n flex: 0 0 83.33333%;\n max-width: 83.33333%;\n }\n .col-lg-11 {\n flex: 0 0 91.66667%;\n max-width: 91.66667%;\n }\n .col-lg-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-lg-1 {\n order: 1;\n }\n .order-lg-2 {\n order: 2;\n }\n .order-lg-3 {\n order: 3;\n }\n .order-lg-4 {\n order: 4;\n }\n .order-lg-5 {\n order: 5;\n }\n .order-lg-6 {\n order: 6;\n }\n .order-lg-7 {\n order: 7;\n }\n .order-lg-8 {\n order: 8;\n }\n .order-lg-9 {\n order: 9;\n }\n .order-lg-10 {\n order: 10;\n }\n .order-lg-11 {\n order: 11;\n }\n .order-lg-12 {\n order: 12;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-xl-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-xl-1 {\n flex: 0 0 8.33333%;\n max-width: 8.33333%;\n }\n .col-xl-2 {\n flex: 0 0 16.66667%;\n max-width: 16.66667%;\n }\n .col-xl-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n flex: 0 0 33.33333%;\n max-width: 33.33333%;\n }\n .col-xl-5 {\n flex: 0 0 41.66667%;\n max-width: 41.66667%;\n }\n .col-xl-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n flex: 0 0 58.33333%;\n max-width: 58.33333%;\n }\n .col-xl-8 {\n flex: 0 0 66.66667%;\n max-width: 66.66667%;\n }\n .col-xl-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n flex: 0 0 83.33333%;\n max-width: 83.33333%;\n }\n .col-xl-11 {\n flex: 0 0 91.66667%;\n max-width: 91.66667%;\n }\n .col-xl-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-xl-1 {\n order: 1;\n }\n .order-xl-2 {\n order: 2;\n }\n .order-xl-3 {\n order: 3;\n }\n .order-xl-4 {\n order: 4;\n }\n .order-xl-5 {\n order: 5;\n }\n .order-xl-6 {\n order: 6;\n }\n .order-xl-7 {\n order: 7;\n }\n .order-xl-8 {\n order: 8;\n }\n .order-xl-9 {\n order: 9;\n }\n .order-xl-10 {\n order: 10;\n }\n .order-xl-11 {\n order: 11;\n }\n .order-xl-12 {\n order: 12;\n }\n}\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 1rem;\n background-color: transparent;\n}\n\n.table th,\n.table td {\n padding: 0.75rem;\n vertical-align: top;\n border-top: 1px solid #e9ecef;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom: 2px solid #e9ecef;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #e9ecef;\n}\n\n.table .table {\n background-color: #fff;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.3rem;\n}\n\n.table-bordered {\n border: 1px solid #e9ecef;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #e9ecef;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #b8daff;\n}\n\n.table-hover .table-primary:hover {\n background-color: #9fcdff;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #9fcdff;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #dddfe2;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #cfd2d6;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #cfd2d6;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.thead-inverse th {\n color: #fff;\n background-color: #212529;\n}\n\n.thead-default th {\n color: #495057;\n background-color: #e9ecef;\n}\n\n.table-inverse {\n color: #fff;\n background-color: #212529;\n}\n\n.table-inverse th,\n.table-inverse td,\n.table-inverse thead th {\n border-color: #32383e;\n}\n\n.table-inverse.table-bordered {\n border: 0;\n}\n\n.table-inverse.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-inverse.table-hover tbody tr:hover {\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 991px) {\n .table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive.table-bordered {\n border: 0;\n }\n}\n\n.form-control {\n display: block;\n width: 100%;\n padding: 0.5rem 0.75rem;\n font-size: 1rem;\n line-height: 1.25;\n color: #495057;\n background-color: #fff;\n background-image: none;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: none;\n}\n\n.form-control::placeholder {\n color: #868e96;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\nselect.form-control:not([size]):not([multiple]) {\n height: calc(2.25rem + 2px);\n}\n\nselect.form-control:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-control-file,\n.form-control-range {\n display: block;\n}\n\n.col-form-label {\n padding-top: calc(0.5rem - 1px * 2);\n padding-bottom: calc(0.5rem - 1px * 2);\n margin-bottom: 0;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem - 1px * 2);\n padding-bottom: calc(0.5rem - 1px * 2);\n font-size: 1.25rem;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem - 1px * 2);\n padding-bottom: calc(0.25rem - 1px * 2);\n font-size: 0.875rem;\n}\n\n.col-form-legend {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n margin-bottom: 0;\n font-size: 1rem;\n}\n\n.form-control-plaintext {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n margin-bottom: 0;\n line-height: 1.25;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control,\n.input-group-sm > .form-control-plaintext.input-group-addon,\n.input-group-sm > .input-group-btn > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control,\n.input-group-lg > .form-control-plaintext.input-group-addon,\n.input-group-lg > .input-group-btn > .form-control-plaintext.btn {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm, .input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\nselect.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]),\n.input-group-sm > select.input-group-addon:not([size]):not([multiple]),\n.input-group-sm > .input-group-btn > select.btn:not([size]):not([multiple]) {\n height: calc(1.8125rem + 2px);\n}\n\n.form-control-lg, .input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\nselect.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]),\n.input-group-lg > select.input-group-addon:not([size]):not([multiple]),\n.input-group-lg > .input-group-btn > select.btn:not([size]):not([multiple]) {\n height: calc(2.3125rem + 2px);\n}\n\n.form-group {\n margin-bottom: 1rem;\n}\n\n.form-text {\n display: block;\n margin-top: 0.25rem;\n}\n\n.form-row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-check {\n position: relative;\n display: block;\n margin-bottom: 0.5rem;\n}\n\n.form-check.disabled .form-check-label {\n color: #868e96;\n}\n\n.form-check-label {\n padding-left: 1.25rem;\n margin-bottom: 0;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: 0.25rem;\n margin-left: -1.25rem;\n}\n\n.form-check-input:only-child {\n position: static;\n}\n\n.form-check-inline {\n display: inline-block;\n}\n\n.form-check-inline .form-check-label {\n vertical-align: middle;\n}\n\n.form-check-inline + .form-check-inline {\n margin-left: 0.75rem;\n}\n\n.invalid-feedback {\n display: none;\n margin-top: .25rem;\n font-size: .875rem;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n width: 250px;\n padding: .5rem;\n margin-top: .1rem;\n font-size: .875rem;\n line-height: 1;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.8);\n border-radius: .2rem;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid, .was-validated\n.custom-select:valid,\n.custom-select.is-valid {\n border-color: #28a745;\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated\n.custom-select:valid:focus,\n.custom-select.is-valid:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-control:valid ~ .invalid-feedback,\n.was-validated .form-control:valid ~ .invalid-tooltip, .form-control.is-valid ~ .invalid-feedback,\n.form-control.is-valid ~ .invalid-tooltip, .was-validated\n.custom-select:valid ~ .invalid-feedback,\n.was-validated\n.custom-select:valid ~ .invalid-tooltip,\n.custom-select.is-valid ~ .invalid-feedback,\n.custom-select.is-valid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:valid + .form-check-label, .form-check-input.is-valid + .form-check-label {\n color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-indicator, .custom-control-input.is-valid ~ .custom-control-indicator {\n background-color: rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-description, .custom-control-input.is-valid ~ .custom-control-description {\n color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-control, .custom-file-input.is-valid ~ .custom-file-control {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-control::before, .custom-file-input.is-valid ~ .custom-file-control::before {\n border-color: inherit;\n}\n\n.was-validated .custom-file-input:valid:focus, .custom-file-input.is-valid:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid, .was-validated\n.custom-select:invalid,\n.custom-select.is-invalid {\n border-color: #dc3545;\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated\n.custom-select:invalid:focus,\n.custom-select.is-invalid:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-control:invalid ~ .invalid-feedback,\n.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,\n.form-control.is-invalid ~ .invalid-tooltip, .was-validated\n.custom-select:invalid ~ .invalid-feedback,\n.was-validated\n.custom-select:invalid ~ .invalid-tooltip,\n.custom-select.is-invalid ~ .invalid-feedback,\n.custom-select.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:invalid + .form-check-label, .form-check-input.is-invalid + .form-check-label {\n color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-indicator, .custom-control-input.is-invalid ~ .custom-control-indicator {\n background-color: rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-description, .custom-control-input.is-invalid ~ .custom-control-description {\n color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-control, .custom-file-input.is-invalid ~ .custom-file-control {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-control::before, .custom-file-input.is-invalid ~ .custom-file-control::before {\n border-color: inherit;\n}\n\n.was-validated .custom-file-input:invalid:focus, .custom-file-input.is-invalid:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.form-inline {\n display: flex;\n flex-flow: row wrap;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: flex;\n flex: 0 0 auto;\n flex-flow: row wrap;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group {\n width: auto;\n }\n .form-inline .form-control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-check {\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n margin-top: 0;\n margin-bottom: 0;\n }\n .form-inline .form-check-label {\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n .form-inline .custom-control {\n display: flex;\n align-items: center;\n justify-content: center;\n padding-left: 0;\n }\n .form-inline .custom-control-indicator {\n position: static;\n display: inline-block;\n margin-right: 0.25rem;\n vertical-align: text-bottom;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n\n.btn {\n display: inline-block;\n font-weight: normal;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n user-select: none;\n border: 1px solid transparent;\n padding: 0.5rem 0.75rem;\n font-size: 1rem;\n line-height: 1.25;\n border-radius: 0.25rem;\n transition: all 0.15s ease-in-out;\n}\n\n.btn:focus, .btn:hover {\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: .65;\n}\n\n.btn:active, .btn.active {\n background-image: none;\n}\n\na.btn.disabled,\nfieldset[disabled] a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:active, .btn-primary.active,\n.show > .btn-primary.dropdown-toggle {\n background-color: #0069d9;\n background-image: none;\n border-color: #0062cc;\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #868e96;\n border-color: #868e96;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #727b84;\n border-color: #6c757d;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n background-color: #868e96;\n border-color: #868e96;\n}\n\n.btn-secondary:active, .btn-secondary.active,\n.show > .btn-secondary.dropdown-toggle {\n background-color: #727b84;\n background-image: none;\n border-color: #6c757d;\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:active, .btn-success.active,\n.show > .btn-success.dropdown-toggle {\n background-color: #218838;\n background-image: none;\n border-color: #1e7e34;\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:active, .btn-info.active,\n.show > .btn-info.dropdown-toggle {\n background-color: #138496;\n background-image: none;\n border-color: #117a8b;\n}\n\n.btn-warning {\n color: #111;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #111;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:active, .btn-warning.active,\n.show > .btn-warning.dropdown-toggle {\n background-color: #e0a800;\n background-image: none;\n border-color: #d39e00;\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:active, .btn-danger.active,\n.show > .btn-danger.dropdown-toggle {\n background-color: #c82333;\n background-image: none;\n border-color: #bd2130;\n}\n\n.btn-light {\n color: #111;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #111;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:active, .btn-light.active,\n.show > .btn-light.dropdown-toggle {\n background-color: #e2e6ea;\n background-image: none;\n border-color: #dae0e5;\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:active, .btn-dark.active,\n.show > .btn-dark.dropdown-toggle {\n background-color: #23272b;\n background-image: none;\n border-color: #1d2124;\n}\n\n.btn-outline-primary {\n color: #007bff;\n background-color: transparent;\n background-image: none;\n border-color: #007bff;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-outline-primary:active, .btn-outline-primary.active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-secondary {\n color: #868e96;\n background-color: transparent;\n background-image: none;\n border-color: #868e96;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #868e96;\n border-color: #868e96;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #868e96;\n background-color: transparent;\n}\n\n.btn-outline-secondary:active, .btn-outline-secondary.active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #868e96;\n border-color: #868e96;\n}\n\n.btn-outline-success {\n color: #28a745;\n background-color: transparent;\n background-image: none;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:active, .btn-outline-success.active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-info {\n color: #17a2b8;\n background-color: transparent;\n background-image: none;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:active, .btn-outline-info.active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-warning {\n color: #ffc107;\n background-color: transparent;\n background-image: none;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #fff;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:active, .btn-outline-warning.active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #fff;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-danger {\n color: #dc3545;\n background-color: transparent;\n background-image: none;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:active, .btn-outline-danger.active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n background-color: transparent;\n background-image: none;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #fff;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:active, .btn-outline-light.active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #fff;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-dark {\n color: #343a40;\n background-color: transparent;\n background-image: none;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:active, .btn-outline-dark.active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-link {\n font-weight: normal;\n color: #007bff;\n border-radius: 0;\n}\n\n.btn-link, .btn-link:active, .btn-link.active, .btn-link:disabled {\n background-color: transparent;\n}\n\n.btn-link, .btn-link:focus, .btn-link:active {\n border-color: transparent;\n box-shadow: none;\n}\n\n.btn-link:hover {\n border-color: transparent;\n}\n\n.btn-link:focus, .btn-link:hover {\n color: #0056b3;\n text-decoration: underline;\n background-color: transparent;\n}\n\n.btn-link:disabled {\n color: #868e96;\n}\n\n.btn-link:disabled:focus, .btn-link:disabled:hover {\n text-decoration: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n\n.fade {\n opacity: 0;\n transition: opacity 0.15s linear;\n}\n\n.fade.show {\n opacity: 1;\n}\n\n.collapse {\n display: none;\n}\n\n.collapse.show {\n display: block;\n}\n\ntr.collapse.show {\n display: table-row;\n}\n\ntbody.collapse.show {\n display: table-row-group;\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n.dropup,\n.dropdown {\n position: relative;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropup .dropdown-menu {\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n border-top: 0;\n border-bottom: 0.3em solid;\n}\n\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: normal;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background: none;\n border: 0;\n}\n\n.dropdown-item:focus, .dropdown-item:hover {\n color: #16181b;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #007bff;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #868e96;\n background-color: transparent;\n}\n\n.show > a {\n outline: 0;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #868e96;\n white-space: nowrap;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n flex: 0 1 auto;\n margin-bottom: 0;\n}\n\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover {\n z-index: 2;\n}\n\n.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group,\n.btn-group-vertical .btn + .btn,\n.btn-group-vertical .btn + .btn-group,\n.btn-group-vertical .btn-group + .btn,\n.btn-group-vertical .btn-group + .btn-group {\n margin-left: -1px;\n}\n\n.btn-toolbar {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group > .btn-group {\n float: left;\n}\n\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn + .dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.btn + .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n display: inline-flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.btn-group-vertical .btn,\n.btn-group-vertical .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.input-group {\n position: relative;\n display: flex;\n width: 100%;\n}\n\n.input-group .form-control {\n position: relative;\n z-index: 2;\n flex: 1 1 auto;\n width: 1%;\n margin-bottom: 0;\n}\n\n.input-group .form-control:focus, .input-group .form-control:active, .input-group .form-control:hover {\n z-index: 3;\n}\n\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: flex;\n align-items: center;\n}\n\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n\n.input-group-addon,\n.input-group-btn {\n white-space: nowrap;\n vertical-align: middle;\n}\n\n.input-group-addon {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: normal;\n line-height: 1.25;\n color: #495057;\n text-align: center;\n background-color: #e9ecef;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.input-group-addon.form-control-sm,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .input-group-addon.btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n border-radius: 0.2rem;\n}\n\n.input-group-addon.form-control-lg,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .input-group-addon.btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n border-radius: 0.3rem;\n}\n\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group .form-control:not(:last-child),\n.input-group-addon:not(:last-child),\n.input-group-btn:not(:last-child) > .btn,\n.input-group-btn:not(:last-child) > .btn-group > .btn,\n.input-group-btn:not(:last-child) > .dropdown-toggle,\n.input-group-btn:not(:first-child) > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:not(:first-child) > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group-addon:not(:last-child) {\n border-right: 0;\n}\n\n.input-group .form-control:not(:first-child),\n.input-group-addon:not(:first-child),\n.input-group-btn:not(:first-child) > .btn,\n.input-group-btn:not(:first-child) > .btn-group > .btn,\n.input-group-btn:not(:first-child) > .dropdown-toggle,\n.input-group-btn:not(:last-child) > .btn:not(:first-child),\n.input-group-btn:not(:last-child) > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.form-control + .input-group-addon:not(:first-child) {\n border-left: 0;\n}\n\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n\n.input-group-btn > .btn {\n position: relative;\n}\n\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n\n.input-group-btn > .btn:focus, .input-group-btn > .btn:active, .input-group-btn > .btn:hover {\n z-index: 3;\n}\n\n.input-group-btn:not(:last-child) > .btn,\n.input-group-btn:not(:last-child) > .btn-group {\n margin-right: -1px;\n}\n\n.input-group-btn:not(:first-child) > .btn,\n.input-group-btn:not(:first-child) > .btn-group {\n z-index: 2;\n margin-left: -1px;\n}\n\n.input-group-btn:not(:first-child) > .btn:focus, .input-group-btn:not(:first-child) > .btn:active, .input-group-btn:not(:first-child) > .btn:hover,\n.input-group-btn:not(:first-child) > .btn-group:focus,\n.input-group-btn:not(:first-child) > .btn-group:active,\n.input-group-btn:not(:first-child) > .btn-group:hover {\n z-index: 3;\n}\n\n.custom-control {\n position: relative;\n display: inline-flex;\n min-height: 1.5rem;\n padding-left: 1.5rem;\n margin-right: 1rem;\n}\n\n.custom-control-input {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.custom-control-input:checked ~ .custom-control-indicator {\n color: #fff;\n background-color: #007bff;\n}\n\n.custom-control-input:focus ~ .custom-control-indicator {\n box-shadow: 0 0 0 1px #fff, 0 0 0 3px #007bff;\n}\n\n.custom-control-input:active ~ .custom-control-indicator {\n color: #fff;\n background-color: #b3d7ff;\n}\n\n.custom-control-input:disabled ~ .custom-control-indicator {\n background-color: #e9ecef;\n}\n\n.custom-control-input:disabled ~ .custom-control-description {\n color: #868e96;\n}\n\n.custom-control-indicator {\n position: absolute;\n top: 0.25rem;\n left: 0;\n display: block;\n width: 1rem;\n height: 1rem;\n pointer-events: none;\n user-select: none;\n background-color: #ddd;\n background-repeat: no-repeat;\n background-position: center center;\n background-size: 50% 50%;\n}\n\n.custom-checkbox .custom-control-indicator {\n border-radius: 0.25rem;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-indicator {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E\");\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-indicator {\n background-color: #007bff;\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E\");\n}\n\n.custom-radio .custom-control-indicator {\n border-radius: 50%;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-indicator {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E\");\n}\n\n.custom-controls-stacked {\n display: flex;\n flex-direction: column;\n}\n\n.custom-controls-stacked .custom-control {\n margin-bottom: 0.25rem;\n}\n\n.custom-controls-stacked .custom-control + .custom-control {\n margin-left: 0;\n}\n\n.custom-select {\n display: inline-block;\n max-width: 100%;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n line-height: 1.25;\n color: #495057;\n vertical-align: middle;\n background: #fff url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23333' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E\") no-repeat right 0.75rem center;\n background-size: 8px 10px;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n appearance: none;\n}\n\n.custom-select:focus {\n border-color: #80bdff;\n outline: none;\n}\n\n.custom-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.custom-select:disabled {\n color: #868e96;\n background-color: #e9ecef;\n}\n\n.custom-select::-ms-expand {\n opacity: 0;\n}\n\n.custom-select-sm {\n height: calc(1.8125rem + 2px);\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n font-size: 75%;\n}\n\n.custom-file {\n position: relative;\n display: inline-block;\n max-width: 100%;\n height: 2.5rem;\n margin-bottom: 0;\n}\n\n.custom-file-input {\n min-width: 14rem;\n max-width: 100%;\n height: 2.5rem;\n margin: 0;\n opacity: 0;\n}\n\n.custom-file-control {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 5;\n height: 2.5rem;\n padding: 0.5rem 1rem;\n line-height: 1.5;\n color: #495057;\n pointer-events: none;\n user-select: none;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.custom-file-control:lang(en):empty::after {\n content: \"Choose file...\";\n}\n\n.custom-file-control::before {\n position: absolute;\n top: -1px;\n right: -1px;\n bottom: -1px;\n z-index: 6;\n display: block;\n height: 2.5rem;\n padding: 0.5rem 1rem;\n line-height: 1.5;\n color: #495057;\n background-color: #e9ecef;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0 0.25rem 0.25rem 0;\n}\n\n.custom-file-control:lang(en)::before {\n content: \"Browse\";\n}\n\n.nav {\n display: flex;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:focus, .nav-link:hover {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #868e96;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #ddd;\n}\n\n.nav-tabs .nav-item {\n margin-bottom: -1px;\n}\n\n.nav-tabs .nav-link {\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover {\n border-color: #e9ecef #e9ecef #ddd;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #868e96;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #ddd #ddd #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.show > .nav-pills .nav-link {\n color: #fff;\n background-color: #007bff;\n}\n\n.nav-fill .nav-item {\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified .nav-item {\n flex-basis: 0;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 1rem;\n}\n\n.navbar > .container,\n.navbar > .container-fluid {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n margin-right: 1rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:focus, .navbar-brand:hover {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n float: none;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n flex-basis: 100%;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:focus, .navbar-toggler:hover {\n text-decoration: none;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n@media (max-width: 575px) {\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu-right {\n right: 0;\n left: auto;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: .5rem;\n padding-left: .5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-collapse {\n display: flex !important;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 767px) {\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu-right {\n right: 0;\n left: auto;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: .5rem;\n padding-left: .5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-collapse {\n display: flex !important;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 991px) {\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu-right {\n right: 0;\n left: auto;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: .5rem;\n padding-left: .5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-collapse {\n display: flex !important;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 1199px) {\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu-right {\n right: 0;\n left: auto;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: .5rem;\n padding-left: .5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-collapse {\n display: flex !important;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-expand .navbar-nav {\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: .5rem;\n padding-left: .5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-collapse {\n display: flex !important;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-dark .navbar-brand {\n color: white;\n}\n\n.navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover {\n color: white;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: white;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.card {\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card-body {\n flex: 1 1 auto;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card > .list-group:first-child .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.card > .list-group:last-child .list-group-item:last-child {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n}\n\n.card-img {\n width: 100%;\n border-radius: calc(0.25rem - 1px);\n}\n\n.card-img-top {\n width: 100%;\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img-bottom {\n width: 100%;\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n@media (min-width: 576px) {\n .card-deck {\n display: flex;\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n display: flex;\n flex: 1 0 0%;\n flex-direction: column;\n margin-right: 15px;\n margin-left: 15px;\n }\n}\n\n@media (min-width: 576px) {\n .card-group {\n display: flex;\n flex-flow: row wrap;\n }\n .card-group .card {\n flex: 1 0 0%;\n }\n .card-group .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group .card:first-child {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group .card:first-child .card-img-top {\n border-top-right-radius: 0;\n }\n .card-group .card:first-child .card-img-bottom {\n border-bottom-right-radius: 0;\n }\n .card-group .card:last-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group .card:last-child .card-img-top {\n border-top-left-radius: 0;\n }\n .card-group .card:last-child .card-img-bottom {\n border-bottom-left-radius: 0;\n }\n .card-group .card:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n .card-group .card:not(:first-child):not(:last-child) .card-img-top,\n .card-group .card:not(:first-child):not(:last-child) .card-img-bottom {\n border-radius: 0;\n }\n}\n\n.card-columns .card {\n margin-bottom: 0.75rem;\n}\n\n@media (min-width: 576px) {\n .card-columns {\n column-count: 3;\n column-gap: 1.25rem;\n }\n .card-columns .card {\n display: inline-block;\n width: 100%;\n }\n}\n\n.breadcrumb {\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.breadcrumb-item {\n float: left;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n display: inline-block;\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n color: #868e96;\n content: \"/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #868e96;\n}\n\n.pagination {\n display: flex;\n padding-left: 0;\n list-style: none;\n border-radius: 0.25rem;\n}\n\n.page-item:first-child .page-link {\n margin-left: 0;\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.page-item.active .page-link {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.page-item.disabled .page-link {\n color: #868e96;\n pointer-events: none;\n background-color: #fff;\n border-color: #ddd;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: 0.5rem 0.75rem;\n margin-left: -1px;\n line-height: 1.25;\n color: #007bff;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n\n.page-link:focus, .page-link:hover {\n color: #0056b3;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #ddd;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.4em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.badge-pill {\n padding-right: 0.6em;\n padding-left: 0.6em;\n border-radius: 10rem;\n}\n\n.badge-primary {\n color: #fff;\n background-color: #007bff;\n}\n\n.badge-primary[href]:focus, .badge-primary[href]:hover {\n color: #fff;\n text-decoration: none;\n background-color: #0062cc;\n}\n\n.badge-secondary {\n color: #fff;\n background-color: #868e96;\n}\n\n.badge-secondary[href]:focus, .badge-secondary[href]:hover {\n color: #fff;\n text-decoration: none;\n background-color: #6c757d;\n}\n\n.badge-success {\n color: #fff;\n background-color: #28a745;\n}\n\n.badge-success[href]:focus, .badge-success[href]:hover {\n color: #fff;\n text-decoration: none;\n background-color: #1e7e34;\n}\n\n.badge-info {\n color: #fff;\n background-color: #17a2b8;\n}\n\n.badge-info[href]:focus, .badge-info[href]:hover {\n color: #fff;\n text-decoration: none;\n background-color: #117a8b;\n}\n\n.badge-warning {\n color: #111;\n background-color: #ffc107;\n}\n\n.badge-warning[href]:focus, .badge-warning[href]:hover {\n color: #111;\n text-decoration: none;\n background-color: #d39e00;\n}\n\n.badge-danger {\n color: #fff;\n background-color: #dc3545;\n}\n\n.badge-danger[href]:focus, .badge-danger[href]:hover {\n color: #fff;\n text-decoration: none;\n background-color: #bd2130;\n}\n\n.badge-light {\n color: #111;\n background-color: #f8f9fa;\n}\n\n.badge-light[href]:focus, .badge-light[href]:hover {\n color: #111;\n text-decoration: none;\n background-color: #dae0e5;\n}\n\n.badge-dark {\n color: #fff;\n background-color: #343a40;\n}\n\n.badge-dark[href]:focus, .badge-dark[href]:hover {\n color: #fff;\n text-decoration: none;\n background-color: #1d2124;\n}\n\n.jumbotron {\n padding: 2rem 1rem;\n margin-bottom: 2rem;\n background-color: #e9ecef;\n border-radius: 0.3rem;\n}\n\n@media (min-width: 576px) {\n .jumbotron {\n padding: 4rem 2rem;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n border-radius: 0;\n}\n\n.alert {\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: bold;\n}\n\n.alert-dismissible .close {\n position: relative;\n top: -0.75rem;\n right: -1.25rem;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #004085;\n background-color: #cce5ff;\n border-color: #b8daff;\n}\n\n.alert-primary hr {\n border-top-color: #9fcdff;\n}\n\n.alert-primary .alert-link {\n color: #002752;\n}\n\n.alert-secondary {\n color: #464a4e;\n background-color: #e7e8ea;\n border-color: #dddfe2;\n}\n\n.alert-secondary hr {\n border-top-color: #cfd2d6;\n}\n\n.alert-secondary .alert-link {\n color: #2e3133;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success hr {\n border-top-color: #b1dfbb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info hr {\n border-top-color: #abdde5;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning hr {\n border-top-color: #ffe8a1;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger hr {\n border-top-color: #f1b0b7;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light hr {\n border-top-color: #ececf6;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark hr {\n border-top-color: #b9bbbe;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n.progress {\n display: flex;\n overflow: hidden;\n font-size: 0.75rem;\n line-height: 1rem;\n text-align: center;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n height: 1rem;\n line-height: 1rem;\n color: #fff;\n background-color: #007bff;\n transition: width 0.6s ease;\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n animation: progress-bar-stripes 1s linear infinite;\n}\n\n.media {\n display: flex;\n align-items: flex-start;\n}\n\n.media-body {\n flex: 1;\n}\n\n.list-group {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:focus, .list-group-item-action:hover {\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.list-group-item:focus, .list-group-item:hover {\n text-decoration: none;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #868e96;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.list-group-flush .list-group-item {\n border-right: 0;\n border-left: 0;\n border-radius: 0;\n}\n\n.list-group-flush:first-child .list-group-item:first-child {\n border-top: 0;\n}\n\n.list-group-flush:last-child .list-group-item:last-child {\n border-bottom: 0;\n}\n\n.list-group-item-primary {\n color: #004085;\n background-color: #b8daff;\n}\n\na.list-group-item-primary,\nbutton.list-group-item-primary {\n color: #004085;\n}\n\na.list-group-item-primary:focus, a.list-group-item-primary:hover,\nbutton.list-group-item-primary:focus,\nbutton.list-group-item-primary:hover {\n color: #004085;\n background-color: #9fcdff;\n}\n\na.list-group-item-primary.active,\nbutton.list-group-item-primary.active {\n color: #fff;\n background-color: #004085;\n border-color: #004085;\n}\n\n.list-group-item-secondary {\n color: #464a4e;\n background-color: #dddfe2;\n}\n\na.list-group-item-secondary,\nbutton.list-group-item-secondary {\n color: #464a4e;\n}\n\na.list-group-item-secondary:focus, a.list-group-item-secondary:hover,\nbutton.list-group-item-secondary:focus,\nbutton.list-group-item-secondary:hover {\n color: #464a4e;\n background-color: #cfd2d6;\n}\n\na.list-group-item-secondary.active,\nbutton.list-group-item-secondary.active {\n color: #fff;\n background-color: #464a4e;\n border-color: #464a4e;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\na.list-group-item-success,\nbutton.list-group-item-success {\n color: #155724;\n}\n\na.list-group-item-success:focus, a.list-group-item-success:hover,\nbutton.list-group-item-success:focus,\nbutton.list-group-item-success:hover {\n color: #155724;\n background-color: #b1dfbb;\n}\n\na.list-group-item-success.active,\nbutton.list-group-item-success.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\na.list-group-item-info,\nbutton.list-group-item-info {\n color: #0c5460;\n}\n\na.list-group-item-info:focus, a.list-group-item-info:hover,\nbutton.list-group-item-info:focus,\nbutton.list-group-item-info:hover {\n color: #0c5460;\n background-color: #abdde5;\n}\n\na.list-group-item-info.active,\nbutton.list-group-item-info.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\na.list-group-item-warning,\nbutton.list-group-item-warning {\n color: #856404;\n}\n\na.list-group-item-warning:focus, a.list-group-item-warning:hover,\nbutton.list-group-item-warning:focus,\nbutton.list-group-item-warning:hover {\n color: #856404;\n background-color: #ffe8a1;\n}\n\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\na.list-group-item-danger,\nbutton.list-group-item-danger {\n color: #721c24;\n}\n\na.list-group-item-danger:focus, a.list-group-item-danger:hover,\nbutton.list-group-item-danger:focus,\nbutton.list-group-item-danger:hover {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\na.list-group-item-light,\nbutton.list-group-item-light {\n color: #818182;\n}\n\na.list-group-item-light:focus, a.list-group-item-light:hover,\nbutton.list-group-item-light:focus,\nbutton.list-group-item-light:hover {\n color: #818182;\n background-color: #ececf6;\n}\n\na.list-group-item-light.active,\nbutton.list-group-item-light.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\na.list-group-item-dark,\nbutton.list-group-item-dark {\n color: #1b1e21;\n}\n\na.list-group-item-dark:focus, a.list-group-item-dark:hover,\nbutton.list-group-item-dark:focus,\nbutton.list-group-item-dark:hover {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\na.list-group-item-dark.active,\nbutton.list-group-item-dark.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n float: right;\n font-size: 1.5rem;\n font-weight: bold;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:focus, .close:hover {\n color: #000;\n text-decoration: none;\n opacity: .75;\n}\n\nbutton.close {\n padding: 0;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n display: none;\n overflow: hidden;\n outline: 0;\n}\n\n.modal.fade .modal-dialog {\n transition: transform 0.3s ease-out;\n transform: translate(0, -25%);\n}\n\n.modal.show .modal-dialog {\n transform: translate(0, 0);\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n.modal-content {\n position: relative;\n display: flex;\n flex-direction: column;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 15px;\n border-bottom: 1px solid #e9ecef;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n flex: 1 1 auto;\n padding: 15px;\n}\n\n.modal-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding: 15px;\n border-top: 1px solid #e9ecef;\n}\n\n.modal-footer > :not(:first-child) {\n margin-left: .25rem;\n}\n\n.modal-footer > :not(:last-child) {\n margin-right: .25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 30px auto;\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg {\n max-width: 800px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .arrow {\n position: absolute;\n display: block;\n width: 5px;\n height: 5px;\n}\n\n.tooltip.bs-tooltip-top, .tooltip.bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 5px 0;\n}\n\n.tooltip.bs-tooltip-top .arrow, .tooltip.bs-tooltip-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.tooltip.bs-tooltip-top .arrow::before, .tooltip.bs-tooltip-auto[x-placement^=\"top\"] .arrow::before {\n margin-left: -3px;\n content: \"\";\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n\n.tooltip.bs-tooltip-right, .tooltip.bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 5px;\n}\n\n.tooltip.bs-tooltip-right .arrow, .tooltip.bs-tooltip-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n}\n\n.tooltip.bs-tooltip-right .arrow::before, .tooltip.bs-tooltip-auto[x-placement^=\"right\"] .arrow::before {\n margin-top: -3px;\n content: \"\";\n border-width: 5px 5px 5px 0;\n border-right-color: #000;\n}\n\n.tooltip.bs-tooltip-bottom, .tooltip.bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 5px 0;\n}\n\n.tooltip.bs-tooltip-bottom .arrow, .tooltip.bs-tooltip-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.tooltip.bs-tooltip-bottom .arrow::before, .tooltip.bs-tooltip-auto[x-placement^=\"bottom\"] .arrow::before {\n margin-left: -3px;\n content: \"\";\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n\n.tooltip.bs-tooltip-left, .tooltip.bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 5px;\n}\n\n.tooltip.bs-tooltip-left .arrow, .tooltip.bs-tooltip-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n}\n\n.tooltip.bs-tooltip-left .arrow::before, .tooltip.bs-tooltip-auto[x-placement^=\"left\"] .arrow::before {\n right: 0;\n margin-top: -3px;\n content: \"\";\n border-width: 5px 0 5px 5px;\n border-left-color: #000;\n}\n\n.tooltip .arrow::before {\n position: absolute;\n border-color: transparent;\n border-style: solid;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n padding: 1px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .arrow {\n position: absolute;\n display: block;\n width: 10px;\n height: 5px;\n}\n\n.popover .arrow::before,\n.popover .arrow::after {\n position: absolute;\n display: block;\n border-color: transparent;\n border-style: solid;\n}\n\n.popover .arrow::before {\n content: \"\";\n border-width: 11px;\n}\n\n.popover .arrow::after {\n content: \"\";\n border-width: 11px;\n}\n\n.popover.bs-popover-top, .popover.bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 10px;\n}\n\n.popover.bs-popover-top .arrow, .popover.bs-popover-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^=\"top\"] .arrow::before,\n.popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n border-bottom-width: 0;\n}\n\n.popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^=\"top\"] .arrow::before {\n bottom: -11px;\n margin-left: -6px;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n.popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n bottom: -10px;\n margin-left: -6px;\n border-top-color: #fff;\n}\n\n.popover.bs-popover-right, .popover.bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 10px;\n}\n\n.popover.bs-popover-right .arrow, .popover.bs-popover-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n}\n\n.popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^=\"right\"] .arrow::before,\n.popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n margin-top: -8px;\n border-left-width: 0;\n}\n\n.popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^=\"right\"] .arrow::before {\n left: -11px;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n.popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n left: -10px;\n border-right-color: #fff;\n}\n\n.popover.bs-popover-bottom, .popover.bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 10px;\n}\n\n.popover.bs-popover-bottom .arrow, .popover.bs-popover-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^=\"bottom\"] .arrow::before,\n.popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n margin-left: -7px;\n border-top-width: 0;\n}\n\n.popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^=\"bottom\"] .arrow::before {\n top: -11px;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n.popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n top: -10px;\n border-bottom-color: #fff;\n}\n\n.popover.bs-popover-bottom .popover-header::before, .popover.bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 20px;\n margin-left: -10px;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.popover.bs-popover-left, .popover.bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 10px;\n}\n\n.popover.bs-popover-left .arrow, .popover.bs-popover-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n}\n\n.popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^=\"left\"] .arrow::before,\n.popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n margin-top: -8px;\n border-right-width: 0;\n}\n\n.popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^=\"left\"] .arrow::before {\n right: -11px;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n.popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n right: -10px;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 8px 14px;\n margin-bottom: 0;\n font-size: 1rem;\n color: inherit;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 9px 14px;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-item {\n position: relative;\n display: none;\n align-items: center;\n width: 100%;\n transition: transform 0.6s ease;\n backface-visibility: hidden;\n perspective: 1000px;\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next,\n.carousel-item-prev {\n position: absolute;\n top: 0;\n}\n\n.carousel-item-next.carousel-item-left,\n.carousel-item-prev.carousel-item-right {\n transform: translateX(0);\n}\n\n@supports (transform-style: preserve-3d) {\n .carousel-item-next.carousel-item-left,\n .carousel-item-prev.carousel-item-right {\n transform: translate3d(0, 0, 0);\n }\n}\n\n.carousel-item-next,\n.active.carousel-item-right {\n transform: translateX(100%);\n}\n\n@supports (transform-style: preserve-3d) {\n .carousel-item-next,\n .active.carousel-item-right {\n transform: translate3d(100%, 0, 0);\n }\n}\n\n.carousel-item-prev,\n.active.carousel-item-left {\n transform: translateX(-100%);\n}\n\n@supports (transform-style: preserve-3d) {\n .carousel-item-prev,\n .active.carousel-item-left {\n transform: translate3d(-100%, 0, 0);\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n}\n\n.carousel-control-prev:focus, .carousel-control-prev:hover,\n.carousel-control-next:focus,\n.carousel-control-next:hover {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: .9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: transparent no-repeat center center;\n background-size: 100% 100%;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M4 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M1.5 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 10px;\n left: 0;\n z-index: 15;\n display: flex;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n position: relative;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n background-color: rgba(255, 255, 255, 0.5);\n}\n\n.carousel-indicators li::before {\n position: absolute;\n top: -10px;\n left: 0;\n display: inline-block;\n width: 100%;\n height: 10px;\n content: \"\";\n}\n\n.carousel-indicators li::after {\n position: absolute;\n bottom: -10px;\n left: 0;\n display: inline-block;\n width: 100%;\n height: 10px;\n content: \"\";\n}\n\n.carousel-indicators .active {\n background-color: #fff;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.bg-primary {\n background-color: #007bff !important;\n}\n\na.bg-primary:focus, a.bg-primary:hover {\n background-color: #0062cc !important;\n}\n\n.bg-secondary {\n background-color: #868e96 !important;\n}\n\na.bg-secondary:focus, a.bg-secondary:hover {\n background-color: #6c757d !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\na.bg-success:focus, a.bg-success:hover {\n background-color: #1e7e34 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\na.bg-info:focus, a.bg-info:hover {\n background-color: #117a8b !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\na.bg-warning:focus, a.bg-warning:hover {\n background-color: #d39e00 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\na.bg-danger:focus, a.bg-danger:hover {\n background-color: #bd2130 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\na.bg-light:focus, a.bg-light:hover {\n background-color: #dae0e5 !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\na.bg-dark:focus, a.bg-dark:hover {\n background-color: #1d2124 !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.border {\n border: 1px solid #e9ecef !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #007bff !important;\n}\n\n.border-secondary {\n border-color: #868e96 !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-top-left-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-circle {\n border-radius: 50%;\n}\n\n.rounded-0 {\n border-radius: 0;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: flex !important;\n}\n\n.d-inline-flex {\n display: inline-flex !important;\n}\n\n@media (min-width: 576px) {\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 768px) {\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: flex !important;\n }\n .d-md-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 992px) {\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 1200px) {\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: inline-flex !important;\n }\n}\n\n.d-print-block {\n display: none !important;\n}\n\n@media print {\n .d-print-block {\n display: block !important;\n }\n}\n\n.d-print-inline {\n display: none !important;\n}\n\n@media print {\n .d-print-inline {\n display: inline !important;\n }\n}\n\n.d-print-inline-block {\n display: none !important;\n}\n\n@media print {\n .d-print-inline-block {\n display: inline-block !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n}\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.85714%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-4by3::before {\n padding-top: 75%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.flex-row {\n flex-direction: row !important;\n}\n\n.flex-column {\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n flex-direction: column-reverse !important;\n}\n\n.flex-wrap {\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n}\n\n.justify-content-start {\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n justify-content: center !important;\n}\n\n.justify-content-between {\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n justify-content: space-around !important;\n}\n\n.align-items-start {\n align-items: flex-start !important;\n}\n\n.align-items-end {\n align-items: flex-end !important;\n}\n\n.align-items-center {\n align-items: center !important;\n}\n\n.align-items-baseline {\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n align-items: stretch !important;\n}\n\n.align-content-start {\n align-content: flex-start !important;\n}\n\n.align-content-end {\n align-content: flex-end !important;\n}\n\n.align-content-center {\n align-content: center !important;\n}\n\n.align-content-between {\n align-content: space-between !important;\n}\n\n.align-content-around {\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n align-content: stretch !important;\n}\n\n.align-self-auto {\n align-self: auto !important;\n}\n\n.align-self-start {\n align-self: flex-start !important;\n}\n\n.align-self-end {\n align-self: flex-end !important;\n}\n\n.align-self-center {\n align-self: center !important;\n}\n\n.align-self-baseline {\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n align-self: stretch !important;\n}\n\n@media (min-width: 576px) {\n .flex-sm-row {\n flex-direction: row !important;\n }\n .flex-sm-column {\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-sm-wrap {\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-sm-start {\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n justify-content: center !important;\n }\n .justify-content-sm-between {\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n align-items: center !important;\n }\n .align-items-sm-baseline {\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n align-items: stretch !important;\n }\n .align-content-sm-start {\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n align-content: center !important;\n }\n .align-content-sm-between {\n align-content: space-between !important;\n }\n .align-content-sm-around {\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n align-self: auto !important;\n }\n .align-self-sm-start {\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n align-self: center !important;\n }\n .align-self-sm-baseline {\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 768px) {\n .flex-md-row {\n flex-direction: row !important;\n }\n .flex-md-column {\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-md-wrap {\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-md-start {\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n justify-content: center !important;\n }\n .justify-content-md-between {\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n justify-content: space-around !important;\n }\n .align-items-md-start {\n align-items: flex-start !important;\n }\n .align-items-md-end {\n align-items: flex-end !important;\n }\n .align-items-md-center {\n align-items: center !important;\n }\n .align-items-md-baseline {\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n align-items: stretch !important;\n }\n .align-content-md-start {\n align-content: flex-start !important;\n }\n .align-content-md-end {\n align-content: flex-end !important;\n }\n .align-content-md-center {\n align-content: center !important;\n }\n .align-content-md-between {\n align-content: space-between !important;\n }\n .align-content-md-around {\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n align-content: stretch !important;\n }\n .align-self-md-auto {\n align-self: auto !important;\n }\n .align-self-md-start {\n align-self: flex-start !important;\n }\n .align-self-md-end {\n align-self: flex-end !important;\n }\n .align-self-md-center {\n align-self: center !important;\n }\n .align-self-md-baseline {\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 992px) {\n .flex-lg-row {\n flex-direction: row !important;\n }\n .flex-lg-column {\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-lg-wrap {\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-lg-start {\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n justify-content: center !important;\n }\n .justify-content-lg-between {\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n align-items: center !important;\n }\n .align-items-lg-baseline {\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n align-items: stretch !important;\n }\n .align-content-lg-start {\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n align-content: center !important;\n }\n .align-content-lg-between {\n align-content: space-between !important;\n }\n .align-content-lg-around {\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n align-self: auto !important;\n }\n .align-self-lg-start {\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n align-self: center !important;\n }\n .align-self-lg-baseline {\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 1200px) {\n .flex-xl-row {\n flex-direction: row !important;\n }\n .flex-xl-column {\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-xl-wrap {\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-xl-start {\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n justify-content: center !important;\n }\n .justify-content-xl-between {\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n align-items: center !important;\n }\n .align-items-xl-baseline {\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n align-items: stretch !important;\n }\n .align-content-xl-start {\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n align-content: center !important;\n }\n .align-content-xl-between {\n align-content: space-between !important;\n }\n .align-content-xl-around {\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n align-self: auto !important;\n }\n .align-self-xl-start {\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n align-self: center !important;\n }\n .align-self-xl-baseline {\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n align-self: stretch !important;\n }\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports (position: sticky) {\n .sticky-top {\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n clip-path: inset(50%);\n border: 0;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n clip-path: none;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.mt-0 {\n margin-top: 0 !important;\n}\n\n.mr-0 {\n margin-right: 0 !important;\n}\n\n.mb-0 {\n margin-bottom: 0 !important;\n}\n\n.ml-0 {\n margin-left: 0 !important;\n}\n\n.mx-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n}\n\n.my-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.mt-1 {\n margin-top: 0.25rem !important;\n}\n\n.mr-1 {\n margin-right: 0.25rem !important;\n}\n\n.mb-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.ml-1 {\n margin-left: 0.25rem !important;\n}\n\n.mx-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n}\n\n.my-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.mt-2 {\n margin-top: 0.5rem !important;\n}\n\n.mr-2 {\n margin-right: 0.5rem !important;\n}\n\n.mb-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.ml-2 {\n margin-left: 0.5rem !important;\n}\n\n.mx-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n}\n\n.my-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.mt-3 {\n margin-top: 1rem !important;\n}\n\n.mr-3 {\n margin-right: 1rem !important;\n}\n\n.mb-3 {\n margin-bottom: 1rem !important;\n}\n\n.ml-3 {\n margin-left: 1rem !important;\n}\n\n.mx-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n}\n\n.my-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.mt-4 {\n margin-top: 1.5rem !important;\n}\n\n.mr-4 {\n margin-right: 1.5rem !important;\n}\n\n.mb-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.ml-4 {\n margin-left: 1.5rem !important;\n}\n\n.mx-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n}\n\n.my-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.mt-5 {\n margin-top: 3rem !important;\n}\n\n.mr-5 {\n margin-right: 3rem !important;\n}\n\n.mb-5 {\n margin-bottom: 3rem !important;\n}\n\n.ml-5 {\n margin-left: 3rem !important;\n}\n\n.mx-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n}\n\n.my-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.pt-0 {\n padding-top: 0 !important;\n}\n\n.pr-0 {\n padding-right: 0 !important;\n}\n\n.pb-0 {\n padding-bottom: 0 !important;\n}\n\n.pl-0 {\n padding-left: 0 !important;\n}\n\n.px-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n}\n\n.py-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.pt-1 {\n padding-top: 0.25rem !important;\n}\n\n.pr-1 {\n padding-right: 0.25rem !important;\n}\n\n.pb-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pl-1 {\n padding-left: 0.25rem !important;\n}\n\n.px-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n}\n\n.py-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.pt-2 {\n padding-top: 0.5rem !important;\n}\n\n.pr-2 {\n padding-right: 0.5rem !important;\n}\n\n.pb-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pl-2 {\n padding-left: 0.5rem !important;\n}\n\n.px-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n}\n\n.py-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.pt-3 {\n padding-top: 1rem !important;\n}\n\n.pr-3 {\n padding-right: 1rem !important;\n}\n\n.pb-3 {\n padding-bottom: 1rem !important;\n}\n\n.pl-3 {\n padding-left: 1rem !important;\n}\n\n.px-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n}\n\n.py-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.pt-4 {\n padding-top: 1.5rem !important;\n}\n\n.pr-4 {\n padding-right: 1.5rem !important;\n}\n\n.pb-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pl-4 {\n padding-left: 1.5rem !important;\n}\n\n.px-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n}\n\n.py-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.pt-5 {\n padding-top: 3rem !important;\n}\n\n.pr-5 {\n padding-right: 3rem !important;\n}\n\n.pb-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-5 {\n padding-left: 3rem !important;\n}\n\n.px-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n}\n\n.py-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mt-auto {\n margin-top: auto !important;\n}\n\n.mr-auto {\n margin-right: auto !important;\n}\n\n.mb-auto {\n margin-bottom: auto !important;\n}\n\n.ml-auto {\n margin-left: auto !important;\n}\n\n.mx-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n}\n\n.my-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n}\n\n@media (min-width: 576px) {\n .m-sm-0 {\n margin: 0 !important;\n }\n .mt-sm-0 {\n margin-top: 0 !important;\n }\n .mr-sm-0 {\n margin-right: 0 !important;\n }\n .mb-sm-0 {\n margin-bottom: 0 !important;\n }\n .ml-sm-0 {\n margin-left: 0 !important;\n }\n .mx-sm-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .my-sm-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .mt-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mr-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mb-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-sm-1 {\n margin-left: 0.25rem !important;\n }\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .my-sm-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .mt-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mr-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mb-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-sm-2 {\n margin-left: 0.5rem !important;\n }\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .my-sm-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .mt-sm-3 {\n margin-top: 1rem !important;\n }\n .mr-sm-3 {\n margin-right: 1rem !important;\n }\n .mb-sm-3 {\n margin-bottom: 1rem !important;\n }\n .ml-sm-3 {\n margin-left: 1rem !important;\n }\n .mx-sm-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .my-sm-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .mt-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mr-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mb-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-sm-4 {\n margin-left: 1.5rem !important;\n }\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .my-sm-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .mt-sm-5 {\n margin-top: 3rem !important;\n }\n .mr-sm-5 {\n margin-right: 3rem !important;\n }\n .mb-sm-5 {\n margin-bottom: 3rem !important;\n }\n .ml-sm-5 {\n margin-left: 3rem !important;\n }\n .mx-sm-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .my-sm-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .pt-sm-0 {\n padding-top: 0 !important;\n }\n .pr-sm-0 {\n padding-right: 0 !important;\n }\n .pb-sm-0 {\n padding-bottom: 0 !important;\n }\n .pl-sm-0 {\n padding-left: 0 !important;\n }\n .px-sm-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .py-sm-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .pt-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pr-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pb-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-sm-1 {\n padding-left: 0.25rem !important;\n }\n .px-sm-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .py-sm-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .pt-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pr-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pb-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-sm-2 {\n padding-left: 0.5rem !important;\n }\n .px-sm-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .py-sm-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .pt-sm-3 {\n padding-top: 1rem !important;\n }\n .pr-sm-3 {\n padding-right: 1rem !important;\n }\n .pb-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pl-sm-3 {\n padding-left: 1rem !important;\n }\n .px-sm-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .py-sm-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .pt-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pr-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pb-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-sm-4 {\n padding-left: 1.5rem !important;\n }\n .px-sm-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .py-sm-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .pt-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-5 {\n padding-left: 3rem !important;\n }\n .px-sm-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-sm-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mt-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-auto {\n margin-left: auto !important;\n }\n .mx-sm-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-sm-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n}\n\n@media (min-width: 768px) {\n .m-md-0 {\n margin: 0 !important;\n }\n .mt-md-0 {\n margin-top: 0 !important;\n }\n .mr-md-0 {\n margin-right: 0 !important;\n }\n .mb-md-0 {\n margin-bottom: 0 !important;\n }\n .ml-md-0 {\n margin-left: 0 !important;\n }\n .mx-md-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .my-md-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .mt-md-1 {\n margin-top: 0.25rem !important;\n }\n .mr-md-1 {\n margin-right: 0.25rem !important;\n }\n .mb-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-md-1 {\n margin-left: 0.25rem !important;\n }\n .mx-md-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .my-md-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .mt-md-2 {\n margin-top: 0.5rem !important;\n }\n .mr-md-2 {\n margin-right: 0.5rem !important;\n }\n .mb-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-md-2 {\n margin-left: 0.5rem !important;\n }\n .mx-md-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .my-md-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .mt-md-3 {\n margin-top: 1rem !important;\n }\n .mr-md-3 {\n margin-right: 1rem !important;\n }\n .mb-md-3 {\n margin-bottom: 1rem !important;\n }\n .ml-md-3 {\n margin-left: 1rem !important;\n }\n .mx-md-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .my-md-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .mt-md-4 {\n margin-top: 1.5rem !important;\n }\n .mr-md-4 {\n margin-right: 1.5rem !important;\n }\n .mb-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-md-4 {\n margin-left: 1.5rem !important;\n }\n .mx-md-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .my-md-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .mt-md-5 {\n margin-top: 3rem !important;\n }\n .mr-md-5 {\n margin-right: 3rem !important;\n }\n .mb-md-5 {\n margin-bottom: 3rem !important;\n }\n .ml-md-5 {\n margin-left: 3rem !important;\n }\n .mx-md-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .my-md-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .pt-md-0 {\n padding-top: 0 !important;\n }\n .pr-md-0 {\n padding-right: 0 !important;\n }\n .pb-md-0 {\n padding-bottom: 0 !important;\n }\n .pl-md-0 {\n padding-left: 0 !important;\n }\n .px-md-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .py-md-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .pt-md-1 {\n padding-top: 0.25rem !important;\n }\n .pr-md-1 {\n padding-right: 0.25rem !important;\n }\n .pb-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-md-1 {\n padding-left: 0.25rem !important;\n }\n .px-md-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .py-md-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .pt-md-2 {\n padding-top: 0.5rem !important;\n }\n .pr-md-2 {\n padding-right: 0.5rem !important;\n }\n .pb-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-md-2 {\n padding-left: 0.5rem !important;\n }\n .px-md-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .py-md-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .pt-md-3 {\n padding-top: 1rem !important;\n }\n .pr-md-3 {\n padding-right: 1rem !important;\n }\n .pb-md-3 {\n padding-bottom: 1rem !important;\n }\n .pl-md-3 {\n padding-left: 1rem !important;\n }\n .px-md-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .py-md-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .pt-md-4 {\n padding-top: 1.5rem !important;\n }\n .pr-md-4 {\n padding-right: 1.5rem !important;\n }\n .pb-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-md-4 {\n padding-left: 1.5rem !important;\n }\n .px-md-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .py-md-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .pt-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-5 {\n padding-left: 3rem !important;\n }\n .px-md-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-md-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mt-md-auto {\n margin-top: auto !important;\n }\n .mr-md-auto {\n margin-right: auto !important;\n }\n .mb-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-auto {\n margin-left: auto !important;\n }\n .mx-md-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-md-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n}\n\n@media (min-width: 992px) {\n .m-lg-0 {\n margin: 0 !important;\n }\n .mt-lg-0 {\n margin-top: 0 !important;\n }\n .mr-lg-0 {\n margin-right: 0 !important;\n }\n .mb-lg-0 {\n margin-bottom: 0 !important;\n }\n .ml-lg-0 {\n margin-left: 0 !important;\n }\n .mx-lg-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .my-lg-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .mt-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mr-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mb-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-lg-1 {\n margin-left: 0.25rem !important;\n }\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .my-lg-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .mt-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mr-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mb-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-lg-2 {\n margin-left: 0.5rem !important;\n }\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .my-lg-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .mt-lg-3 {\n margin-top: 1rem !important;\n }\n .mr-lg-3 {\n margin-right: 1rem !important;\n }\n .mb-lg-3 {\n margin-bottom: 1rem !important;\n }\n .ml-lg-3 {\n margin-left: 1rem !important;\n }\n .mx-lg-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .my-lg-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .mt-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mr-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mb-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-lg-4 {\n margin-left: 1.5rem !important;\n }\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .my-lg-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .mt-lg-5 {\n margin-top: 3rem !important;\n }\n .mr-lg-5 {\n margin-right: 3rem !important;\n }\n .mb-lg-5 {\n margin-bottom: 3rem !important;\n }\n .ml-lg-5 {\n margin-left: 3rem !important;\n }\n .mx-lg-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .my-lg-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .pt-lg-0 {\n padding-top: 0 !important;\n }\n .pr-lg-0 {\n padding-right: 0 !important;\n }\n .pb-lg-0 {\n padding-bottom: 0 !important;\n }\n .pl-lg-0 {\n padding-left: 0 !important;\n }\n .px-lg-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .py-lg-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .pt-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pr-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pb-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-lg-1 {\n padding-left: 0.25rem !important;\n }\n .px-lg-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .py-lg-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .pt-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pr-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pb-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-lg-2 {\n padding-left: 0.5rem !important;\n }\n .px-lg-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .py-lg-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .pt-lg-3 {\n padding-top: 1rem !important;\n }\n .pr-lg-3 {\n padding-right: 1rem !important;\n }\n .pb-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pl-lg-3 {\n padding-left: 1rem !important;\n }\n .px-lg-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .py-lg-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .pt-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pr-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pb-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-lg-4 {\n padding-left: 1.5rem !important;\n }\n .px-lg-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .py-lg-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .pt-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-5 {\n padding-left: 3rem !important;\n }\n .px-lg-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-lg-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mt-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-auto {\n margin-left: auto !important;\n }\n .mx-lg-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-lg-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n}\n\n@media (min-width: 1200px) {\n .m-xl-0 {\n margin: 0 !important;\n }\n .mt-xl-0 {\n margin-top: 0 !important;\n }\n .mr-xl-0 {\n margin-right: 0 !important;\n }\n .mb-xl-0 {\n margin-bottom: 0 !important;\n }\n .ml-xl-0 {\n margin-left: 0 !important;\n }\n .mx-xl-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .my-xl-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .mt-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mr-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mb-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-xl-1 {\n margin-left: 0.25rem !important;\n }\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .my-xl-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .mt-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mr-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mb-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-xl-2 {\n margin-left: 0.5rem !important;\n }\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .my-xl-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .mt-xl-3 {\n margin-top: 1rem !important;\n }\n .mr-xl-3 {\n margin-right: 1rem !important;\n }\n .mb-xl-3 {\n margin-bottom: 1rem !important;\n }\n .ml-xl-3 {\n margin-left: 1rem !important;\n }\n .mx-xl-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .my-xl-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .mt-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mr-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mb-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-xl-4 {\n margin-left: 1.5rem !important;\n }\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .my-xl-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .mt-xl-5 {\n margin-top: 3rem !important;\n }\n .mr-xl-5 {\n margin-right: 3rem !important;\n }\n .mb-xl-5 {\n margin-bottom: 3rem !important;\n }\n .ml-xl-5 {\n margin-left: 3rem !important;\n }\n .mx-xl-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .my-xl-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .pt-xl-0 {\n padding-top: 0 !important;\n }\n .pr-xl-0 {\n padding-right: 0 !important;\n }\n .pb-xl-0 {\n padding-bottom: 0 !important;\n }\n .pl-xl-0 {\n padding-left: 0 !important;\n }\n .px-xl-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .py-xl-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .pt-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pr-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pb-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-xl-1 {\n padding-left: 0.25rem !important;\n }\n .px-xl-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .py-xl-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .pt-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pr-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pb-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-xl-2 {\n padding-left: 0.5rem !important;\n }\n .px-xl-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .py-xl-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .pt-xl-3 {\n padding-top: 1rem !important;\n }\n .pr-xl-3 {\n padding-right: 1rem !important;\n }\n .pb-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pl-xl-3 {\n padding-left: 1rem !important;\n }\n .px-xl-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .py-xl-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .pt-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pr-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pb-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-xl-4 {\n padding-left: 1.5rem !important;\n }\n .px-xl-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .py-xl-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .pt-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-5 {\n padding-left: 3rem !important;\n }\n .px-xl-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-xl-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mt-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-auto {\n margin-left: auto !important;\n }\n .mx-xl-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-xl-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n@media (min-width: 576px) {\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 768px) {\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 992px) {\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 1200px) {\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.font-weight-normal {\n font-weight: normal;\n}\n\n.font-weight-bold {\n font-weight: bold;\n}\n\n.font-italic {\n font-style: italic;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-primary {\n color: #007bff !important;\n}\n\na.text-primary:focus, a.text-primary:hover {\n color: #0062cc !important;\n}\n\n.text-secondary {\n color: #868e96 !important;\n}\n\na.text-secondary:focus, a.text-secondary:hover {\n color: #6c757d !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\na.text-success:focus, a.text-success:hover {\n color: #1e7e34 !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\na.text-info:focus, a.text-info:hover {\n color: #117a8b !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\na.text-warning:focus, a.text-warning:hover {\n color: #d39e00 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\na.text-danger:focus, a.text-danger:hover {\n color: #bd2130 !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\na.text-light:focus, a.text-light:hover {\n color: #dae0e5 !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\na.text-dark:focus, a.text-dark:hover {\n color: #1d2124 !important;\n}\n\n.text-muted {\n color: #868e96 !important;\n}\n\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n","// scss-lint:disable QualifyingElement, DuplicateProperty, VendorPrefix\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n// 2. Change the default font family in all browsers.\n// 3. Correct the line height in all browsers.\n// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.\n// 5. Setting @viewport causes scrollbars to overlap content in IE11 and Edge, so\n// we force a non-overlapping, non-auto-hiding scrollbar to counteract.\n// 6. Change the default tap highlight to be completely transparent in iOS.\n\nhtml {\n box-sizing: border-box; // 1\n font-family: sans-serif; // 2\n line-height: 1.15; // 3\n -webkit-text-size-adjust: 100%; // 4\n -ms-text-size-adjust: 100%; // 4\n -ms-overflow-style: scrollbar; // 5\n -webkit-tap-highlight-color: rgba(0,0,0,0); // 6\n}\n\n*,\n*::before,\n*::after {\n box-sizing: inherit; // 1\n}\n\n// IE10+ doesn't honor `` in some cases.\n@at-root {\n @-ms-viewport { width: device-width; }\n}\n\n// Shim for \"new\" HTML5 structural elements to display correctly (IE10, older browsers)\narticle, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n font-size: $font-size-base;\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n background-color: $body-bg; // 2\n}\n\n// Suppress the focus outline on elements that cannot be accessed via keyboard.\n// This prevents an unwanted focus outline from appearing around elements that\n// might still respond to pointer events.\n//\n// Credit: https://github.com/suitcss/base\n[tabindex=\"-1\"]:focus {\n outline: none !important;\n}\n\n\n// Content grouping\n//\n// 1. Add the correct box sizing in Firefox.\n// 2. Show the overflow in Edge and IE.\n\nhr {\n box-sizing: content-box; // 1\n height: 0; // 1\n overflow: visible; // 2\n}\n\n\n//\n// Typography\n//\n\n// Remove top margins from headings\n//\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: .5rem;\n}\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\n// Abbreviations\n//\n// 1. Remove the bottom border in Firefox 39-.\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Duplicate behavior to the data-* attribute for our tooltip plugin\n\nabbr[title],\nabbr[data-original-title] { // 4\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n border-bottom: 0; // 1\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // Undo browser default\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\ndfn {\n font-style: italic; // Add the correct font style in Android 4.3-\n}\n\nb,\nstrong {\n font-weight: bolder; // Add the correct font weight in Chrome, Edge, and Safari\n}\n\nsmall {\n font-size: 80%; // Add the correct font size in all browsers\n}\n\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n//\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n//\n// Links\n//\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n background-color: transparent; // Remove the gray background on active links in IE 10.\n -webkit-text-decoration-skip: objects; // Remove gaps in links underline in iOS 8+ and Safari 8+.\n\n @include hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href)\n// which have not been made explicitly keyboard-focusable (without tabindex).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n\n @include hover-focus {\n color: inherit;\n text-decoration: none;\n }\n\n &:focus {\n outline: 0;\n }\n}\n\n\n//\n// Code\n//\n\npre,\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; // Correct the inheritance and scaling of font size in all browsers.\n font-size: 1em; // Correct the odd `em` font sizing in all browsers.\n}\n\npre {\n // Remove browser default top margin\n margin-top: 0;\n // Reset browser default of `1em` to use `rem`s\n margin-bottom: 1rem;\n // Don't allow content to break outside\n overflow: auto;\n}\n\n\n//\n// Figures\n//\n\nfigure {\n // Apply a consistent margin strategy (matches our type styles).\n margin: 0 0 1rem;\n}\n\n\n//\n// Images and content\n//\n\nimg {\n vertical-align: middle;\n border-style: none; // Remove the border on images inside links in IE 10-.\n}\n\nsvg:not(:root) {\n overflow: hidden; // Hide the overflow in IE\n}\n\n\n// Avoid 300ms click delay on touch devices that support the `touch-action` CSS property.\n//\n// In particular, unlike most other browsers, IE11+Edge on Windows 10 on touch devices and IE Mobile 10-11\n// DON'T remove the click delay when `` is present.\n// However, they DO support removing the click delay via `touch-action: manipulation`.\n// See:\n// * https://v4-alpha.getbootstrap.com/content/reboot/#click-delay-optimization-for-touch\n// * http://caniuse.com/#feat=css-touch-action\n// * https://patrickhlauke.github.io/touch/tests/results/#suppressing-300ms-delay\n\na,\narea,\nbutton,\n[role=\"button\"],\ninput,\nlabel,\nselect,\nsummary,\ntextarea {\n touch-action: manipulation;\n}\n\n\n//\n// Tables\n//\n\ntable {\n border-collapse: collapse; // Prevent double borders\n}\n\ncaption {\n padding-top: $table-cell-padding;\n padding-bottom: $table-cell-padding;\n color: $text-muted;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n // Matches default `` alignment\n text-align: left;\n}\n\n\n//\n// Forms\n//\n\nlabel {\n // Allow labels to use `margin` for spacing.\n display: inline-block;\n margin-bottom: .5rem;\n}\n\n// Work around a Firefox/IE bug where the transparent `button` background\n// results in a loss of the default `button` focus styles.\n//\n// Credit: https://github.com/suitcss/base/\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // Remove the margin in Firefox and Safari\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible; // Show the overflow in Edge\n}\n\nbutton,\nselect {\n text-transform: none; // Remove the inheritance of text transform in Firefox\n}\n\n// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`\n// controls in Android 4.\n// 2. Correct the inability to style clickable types in iOS and Safari.\nbutton,\nhtml [type=\"button\"], // 1\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button; // 2\n}\n\n// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box; // 1. Add the correct box sizing in IE 10-\n padding: 0; // 2. Remove the padding in IE 10-\n}\n\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n // Remove the default appearance of temporal inputs to avoid a Mobile Safari\n // bug where setting a custom line-height prevents text from being vertically\n // centered within the input.\n // See https://bugs.webkit.org/show_bug.cgi?id=139848\n // and https://github.com/twbs/bootstrap/issues/11266\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto; // Remove the default vertical scrollbar in IE.\n // Textareas should really only resize vertically so they don't break their (horizontal) containers.\n resize: vertical;\n}\n\nfieldset {\n // Browsers set a default `min-width: min-content;` on fieldsets,\n // unlike e.g. `

`s, which have `min-width: 0;` by default.\n // So we reset that to ensure fieldsets behave more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359\n // and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements\n min-width: 0;\n // Reset the default outline behavior of fieldsets so they don't affect page layout.\n padding: 0;\n margin: 0;\n border: 0;\n}\n\n// 1. Correct the text wrapping in Edge and IE.\n// 2. Correct the color inheritance from `fieldset` elements in IE.\nlegend {\n display: block;\n width: 100%;\n max-width: 100%; // 1\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit; // 2\n white-space: normal; // 1\n}\n\nprogress {\n vertical-align: baseline; // Add the correct vertical alignment in Chrome, Firefox, and Opera.\n}\n\n// Correct the cursor style of increment and decrement buttons in Chrome.\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n // This overrides the extra rounded corners on search inputs in iOS so that our\n // `.form-control` class can properly style them. Note that this cannot simply\n // be added to `.form-control` as it's not specific enough. For details, see\n // https://github.com/twbs/bootstrap/issues/11586.\n outline-offset: -2px; // 2. Correct the outline style in Safari.\n -webkit-appearance: none;\n}\n\n//\n// Remove the inner padding and cancel buttons in Chrome and Safari on macOS.\n//\n\n[type=\"search\"]::-webkit-search-cancel-button,\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// 1. Correct the inability to style clickable types in iOS and Safari.\n// 2. Change font properties to `inherit` in Safari.\n//\n\n::-webkit-file-upload-button {\n font: inherit; // 2\n -webkit-appearance: button; // 1\n}\n\n//\n// Correct element displays\n//\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item; // Add the correct display in all browsers\n}\n\ntemplate {\n display: none; // Add the correct display in IE\n}\n\n// Always hide an element with the `hidden` HTML attribute (from PureCSS).\n// Needed for proper display in IE 10-.\n[hidden] {\n display: none !important;\n}\n","// Variables\n//\n// Copy settings from this file into the provided `_custom.scss` to override\n// the Bootstrap defaults without modifying key, versioned files.\n//\n// Variables should follow the `$component-state-property-size` formula for\n// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.\n\n// Table of Contents\n//\n// Color system\n// Options\n// Spacing\n// Body\n// Links\n// Grid breakpoints\n// Grid containers\n// Grid columns\n// Fonts\n// Components\n// Tables\n// Buttons\n// Forms\n// Dropdowns\n// Z-index master list\n// Navs\n// Navbar\n// Pagination\n// Jumbotron\n// Form states and alerts\n// Cards\n// Tooltips\n// Popovers\n// Badges\n// Modals\n// Alerts\n// Progress bars\n// List group\n// Image thumbnails\n// Figures\n// Breadcrumbs\n// Carousel\n// Close\n// Code\n\n\n//\n// Color system\n//\n\n$white: #fff !default;\n$gray-100: #f8f9fa !default;\n$gray-200: #e9ecef !default;\n$gray-300: #dee2e6 !default;\n$gray-400: #ced4da !default;\n$gray-500: #adb5bd !default;\n$gray-600: #868e96 !default;\n$gray-700: #495057 !default;\n$gray-800: #343a40 !default;\n$gray-900: #212529 !default;\n$black: #000 !default;\n\n$grays: (\n 100: $gray-100,\n 200: $gray-200,\n 300: $gray-300,\n 400: $gray-400,\n 500: $gray-500,\n 600: $gray-600,\n 700: $gray-700,\n 800: $gray-800,\n 900: $gray-900\n) !default;\n\n$blue: #007bff !default;\n$indigo: #6610f2 !default;\n$purple: #6f42c1 !default;\n$pink: #e83e8c !default;\n$red: #dc3545 !default;\n$orange: #fd7e14 !default;\n$yellow: #ffc107 !default;\n$green: #28a745 !default;\n$teal: #20c997 !default;\n$cyan: #17a2b8 !default;\n\n$colors: (\n blue: $blue,\n indigo: $indigo,\n purple: $purple,\n pink: $pink,\n red: $red,\n orange: $orange,\n yellow: $yellow,\n green: $green,\n teal: $teal,\n cyan: $cyan,\n white: $white,\n gray: $gray-600,\n gray-dark: $gray-800\n) !default;\n\n$theme-colors: (\n primary: $blue,\n secondary: $gray-600,\n success: $green,\n info: $cyan,\n warning: $yellow,\n danger: $red,\n light: $gray-100,\n dark: $gray-800\n) !default;\n\n// Set a specific jump point for requesting color jumps\n$theme-color-interval: 8% !default;\n\n\n// Options\n//\n// Quickly modify global styling by enabling or disabling optional features.\n\n$enable-rounded: true !default;\n$enable-shadows: false !default;\n$enable-gradients: false !default;\n$enable-transitions: true !default;\n$enable-hover-media-query: false !default;\n$enable-grid-classes: true !default;\n$enable-print-styles: true !default;\n\n\n// Spacing\n//\n// Control the default styling of most Bootstrap elements by modifying these\n// variables. Mostly focused on spacing.\n// You can add more entries to the $spacers map, should you need more variation.\n\n$spacer: 1rem !default;\n$spacers: (\n 0: 0,\n 1: ($spacer * .25),\n 2: ($spacer * .5),\n 3: $spacer,\n 4: ($spacer * 1.5),\n 5: ($spacer * 3)\n) !default;\n\n// This variable affects the `.h-*` and `.w-*` classes.\n$sizes: (\n 25: 25%,\n 50: 50%,\n 75: 75%,\n 100: 100%\n) !default;\n\n// Body\n//\n// Settings for the `` element.\n\n$body-bg: $white !default;\n$body-color: $gray-900 !default;\n\n// Links\n//\n// Style anchor elements.\n\n$link-color: theme-color(\"primary\") !default;\n$link-decoration: none !default;\n$link-hover-color: darken($link-color, 15%) !default;\n$link-hover-decoration: underline !default;\n\n\n// Grid breakpoints\n//\n// Define the minimum dimensions at which your layout will change,\n// adapting to different screen sizes, for use in media queries.\n\n$grid-breakpoints: (\n xs: 0,\n sm: 576px,\n md: 768px,\n lg: 992px,\n xl: 1200px\n) !default;\n@include _assert-ascending($grid-breakpoints, \"$grid-breakpoints\");\n@include _assert-starts-at-zero($grid-breakpoints);\n\n\n// Grid containers\n//\n// Define the maximum width of `.container` for different screen sizes.\n\n$container-max-widths: (\n sm: 540px,\n md: 720px,\n lg: 960px,\n xl: 1140px\n) !default;\n@include _assert-ascending($container-max-widths, \"$container-max-widths\");\n\n\n// Grid columns\n//\n// Set the number of columns and specify the width of the gutters.\n\n$grid-columns: 12 !default;\n$grid-gutter-width: 30px !default;\n\n// Components\n//\n// Define common padding and border radius sizes and more.\n\n$line-height-lg: 1.5 !default;\n$line-height-sm: 1.5 !default;\n\n$border-width: 1px !default;\n\n$border-radius: .25rem !default;\n$border-radius-lg: .3rem !default;\n$border-radius-sm: .2rem !default;\n\n$component-active-color: $white !default;\n$component-active-bg: theme-color(\"primary\") !default;\n\n$caret-width: .3em !default;\n\n$transition-base: all .2s ease-in-out !default;\n$transition-fade: opacity .15s linear !default;\n$transition-collapse: height .35s ease !default;\n\n\n// Fonts\n//\n// Font, line-height, and color for body text, headings, and more.\n\n$font-family-sans-serif: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif !default;\n$font-family-monospace: Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !default;\n$font-family-base: $font-family-sans-serif !default;\n\n$font-size-base: 1rem !default; // Assumes the browser default, typically `16px`\n$font-size-lg: 1.25rem !default;\n$font-size-sm: .875rem !default;\n\n$font-weight-normal: normal !default;\n$font-weight-bold: bold !default;\n\n$font-weight-base: $font-weight-normal !default;\n$line-height-base: 1.5 !default;\n\n$h1-font-size: 2.5rem !default;\n$h2-font-size: 2rem !default;\n$h3-font-size: 1.75rem !default;\n$h4-font-size: 1.5rem !default;\n$h5-font-size: 1.25rem !default;\n$h6-font-size: 1rem !default;\n\n$headings-margin-bottom: ($spacer / 2) !default;\n$headings-font-family: inherit !default;\n$headings-font-weight: 500 !default;\n$headings-line-height: 1.1 !default;\n$headings-color: inherit !default;\n\n$display1-size: 6rem !default;\n$display2-size: 5.5rem !default;\n$display3-size: 4.5rem !default;\n$display4-size: 3.5rem !default;\n\n$display1-weight: 300 !default;\n$display2-weight: 300 !default;\n$display3-weight: 300 !default;\n$display4-weight: 300 !default;\n$display-line-height: $headings-line-height !default;\n\n$lead-font-size: 1.25rem !default;\n$lead-font-weight: 300 !default;\n\n$small-font-size: 80% !default;\n\n$text-muted: $gray-600 !default;\n\n$blockquote-small-color: $gray-600 !default;\n$blockquote-font-size: ($font-size-base * 1.25) !default;\n\n$hr-border-color: rgba($black,.1) !default;\n$hr-border-width: $border-width !default;\n\n$mark-padding: .2em !default;\n\n$dt-font-weight: $font-weight-bold !default;\n\n$kbd-box-shadow: inset 0 -.1rem 0 rgba($black,.25) !default;\n$nested-kbd-font-weight: $font-weight-bold !default;\n\n$list-inline-padding: 5px !default;\n\n$mark-bg: #fcf8e3 !default;\n\n\n// Tables\n//\n// Customizes the `.table` component with basic values, each used across all table variations.\n\n$table-cell-padding: .75rem !default;\n$table-cell-padding-sm: .3rem !default;\n\n$table-bg: transparent !default;\n$table-accent-bg: rgba($black,.05) !default;\n$table-hover-bg: rgba($black,.075) !default;\n$table-active-bg: $table-hover-bg !default;\n\n$table-border-width: $border-width !default;\n$table-border-color: $gray-200 !default;\n\n$table-head-bg: $gray-200 !default;\n$table-head-color: $gray-700 !default;\n\n$table-inverse-bg: $gray-900 !default;\n$table-inverse-accent-bg: rgba($white, .05) !default;\n$table-inverse-hover-bg: rgba($white, .075) !default;\n$table-inverse-border-color: lighten($gray-900, 7.5%) !default;\n$table-inverse-color: $body-bg !default;\n\n\n// Buttons\n//\n// For each of Bootstrap's buttons, define text, background and border color.\n\n$input-btn-padding-y: .5rem !default;\n$input-btn-padding-x: .75rem !default;\n$input-btn-line-height: 1.25 !default;\n\n$input-btn-padding-y-sm: .25rem !default;\n$input-btn-padding-x-sm: .5rem !default;\n$input-btn-line-height-sm: 1.5 !default;\n\n$input-btn-padding-y-lg: .5rem !default;\n$input-btn-padding-x-lg: 1rem !default;\n$input-btn-line-height-lg: 1.5 !default;\n\n$btn-font-weight: $font-weight-normal !default;\n$btn-box-shadow: inset 0 1px 0 rgba($white,.15), 0 1px 1px rgba($black,.075) !default;\n$btn-focus-box-shadow: 0 0 0 3px rgba(theme-color(\"primary\"), .25) !default;\n$btn-active-box-shadow: inset 0 3px 5px rgba($black,.125) !default;\n\n$btn-link-disabled-color: $gray-600 !default;\n\n$btn-block-spacing-y: .5rem !default;\n\n// Allows for customizing button radius independently from global border radius\n$btn-border-radius: $border-radius !default;\n$btn-border-radius-lg: $border-radius-lg !default;\n$btn-border-radius-sm: $border-radius-sm !default;\n\n$btn-transition: all .15s ease-in-out !default;\n\n\n// Forms\n\n$input-bg: $white !default;\n$input-disabled-bg: $gray-200 !default;\n\n$input-color: $gray-700 !default;\n$input-border-color: rgba($black,.15) !default;\n$input-btn-border-width: $border-width !default; // For form controls and buttons\n$input-box-shadow: inset 0 1px 1px rgba($black,.075) !default;\n\n$input-border-radius: $border-radius !default;\n$input-border-radius-lg: $border-radius-lg !default;\n$input-border-radius-sm: $border-radius-sm !default;\n\n$input-focus-bg: $input-bg !default;\n$input-focus-border-color: lighten(theme-color(\"primary\"), 25%) !default;\n$input-focus-box-shadow: $input-box-shadow, $btn-focus-box-shadow !default;\n$input-focus-color: $input-color !default;\n\n$input-placeholder-color: $gray-600 !default;\n\n$input-height-border: $input-btn-border-width * 2 !default;\n\n$input-height-inner: ($font-size-base * $input-btn-line-height) + ($input-btn-padding-y * 2) !default;\n$input-height: calc(#{$input-height-inner} + #{$input-height-border}) !default;\n\n$input-height-inner-sm: ($font-size-sm * $input-btn-line-height-sm) + ($input-btn-padding-y-sm * 2) !default;\n$input-height-sm: calc(#{$input-height-inner-sm} + #{$input-height-border}) !default;\n\n$input-height-inner-lg: ($font-size-sm * $input-btn-line-height-lg) + ($input-btn-padding-y-lg * 2) !default;\n$input-height-lg: calc(#{$input-height-inner-lg} + #{$input-height-border}) !default;\n\n$input-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s !default;\n\n$form-text-margin-top: .25rem !default;\n\n$form-check-margin-bottom: .5rem !default;\n$form-check-input-gutter: 1.25rem !default;\n$form-check-input-margin-y: .25rem !default;\n$form-check-input-margin-x: .25rem !default;\n\n$form-check-inline-margin-x: .75rem !default;\n\n$form-group-margin-bottom: 1rem !default;\n\n$input-group-addon-bg: $gray-200 !default;\n$input-group-addon-border-color: $input-border-color !default;\n\n$custom-control-gutter: 1.5rem !default;\n$custom-control-spacer-y: .25rem !default;\n$custom-control-spacer-x: 1rem !default;\n\n$custom-control-indicator-size: 1rem !default;\n$custom-control-indicator-bg: #ddd !default;\n$custom-control-indicator-bg-size: 50% 50% !default;\n$custom-control-indicator-box-shadow: inset 0 .25rem .25rem rgba($black,.1) !default;\n\n$custom-control-indicator-disabled-bg: $gray-200 !default;\n$custom-control-description-disabled-color: $gray-600 !default;\n\n$custom-control-indicator-checked-color: $white !default;\n$custom-control-indicator-checked-bg: theme-color(\"primary\") !default;\n$custom-control-indicator-checked-box-shadow: none !default;\n\n$custom-control-indicator-focus-box-shadow: 0 0 0 1px $body-bg, 0 0 0 3px theme-color(\"primary\") !default;\n\n$custom-control-indicator-active-color: $white !default;\n$custom-control-indicator-active-bg: lighten(theme-color(\"primary\"), 35%) !default;\n$custom-control-indicator-active-box-shadow: none !default;\n\n$custom-checkbox-indicator-border-radius: $border-radius !default;\n$custom-checkbox-indicator-icon-checked: str-replace(url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$custom-control-indicator-checked-color}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E\"), \"#\", \"%23\") !default;\n\n$custom-checkbox-indicator-indeterminate-bg: theme-color(\"primary\") !default;\n$custom-checkbox-indicator-indeterminate-color: $custom-control-indicator-checked-color !default;\n$custom-checkbox-indicator-icon-indeterminate: str-replace(url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='#{$custom-checkbox-indicator-indeterminate-color}' d='M0 2h4'/%3E%3C/svg%3E\"), \"#\", \"%23\") !default;\n$custom-checkbox-indicator-indeterminate-box-shadow: none !default;\n\n$custom-radio-indicator-border-radius: 50% !default;\n$custom-radio-indicator-icon-checked: str-replace(url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='#{$custom-control-indicator-checked-color}'/%3E%3C/svg%3E\"), \"#\", \"%23\") !default;\n\n$custom-select-padding-y: .375rem !default;\n$custom-select-padding-x: .75rem !default;\n$custom-select-height: $input-height !default;\n$custom-select-indicator-padding: 1rem !default; // Extra padding to account for the presence of the background-image based indicator\n$custom-select-line-height: $input-btn-line-height !default;\n$custom-select-color: $input-color !default;\n$custom-select-disabled-color: $gray-600 !default;\n$custom-select-bg: $white !default;\n$custom-select-disabled-bg: $gray-200 !default;\n$custom-select-bg-size: 8px 10px !default; // In pixels because image dimensions\n$custom-select-indicator-color: #333 !default;\n$custom-select-indicator: str-replace(url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E\"), \"#\", \"%23\") !default;\n$custom-select-border-width: $input-btn-border-width !default;\n$custom-select-border-color: $input-border-color !default;\n$custom-select-border-radius: $border-radius !default;\n\n$custom-select-focus-border-color: lighten(theme-color(\"primary\"), 25%) !default;\n$custom-select-focus-box-shadow: inset 0 1px 2px rgba($black, .075), 0 0 5px rgba($custom-select-focus-border-color, .5) !default;\n\n$custom-select-font-size-sm: 75% !default;\n$custom-select-height-sm: $input-height-sm !default;\n\n$custom-file-height: 2.5rem !default;\n$custom-file-width: 14rem !default;\n$custom-file-focus-box-shadow: 0 0 0 .075rem $white, 0 0 0 .2rem theme-color(\"primary\") !default;\n\n$custom-file-padding-y: 1rem !default;\n$custom-file-padding-x: .5rem !default;\n$custom-file-line-height: 1.5 !default;\n$custom-file-color: $gray-700 !default;\n$custom-file-bg: $white !default;\n$custom-file-border-width: $border-width !default;\n$custom-file-border-color: $input-border-color !default;\n$custom-file-border-radius: $border-radius !default;\n$custom-file-box-shadow: inset 0 .2rem .4rem rgba($black,.05) !default;\n$custom-file-button-color: $custom-file-color !default;\n$custom-file-button-bg: $gray-200 !default;\n$custom-file-text: (\n placeholder: (\n en: \"Choose file...\"\n ),\n button-label: (\n en: \"Browse\"\n )\n) !default;\n\n\n// Form validation\n$form-feedback-valid-color: theme-color(\"success\") !default;\n$form-feedback-invalid-color: theme-color(\"danger\") !default;\n\n\n// Dropdowns\n//\n// Dropdown menu container and contents.\n\n$dropdown-min-width: 10rem !default;\n$dropdown-padding-y: .5rem !default;\n$dropdown-spacer: .125rem !default;\n$dropdown-bg: $white !default;\n$dropdown-border-color: rgba($black,.15) !default;\n$dropdown-border-width: $border-width !default;\n$dropdown-divider-bg: $gray-200 !default;\n$dropdown-box-shadow: 0 .5rem 1rem rgba($black,.175) !default;\n\n$dropdown-link-color: $gray-900 !default;\n$dropdown-link-hover-color: darken($gray-900, 5%) !default;\n$dropdown-link-hover-bg: $gray-100 !default;\n\n$dropdown-link-active-color: $component-active-color !default;\n$dropdown-link-active-bg: $component-active-bg !default;\n\n$dropdown-link-disabled-color: $gray-600 !default;\n\n$dropdown-item-padding-y: .25rem !default;\n$dropdown-item-padding-x: 1.5rem !default;\n\n$dropdown-header-color: $gray-600 !default;\n\n\n// Z-index master list\n//\n// Warning: Avoid customizing these values. They're used for a bird's eye view\n// of components dependent on the z-axis and are designed to all work together.\n\n$zindex-dropdown: 1000 !default;\n$zindex-sticky: 1020 !default;\n$zindex-fixed: 1030 !default;\n$zindex-modal-backdrop: 1040 !default;\n$zindex-modal: 1050 !default;\n$zindex-popover: 1060 !default;\n$zindex-tooltip: 1070 !default;\n\n// Navs\n\n$nav-link-padding-y: .5rem !default;\n$nav-link-padding-x: 1rem !default;\n$nav-link-disabled-color: $gray-600 !default;\n\n$nav-tabs-border-color: #ddd !default;\n$nav-tabs-border-width: $border-width !default;\n$nav-tabs-border-radius: $border-radius !default;\n$nav-tabs-link-hover-border-color: $gray-200 !default;\n$nav-tabs-link-active-color: $gray-700 !default;\n$nav-tabs-link-active-bg: $body-bg !default;\n$nav-tabs-link-active-border-color: #ddd !default;\n\n$nav-pills-border-radius: $border-radius !default;\n$nav-pills-link-active-color: $component-active-color !default;\n$nav-pills-link-active-bg: $component-active-bg !default;\n\n// Navbar\n\n$navbar-padding-y: ($spacer / 2) !default;\n$navbar-padding-x: $spacer !default;\n\n$navbar-brand-font-size: $font-size-lg !default;\n// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link\n$nav-link-height: $navbar-brand-font-size * $line-height-base !default;\n$navbar-brand-height: ($font-size-base * $line-height-base + $nav-link-padding-y * 2) !default;\n$navbar-brand-padding-y: ($navbar-brand-height - $nav-link-height) / 2 !default;\n\n$navbar-toggler-padding-y: .25rem !default;\n$navbar-toggler-padding-x: .75rem !default;\n$navbar-toggler-font-size: $font-size-lg !default;\n$navbar-toggler-border-radius: $btn-border-radius !default;\n\n$navbar-dark-color: rgba($white,.5) !default;\n$navbar-dark-hover-color: rgba($white,.75) !default;\n$navbar-dark-active-color: rgba($white,1) !default;\n$navbar-dark-disabled-color: rgba($white,.25) !default;\n$navbar-dark-toggler-icon-bg: str-replace(url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-dark-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E\"), \"#\", \"%23\") !default;\n$navbar-dark-toggler-border-color: rgba($white,.1) !default;\n\n$navbar-light-color: rgba($black,.5) !default;\n$navbar-light-hover-color: rgba($black,.7) !default;\n$navbar-light-active-color: rgba($black,.9) !default;\n$navbar-light-disabled-color: rgba($black,.3) !default;\n$navbar-light-toggler-icon-bg: str-replace(url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-light-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E\"), \"#\", \"%23\") !default;\n$navbar-light-toggler-border-color: rgba($black,.1) !default;\n\n// Pagination\n\n$pagination-padding-y: .5rem !default;\n$pagination-padding-x: .75rem !default;\n$pagination-padding-y-sm: .25rem !default;\n$pagination-padding-x-sm: .5rem !default;\n$pagination-padding-y-lg: .75rem !default;\n$pagination-padding-x-lg: 1.5rem !default;\n$pagination-line-height: 1.25 !default;\n\n$pagination-color: $link-color !default;\n$pagination-bg: $white !default;\n$pagination-border-width: $border-width !default;\n$pagination-border-color: #ddd !default;\n\n$pagination-hover-color: $link-hover-color !default;\n$pagination-hover-bg: $gray-200 !default;\n$pagination-hover-border-color: #ddd !default;\n\n$pagination-active-color: $white !default;\n$pagination-active-bg: theme-color(\"primary\") !default;\n$pagination-active-border-color: theme-color(\"primary\") !default;\n\n$pagination-disabled-color: $gray-600 !default;\n$pagination-disabled-bg: $white !default;\n$pagination-disabled-border-color: #ddd !default;\n\n\n// Jumbotron\n\n$jumbotron-padding: 2rem !default;\n$jumbotron-bg: $gray-200 !default;\n\n\n// Cards\n\n$card-spacer-y: .75rem !default;\n$card-spacer-x: 1.25rem !default;\n$card-border-width: 1px !default;\n$card-border-radius: $border-radius !default;\n$card-border-color: rgba($black,.125) !default;\n$card-inner-border-radius: calc(#{$card-border-radius} - #{$card-border-width}) !default;\n$card-cap-bg: rgba($black, .03) !default;\n$card-bg: $white !default;\n\n$card-img-overlay-padding: 1.25rem !default;\n\n$card-deck-margin: ($grid-gutter-width / 2) !default;\n\n$card-columns-count: 3 !default;\n$card-columns-gap: 1.25rem !default;\n$card-columns-margin: $card-spacer-y !default;\n\n\n// Tooltips\n\n$tooltip-max-width: 200px !default;\n$tooltip-color: $white !default;\n$tooltip-bg: $black !default;\n$tooltip-opacity: .9 !default;\n$tooltip-padding-y: 3px !default;\n$tooltip-padding-x: 8px !default;\n$tooltip-margin: 0 !default;\n\n\n$tooltip-arrow-width: 5px !default;\n$tooltip-arrow-height: 5px !default;\n$tooltip-arrow-color: $tooltip-bg !default;\n\n\n// Popovers\n\n$popover-inner-padding: 1px !default;\n$popover-bg: $white !default;\n$popover-max-width: 276px !default;\n$popover-border-width: $border-width !default;\n$popover-border-color: rgba($black,.2) !default;\n$popover-box-shadow: 0 5px 10px rgba($black,.2) !default;\n\n$popover-header-bg: darken($popover-bg, 3%) !default;\n$popover-header-color: $headings-color !default;\n$popover-header-padding-y: 8px !default;\n$popover-header-padding-x: 14px !default;\n\n$popover-body-color: $body-color !default;\n$popover-body-padding-y: 9px !default;\n$popover-body-padding-x: 14px !default;\n\n$popover-arrow-width: 10px !default;\n$popover-arrow-height: 5px !default;\n$popover-arrow-color: $popover-bg !default;\n\n$popover-arrow-outer-width: ($popover-arrow-width + 1px) !default;\n$popover-arrow-outer-color: fade-in($popover-border-color, .05) !default;\n\n\n// Badges\n\n$badge-color: $white !default;\n$badge-font-size: 75% !default;\n$badge-font-weight: $font-weight-bold !default;\n$badge-padding-y: .25em !default;\n$badge-padding-x: .4em !default;\n\n$badge-pill-padding-x: .6em !default;\n// Use a higher than normal value to ensure completely rounded edges when\n// customizing padding or font-size on labels.\n$badge-pill-border-radius: 10rem !default;\n\n\n// Modals\n\n// Padding applied to the modal body\n$modal-inner-padding: 15px !default;\n\n$modal-dialog-margin: 10px !default;\n$modal-dialog-margin-y-sm-up: 30px !default;\n\n$modal-title-line-height: $line-height-base !default;\n\n$modal-content-bg: $white !default;\n$modal-content-border-color: rgba($black,.2) !default;\n$modal-content-border-width: $border-width !default;\n$modal-content-box-shadow-xs: 0 3px 9px rgba($black,.5) !default;\n$modal-content-box-shadow-sm-up: 0 5px 15px rgba($black,.5) !default;\n\n$modal-backdrop-bg: $black !default;\n$modal-backdrop-opacity: .5 !default;\n$modal-header-border-color: $gray-200 !default;\n$modal-footer-border-color: $modal-header-border-color !default;\n$modal-header-border-width: $modal-content-border-width !default;\n$modal-footer-border-width: $modal-header-border-width !default;\n$modal-header-padding: 15px !default;\n\n$modal-lg: 800px !default;\n$modal-md: 500px !default;\n$modal-sm: 300px !default;\n\n$modal-transition: transform .3s ease-out !default;\n\n\n// Alerts\n//\n// Define alert colors, border radius, and padding.\n\n$alert-padding-y: .75rem !default;\n$alert-padding-x: 1.25rem !default;\n$alert-margin-bottom: 1rem !default;\n$alert-border-radius: $border-radius !default;\n$alert-link-font-weight: $font-weight-bold !default;\n$alert-border-width: $border-width !default;\n\n\n// Progress bars\n\n$progress-height: 1rem !default;\n$progress-font-size: .75rem !default;\n$progress-bg: $gray-200 !default;\n$progress-border-radius: $border-radius !default;\n$progress-box-shadow: inset 0 .1rem .1rem rgba($black,.1) !default;\n$progress-bar-color: $white !default;\n$progress-bar-bg: theme-color(\"primary\") !default;\n$progress-bar-animation-timing: 1s linear infinite !default;\n$progress-bar-transition: width .6s ease !default;\n\n// List group\n\n$list-group-bg: $white !default;\n$list-group-border-color: rgba($black,.125) !default;\n$list-group-border-width: $border-width !default;\n$list-group-border-radius: $border-radius !default;\n\n$list-group-item-padding-y: .75rem !default;\n$list-group-item-padding-x: 1.25rem !default;\n\n$list-group-hover-bg: $gray-100 !default;\n$list-group-active-color: $component-active-color !default;\n$list-group-active-bg: $component-active-bg !default;\n$list-group-active-border-color: $list-group-active-bg !default;\n\n$list-group-disabled-color: $gray-600 !default;\n$list-group-disabled-bg: $list-group-bg !default;\n\n$list-group-action-color: $gray-700 !default;\n$list-group-action-hover-color: $list-group-action-color !default;\n\n$list-group-action-active-color: $body-color !default;\n$list-group-action-active-bg: $gray-200 !default;\n\n\n// Image thumbnails\n\n$thumbnail-padding: .25rem !default;\n$thumbnail-bg: $body-bg !default;\n$thumbnail-border-width: $border-width !default;\n$thumbnail-border-color: #ddd !default;\n$thumbnail-border-radius: $border-radius !default;\n$thumbnail-box-shadow: 0 1px 2px rgba($black,.075) !default;\n$thumbnail-transition: all .2s ease-in-out !default;\n\n\n// Figures\n\n$figure-caption-font-size: 90% !default;\n$figure-caption-color: $gray-600 !default;\n\n\n// Breadcrumbs\n\n$breadcrumb-padding-y: .75rem !default;\n$breadcrumb-padding-x: 1rem !default;\n$breadcrumb-item-padding: .5rem !default;\n\n$breadcrumb-bg: $gray-200 !default;\n$breadcrumb-divider-color: $gray-600 !default;\n$breadcrumb-active-color: $gray-600 !default;\n$breadcrumb-divider: \"/\" !default;\n\n\n// Carousel\n\n$carousel-control-color: $white !default;\n$carousel-control-width: 15% !default;\n$carousel-control-opacity: .5 !default;\n\n$carousel-indicator-width: 30px !default;\n$carousel-indicator-height: 3px !default;\n$carousel-indicator-spacer: 3px !default;\n$carousel-indicator-active-bg: $white !default;\n\n$carousel-caption-width: 70% !default;\n$carousel-caption-color: $white !default;\n\n$carousel-control-icon-width: 20px !default;\n\n$carousel-control-prev-icon-bg: str-replace(url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M4 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E\"), \"#\", \"%23\") !default;\n$carousel-control-next-icon-bg: str-replace(url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M1.5 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E\"), \"#\", \"%23\") !default;\n\n$carousel-transition: transform .6s ease !default;\n\n\n// Close\n\n$close-font-size: $font-size-base * 1.5 !default;\n$close-font-weight: $font-weight-bold !default;\n$close-color: $black !default;\n$close-text-shadow: 0 1px 0 $white !default;\n\n// Code\n\n$code-font-size: 90% !default;\n$code-padding-y: .2rem !default;\n$code-padding-x: .4rem !default;\n$code-color: #bd4147 !default;\n$code-bg: $gray-100 !default;\n\n$kbd-color: $white !default;\n$kbd-bg: $gray-900 !default;\n\n$pre-color: $gray-900 !default;\n$pre-scrollable-max-height: 340px !default;\n","@mixin hover {\n // TODO: re-enable along with mq4-hover-shim\n// @if $enable-hover-media-query {\n// // See Media Queries Level 4: https://drafts.csswg.org/mediaqueries/#hover\n// // Currently shimmed by https://github.com/twbs/mq4-hover-shim\n// @media (hover: hover) {\n// &:hover { @content }\n// }\n// }\n// @else {\n// scss-lint:disable Indentation\n &:hover { @content }\n// scss-lint:enable Indentation\n// }\n}\n\n\n@mixin hover-focus {\n @if $enable-hover-media-query {\n &:focus { @content }\n @include hover { @content }\n } @else {\n &:focus,\n &:hover {\n @content\n }\n }\n}\n\n@mixin plain-hover-focus {\n @if $enable-hover-media-query {\n &,\n &:focus {\n @content\n }\n @include hover { @content }\n } @else {\n &,\n &:focus,\n &:hover {\n @content\n }\n }\n}\n\n@mixin hover-focus-active {\n @if $enable-hover-media-query {\n &:focus,\n &:active {\n @content\n }\n @include hover { @content }\n } @else {\n &:focus,\n &:active,\n &:hover {\n @content\n }\n }\n}\n","//\n// Headings\n//\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1, .h1 { font-size: $h1-font-size; }\nh2, .h2 { font-size: $h2-font-size; }\nh3, .h3 { font-size: $h3-font-size; }\nh4, .h4 { font-size: $h4-font-size; }\nh5, .h5 { font-size: $h5-font-size; }\nh6, .h6 { font-size: $h6-font-size; }\n\n.lead {\n font-size: $lead-font-size;\n font-weight: $lead-font-weight;\n}\n\n// Type display classes\n.display-1 {\n font-size: $display1-size;\n font-weight: $display1-weight;\n line-height: $display-line-height;\n}\n.display-2 {\n font-size: $display2-size;\n font-weight: $display2-weight;\n line-height: $display-line-height;\n}\n.display-3 {\n font-size: $display3-size;\n font-weight: $display3-weight;\n line-height: $display-line-height;\n}\n.display-4 {\n font-size: $display4-size;\n font-weight: $display4-weight;\n line-height: $display-line-height;\n}\n\n\n//\n// Horizontal rules\n//\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: $hr-border-width solid $hr-border-color;\n}\n\n\n//\n// Emphasis\n//\n\nsmall,\n.small {\n font-size: $small-font-size;\n font-weight: $font-weight-normal;\n}\n\nmark,\n.mark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n//\n// Lists\n//\n\n.list-unstyled {\n @include list-unstyled;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n @include list-unstyled;\n}\n.list-inline-item {\n display: inline-block;\n\n &:not(:last-child) {\n margin-right: $list-inline-padding;\n }\n}\n\n\n//\n// Misc\n//\n\n// Builds on `abbr`\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n// Blockquotes\n.blockquote {\n margin-bottom: $spacer;\n font-size: $blockquote-font-size;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%; // back to default font-size\n color: $blockquote-small-color;\n\n &::before {\n content: \"\\2014 \\00A0\"; // em dash, nbsp\n }\n}\n","// Lists\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n@mixin list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n","// Responsive images (ensure images don't scale beyond their parents)\n//\n// This is purposefully opt-in via an explicit class rather than being the default for all ``s.\n// We previously tried the \"images are responsive by default\" approach in Bootstrap v2,\n// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)\n// which weren't expecting the images within themselves to be involuntarily resized.\n// See also https://github.com/twbs/bootstrap/issues/18178\n.img-fluid {\n @include img-fluid;\n}\n\n\n// Image thumbnails\n.img-thumbnail {\n padding: $thumbnail-padding;\n background-color: $thumbnail-bg;\n border: $thumbnail-border-width solid $thumbnail-border-color;\n @include border-radius($thumbnail-border-radius);\n @include transition($thumbnail-transition);\n @include box-shadow($thumbnail-box-shadow);\n\n // Keep them at most 100% wide\n @include img-fluid;\n}\n\n//\n// Figures\n//\n\n.figure {\n // Ensures the caption's text aligns with the image.\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: ($spacer / 2);\n line-height: 1;\n}\n\n.figure-caption {\n font-size: $figure-caption-font-size;\n color: $figure-caption-color;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n\n@mixin img-fluid {\n // Part 1: Set a maximum relative to the parent\n max-width: 100%;\n // Part 2: Override the height to auto, otherwise images will be stretched\n // when setting a width and height attribute on the img element.\n height: auto;\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size.\n\n@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) {\n background-image: url($file-1x);\n\n // Autoprefixer takes care of adding -webkit-min-device-pixel-ratio and -o-min-device-pixel-ratio,\n // but doesn't convert dppx=>dpi.\n // There's no such thing as unprefixed min-device-pixel-ratio since it's nonstandard.\n // Compatibility info: http://caniuse.com/#feat=css-media-resolution\n @media\n only screen and (min-resolution: 192dpi), // IE9-11 don't support dppx\n only screen and (min-resolution: 2dppx) { // Standardized\n background-image: url($file-2x);\n background-size: $width-1x $height-1x;\n }\n}\n","// Single side border-radius\n\n@mixin border-radius($radius: $border-radius) {\n @if $enable-rounded {\n border-radius: $radius;\n }\n}\n\n@mixin border-top-radius($radius) {\n @if $enable-rounded {\n border-top-left-radius: $radius;\n border-top-right-radius: $radius;\n }\n}\n\n@mixin border-right-radius($radius) {\n @if $enable-rounded {\n border-top-right-radius: $radius;\n border-bottom-right-radius: $radius;\n }\n}\n\n@mixin border-bottom-radius($radius) {\n @if $enable-rounded {\n border-bottom-right-radius: $radius;\n border-bottom-left-radius: $radius;\n }\n}\n\n@mixin border-left-radius($radius) {\n @if $enable-rounded {\n border-top-left-radius: $radius;\n border-bottom-left-radius: $radius;\n }\n}\n","@mixin transition($transition...) {\n @if $enable-transitions {\n @if length($transition) == 0 {\n transition: $transition-base;\n } @else {\n transition: $transition;\n }\n }\n}\n","// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: $font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: $code-padding-y $code-padding-x;\n font-size: $code-font-size;\n color: $code-color;\n background-color: $code-bg;\n @include border-radius($border-radius);\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n padding: 0;\n color: inherit;\n background-color: inherit;\n }\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: $code-padding-y $code-padding-x;\n font-size: $code-font-size;\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n @include box-shadow($kbd-box-shadow);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: $nested-kbd-font-weight;\n @include box-shadow(none);\n }\n}\n\n// Blocks of code\npre {\n display: block;\n margin-top: 0;\n margin-bottom: 1rem;\n font-size: $code-font-size;\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: $pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n@if $enable-grid-classes {\n .container {\n @include make-container();\n @include make-container-max-widths();\n }\n}\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but with 100% width for\n// fluid, full width layouts.\n\n@if $enable-grid-classes {\n .container-fluid {\n width: 100%;\n @include make-container();\n }\n}\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n@if $enable-grid-classes {\n .row {\n @include make-row();\n }\n\n // Remove the negative margin from default .row, then the horizontal padding\n // from all immediate children columns (to prevent runaway style inheritance).\n .no-gutters {\n margin-right: 0;\n margin-left: 0;\n\n > .col,\n > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n }\n }\n}\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n@if $enable-grid-classes {\n @include make-grid-columns();\n}\n","/// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n@mixin make-container() {\n margin-right: auto;\n margin-left: auto;\n padding-right: ($grid-gutter-width / 2);\n padding-left: ($grid-gutter-width / 2);\n width: 100%;\n}\n\n\n// For each breakpoint, define the maximum width of the container in a media query\n@mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) {\n @each $breakpoint, $container-max-width in $max-widths {\n @include media-breakpoint-up($breakpoint, $breakpoints) {\n max-width: $container-max-width;\n }\n }\n}\n\n@mixin make-row() {\n display: flex;\n flex-wrap: wrap;\n margin-right: ($grid-gutter-width / -2);\n margin-left: ($grid-gutter-width / -2);\n}\n\n@mixin make-col-ready() {\n position: relative;\n // Prevent columns from becoming too narrow when at smaller grid tiers by\n // always setting `width: 100%;`. This works because we use `flex` values\n // later on to override this initial width.\n width: 100%;\n min-height: 1px; // Prevent collapsing\n padding-right: ($grid-gutter-width / 2);\n padding-left: ($grid-gutter-width / 2);\n}\n\n@mixin make-col($size, $columns: $grid-columns) {\n flex: 0 0 percentage($size / $columns);\n // Add a `max-width` to ensure content within each column does not blow out\n // the width of the column. Applies to IE10+ and Firefox. Chrome and Safari\n // do not appear to require this.\n max-width: percentage($size / $columns);\n}\n","// Breakpoint viewport sizes and media queries.\n//\n// Breakpoints are defined as a map of (name: minimum width), order from small to large:\n//\n// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)\n//\n// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.\n\n// Name of the next breakpoint, or null for the last breakpoint.\n//\n// >> breakpoint-next(sm)\n// md\n// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// md\n// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))\n// md\n@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {\n $n: index($breakpoint-names, $name);\n @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);\n}\n\n// Minimum breakpoint width. Null for the smallest (first) breakpoint.\n//\n// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 576px\n@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {\n $min: map-get($breakpoints, $name);\n @return if($min != 0, $min, null);\n}\n\n// Maximum breakpoint width. Null for the largest (last) breakpoint.\n// The maximum value is calculated as the minimum of the next one less 0.1.\n//\n// >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 767px\n@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {\n $next: breakpoint-next($name, $breakpoints);\n @return if($next, breakpoint-min($next, $breakpoints) - 1px, null);\n}\n\n// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash infront.\n// Useful for making responsive utilities.\n//\n// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"\" (Returns a blank string)\n// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"-sm\"\n@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {\n @return if(breakpoint-min($name, $breakpoints) == null, \"\", \"-#{$name}\");\n}\n\n// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.\n// Makes the @content apply to the given breakpoint and wider.\n@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n @if $min {\n @media (min-width: $min) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media of at most the maximum breakpoint width. No query for the largest breakpoint.\n// Makes the @content apply to the given breakpoint and narrower.\n@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {\n $max: breakpoint-max($name, $breakpoints);\n @if $max {\n @media (max-width: $max) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media that spans multiple breakpoint widths.\n// Makes the @content apply between the min and max breakpoints\n@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($lower, $breakpoints);\n $max: breakpoint-max($upper, $breakpoints);\n\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n}\n\n// Media between the breakpoint's minimum and maximum widths.\n// No minimum for the smallest breakpoint, and no maximum for the largest one.\n// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.\n@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n $max: breakpoint-max($name, $breakpoints);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($name)\n } @else if $min == null {\n @include media-breakpoint-down($name)\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `$grid-columns`.\n\n@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {\n // Common properties for all breakpoints\n %grid-column {\n position: relative;\n width: 100%;\n min-height: 1px; // Prevent columns from collapsing when empty\n padding-right: ($gutter / 2);\n padding-left: ($gutter / 2);\n }\n\n @each $breakpoint in map-keys($breakpoints) {\n $infix: breakpoint-infix($breakpoint, $breakpoints);\n\n // Allow columns to stretch full width below their breakpoints\n @for $i from 1 through $columns {\n .col#{$infix}-#{$i} {\n @extend %grid-column;\n }\n }\n .col#{$infix},\n .col#{$infix}-auto {\n @extend %grid-column;\n }\n\n @include media-breakpoint-up($breakpoint, $breakpoints) {\n // Provide basic `.col-{bp}` classes for equal-width flexbox columns\n .col#{$infix} {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col#{$infix}-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none; // Reset earlier grid tiers\n }\n\n @for $i from 1 through $columns {\n .col#{$infix}-#{$i} {\n @include make-col($i, $columns);\n }\n }\n\n @for $i from 1 through $columns {\n .order#{$infix}-#{$i} {\n order: $i;\n }\n }\n }\n }\n}\n","//\n// Basic Bootstrap table\n//\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: $spacer;\n background-color: $table-bg; // Reset for nesting within parents with `background-color`.\n\n th,\n td {\n padding: $table-cell-padding;\n vertical-align: top;\n border-top: $table-border-width solid $table-border-color;\n }\n\n thead th {\n vertical-align: bottom;\n border-bottom: (2 * $table-border-width) solid $table-border-color;\n }\n\n tbody + tbody {\n border-top: (2 * $table-border-width) solid $table-border-color;\n }\n\n .table {\n background-color: $body-bg;\n }\n}\n\n\n//\n// Condensed table w/ half padding\n//\n\n.table-sm {\n th,\n td {\n padding: $table-cell-padding-sm;\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: $table-border-width solid $table-border-color;\n\n th,\n td {\n border: $table-border-width solid $table-border-color;\n }\n\n thead {\n th,\n td {\n border-bottom-width: (2 * $table-border-width);\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n tbody tr:nth-of-type(odd) {\n background-color: $table-accent-bg;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n tbody tr {\n @include hover {\n background-color: $table-hover-bg;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n@each $color, $value in $theme-colors {\n @include table-row-variant($color, theme-color-level($color, -9));\n}\n\n@include table-row-variant(active, $table-active-bg);\n\n\n// Inverse styles\n//\n// Same table markup, but inverted color scheme: dark background and light text.\n\n.thead-inverse {\n th {\n color: $table-inverse-color;\n background-color: $table-inverse-bg;\n }\n}\n\n.thead-default {\n th {\n color: $table-head-color;\n background-color: $table-head-bg;\n }\n}\n\n.table-inverse {\n color: $table-inverse-color;\n background-color: $table-inverse-bg;\n\n th,\n td,\n thead th {\n border-color: $table-inverse-border-color;\n }\n\n &.table-bordered {\n border: 0;\n }\n\n &.table-striped {\n tbody tr:nth-of-type(odd) {\n background-color: $table-inverse-accent-bg;\n }\n }\n\n &.table-hover {\n tbody tr {\n @include hover {\n background-color: $table-inverse-hover-bg;\n }\n }\n }\n}\n\n\n// Responsive tables\n//\n// Add `.table-responsive` to `.table`s and we'll make them mobile friendly by\n// enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n @include media-breakpoint-down(md) {\n display: block;\n width: 100%;\n overflow-x: auto;\n -ms-overflow-style: -ms-autohiding-scrollbar; // See https://github.com/twbs/bootstrap/pull/10057\n\n // Prevent double border on horizontal scroll due to use of `display: block;`\n &.table-bordered {\n border: 0;\n }\n }\n}\n","// Tables\n\n@mixin table-row-variant($state, $background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table-#{$state} {\n &,\n > th,\n > td {\n background-color: $background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover {\n $hover-background: darken($background, 5%);\n\n .table-#{$state} {\n @include hover {\n background-color: $hover-background;\n\n > td,\n > th {\n background-color: $hover-background;\n }\n }\n }\n }\n}\n","// Bootstrap functions\n//\n// Utility mixins and functions for evalutating source code across our variables, maps, and mixins.\n\n// Ascending\n// Used to evaluate Sass maps like our grid breakpoints.\n@mixin _assert-ascending($map, $map-name) {\n $prev-key: null;\n $prev-num: null;\n @each $key, $num in $map {\n @if $prev-num == null {\n // Do nothing\n } @else if not comparable($prev-num, $num) {\n @warn \"Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !\";\n } @else if $prev-num >= $num {\n @warn \"Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !\";\n }\n $prev-key: $key;\n $prev-num: $num;\n }\n}\n\n// Starts at zero\n// Another grid mixin that ensures the min-width of the lowest breakpoint starts at 0.\n@mixin _assert-starts-at-zero($map) {\n $values: map-values($map);\n $first-value: nth($values, 1);\n @if $first-value != 0 {\n @warn \"First breakpoint in `$grid-breakpoints` must start at 0, but starts at #{$first-value}.\";\n }\n}\n\n// Replace `$search` with `$replace` in `$string`\n// Used on our SVG icon backgrounds for custom forms.\n//\n// @author Hugo Giraudel\n// @param {String} $string - Initial string\n// @param {String} $search - Substring to replace\n// @param {String} $replace ('') - New value\n// @return {String} - Updated string\n@function str-replace($string, $search, $replace: \"\") {\n $index: str-index($string, $search);\n\n @if $index {\n @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);\n }\n\n @return $string;\n}\n\n// Color contrast\n@mixin color-yiq($color) {\n $r: red($color);\n $g: green($color);\n $b: blue($color);\n\n $yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;\n\n @if ($yiq >= 150) {\n color: #111;\n } @else {\n color: #fff;\n }\n}\n\n// Retreive color Sass maps\n@function color($key: \"blue\") {\n @return map-get($colors, $key);\n}\n\n@function theme-color($key: \"primary\") {\n @return map-get($theme-colors, $key);\n}\n\n@function grayscale($key: \"100\") {\n @return map-get($grays, $key);\n}\n\n// Request a theme color level\n@function theme-color-level($color-name: \"primary\", $level: 0) {\n $color: theme-color($color-name);\n $color-base: if($level > 0, #000, #fff);\n\n @if $level < 0 {\n // Lighter values need a quick double negative for the Sass math to work\n @return mix($color-base, $color, $level * -1 * $theme-color-interval);\n } @else {\n @return mix($color-base, $color, $level * $theme-color-interval);\n }\n}\n","// scss-lint:disable QualifyingElement, VendorPrefix\n\n//\n// Textual form controls\n//\n\n.form-control {\n display: block;\n width: 100%;\n // // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n // height: $input-height;\n padding: $input-btn-padding-y $input-btn-padding-x;\n font-size: $font-size-base;\n line-height: $input-btn-line-height;\n color: $input-color;\n background-color: $input-bg;\n // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214.\n background-image: none;\n background-clip: padding-box;\n border: $input-btn-border-width solid $input-border-color;\n\n // Note: This has no effect on `s in CSS.\n @if $enable-rounded {\n // Manually use the if/else instead of the mixin to account for iOS override\n border-radius: $input-border-radius;\n } @else {\n // Otherwise undo the iOS default\n border-radius: 0;\n }\n\n @include box-shadow($input-box-shadow);\n @include transition($input-transition);\n\n // Unstyle the caret on ` receives focus\n // in IE and (under certain conditions) Edge, as it looks bad and cannot be made to\n // match the appearance of the native widget.\n // See https://github.com/twbs/bootstrap/issues/19398.\n color: $input-color;\n background-color: $input-bg;\n }\n}\n\n// Make file inputs better match text inputs by forcing them to new lines.\n.form-control-file,\n.form-control-range {\n display: block;\n}\n\n\n//\n// Labels\n//\n\n// For use with horizontal and inline forms, when you need the label text to\n// align with the form controls.\n.col-form-label {\n padding-top: calc(#{$input-btn-padding-y} - #{$input-btn-border-width} * 2);\n padding-bottom: calc(#{$input-btn-padding-y} - #{$input-btn-border-width} * 2);\n margin-bottom: 0; // Override the `
\n" + + " ") + }; + + that.addcatform = function() { + var title = $(".input_title_add").val(); + var slug = $(".input_slug_add").val(); + var content = $(".textarea_content_add").val(); + var icon = $(".input_image_add").val(); + + if (title && content) { + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: App.GetBaseUrl() + "admin/ajax/cat/add", + data: {'title': title, 'content': content, 'slug': slug, 'icon': icon}, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code = 1) { + App.NotifToast("success", data.title, data.message); + setTimeout(function() { + location.reload(); + }, 3000); + } + else { + App.NotifToast("error", "Erreur", data.message); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + } + }; + + that.init = function() { + $(".input_title").bind("paste keyup", function() { + that.updatetitlecat($(this)); + }); + + $(".textarea_content").bind("paste keyup", function() { + that.updatecontentcat($(this)); + }); + + $(".add_cat").click(function() { + that.addcat($(this)); + that.TitleToLink(); + $(".add_cat_form").submit(function(e) { + e.preventDefault(); + that.addcatform(); + return false; + }); + }); + }; + + return that; + +})(); + +$(document).ready(function () { + Cat.init(); +}); \ No newline at end of file diff --git a/public/assets/js/admin/comments.js b/public/assets/js/admin/comments.js new file mode 100644 index 0000000..5a2993f --- /dev/null +++ b/public/assets/js/admin/comments.js @@ -0,0 +1,78 @@ +var Comments = (function(){ + + var that = {}; + + that.ValideComments = function(get) { + var parent_li = get.parent("li"); + var comments_id = parent_li.data("comments"); + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: App.GetBaseUrl() + "admin/ajax/comments/valide", + data: {'id': comments_id}, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code = 1) { + App.NotifToast("success", data.title, data.message); + parent_li.hide("slow"); + } + else { + App.NotifToast("error", "Erreur", data.message); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + }; + + that.RefuseComments = function(get) { + var parent_li = get.parent("li"); + var comments_id = parent_li.data("comments"); + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: App.GetBaseUrl() + "admin/ajax/comments/refuse", + data: {'id': comments_id}, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code = 1) { + App.NotifToast("success", data.title, data.message); + parent_li.hide("slow"); + } + else { + App.NotifToast("error", "Erreur", data.message); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + }; + + that.init = function() { + $(".comments-valide").click(function() { + that.ValideComments($(this)); + }); + $(".comments-refuse").click(function() { + that.RefuseComments($(this)); + }); + }; + + return that; + +})(); + +$(document).ready(function () { + Comments.init(); +}); \ No newline at end of file diff --git a/public/assets/js/admin/config.js b/public/assets/js/admin/config.js new file mode 100644 index 0000000..197730d --- /dev/null +++ b/public/assets/js/admin/config.js @@ -0,0 +1,88 @@ +var Config = (function(){ + + var that = {}; + + that.SaveParams = function(get) { + + var parent_class = get.parents(".justify-content-sm-center"); + var params_id = parent_class.data("configid"); + var input_key = parent_class.children().find("#params_key").val(); + var input_data = parent_class.children().find("#params_data").val(); + + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: App.GetBaseUrl() + "admin/ajax/config/updateparams", + data: {'id': params_id, 'key': input_key, 'data': input_data}, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code = 1) { + console.log(data) + App.NotifToast("success", data.title, data.message); + } + else { + App.NotifToast("error", "Erreur", data.message); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + + }; + + that.DelParams = function(get) { + + var parent_class = get.parents(".justify-content-sm-center"); + var params_id = parent_class.data("configid"); + + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: App.GetBaseUrl() + "admin/ajax/config/delparams", + data: {'id': params_id}, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code = 1) { + App.NotifToast("success", data.title, data.message); + } + else { + App.NotifToast("error", "Erreur", data.message); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + + }; + + that.init = function() { + + $(".save-config").click(function() { + that.SaveParams($(this)); + }); + + $(".del-config").click(function() { + that.DelParams($(this)); + }); + + }; + + return that; + +})(); + +$(document).ready(function () { + Config.init(); +}); \ No newline at end of file diff --git a/public/assets/js/admin/contact.js b/public/assets/js/admin/contact.js new file mode 100644 index 0000000..083996e --- /dev/null +++ b/public/assets/js/admin/contact.js @@ -0,0 +1,124 @@ +var Contact = (function(){ + + var that = {}; + + that.reponse = function(get) { + + var sujet = $("#inputsujet").val(); + var message = $("#inputmessage").val(); + var contact_id = $(get).data("contact") + + if (sujet && message) { + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: App.GetBaseUrl() + "admin/ajax/contact/rep", + data: {'id': contact_id, 'sujet': sujet, 'message': message}, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code = 1) { + App.NotifToast("success", data.title, data.message); + } + else { + App.NotifToast("error", "Erreur", data.message); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + } + else { + App.NotifToast("error", "Erreur", "Merci de remplire tout les champs"); + } + + }; + + that.markedview = function(get) { + var parent_li = get.parent("li"); + var contact_id = parent_li.data("contact"); + + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: App.GetBaseUrl() + "admin/ajax/contact/markedview", + data: {'id': contact_id}, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code = 1) { + App.NotifToast("success", data.title, data.message); + parent_li.hide("slow"); + } + else { + App.NotifToast("error", "Erreur", data.message); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + + }; + + that.delete = function(get) { + var parent_li = get.parent("li"); + var contact_id = parent_li.data("contact"); + + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: App.GetBaseUrl() + "admin/ajax/contact/del", + data: {'id': contact_id}, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code = 1) { + App.NotifToast("success", data.title, data.message); + parent_li.hide("slow"); + } + else { + App.NotifToast("error", "Erreur", data.message); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + + }; + + that.init = function() { + $(".repcontact").submit(function(e) { + e.preventDefault(); + that.reponse($(this)); + return false; + }); + $(".markedview-contact").click(function() { + that.markedview($(this)); + }); + $(".del-contact").click(function() { + that.delete($(this)); + }); + }; + + return that; + +})(); + +$(document).ready(function () { + Contact.init(); +}); \ No newline at end of file diff --git a/public/assets/js/blog/app.js b/public/assets/js/blog/app.js new file mode 100644 index 0000000..57c9606 --- /dev/null +++ b/public/assets/js/blog/app.js @@ -0,0 +1,65 @@ +var App = { + research: function() { + $(".click_search").click(function(e) { + e.preventDefault(); + $(".recherche_modal").fadeIn("slow", function() { + $(".modal").slideDown("slow"); + $(".del_modal").click(function(ev) { + $(".modal").slideUp("slow", function() { + $(".recherche_modal").fadeOut("slow"); + }); + }); + $(window).click(function(event) { + if (event.target.className == "recherche_modal") { + $(".modal").slideUp("slow", function() { + $(".recherche_modal").fadeOut("slow"); + }); + } + }); + }); + return false; + }); + + $(".tablinks").click(function(e) { + var link = $(this).data("tabsnav"); + $(".tabsnavtitle > .active").removeClass("active"); + $(this).addClass("active"); + $(".tabsnavcontent > .show").removeClass("show"); + $(".tabsnavcontent > #" + link).addClass("show") + }); + + }, + avert_cookies: function() { + if (!Cookies.get('cookie_ok')) { + $(".eupopup-container").css("display", "block"); + $(".eupopup-button_1").click(function() { + $(".eupopup-container").slideUp("slow", function() { + Cookies.set('cookie_ok', true, { expires: 365 }); + }); + }); + } + }, + scroll_top: function() { + $(document).on( 'scroll', function(){ + if ($(window).scrollTop() > 100) { + $('.scroll-top-wrapper').addClass('show'); + } else { + $('.scroll-top-wrapper').removeClass('show'); + } + }); + + $('.scroll-top-wrapper').on('click', scrollToTop); + function scrollToTop() { + verticalOffset = typeof(verticalOffset) != 'undefined' ? verticalOffset : 0; + element = $('body'); + offset = element.offset(); + offsetTop = offset.top; + $('html, body').animate({scrollTop: offsetTop}, 500, 'linear'); + } + }, + init: function() { + this.research(); + this.avert_cookies(); + this.scroll_top(); + } +}; \ No newline at end of file diff --git a/public/assets/js/blog/article.js b/public/assets/js/blog/article.js new file mode 100644 index 0000000..c27333c --- /dev/null +++ b/public/assets/js/blog/article.js @@ -0,0 +1,112 @@ +var Article = (function(){ + + var that = {}; + + that.init = function() { + that.AddComments(); + that.bloc_share(); + }; + + that.GetBaseUrl = function () { + if(window.location.host == "deathart.dev") { + return window.location.protocol + "//" + window.location.host + "/"; + } + else { + return "https://www.deathart.fr/"; + } + }; + + that.AddComments = function() { + + $.ajaxSetup({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + } + }); + + $(".add_com").submit(function(e) { + e.preventDefault(); + + var name = $('#name'); + var email = $('#email'); + var message = $('#com'); + var post = $(this).data("post") + var error = false; + + if(!name.val()) { + error = true; + } + + if(!email.val()) { + error = true; + } + + if(!message.val()) { + error = true; + } + + if(!error) { + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: that.GetBaseUrl() + "comments/add", + data: { 'post_id' : post,'name': name.val(), 'email': email.val(), 'message': message.val() }, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code == 1) { + $(".message_add_coms").html("" + data.message + ""); + setTimeout(function() { + location.reload(); + }, 3000); + } + else if(data.code == 2) { + $(".message_add_coms").html("" + data.message + ""); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + } + else { + $(".message_add_coms").html("Merci de remplire les champs"); + } + + return false; + }); + + }; + + that.bloc_share = function() { + + var share = $('#share'); + + var vTop = share.offset().top - parseFloat(share.css('margin-top').replace(/auto/, 0)); + + $(window).scroll(function(event) { + + var y = $(this).scrollTop(); + + if (y >= vTop) { + share.addClass('fixed'); + } else { + share.removeClass('fixed'); + } + + }); + }; + + return that; + +})(); + +$(document).ready(function () { + Article.init(); +}); \ No newline at end of file diff --git a/public/assets/js/blog/contact.js b/public/assets/js/blog/contact.js new file mode 100644 index 0000000..43c0099 --- /dev/null +++ b/public/assets/js/blog/contact.js @@ -0,0 +1,69 @@ +var Contact = (function(){ + + var that = {}; + + that.GetBaseUrl = function () { + if(window.location.host == "deathart.dev") { + return window.location.protocol + "//" + window.location.host + "/"; + } + else { + return "https://www.deathart.fr/"; + } + }; + + that.contactform = function() { + + var name = $("#name").val(); + var email = $("#email").val(); + var sujet = $("#sujet").val(); + var msg = $("#msg").val(); + + if(name && email && sujet && msg) { + + $.ajax({ + beforeSend: function (xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", $('meta[name="_token"]').attr('content')); + } + }, + method: "POST", + url: that.GetBaseUrl() + "contact/add", + data: { 'name': name, 'email': email, 'sujet': sujet, 'message': msg }, + dataType: 'json', + cache: false, + success: function (data) { + if(data.code == 1) { + $(".message_contact").html("" + data.message + ""); + $(".contact").hide(); + } + else if(data.code == 2) { + $(".message_contact").html("" + data.message + ""); + } + }, + error: function (data) { + console.log(data.responseText); + } + }); + + } + else { + $(".message_contact").html("Merci de remplir tout les champs"); + } + + }; + + that.init = function() { + $(".contact").submit(function(e) { + e.preventDefault(); + that.contactform(); + return false; + }); + }; + + return that; + +})(); + +$(document).ready(function () { + Contact.init(); +}); \ No newline at end of file diff --git a/public/assets/js/cookie.js b/public/assets/js/cookie.js new file mode 100644 index 0000000..01f1d8b --- /dev/null +++ b/public/assets/js/cookie.js @@ -0,0 +1,166 @@ +/*! + * JavaScript Cookie v2.1.4 + * https://github.com/js-cookie/js-cookie + * + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack + * Released under the MIT license + */ +; +(function(factory) { + var registeredInModuleLoader = false; + if (typeof define === 'function' && define.amd) { + define(factory); + registeredInModuleLoader = true; + } + if (typeof exports === 'object') { + module.exports = factory(); + registeredInModuleLoader = true; + } + if (!registeredInModuleLoader) { + var OldCookies = window.Cookies; + var api = window.Cookies = factory(); + api.noConflict = function() { + window.Cookies = OldCookies; + return api; + }; + } +}(function() { + function extend() { + var i = 0; + var result = {}; + for (; i < arguments.length; i++) { + var attributes = arguments[i]; + for (var key in attributes) { + result[key] = attributes[key]; + } + } + return result; + } + + function init(converter) { + function api(key, value, attributes) { + var result; + if (typeof document === 'undefined') { + return; + } + + // Write + + if (arguments.length > 1) { + attributes = extend({ + path: '/' + }, api.defaults, attributes); + + if (typeof attributes.expires === 'number') { + var expires = new Date(); + expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); + attributes.expires = expires; + } + + // We're using "expires" because "max-age" is not supported by IE + attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; + + try { + result = JSON.stringify(value); + if (/^[\{\[]/.test(result)) { + value = result; + } + } catch (e) {} + + if (!converter.write) { + value = encodeURIComponent(String(value)) + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + } else { + value = converter.write(value, key); + } + + key = encodeURIComponent(String(key)); + key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); + key = key.replace(/[\(\)]/g, escape); + + var stringifiedAttributes = ''; + + for (var attributeName in attributes) { + if (!attributes[attributeName]) { + continue; + } + stringifiedAttributes += '; ' + attributeName; + if (attributes[attributeName] === true) { + continue; + } + stringifiedAttributes += '=' + attributes[attributeName]; + } + return (document.cookie = key + '=' + value + stringifiedAttributes); + } + + // Read + + if (!key) { + result = {}; + } + + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. Also prevents odd result when + // calling "get()" + var cookies = document.cookie ? document.cookie.split('; ') : []; + var rdecode = /(%[0-9A-Z]{2})+/g; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var cookie = parts.slice(1).join('='); + + if (cookie.charAt(0) === '"') { + cookie = cookie.slice(1, -1); + } + + try { + var name = parts[0].replace(rdecode, decodeURIComponent); + cookie = converter.read ? + converter.read(cookie, name) : converter(cookie, name) || + cookie.replace(rdecode, decodeURIComponent); + + if (this.json) { + try { + cookie = JSON.parse(cookie); + } catch (e) {} + } + + if (key === name) { + result = cookie; + break; + } + + if (!key) { + result[name] = cookie; + } + } catch (e) {} + } + + return result; + } + + api.set = api; + api.get = function(key) { + return api.call(api, key); + }; + api.getJSON = function() { + return api.apply({ + json: true + }, [].slice.call(arguments)); + }; + api.defaults = {}; + + api.remove = function(key, attributes) { + api(key, '', extend(attributes, { + expires: -1 + })); + }; + + api.withConverter = init; + + return api; + } + + return init(function() {}); +})); \ No newline at end of file diff --git a/public/assets/js/jquery.toast.js b/public/assets/js/jquery.toast.js new file mode 100644 index 0000000..51e8679 --- /dev/null +++ b/public/assets/js/jquery.toast.js @@ -0,0 +1,372 @@ +// jQuery toast plugin created by Kamran Ahmed copyright MIT license 2015 +if ( typeof Object.create !== 'function' ) { + Object.create = function( obj ) { + function F() {} + F.prototype = obj; + return new F(); + }; +} + +(function( $, window, document, undefined ) { + + "use strict"; + + var Toast = { + + _positionClasses : ['bottom-left', 'bottom-right', 'top-right', 'top-left', 'bottom-center', 'top-center', 'mid-center'], + _defaultIcons : ['success', 'error', 'info', 'warning'], + + init: function (options, elem) { + this.prepareOptions(options, $.toast.options); + this.process(); + }, + + prepareOptions: function(options, options_to_extend) { + var _options = {}; + if ( ( typeof options === 'string' ) || ( options instanceof Array ) ) { + _options.text = options; + } else { + _options = options; + } + this.options = $.extend( {}, options_to_extend, _options ); + }, + + process: function () { + this.setup(); + this.addToDom(); + this.position(); + this.bindToast(); + this.animate(); + }, + + setup: function () { + + var _toastContent = ''; + + this._toastEl = this._toastEl || $('
', { + class : 'jq-toast-single' + }); + + // For the loader on top + _toastContent += ''; + + if ( this.options.allowToastClose ) { + _toastContent += '×'; + }; + + if ( this.options.text instanceof Array ) { + + if ( this.options.heading ) { + _toastContent +='

' + this.options.heading + '

'; + }; + + _toastContent += '
    '; + for (var i = 0; i < this.options.text.length; i++) { + _toastContent += '
  • ' + this.options.text[i] + '
  • '; + } + _toastContent += '
'; + + } else { + if ( this.options.heading ) { + _toastContent +='

' + this.options.heading + '

'; + }; + _toastContent += this.options.text; + } + + this._toastEl.html( _toastContent ); + + if ( this.options.bgColor !== false ) { + this._toastEl.css("background-color", this.options.bgColor); + }; + + if ( this.options.textColor !== false ) { + this._toastEl.css("color", this.options.textColor); + }; + + if ( this.options.textAlign ) { + this._toastEl.css('text-align', this.options.textAlign); + } + + if ( this.options.icon !== false ) { + this._toastEl.addClass('jq-has-icon'); + + if ( $.inArray(this.options.icon, this._defaultIcons) !== -1 ) { + this._toastEl.addClass('jq-icon-' + this.options.icon); + }; + }; + + if ( this.options.class !== false ){ + this._toastEl.addClass(this.options.class) + } + }, + + position: function () { + if ( ( typeof this.options.position === 'string' ) && ( $.inArray( this.options.position, this._positionClasses) !== -1 ) ) { + + if ( this.options.position === 'bottom-center' ) { + this._container.css({ + left: ( $(window).outerWidth() / 2 ) - this._container.outerWidth()/2, + bottom: 20 + }); + } else if ( this.options.position === 'top-center' ) { + this._container.css({ + left: ( $(window).outerWidth() / 2 ) - this._container.outerWidth()/2, + top: 20 + }); + } else if ( this.options.position === 'mid-center' ) { + this._container.css({ + left: ( $(window).outerWidth() / 2 ) - this._container.outerWidth()/2, + top: ( $(window).outerHeight() / 2 ) - this._container.outerHeight()/2 + }); + } else { + this._container.addClass( this.options.position ); + } + + } else if ( typeof this.options.position === 'object' ) { + this._container.css({ + top : this.options.position.top ? this.options.position.top : 'auto', + bottom : this.options.position.bottom ? this.options.position.bottom : 'auto', + left : this.options.position.left ? this.options.position.left : 'auto', + right : this.options.position.right ? this.options.position.right : 'auto' + }); + } else { + this._container.addClass( 'bottom-left' ); + } + }, + + bindToast: function () { + + var that = this; + + this._toastEl.on('afterShown', function () { + that.processLoader(); + }); + + this._toastEl.find('.close-jq-toast-single').on('click', function ( e ) { + + e.preventDefault(); + + if( that.options.showHideTransition === 'fade') { + that._toastEl.trigger('beforeHide'); + that._toastEl.fadeOut(function () { + that._toastEl.trigger('afterHidden'); + }); + } else if ( that.options.showHideTransition === 'slide' ) { + that._toastEl.trigger('beforeHide'); + that._toastEl.slideUp(function () { + that._toastEl.trigger('afterHidden'); + }); + } else { + that._toastEl.trigger('beforeHide'); + that._toastEl.hide(function () { + that._toastEl.trigger('afterHidden'); + }); + } + }); + + if ( typeof this.options.beforeShow == 'function' ) { + this._toastEl.on('beforeShow', function () { + that.options.beforeShow(that._toastEl); + }); + }; + + if ( typeof this.options.afterShown == 'function' ) { + this._toastEl.on('afterShown', function () { + that.options.afterShown(that._toastEl); + }); + }; + + if ( typeof this.options.beforeHide == 'function' ) { + this._toastEl.on('beforeHide', function () { + that.options.beforeHide(that._toastEl); + }); + }; + + if ( typeof this.options.afterHidden == 'function' ) { + this._toastEl.on('afterHidden', function () { + that.options.afterHidden(that._toastEl); + }); + }; + + if ( typeof this.options.onClick == 'function' ) { + this._toastEl.on('click', function () { + that.options.onClick(that._toastEl); + }); + }; + }, + + addToDom: function () { + + var _container = $('.jq-toast-wrap'); + + if ( _container.length === 0 ) { + + _container = $('
',{ + class: "jq-toast-wrap" + }); + + $('body').append( _container ); + + } else if ( !this.options.stack || isNaN( parseInt(this.options.stack, 10) ) ) { + _container.empty(); + } + + _container.find('.jq-toast-single:hidden').remove(); + + _container.append( this._toastEl ); + + if ( this.options.stack && !isNaN( parseInt( this.options.stack ), 10 ) ) { + + var _prevToastCount = _container.find('.jq-toast-single').length, + _extToastCount = _prevToastCount - this.options.stack; + + if ( _extToastCount > 0 ) { + $('.jq-toast-wrap').find('.jq-toast-single').slice(0, _extToastCount).remove(); + }; + + } + + this._container = _container; + }, + + canAutoHide: function () { + return ( this.options.hideAfter !== false ) && !isNaN( parseInt( this.options.hideAfter, 10 ) ); + }, + + processLoader: function () { + // Show the loader only, if auto-hide is on and loader is demanded + if (!this.canAutoHide() || this.options.loader === false) { + return false; + } + + var loader = this._toastEl.find('.jq-toast-loader'); + + // 400 is the default time that jquery uses for fade/slide + // Divide by 1000 for milliseconds to seconds conversion + var transitionTime = (this.options.hideAfter - 400) / 1000 + 's'; + var loaderBg = this.options.loaderBg; + + var style = loader.attr('style') || ''; + style = style.substring(0, style.indexOf('-webkit-transition')); // Remove the last transition definition + + style += '-webkit-transition: width ' + transitionTime + ' ease-in; \ + -o-transition: width ' + transitionTime + ' ease-in; \ + transition: width ' + transitionTime + ' ease-in; \ + background-color: ' + loaderBg + ';'; + + + loader.attr('style', style).addClass('jq-toast-loaded'); + }, + + animate: function () { + + var that = this; + + this._toastEl.hide(); + + this._toastEl.trigger('beforeShow'); + + if ( this.options.showHideTransition.toLowerCase() === 'fade' ) { + this._toastEl.fadeIn(function ( ){ + that._toastEl.trigger('afterShown'); + }); + } else if ( this.options.showHideTransition.toLowerCase() === 'slide' ) { + this._toastEl.slideDown(function ( ){ + that._toastEl.trigger('afterShown'); + }); + } else { + this._toastEl.show(function ( ){ + that._toastEl.trigger('afterShown'); + }); + } + + if (this.canAutoHide()) { + + var that = this; + + window.setTimeout(function(){ + + if ( that.options.showHideTransition.toLowerCase() === 'fade' ) { + that._toastEl.trigger('beforeHide'); + that._toastEl.fadeOut(function () { + that._toastEl.trigger('afterHidden'); + }); + } else if ( that.options.showHideTransition.toLowerCase() === 'slide' ) { + that._toastEl.trigger('beforeHide'); + that._toastEl.slideUp(function () { + that._toastEl.trigger('afterHidden'); + }); + } else { + that._toastEl.trigger('beforeHide'); + that._toastEl.hide(function () { + that._toastEl.trigger('afterHidden'); + }); + } + + }, this.options.hideAfter); + }; + }, + + reset: function ( resetWhat ) { + + if ( resetWhat === 'all' ) { + $('.jq-toast-wrap').remove(); + } else { + this._toastEl.remove(); + } + + }, + + update: function(options) { + this.prepareOptions(options, this.options); + this.setup(); + this.bindToast(); + }, + + close: function() { + this._toastEl.find('.close-jq-toast-single').click(); + } + }; + + $.toast = function(options) { + var toast = Object.create(Toast); + toast.init(options, this); + + return { + + reset: function ( what ) { + toast.reset( what ); + }, + + update: function( options ) { + toast.update( options ); + }, + + close: function( ) { + toast.close( ); + } + } + }; + + $.toast.options = { + text: '', + heading: '', + showHideTransition: 'fade', + allowToastClose: true, + hideAfter: 3000, + loader: true, + loaderBg: '#9EC600', + stack: 5, + position: 'bottom-left', + bgColor: false, + textColor: false, + textAlign: 'left', + icon: false, + beforeShow: function () {}, + afterShown: function () {}, + beforeHide: function () {}, + afterHidden: function () {}, + onClick: function () {} + }; + +})( jQuery, window, document ); diff --git a/public/assets/js/prism.js b/public/assets/js/prism.js new file mode 100644 index 0000000..617d08c --- /dev/null +++ b/public/assets/js/prism.js @@ -0,0 +1,40 @@ +/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+actionscript+apacheconf+bash+basic+batch+c+csharp+cpp+css-extras+git+http+java+json+less+markdown+php+php-extras+powershell+properties+sass+scss+smarty+sql+twig+typescript+vbnet+wiki+yaml&plugins=line-numbers+toolbar+remove-initial-line-feed+keep-markup+show-language+copy-to-clipboard */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(w instanceof s)){h.lastIndex=0;var _=h.exec(w),P=1;if(!_&&m&&b!=t.length-1){if(h.lastIndex=k,_=h.exec(e),!_)break;for(var A=_.index+(d?_[1].length:0),j=_.index+_[0].length,x=b,O=k,S=t.length;S>x&&(j>O||!t[x].type&&!t[x-1].greedy);++x)O+=t[x].length,A>=O&&(++b,k=O);if(t[b]instanceof s||t[x-1].greedy)continue;P=x-b,w=e.slice(k,O),_.index-=k}if(_){d&&(p=_[1].length);var A=_.index+p,_=_[0].slice(p),j=A+_.length,N=w.slice(0,A),C=w.slice(j),E=[b,P];N&&(++b,k+=N.length,E.push(N));var I=new s(u,f?n.tokenize(_,f):_,y,_,m);if(E.push(I),C&&E.push(C),Array.prototype.splice.apply(t,E),1!=P&&n.matchGrammar(e,t,a,b,k,!0,u),l)break}else if(l)break}}}}},tokenize:function(e,t){var a=[e],r=t.rest;if(r){for(var i in r)t[i]=r[i];delete t.rest}return n.matchGrammar(e,a,t,0,0,!1),a},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o=Object.keys(i.attributes).map(function(e){return e+'="'+(i.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\s\S])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\s\S]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; +Prism.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\s\S]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; +Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/-[-=]?|\+[+=]?|!=?=?|<>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[[^\]\r\n]+]|\\.|[^\/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\s\S]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; +Prism.languages.actionscript=Prism.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|else|extends|finally|for|function|if|implements|import|in|instanceof|interface|internal|is|native|new|null|package|private|protected|public|return|super|switch|this|throw|try|typeof|use|var|void|while|with|dynamic|each|final|get|include|namespace|native|override|set|static)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<>?>?|[!=]=?)=?|[~?@]/}),Prism.languages.actionscript["class-name"].alias="function",Prism.languages.markup&&Prism.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\\1|\\?(?!\1)[\s\S])*\2)*\s*\/?>/,lookbehind:!0,inside:{rest:Prism.languages.markup}}}); +Prism.languages.apacheconf={comment:/#.*/,"directive-inline":{pattern:/^(\s*)\b(AcceptFilter|AcceptPathInfo|AccessFileName|Action|AddAlt|AddAltByEncoding|AddAltByType|AddCharset|AddDefaultCharset|AddDescription|AddEncoding|AddHandler|AddIcon|AddIconByEncoding|AddIconByType|AddInputFilter|AddLanguage|AddModuleInfo|AddOutputFilter|AddOutputFilterByType|AddType|Alias|AliasMatch|Allow|AllowCONNECT|AllowEncodedSlashes|AllowMethods|AllowOverride|AllowOverrideList|Anonymous|Anonymous_LogEmail|Anonymous_MustGiveEmail|Anonymous_NoUserID|Anonymous_VerifyEmail|AsyncRequestWorkerFactor|AuthBasicAuthoritative|AuthBasicFake|AuthBasicProvider|AuthBasicUseDigestAlgorithm|AuthDBDUserPWQuery|AuthDBDUserRealmQuery|AuthDBMGroupFile|AuthDBMType|AuthDBMUserFile|AuthDigestAlgorithm|AuthDigestDomain|AuthDigestNonceLifetime|AuthDigestProvider|AuthDigestQop|AuthDigestShmemSize|AuthFormAuthoritative|AuthFormBody|AuthFormDisableNoStore|AuthFormFakeBasicAuth|AuthFormLocation|AuthFormLoginRequiredLocation|AuthFormLoginSuccessLocation|AuthFormLogoutLocation|AuthFormMethod|AuthFormMimetype|AuthFormPassword|AuthFormProvider|AuthFormSitePassphrase|AuthFormSize|AuthFormUsername|AuthGroupFile|AuthLDAPAuthorizePrefix|AuthLDAPBindAuthoritative|AuthLDAPBindDN|AuthLDAPBindPassword|AuthLDAPCharsetConfig|AuthLDAPCompareAsUser|AuthLDAPCompareDNOnServer|AuthLDAPDereferenceAliases|AuthLDAPGroupAttribute|AuthLDAPGroupAttributeIsDN|AuthLDAPInitialBindAsUser|AuthLDAPInitialBindPattern|AuthLDAPMaxSubGroupDepth|AuthLDAPRemoteUserAttribute|AuthLDAPRemoteUserIsDN|AuthLDAPSearchAsUser|AuthLDAPSubGroupAttribute|AuthLDAPSubGroupClass|AuthLDAPUrl|AuthMerging|AuthName|AuthnCacheContext|AuthnCacheEnable|AuthnCacheProvideFor|AuthnCacheSOCache|AuthnCacheTimeout|AuthnzFcgiCheckAuthnProvider|AuthnzFcgiDefineProvider|AuthType|AuthUserFile|AuthzDBDLoginToReferer|AuthzDBDQuery|AuthzDBDRedirectQuery|AuthzDBMType|AuthzSendForbiddenOnFailure|BalancerGrowth|BalancerInherit|BalancerMember|BalancerPersist|BrowserMatch|BrowserMatchNoCase|BufferedLogs|BufferSize|CacheDefaultExpire|CacheDetailHeader|CacheDirLength|CacheDirLevels|CacheDisable|CacheEnable|CacheFile|CacheHeader|CacheIgnoreCacheControl|CacheIgnoreHeaders|CacheIgnoreNoLastMod|CacheIgnoreQueryString|CacheIgnoreURLSessionIdentifiers|CacheKeyBaseURL|CacheLastModifiedFactor|CacheLock|CacheLockMaxAge|CacheLockPath|CacheMaxExpire|CacheMaxFileSize|CacheMinExpire|CacheMinFileSize|CacheNegotiatedDocs|CacheQuickHandler|CacheReadSize|CacheReadTime|CacheRoot|CacheSocache|CacheSocacheMaxSize|CacheSocacheMaxTime|CacheSocacheMinTime|CacheSocacheReadSize|CacheSocacheReadTime|CacheStaleOnError|CacheStoreExpired|CacheStoreNoStore|CacheStorePrivate|CGIDScriptTimeout|CGIMapExtension|CharsetDefault|CharsetOptions|CharsetSourceEnc|CheckCaseOnly|CheckSpelling|ChrootDir|ContentDigest|CookieDomain|CookieExpires|CookieName|CookieStyle|CookieTracking|CoreDumpDirectory|CustomLog|Dav|DavDepthInfinity|DavGenericLockDB|DavLockDB|DavMinTimeout|DBDExptime|DBDInitSQL|DBDKeep|DBDMax|DBDMin|DBDParams|DBDPersist|DBDPrepareSQL|DBDriver|DefaultIcon|DefaultLanguage|DefaultRuntimeDir|DefaultType|Define|DeflateBufferSize|DeflateCompressionLevel|DeflateFilterNote|DeflateInflateLimitRequestBody|DeflateInflateRatioBurst|DeflateInflateRatioLimit|DeflateMemLevel|DeflateWindowSize|Deny|DirectoryCheckHandler|DirectoryIndex|DirectoryIndexRedirect|DirectorySlash|DocumentRoot|DTracePrivileges|DumpIOInput|DumpIOOutput|EnableExceptionHook|EnableMMAP|EnableSendfile|Error|ErrorDocument|ErrorLog|ErrorLogFormat|Example|ExpiresActive|ExpiresByType|ExpiresDefault|ExtendedStatus|ExtFilterDefine|ExtFilterOptions|FallbackResource|FileETag|FilterChain|FilterDeclare|FilterProtocol|FilterProvider|FilterTrace|ForceLanguagePriority|ForceType|ForensicLog|GprofDir|GracefulShutdownTimeout|Group|Header|HeaderName|HeartbeatAddress|HeartbeatListen|HeartbeatMaxServers|HeartbeatStorage|HeartbeatStorage|HostnameLookups|IdentityCheck|IdentityCheckTimeout|ImapBase|ImapDefault|ImapMenu|Include|IncludeOptional|IndexHeadInsert|IndexIgnore|IndexIgnoreReset|IndexOptions|IndexOrderDefault|IndexStyleSheet|InputSed|ISAPIAppendLogToErrors|ISAPIAppendLogToQuery|ISAPICacheFile|ISAPIFakeAsync|ISAPILogNotSupported|ISAPIReadAheadBuffer|KeepAlive|KeepAliveTimeout|KeptBodySize|LanguagePriority|LDAPCacheEntries|LDAPCacheTTL|LDAPConnectionPoolTTL|LDAPConnectionTimeout|LDAPLibraryDebug|LDAPOpCacheEntries|LDAPOpCacheTTL|LDAPReferralHopLimit|LDAPReferrals|LDAPRetries|LDAPRetryDelay|LDAPSharedCacheFile|LDAPSharedCacheSize|LDAPTimeout|LDAPTrustedClientCert|LDAPTrustedGlobalCert|LDAPTrustedMode|LDAPVerifyServerCert|LimitInternalRecursion|LimitRequestBody|LimitRequestFields|LimitRequestFieldSize|LimitRequestLine|LimitXMLRequestBody|Listen|ListenBackLog|LoadFile|LoadModule|LogFormat|LogLevel|LogMessage|LuaAuthzProvider|LuaCodeCache|LuaHookAccessChecker|LuaHookAuthChecker|LuaHookCheckUserID|LuaHookFixups|LuaHookInsertFilter|LuaHookLog|LuaHookMapToStorage|LuaHookTranslateName|LuaHookTypeChecker|LuaInherit|LuaInputFilter|LuaMapHandler|LuaOutputFilter|LuaPackageCPath|LuaPackagePath|LuaQuickHandler|LuaRoot|LuaScope|MaxConnectionsPerChild|MaxKeepAliveRequests|MaxMemFree|MaxRangeOverlaps|MaxRangeReversals|MaxRanges|MaxRequestWorkers|MaxSpareServers|MaxSpareThreads|MaxThreads|MergeTrailers|MetaDir|MetaFiles|MetaSuffix|MimeMagicFile|MinSpareServers|MinSpareThreads|MMapFile|ModemStandard|ModMimeUsePathInfo|MultiviewsMatch|Mutex|NameVirtualHost|NoProxy|NWSSLTrustedCerts|NWSSLUpgradeable|Options|Order|OutputSed|PassEnv|PidFile|PrivilegesMode|Protocol|ProtocolEcho|ProxyAddHeaders|ProxyBadHeader|ProxyBlock|ProxyDomain|ProxyErrorOverride|ProxyExpressDBMFile|ProxyExpressDBMType|ProxyExpressEnable|ProxyFtpDirCharset|ProxyFtpEscapeWildcards|ProxyFtpListOnWildcard|ProxyHTMLBufSize|ProxyHTMLCharsetOut|ProxyHTMLDocType|ProxyHTMLEnable|ProxyHTMLEvents|ProxyHTMLExtended|ProxyHTMLFixups|ProxyHTMLInterp|ProxyHTMLLinks|ProxyHTMLMeta|ProxyHTMLStripComments|ProxyHTMLURLMap|ProxyIOBufferSize|ProxyMaxForwards|ProxyPass|ProxyPassInherit|ProxyPassInterpolateEnv|ProxyPassMatch|ProxyPassReverse|ProxyPassReverseCookieDomain|ProxyPassReverseCookiePath|ProxyPreserveHost|ProxyReceiveBufferSize|ProxyRemote|ProxyRemoteMatch|ProxyRequests|ProxySCGIInternalRedirect|ProxySCGISendfile|ProxySet|ProxySourceAddress|ProxyStatus|ProxyTimeout|ProxyVia|ReadmeName|ReceiveBufferSize|Redirect|RedirectMatch|RedirectPermanent|RedirectTemp|ReflectorHeader|RemoteIPHeader|RemoteIPInternalProxy|RemoteIPInternalProxyList|RemoteIPProxiesHeader|RemoteIPTrustedProxy|RemoteIPTrustedProxyList|RemoveCharset|RemoveEncoding|RemoveHandler|RemoveInputFilter|RemoveLanguage|RemoveOutputFilter|RemoveType|RequestHeader|RequestReadTimeout|Require|RewriteBase|RewriteCond|RewriteEngine|RewriteMap|RewriteOptions|RewriteRule|RLimitCPU|RLimitMEM|RLimitNPROC|Satisfy|ScoreBoardFile|Script|ScriptAlias|ScriptAliasMatch|ScriptInterpreterSource|ScriptLog|ScriptLogBuffer|ScriptLogLength|ScriptSock|SecureListen|SeeRequestTail|SendBufferSize|ServerAdmin|ServerAlias|ServerLimit|ServerName|ServerPath|ServerRoot|ServerSignature|ServerTokens|Session|SessionCookieName|SessionCookieName2|SessionCookieRemove|SessionCryptoCipher|SessionCryptoDriver|SessionCryptoPassphrase|SessionCryptoPassphraseFile|SessionDBDCookieName|SessionDBDCookieName2|SessionDBDCookieRemove|SessionDBDDeleteLabel|SessionDBDInsertLabel|SessionDBDPerUser|SessionDBDSelectLabel|SessionDBDUpdateLabel|SessionEnv|SessionExclude|SessionHeader|SessionInclude|SessionMaxAge|SetEnv|SetEnvIf|SetEnvIfExpr|SetEnvIfNoCase|SetHandler|SetInputFilter|SetOutputFilter|SSIEndTag|SSIErrorMsg|SSIETag|SSILastModified|SSILegacyExprParser|SSIStartTag|SSITimeFormat|SSIUndefinedEcho|SSLCACertificateFile|SSLCACertificatePath|SSLCADNRequestFile|SSLCADNRequestPath|SSLCARevocationCheck|SSLCARevocationFile|SSLCARevocationPath|SSLCertificateChainFile|SSLCertificateFile|SSLCertificateKeyFile|SSLCipherSuite|SSLCompression|SSLCryptoDevice|SSLEngine|SSLFIPS|SSLHonorCipherOrder|SSLInsecureRenegotiation|SSLOCSPDefaultResponder|SSLOCSPEnable|SSLOCSPOverrideResponder|SSLOCSPResponderTimeout|SSLOCSPResponseMaxAge|SSLOCSPResponseTimeSkew|SSLOCSPUseRequestNonce|SSLOpenSSLConfCmd|SSLOptions|SSLPassPhraseDialog|SSLProtocol|SSLProxyCACertificateFile|SSLProxyCACertificatePath|SSLProxyCARevocationCheck|SSLProxyCARevocationFile|SSLProxyCARevocationPath|SSLProxyCheckPeerCN|SSLProxyCheckPeerExpire|SSLProxyCheckPeerName|SSLProxyCipherSuite|SSLProxyEngine|SSLProxyMachineCertificateChainFile|SSLProxyMachineCertificateFile|SSLProxyMachineCertificatePath|SSLProxyProtocol|SSLProxyVerify|SSLProxyVerifyDepth|SSLRandomSeed|SSLRenegBufferSize|SSLRequire|SSLRequireSSL|SSLSessionCache|SSLSessionCacheTimeout|SSLSessionTicketKeyFile|SSLSRPUnknownUserSeed|SSLSRPVerifierFile|SSLStaplingCache|SSLStaplingErrorCacheTimeout|SSLStaplingFakeTryLater|SSLStaplingForceURL|SSLStaplingResponderTimeout|SSLStaplingResponseMaxAge|SSLStaplingResponseTimeSkew|SSLStaplingReturnResponderErrors|SSLStaplingStandardCacheTimeout|SSLStrictSNIVHostCheck|SSLUserName|SSLUseStapling|SSLVerifyClient|SSLVerifyDepth|StartServers|StartThreads|Substitute|Suexec|SuexecUserGroup|ThreadLimit|ThreadsPerChild|ThreadStackSize|TimeOut|TraceEnable|TransferLog|TypesConfig|UnDefine|UndefMacro|UnsetEnv|Use|UseCanonicalName|UseCanonicalPhysicalPort|User|UserDir|VHostCGIMode|VHostCGIPrivs|VHostGroup|VHostPrivs|VHostSecure|VHostUser|VirtualDocumentRoot|VirtualDocumentRootIP|VirtualScriptAlias|VirtualScriptAliasIP|WatchdogInterval|XBitHack|xml2EncAlias|xml2EncDefault|xml2StartParse)\b/im,lookbehind:!0,alias:"property"},"directive-block":{pattern:/<\/?\b(AuthnProviderAlias|AuthzProviderAlias|Directory|DirectoryMatch|Else|ElseIf|Files|FilesMatch|If|IfDefine|IfModule|IfVersion|Limit|LimitExcept|Location|LocationMatch|Macro|Proxy|RequireAll|RequireAny|RequireNone|VirtualHost)\b *.*>/i,inside:{"directive-block":{pattern:/^<\/?\w+/,inside:{punctuation:/^<\/?/},alias:"tag"},"directive-block-parameter":{pattern:/.*[^>]/,inside:{punctuation:/:/,string:{pattern:/("|').*\1/,inside:{variable:/(\$|%)\{?(\w\.?(\+|\-|:)?)+\}?/}}},alias:"attr-value"},punctuation:/>/},alias:"tag"},"directive-flags":{pattern:/\[(\w,?)+\]/,alias:"keyword"},string:{pattern:/("|').*\1/,inside:{variable:/(\$|%)\{?(\w\.?(\+|\-|:)?)+\}?/}},variable:/(\$|%)\{?(\w\.?(\+|\-|:)?)+\}?/,regex:/\^?.*\$|\^.*\$?/}; +!function(e){var t={variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b-?(?:0x[\dA-Fa-f]+|\d*\.?\d+(?:[Ee]-?\d+)?)\b/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\([^)]+\)|`[^`]+`/,inside:{variable:/^\$\(|^`|\)$|`$/}},/\$(?:[a-z0-9_#\?\*!@]+|\{[^}]+\})/i]};e.languages.bash={shebang:{pattern:/^#!\s*\/bin\/bash|^#!\s*\/bin\/sh/,alias:"important"},comment:{pattern:/(^|[^"{\\])#.*/,lookbehind:!0},string:[{pattern:/((?:^|[^<])<<\s*)(?:"|')?(\w+?)(?:"|')?\s*\r?\n(?:[\s\S])*?\r?\n\2/g,lookbehind:!0,greedy:!0,inside:t},{pattern:/(["'])(?:\\\\|\\?[^\\])*?\1/g,greedy:!0,inside:t}],variable:t.variable,"function":{pattern:/(^|\s|;|\||&)(?:alias|apropos|apt-get|aptitude|aspell|awk|basename|bash|bc|bg|builtin|bzip2|cal|cat|cd|cfdisk|chgrp|chmod|chown|chroot|chkconfig|cksum|clear|cmp|comm|command|cp|cron|crontab|csplit|cut|date|dc|dd|ddrescue|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|enable|env|ethtool|eval|exec|expand|expect|export|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|getopts|git|grep|groupadd|groupdel|groupmod|groups|gzip|hash|head|help|hg|history|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|jobs|join|kill|killall|less|link|ln|locate|logname|logout|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|make|man|mkdir|mkfifo|mkisofs|mknod|more|most|mount|mtools|mtr|mv|mmv|nano|netstat|nice|nl|nohup|notify-send|npm|nslookup|open|op|passwd|paste|pathchk|ping|pkill|popd|pr|printcap|printenv|printf|ps|pushd|pv|pwd|quota|quotacheck|quotactl|ram|rar|rcp|read|readarray|readonly|reboot|rename|renice|remsync|rev|rm|rmdir|rsync|screen|scp|sdiff|sed|seq|service|sftp|shift|shopt|shutdown|sleep|slocate|sort|source|split|ssh|stat|strace|su|sudo|sum|suspend|sync|tail|tar|tee|test|time|timeout|times|touch|top|traceroute|trap|tr|tsort|tty|type|ulimit|umask|umount|unalias|uname|unexpand|uniq|units|unrar|unshar|uptime|useradd|userdel|usermod|users|uuencode|uudecode|v|vdir|vi|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yes|zip)(?=$|\s|;|\||&)/,lookbehind:!0},keyword:{pattern:/(^|\s|;|\||&)(?:let|:|\.|if|then|else|elif|fi|for|break|continue|while|in|case|function|select|do|done|until|echo|exit|return|set|declare)(?=$|\s|;|\||&)/,lookbehind:!0},"boolean":{pattern:/(^|\s|;|\||&)(?:true|false)(?=$|\s|;|\||&)/,lookbehind:!0},operator:/&&?|\|\|?|==?|!=?|<<>|<=?|>=?|=~/,punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];]/};var a=t.variable[1].inside;a["function"]=e.languages.bash["function"],a.keyword=e.languages.bash.keyword,a.boolean=e.languages.bash.boolean,a.operator=e.languages.bash.operator,a.punctuation=e.languages.bash.punctuation}(Prism); +Prism.languages.basic={string:/"(?:""|[!#$%&'()*,\/:;<=>?^_ +\-.A-Z\d])*"/i,comment:{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},number:/(?:\b|\B[.-])(?:\d+\.?\d*)(?:E[+-]?\d+)?/i,keyword:/\b(?:AS|BEEP|BLOAD|BSAVE|CALL(?: ABSOLUTE)?|CASE|CHAIN|CHDIR|CLEAR|CLOSE|CLS|COM|COMMON|CONST|DATA|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DIM|DO|DOUBLE|ELSE|ELSEIF|END|ENVIRON|ERASE|ERROR|EXIT|FIELD|FILES|FOR|FUNCTION|GET|GOSUB|GOTO|IF|INPUT|INTEGER|IOCTL|KEY|KILL|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|MKDIR|NAME|NEXT|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPTION BASE|OUT|POKE|PUT|READ|REDIM|REM|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SHARED|SINGLE|SELECT CASE|SHELL|SLEEP|STATIC|STEP|STOP|STRING|SUB|SWAP|SYSTEM|THEN|TIMER|TO|TROFF|TRON|TYPE|UNLOCK|UNTIL|USING|VIEW PRINT|WAIT|WEND|WHILE|WRITE)(?:\$|\b)/i,"function":/\b(?:ABS|ACCESS|ACOS|ANGLE|AREA|ARITHMETIC|ARRAY|ASIN|ASK|AT|ATN|BASE|BEGIN|BREAK|CAUSE|CEIL|CHR|CLIP|COLLATE|COLOR|CON|COS|COSH|COT|CSC|DATE|DATUM|DEBUG|DECIMAL|DEF|DEG|DEGREES|DELETE|DET|DEVICE|DISPLAY|DOT|ELAPSED|EPS|ERASABLE|EXLINE|EXP|EXTERNAL|EXTYPE|FILETYPE|FIXED|FP|GO|GRAPH|HANDLER|IDN|IMAGE|IN|INT|INTERNAL|IP|IS|KEYED|LBOUND|LCASE|LEFT|LEN|LENGTH|LET|LINE|LINES|LOG|LOG10|LOG2|LTRIM|MARGIN|MAT|MAX|MAXNUM|MID|MIN|MISSING|MOD|NATIVE|NUL|NUMERIC|OF|OPTION|ORD|ORGANIZATION|OUTIN|OUTPUT|PI|POINT|POINTER|POINTS|POS|PRINT|PROGRAM|PROMPT|RAD|RADIANS|RANDOMIZE|RECORD|RECSIZE|RECTYPE|RELATIVE|REMAINDER|REPEAT|REST|RETRY|REWRITE|RIGHT|RND|ROUND|RTRIM|SAME|SEC|SELECT|SEQUENTIAL|SET|SETTER|SGN|SIN|SINH|SIZE|SKIP|SQR|STANDARD|STATUS|STR|STREAM|STYLE|TAB|TAN|TANH|TEMPLATE|TEXT|THERE|TIME|TIMEOUT|TRACE|TRANSFORM|TRUNCATE|UBOUND|UCASE|USE|VAL|VARIABLE|VIEWPORT|WHEN|WINDOW|WITH|ZER|ZONEWIDTH)(?:\$|\b)/i,operator:/<[=>]?|>=?|[+\-*\/^=&]|\b(?:AND|EQV|IMP|NOT|OR|XOR)\b/i,punctuation:/[,;:()]/}; +!function(e){var r=/%%?[~:\w]+%?|!\S+!/,t={pattern:/\/[a-z?]+(?=[ :]|$):?|-[a-z]\b|--[a-z-]+\b/im,alias:"attr-name",inside:{punctuation:/:/}},n=/"[^"]*"/,i=/(?:\b|-)\d+\b/;e.languages.batch={comment:[/^::.*/m,{pattern:/((?:^|[&(])[ \t]*)rem\b(?:[^^&)\r\n]|\^(?:\r\n|[\s\S]))*/im,lookbehind:!0}],label:{pattern:/^:.*/m,alias:"property"},command:[{pattern:/((?:^|[&(])[ \t]*)for(?: ?\/[a-z?](?:[ :](?:"[^"]*"|\S+))?)* \S+ in \([^)]+\) do/im,lookbehind:!0,inside:{keyword:/^for\b|\b(?:in|do)\b/i,string:n,parameter:t,variable:r,number:i,punctuation:/[()',]/}},{pattern:/((?:^|[&(])[ \t]*)if(?: ?\/[a-z?](?:[ :](?:"[^"]*"|\S+))?)* (?:not )?(?:cmdextversion \d+|defined \w+|errorlevel \d+|exist \S+|(?:"[^"]*"|\S+)?(?:==| (?:equ|neq|lss|leq|gtr|geq) )(?:"[^"]*"|\S+))/im,lookbehind:!0,inside:{keyword:/^if\b|\b(?:not|cmdextversion|defined|errorlevel|exist)\b/i,string:n,parameter:t,variable:r,number:i,operator:/\^|==|\b(?:equ|neq|lss|leq|gtr|geq)\b/i}},{pattern:/((?:^|[&()])[ \t]*)else\b/im,lookbehind:!0,inside:{keyword:/^else\b/i}},{pattern:/((?:^|[&(])[ \t]*)set(?: ?\/[a-z](?:[ :](?:"[^"]*"|\S+))?)* (?:[^^&)\r\n]|\^(?:\r\n|[\s\S]))*/im,lookbehind:!0,inside:{keyword:/^set\b/i,string:n,parameter:t,variable:[r,/\w+(?=(?:[*\/%+\-&^|]|<<|>>)?=)/],number:i,operator:/[*\/%+\-&^|]=?|<<=?|>>=?|[!~_=]/,punctuation:/[()',]/}},{pattern:/((?:^|[&(])[ \t]*@?)\w+\b(?:[^^&)\r\n]|\^(?:\r\n|[\s\S]))*/im,lookbehind:!0,inside:{keyword:/^\w+\b/i,string:n,parameter:t,label:{pattern:/(^\s*):\S+/m,lookbehind:!0,alias:"property"},variable:r,number:i,operator:/\^/}}],operator:/[&@]/,punctuation:/[()']/}}(Prism); +Prism.languages.c=Prism.languages.extend("clike",{keyword:/\b(_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,operator:/\-[>-]?|\+\+?|!=?|<>?=?|==?|&&?|\|?\||[~^%?*\/]/,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)[ful]*\b/i}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z]+([^\r\n\\]|\\.|\\(?:\r\n?|\n))*/im,lookbehind:!0,alias:"property",inside:{string:{pattern:/(#\s*include\s*)(<.+?>|("|')(\\?.)+?\3)/,lookbehind:!0},directive:{pattern:/(#\s*)\b(define|defined|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,lookbehind:!0,alias:"keyword"}}},constant:/\b(__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/}),delete Prism.languages.c["class-name"],delete Prism.languages.c["boolean"]; +Prism.languages.csharp=Prism.languages.extend("clike",{keyword:/\b(abstract|as|async|await|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|do|double|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|internal|is|lock|long|namespace|new|null|object|operator|out|override|params|private|protected|public|readonly|ref|return|sbyte|sealed|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|virtual|void|volatile|while|add|alias|ascending|async|await|descending|dynamic|from|get|global|group|into|join|let|orderby|partial|remove|select|set|value|var|where|yield)\b/,string:[{pattern:/@("|')(\1\1|\\\1|\\?(?!\1)[\s\S])*\1/,greedy:!0},{pattern:/("|')(\\?.)*?\1/,greedy:!0}],number:/\b-?(0x[\da-f]+|\d*\.?\d+f?)\b/i}),Prism.languages.insertBefore("csharp","keyword",{"generic-method":{pattern:/[a-z0-9_]+\s*<[^>\r\n]+?>\s*(?=\()/i,alias:"function",inside:{keyword:Prism.languages.csharp.keyword,punctuation:/[<>(),.:]/}},preprocessor:{pattern:/(^\s*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(\s*#)\b(define|elif|else|endif|endregion|error|if|line|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}}); +Prism.languages.cpp=Prism.languages.extend("c",{keyword:/\b(alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,"boolean":/\b(true|false)\b/,operator:/[-+]{1,2}|!=?|<{1,2}=?|>{1,2}=?|\->|:{1,2}|={1,2}|\^|~|%|&{1,2}|\|?\||\?|\*|\/|\b(and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/}),Prism.languages.insertBefore("cpp","keyword",{"class-name":{pattern:/(class\s+)[a-z0-9_]+/i,lookbehind:!0}}); +Prism.languages.css.selector={pattern:/[^\{\}\s][^\{\}]*(?=\s*\{)/,inside:{"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+(?:\(.*\))?/,"class":/\.[-:\.\w]+/,id:/#[-:\.\w]+/,attribute:/\[[^\]]+\]/}},Prism.languages.insertBefore("css","function",{hexcode:/#[\da-f]{3,8}/i,entity:/\\[\da-f]{1,8}/i,number:/[\d%\.]+/}); +Prism.languages.git={comment:/^#.*/m,deleted:/^[-–].*/m,inserted:/^\+.*/m,string:/("|')(\\?.)*?\1/m,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s(--|-)\w+/m}},coord:/^@@.*@@$/m,commit_sha1:/^commit \w{40}$/m}; +Prism.languages.http={"request-line":{pattern:/^(POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b\shttps?:\/\/\S+\sHTTP\/[0-9.]+/m,inside:{property:/^(POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b/,"attr-name":/:\w+/}},"response-status":{pattern:/^HTTP\/1.[01] \d+.*/m,inside:{property:{pattern:/(^HTTP\/1.[01] )\d+.*/i,lookbehind:!0}}},"header-name":{pattern:/^[\w-]+:(?=.)/m,alias:"keyword"}};var httpLanguages={"application/json":Prism.languages.javascript,"application/xml":Prism.languages.markup,"text/xml":Prism.languages.markup,"text/html":Prism.languages.markup};for(var contentType in httpLanguages)if(httpLanguages[contentType]){var options={};options[contentType]={pattern:new RegExp("(content-type:\\s*"+contentType+"[\\w\\W]*?)(?:\\r?\\n|\\r){2}[\\w\\W]*","i"),lookbehind:!0,inside:{rest:httpLanguages[contentType]}},Prism.languages.insertBefore("http","header-name",options)}; +Prism.languages.java=Prism.languages.extend("clike",{keyword:/\b(abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,number:/\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,operator:{pattern:/(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,lookbehind:!0}}),Prism.languages.insertBefore("java","function",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0}}); +Prism.languages.json={property:/"(?:\\.|[^\\"])*"(?=\s*:)/gi,string:/"(?!:)(?:\\.|[^\\"])*"(?!:)/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee][+-]?\d+)?)\b/g,punctuation:/[{}[\]);,]/g,operator:/:/g,"boolean":/\b(true|false)\b/gi,"null":/\bnull\b/gi},Prism.languages.jsonp=Prism.languages.json; +Prism.languages.less=Prism.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-]+?(?:\([^{}]+\)|[^(){};])*?(?=\s*\{)/i,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\([^{}]*\)|[^{};@])*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/i,punctuation:/[{}();:,]/,operator:/[+\-*\/]/}),Prism.languages.insertBefore("less","punctuation",{"function":Prism.languages.less.function}),Prism.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-]+.*?(?=[(;])/,lookbehind:!0,alias:"function"}}); +Prism.languages.markdown=Prism.languages.extend("markup",{}),Prism.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},code:[{pattern:/^(?: {4}|\t).+/m,alias:"keyword"},{pattern:/``.+?``|`[^`\n]+`/,alias:"keyword"}],title:[{pattern:/\w+.*(?:\r?\n|\r)(?:==+|--+)/,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:/(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^\*\*|^__|\*\*$|__$/}},italic:{pattern:/(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^[*_]|[*_]$/}},url:{pattern:/!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,inside:{variable:{pattern:/(!?\[)[^\]]+(?=\]$)/,lookbehind:!0},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),Prism.languages.markdown.bold.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.italic.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.bold.inside.italic=Prism.util.clone(Prism.languages.markdown.italic),Prism.languages.markdown.italic.inside.bold=Prism.util.clone(Prism.languages.markdown.bold); +Prism.languages.php=Prism.languages.extend("clike",{keyword:/\b(and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|private|protected|parent|throw|null|echo|print|trait|namespace|final|yield|goto|instanceof|finally|try|catch)\b/i,constant:/\b[A-Z0-9_]{2,}\b/,comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0}}),Prism.languages.insertBefore("php","class-name",{"shell-comment":{pattern:/(^|[^\\])#.*/,lookbehind:!0,alias:"comment"}}),Prism.languages.insertBefore("php","keyword",{delimiter:{pattern:/\?>|<\?(?:php|=)?/i,alias:"important"},variable:/\$\w+\b/i,"package":{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/,lookbehind:!0,inside:{punctuation:/\\/}}}),Prism.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/,lookbehind:!0}}),Prism.languages.markup&&(Prism.hooks.add("before-highlight",function(e){"php"===e.language&&/(?:<\?php|<\?)/gi.test(e.code)&&(e.tokenStack=[],e.backupCode=e.code,e.code=e.code.replace(/(?:<\?php|<\?)[\s\S]*?(?:\?>|$)/gi,function(a){for(var n=e.tokenStack.length;-1!==e.backupCode.indexOf("___PHP"+n+"___");)++n;return e.tokenStack[n]=a,"___PHP"+n+"___"}),e.grammar=Prism.languages.markup)}),Prism.hooks.add("before-insert",function(e){"php"===e.language&&e.backupCode&&(e.code=e.backupCode,delete e.backupCode)}),Prism.hooks.add("after-highlight",function(e){if("php"===e.language&&e.tokenStack){e.grammar=Prism.languages.php;for(var a=0,n=Object.keys(e.tokenStack);a'+Prism.highlight(r,e.grammar,"php").replace(/\$/g,"$$$$")+"")}e.element.innerHTML=e.highlightedCode}})); +Prism.languages.insertBefore("php","variable",{"this":/\$this\b/,global:/\$(?:_(?:SERVER|GET|POST|FILES|REQUEST|SESSION|ENV|COOKIE)|GLOBALS|HTTP_RAW_POST_DATA|argc|argv|php_errormsg|http_response_header)/,scope:{pattern:/\b[\w\\]+::/,inside:{keyword:/(static|self|parent)/,punctuation:/(::|\\)/}}}); +Prism.languages.powershell={comment:[{pattern:/(^|[^`])<#[\s\S]*?#>/,lookbehind:!0},{pattern:/(^|[^`])#.*/,lookbehind:!0}],string:[{pattern:/"(`?[\s\S])*?"/,greedy:!0,inside:{"function":{pattern:/[^`]\$\(.*?\)/,inside:{}}}},{pattern:/'([^']|'')*'/,greedy:!0}],namespace:/\[[a-z][\s\S]*?\]/i,"boolean":/\$(true|false)\b/i,variable:/\$\w+\b/i,"function":[/\b(Add-(Computer|Content|History|Member|PSSnapin|Type)|Checkpoint-Computer|Clear-(Content|EventLog|History|Item|ItemProperty|Variable)|Compare-Object|Complete-Transaction|Connect-PSSession|ConvertFrom-(Csv|Json|StringData)|Convert-Path|ConvertTo-(Csv|Html|Json|Xml)|Copy-(Item|ItemProperty)|Debug-Process|Disable-(ComputerRestore|PSBreakpoint|PSRemoting|PSSessionConfiguration)|Disconnect-PSSession|Enable-(ComputerRestore|PSBreakpoint|PSRemoting|PSSessionConfiguration)|Enter-PSSession|Exit-PSSession|Export-(Alias|Clixml|Console|Csv|FormatData|ModuleMember|PSSession)|ForEach-Object|Format-(Custom|List|Table|Wide)|Get-(Alias|ChildItem|Command|ComputerRestorePoint|Content|ControlPanelItem|Culture|Date|Event|EventLog|EventSubscriber|FormatData|Help|History|Host|HotFix|Item|ItemProperty|Job|Location|Member|Module|Process|PSBreakpoint|PSCallStack|PSDrive|PSProvider|PSSession|PSSessionConfiguration|PSSnapin|Random|Service|TraceSource|Transaction|TypeData|UICulture|Unique|Variable|WmiObject)|Group-Object|Import-(Alias|Clixml|Csv|LocalizedData|Module|PSSession)|Invoke-(Command|Expression|History|Item|RestMethod|WebRequest|WmiMethod)|Join-Path|Limit-EventLog|Measure-(Command|Object)|Move-(Item|ItemProperty)|New-(Alias|Event|EventLog|Item|ItemProperty|Module|ModuleManifest|Object|PSDrive|PSSession|PSSessionConfigurationFile|PSSessionOption|PSTransportOption|Service|TimeSpan|Variable|WebServiceProxy)|Out-(Default|File|GridView|Host|Null|Printer|String)|Pop-Location|Push-Location|Read-Host|Receive-(Job|PSSession)|Register-(EngineEvent|ObjectEvent|PSSessionConfiguration|WmiEvent)|Remove-(Computer|Event|EventLog|Item|ItemProperty|Job|Module|PSBreakpoint|PSDrive|PSSession|PSSnapin|TypeData|Variable|WmiObject)|Rename-(Computer|Item|ItemProperty)|Reset-ComputerMachinePassword|Resolve-Path|Restart-(Computer|Service)|Restore-Computer|Resume-(Job|Service)|Save-Help|Select-(Object|String|Xml)|Send-MailMessage|Set-(Alias|Content|Date|Item|ItemProperty|Location|PSBreakpoint|PSDebug|PSSessionConfiguration|Service|StrictMode|TraceSource|Variable|WmiInstance)|Show-(Command|ControlPanelItem|EventLog)|Sort-Object|Split-Path|Start-(Job|Process|Service|Sleep|Transaction)|Stop-(Computer|Job|Process|Service)|Suspend-(Job|Service)|Tee-Object|Test-(ComputerSecureChannel|Connection|ModuleManifest|Path|PSSessionConfigurationFile)|Trace-Command|Unblock-File|Undo-Transaction|Unregister-(Event|PSSessionConfiguration)|Update-(FormatData|Help|List|TypeData)|Use-Transaction|Wait-(Event|Job|Process)|Where-Object|Write-(Debug|Error|EventLog|Host|Output|Progress|Verbose|Warning))\b/i,/\b(ac|cat|chdir|clc|cli|clp|clv|compare|copy|cp|cpi|cpp|cvpa|dbp|del|diff|dir|ebp|echo|epal|epcsv|epsn|erase|fc|fl|ft|fw|gal|gbp|gc|gci|gcs|gdr|gi|gl|gm|gp|gps|group|gsv|gu|gv|gwmi|iex|ii|ipal|ipcsv|ipsn|irm|iwmi|iwr|kill|lp|ls|measure|mi|mount|move|mp|mv|nal|ndr|ni|nv|ogv|popd|ps|pushd|pwd|rbp|rd|rdr|ren|ri|rm|rmdir|rni|rnp|rp|rv|rvpa|rwmi|sal|saps|sasv|sbp|sc|select|set|shcm|si|sl|sleep|sls|sort|sp|spps|spsv|start|sv|swmi|tee|trcm|type|write)\b/i],keyword:/\b(Begin|Break|Catch|Class|Continue|Data|Define|Do|DynamicParam|Else|ElseIf|End|Exit|Filter|Finally|For|ForEach|From|Function|If|InlineScript|Parallel|Param|Process|Return|Sequence|Switch|Throw|Trap|Try|Until|Using|Var|While|Workflow)\b/i,operator:{pattern:/(\W?)(!|-(eq|ne|gt|ge|lt|le|sh[lr]|not|b?(and|x?or)|(Not)?(Like|Match|Contains|In)|Replace|Join|is(Not)?|as)\b|-[-=]?|\+[+=]?|[*\/%]=?)/i,lookbehind:!0},punctuation:/[|{}[\];(),.]/},Prism.languages.powershell.string[0].inside.boolean=Prism.languages.powershell.boolean,Prism.languages.powershell.string[0].inside.variable=Prism.languages.powershell.variable,Prism.languages.powershell.string[0].inside.function.inside=Prism.util.clone(Prism.languages.powershell); +Prism.languages.properties={comment:/^[ \t]*[#!].*$/m,"attr-value":{pattern:/(^[ \t]*(?:\\(?:\r\n|[\s\S])|[^\\\s:=])+?(?: *[=:] *| ))(?:\\(?:\r\n|[\s\S])|.)+/m,lookbehind:!0},"attr-name":/^[ \t]*(?:\\(?:\r\n|[\s\S])|[^\\\s:=])+?(?= *[ =:]| )/m,punctuation:/[=:]/}; +!function(e){e.languages.sass=e.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t]+.+)*/m,lookbehind:!0}}),e.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,inside:{atrule:/(?:@[\w-]+|[+=])/m}}}),delete e.languages.sass.atrule;var a=/((\$[-_\w]+)|(#\{\$[-_\w]+\}))/i,t=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|or|not)\b/,{pattern:/(\s+)-(?=\s)/,lookbehind:!0}];e.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,inside:{punctuation:/:/,variable:a,operator:t}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s]+.*)/m,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:a,operator:t,important:e.languages.sass.important}}}),delete e.languages.sass.property,delete e.languages.sass.important,delete e.languages.sass.selector,e.languages.insertBefore("sass","punctuation",{selector:{pattern:/([ \t]*)\S(?:,?[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,?[^,\r\n]+)*)*/,lookbehind:!0}})}(Prism); +Prism.languages.scss=Prism.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)*url(?=\()/i,selector:{pattern:/(?=\S)[^@;\{\}\(\)]?([^@;\{\}\(\)]|&|#\{\$[-_\w]+\})+(?=\s*\{(\}|\s|[^\}]+(:|\{)[^\}]+))/m,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-_\w]+/,variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}}}),Prism.languages.insertBefore("scss","atrule",{keyword:[/@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,{pattern:/( +)(?:from|through)(?= )/,lookbehind:!0}]}),Prism.languages.scss.property={pattern:/(?:[\w-]|\$[-_\w]+|#\{\$[-_\w]+\})+(?=\s*:)/i,inside:{variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}},Prism.languages.insertBefore("scss","important",{variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}),Prism.languages.insertBefore("scss","function",{placeholder:{pattern:/%[-_\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},"boolean":/\b(?:true|false)\b/,"null":/\bnull\b/,operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,lookbehind:!0}}),Prism.languages.scss.atrule.inside.rest=Prism.util.clone(Prism.languages.scss); +!function(e){var t=/\{\*[\s\S]+?\*\}|\{[\s\S]+?\}/g,a="{literal}",n="{/literal}",o=!1;e.languages.smarty=e.languages.extend("markup",{smarty:{pattern:t,inside:{delimiter:{pattern:/^\{|\}$/i,alias:"punctuation"},string:/(["'])(?:\\?.)*?\1/,number:/\b-?(?:0x[\dA-Fa-f]+|\d*\.?\d+(?:[Ee][-+]?\d+)?)\b/,variable:[/\$(?!\d)\w+/,/#(?!\d)\w+#/,{pattern:/(\.|->)(?!\d)\w+/,lookbehind:!0},{pattern:/(\[)(?!\d)\w+(?=\])/,lookbehind:!0}],"function":[{pattern:/(\|\s*)@?(?!\d)\w+/,lookbehind:!0},/^\/?(?!\d)\w+/,/(?!\d)\w+(?=\()/],"attr-name":{pattern:/\w+\s*=\s*(?:(?!\d)\w+)?/,inside:{variable:{pattern:/(=\s*)(?!\d)\w+/,lookbehind:!0},operator:/=/}},punctuation:[/[\[\]().,:`]|\->/],operator:[/[+\-*\/%]|==?=?|[!<>]=?|&&|\|\|?/,/\bis\s+(?:not\s+)?(?:div|even|odd)(?:\s+by)?\b/,/\b(?:eq|neq?|gt|lt|gt?e|lt?e|not|mod|or|and)\b/],keyword:/\b(?:false|off|on|no|true|yes)\b/}}}),e.languages.insertBefore("smarty","tag",{"smarty-comment":{pattern:/\{\*[\s\S]*?\*\}/,alias:["smarty","comment"]}}),e.hooks.add("before-highlight",function(e){"smarty"===e.language&&(e.tokenStack=[],e.backupCode=e.code,e.code=e.code.replace(t,function(t){if(t===n&&(o=!1),!o){t===a&&(o=!0);for(var r=e.tokenStack.length;-1!==e.backupCode.indexOf("___SMARTY"+r+"___");)++r;return e.tokenStack[r]=t,"___SMARTY"+r+"___"}return t}))}),e.hooks.add("before-insert",function(e){"smarty"===e.language&&(e.code=e.backupCode,delete e.backupCode)}),e.hooks.add("after-highlight",function(t){if("smarty"===t.language){for(var a=0,n=Object.keys(t.tokenStack);a?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}; +Prism.languages.twig={comment:/\{#[\s\S]*?#\}/,tag:{pattern:/\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}/,inside:{ld:{pattern:/^(?:\{\{\-?|\{%\-?\s*\w+)/,inside:{punctuation:/^(?:\{\{|\{%)\-?/,keyword:/\w+/}},rd:{pattern:/\-?(?:%\}|\}\})$/,inside:{punctuation:/.*/}},string:{pattern:/("|')(?:\\?.)*?\1/,inside:{punctuation:/^['"]|['"]$/}},keyword:/\b(?:even|if|odd)\b/,"boolean":/\b(?:true|false|null)\b/,number:/\b-?(?:0x[\dA-Fa-f]+|\d*\.?\d+([Ee][-+]?\d+)?)\b/,operator:[{pattern:/(\s)(?:and|b\-and|b\-xor|b\-or|ends with|in|is|matches|not|or|same as|starts with)(?=\s)/,lookbehind:!0},/[=<>]=?|!=|\*\*?|\/\/?|\?:?|[-+~%|]/],property:/\b[a-zA-Z_][a-zA-Z0-9_]*\b/,punctuation:/[()\[\]{}:.,]/}},other:{pattern:/\S(?:[\s\S]*\S)?/,inside:Prism.languages.markup}}; +Prism.languages.typescript=Prism.languages.extend("javascript",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield|false|true|module|declare|constructor|string|Function|any|number|boolean|Array|enum|symbol|namespace|abstract|require|type)\b/}),Prism.languages.ts=Prism.languages.typescript; +Prism.languages.vbnet=Prism.languages.extend("basic",{keyword:/(?:\b(?:ADDHANDLER|ADDRESSOF|ALIAS|AND|ANDALSO|AS|BEEP|BLOAD|BOOLEAN|BSAVE|BYREF|BYTE|BYVAL|CALL(?: ABSOLUTE)?|CASE|CATCH|CBOOL|CBYTE|CCHAR|CDATE|CDEC|CDBL|CHAIN|CHAR|CHDIR|CINT|CLASS|CLEAR|CLNG|CLOSE|CLS|COBJ|COM|COMMON|CONST|CONTINUE|CSBYTE|CSHORT|CSNG|CSTR|CTYPE|CUINT|CULNG|CUSHORT|DATA|DATE|DECIMAL|DECLARE|DEFAULT|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DELEGATE|DIM|DIRECTCAST|DO|DOUBLE|ELSE|ELSEIF|END|ENUM|ENVIRON|ERASE|ERROR|EVENT|EXIT|FALSE|FIELD|FILES|FINALLY|FOR(?: EACH)?|FRIEND|FUNCTION|GET|GETTYPE|GETXMLNAMESPACE|GLOBAL|GOSUB|GOTO|HANDLES|IF|IMPLEMENTS|IMPORTS|IN|INHERITS|INPUT|INTEGER|INTERFACE|IOCTL|IS|ISNOT|KEY|KILL|LINE INPUT|LET|LIB|LIKE|LOCATE|LOCK|LONG|LOOP|LSET|ME|MKDIR|MOD|MODULE|MUSTINHERIT|MUSTOVERRIDE|MYBASE|MYCLASS|NAME|NAMESPACE|NARROWING|NEW|NEXT|NOT|NOTHING|NOTINHERITABLE|NOTOVERRIDABLE|OBJECT|OF|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPERATOR|OPEN|OPTION(?: BASE)?|OPTIONAL|OR|ORELSE|OUT|OVERLOADS|OVERRIDABLE|OVERRIDES|PARAMARRAY|PARTIAL|POKE|PRIVATE|PROPERTY|PROTECTED|PUBLIC|PUT|RAISEEVENT|READ|READONLY|REDIM|REM|REMOVEHANDLER|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SBYTE|SELECT(?: CASE)?|SET|SHADOWS|SHARED|SHORT|SINGLE|SHELL|SLEEP|STATIC|STEP|STOP|STRING|STRUCTURE|SUB|SYNCLOCK|SWAP|SYSTEM|THEN|THROW|TIMER|TO|TROFF|TRON|TRUE|TRY|TRYCAST|TYPE|TYPEOF|UINTEGER|ULONG|UNLOCK|UNTIL|USHORT|USING|VIEW PRINT|WAIT|WEND|WHEN|WHILE|WIDENING|WITH|WITHEVENTS|WRITE|WRITEONLY|XOR)|\B(?:#CONST|#ELSE|#ELSEIF|#END|#IF))(?:\$|\b)/i,comment:[{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},{pattern:/(^|[^\\:])'.*/,lookbehind:!0}]}); +Prism.languages.wiki=Prism.languages.extend("markup",{"block-comment":{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0,alias:"comment"},heading:{pattern:/^(=+).+?\1/m,inside:{punctuation:/^=+|=+$/,important:/.+/}},emphasis:{pattern:/('{2,5}).+?\1/,inside:{"bold italic":{pattern:/(''''').+?(?=\1)/,lookbehind:!0},bold:{pattern:/(''')[^'](?:.*?[^'])?(?=\1)/,lookbehind:!0},italic:{pattern:/('')[^'](?:.*?[^'])?(?=\1)/,lookbehind:!0},punctuation:/^''+|''+$/}},hr:{pattern:/^-{4,}/m,alias:"punctuation"},url:[/ISBN +(?:97[89][ -]?)?(?:\d[ -]?){9}[\dx]\b|(?:RFC|PMID) +\d+/i,/\[\[.+?\]\]|\[.+?\]/],variable:[/__[A-Z]+__/,/\{{3}.+?\}{3}/,/\{\{.+?}}/],symbol:[/^#redirect/im,/~{3,5}/],"table-tag":{pattern:/((?:^|[|!])[|!])[^|\r\n]+\|(?!\|)/m,lookbehind:!0,inside:{"table-bar":{pattern:/\|$/,alias:"punctuation"},rest:Prism.languages.markup.tag.inside}},punctuation:/^(?:\{\||\|\}|\|-|[*#:;!|])|\|\||!!/m}),Prism.languages.insertBefore("wiki","tag",{nowiki:{pattern:/<(nowiki|pre|source)\b[\s\S]*?>[\s\S]*?<\/\1>/i,inside:{tag:{pattern:/<(?:nowiki|pre|source)\b[\s\S]*?>|<\/(?:nowiki|pre|source)>/i,inside:Prism.languages.markup.tag.inside}}}}); +Prism.languages.yaml={scalar:{pattern:/([\-:]\s*(![^\s]+)?[ \t]*[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)[^\r\n]+(?:\3[^\r\n]+)*)/,lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:/(\s*(?:^|[:\-,[{\r\n?])[ \t]*(![^\s]+)?[ \t]*)[^\r\n{[\]},#\s]+?(?=\s*:\s)/,lookbehind:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)(\d{4}-\d\d?-\d\d?([tT]|[ \t]+)\d\d?:\d{2}:\d{2}(\.\d*)?[ \t]*(Z|[-+]\d\d?(:\d{2})?)?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(:\d{2}(\.\d*)?)?)(?=[ \t]*($|,|]|}))/m,lookbehind:!0,alias:"number"},"boolean":{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)(true|false)[ \t]*(?=$|,|]|})/im,lookbehind:!0,alias:"important"},"null":{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)(null|~)[ \t]*(?=$|,|]|})/im,lookbehind:!0,alias:"important"},string:{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')(?=[ \t]*($|,|]|}))/m,lookbehind:!0,greedy:!0},number:{pattern:/([:\-,[{]\s*(![^\s]+)?[ \t]*)[+\-]?(0x[\da-f]+|0o[0-7]+|(\d+\.?\d*|\.?\d+)(e[\+\-]?\d+)?|\.inf|\.nan)[ \t]*(?=$|,|]|})/im,lookbehind:!0},tag:/![^\s]+/,important:/[&*][\w]+/,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./}; +!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var e="line-numbers",t=function(e){var t=n(e),r=t["white-space"];if("pre-wrap"===r||"pre-line"===r){var s=e.querySelector("code"),l=e.querySelector(".line-numbers-rows"),a=e.querySelector(".line-numbers-sizer"),i=e.textContent.split("\n");a||(a=document.createElement("span"),a.className="line-numbers-sizer",s.appendChild(a)),a.style.display="block",i.forEach(function(e,t){a.textContent=e||"\n";var n=a.getBoundingClientRect().height;l.children[t].style.height=n+"px"}),a.textContent="",a.style.display="none"}},n=function(e){return e?window.getComputedStyle?getComputedStyle(e):e.currentStyle||null:null};window.addEventListener("resize",function(){Array.prototype.forEach.call(document.querySelectorAll("pre."+e),t)}),Prism.hooks.add("complete",function(e){if(e.code){var n=e.element.parentNode,r=/\s*\bline-numbers\b\s*/;if(n&&/pre/i.test(n.nodeName)&&(r.test(n.className)||r.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){r.test(e.element.className)&&(e.element.className=e.element.className.replace(r," ")),r.test(n.className)||(n.className+=" line-numbers");var s,l=e.code.match(/\n(?!$)/g),a=l?l.length+1:1,i=new Array(a+1);i=i.join(""),s=document.createElement("span"),s.setAttribute("aria-hidden","true"),s.className="line-numbers-rows",s.innerHTML=i,n.hasAttribute("data-start")&&(n.style.counterReset="linenumber "+(parseInt(n.getAttribute("data-start"),10)-1)),e.element.appendChild(s),t(n)}}})}}(); +!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var t=[],e={},n=function(){};Prism.plugins.toolbar={};var a=Prism.plugins.toolbar.registerButton=function(n,a){var o;o="function"==typeof a?a:function(t){var e;return"function"==typeof a.onClick?(e=document.createElement("button"),e.type="button",e.addEventListener("click",function(){a.onClick.call(this,t)})):"string"==typeof a.url?(e=document.createElement("a"),e.href=a.url):e=document.createElement("span"),e.textContent=a.text,e},t.push(e[n]=o)},o=Prism.plugins.toolbar.hook=function(a){var o=a.element.parentNode;if(o&&/pre/i.test(o.nodeName)&&!o.classList.contains("code-toolbar")){o.classList.add("code-toolbar");var r=document.createElement("div");r.classList.add("toolbar"),document.body.hasAttribute("data-toolbar-order")&&(t=document.body.getAttribute("data-toolbar-order").split(",").map(function(t){return e[t]||n})),t.forEach(function(t){var e=t(a);if(e){var n=document.createElement("div");n.classList.add("toolbar-item"),n.appendChild(e),r.appendChild(n)}}),o.appendChild(r)}};a("label",function(t){var e=t.element.parentNode;if(e&&/pre/i.test(e.nodeName)&&e.hasAttribute("data-label")){var n,a,o=e.getAttribute("data-label");try{a=document.querySelector("template#"+o)}catch(r){}return a?n=a.content:(e.hasAttribute("data-url")?(n=document.createElement("a"),n.href=e.getAttribute("data-url")):n=document.createElement("span"),n.textContent=o),n}}),Prism.hooks.add("complete",o)}}(); +!function(){"undefined"!=typeof self&&self.Prism&&self.document&&Prism.hooks.add("before-sanity-check",function(e){if(e.code){var s=e.element.parentNode,n=/\s*\bkeep-initial-line-feed\b\s*/;!s||"pre"!==s.nodeName.toLowerCase()||n.test(s.className)||n.test(e.element.className)||(e.code=e.code.replace(/^(?:\r?\n|\r)/,""))}})}(); +!function(){"undefined"!=typeof self&&self.Prism&&self.document&&document.createRange&&(Prism.plugins.KeepMarkup=!0,Prism.hooks.add("before-highlight",function(e){if(e.element.children.length){var n=0,o=[],t=function(e,d){var r={};d||(r.clone=e.cloneNode(!1),r.posOpen=n,o.push(r));for(var a=0,s=e.childNodes.length;s>a;a++){var l=e.childNodes[a];1===l.nodeType?t(l):3===l.nodeType&&(n+=l.data.length)}d||(r.posClose=n)};t(e.element,!0),o&&o.length&&(e.keepMarkup=o)}}),Prism.hooks.add("after-highlight",function(e){if(e.keepMarkup&&e.keepMarkup.length){var n=function(e,o){for(var t=0,d=e.childNodes.length;d>t;t++){var r=e.childNodes[t];if(1===r.nodeType){if(!n(r,o))return!1}else 3===r.nodeType&&(!o.nodeStart&&o.pos+r.data.length>o.node.posOpen&&(o.nodeStart=r,o.nodeStartPos=o.node.posOpen-o.pos),o.nodeStart&&o.pos+r.data.length>=o.node.posClose&&(o.nodeEnd=r,o.nodeEndPos=o.node.posClose-o.pos),o.pos+=r.data.length);if(o.nodeStart&&o.nodeEnd){var a=document.createRange();return a.setStart(o.nodeStart,o.nodeStartPos),a.setEnd(o.nodeEnd,o.nodeEndPos),o.node.clone.appendChild(a.extractContents()),a.insertNode(o.node.clone),a.detach(),!1}}return!0};e.keepMarkup.forEach(function(o){n(e.element,{node:o,pos:0})}),e.highlightedCode=e.element.innerHTML}}))}(); +!function(){if("undefined"!=typeof self&&self.Prism&&self.document){if(!Prism.plugins.toolbar)return console.warn("Show Languages plugin loaded before Toolbar plugin."),void 0;var e={html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",css:"CSS",clike:"C-like",javascript:"JavaScript",abap:"ABAP",actionscript:"ActionScript",apacheconf:"Apache Configuration",apl:"APL",applescript:"AppleScript",asciidoc:"AsciiDoc",aspnet:"ASP.NET (C#)",autoit:"AutoIt",autohotkey:"AutoHotkey",basic:"BASIC",csharp:"C#",cpp:"C++",coffeescript:"CoffeeScript","css-extras":"CSS Extras",django:"Django/Jinja2",fsharp:"F#",glsl:"GLSL",graphql:"GraphQL",http:"HTTP",inform7:"Inform 7",json:"JSON",latex:"LaTeX",livescript:"LiveScript",lolcode:"LOLCODE",matlab:"MATLAB",mel:"MEL",n4js:"N4JS",nasm:"NASM",nginx:"nginx",nsis:"NSIS",objectivec:"Objective-C",ocaml:"OCaml",parigp:"PARI/GP",php:"PHP","php-extras":"PHP Extras",powershell:"PowerShell",properties:".properties",protobuf:"Protocol Buffers",jsx:"React JSX",renpy:"Ren'py",rest:"reST (reStructuredText)",sas:"SAS",sass:"Sass (Sass)",scss:"Sass (Scss)",sql:"SQL",typescript:"TypeScript",vbnet:"VB.Net",vhdl:"VHDL",vim:"vim",wiki:"Wiki markup",xojo:"Xojo (REALbasic)",yaml:"YAML"};Prism.plugins.toolbar.registerButton("show-language",function(t){var a=t.element.parentNode;if(a&&/pre/i.test(a.nodeName)){var s=a.getAttribute("data-language")||e[t.language]||t.language.substring(0,1).toUpperCase()+t.language.substring(1),r=document.createElement("span");return r.textContent=s,r}})}}(); +!function(){if("undefined"!=typeof self&&self.Prism&&self.document){if(!Prism.plugins.toolbar)return console.warn("Copy to Clipboard plugin loaded before Toolbar plugin."),void 0;var o=window.Clipboard||void 0;o||"function"!=typeof require||(o=require("clipboard"));var e=[];if(!o){var t=document.createElement("script"),n=document.querySelector("head");t.onload=function(){if(o=window.Clipboard)for(;e.length;)e.pop()()},t.src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.8/clipboard.min.js",n.appendChild(t)}Prism.plugins.toolbar.registerButton("copy-to-clipboard",function(t){function n(){var e=new o(i,{text:function(){return t.code}});e.on("success",function(){i.textContent="Copied!",r()}),e.on("error",function(){i.textContent="Press Ctrl+C to copy",r()})}function r(){setTimeout(function(){i.textContent="Copy"},5e3)}var i=document.createElement("a");return i.textContent="Copy",o?n():e.push(n),i})}}(); diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7ecfce214cbdbedc15d8348babeff5cd7e720488 GIT binary patch literal 5430 zcmeHL&npB`9Dn5Kuy>cuLGmAPauLohTgugsgTofC9NbV!aa0tgTofgxl%f=?Y?Sh& zWMxI9hibZ5LZ@~9&$;7J-7ai$2=7ByD3A3P1*hfQz|8$LL= zUz~w$;90HjJ=)Ss!V^NuI2Za%(z#lli~7u{+nbyOi;~<-#pGX{K<~tb_C6Lj^YSY9 zuHBEb(bRJ+>$(mjNuamQtlmY!MgdXJjDiJ*`fSvC!*oskp+}sjm(MWyz%r29BW!f*m(x|(w?cOiYFaD0y8pq z9eCfscMhGYa>i@!YdSYJ(-6XZ(W@wc#d9lm1l3S%FF!rC_waiB@V}AM)ez?!Gp$os wgQ-p&yk8A*^lZ5Jw#)Gj@LI_qy{H^P{^jt7C;ag)RHAYMwkN>;6;PJx7hHm1jsO4v literal 0 HcmV?d00001 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..24e2031 --- /dev/null +++ b/public/index.php @@ -0,0 +1,35 @@ +systemDirectory,'/ ').'/bootstrap.php'; + +/* + *--------------------------------------------------------------- + * LAUNCH THE APPLICATION + *--------------------------------------------------------------- + * Now that everything is setup, it's time to actually fire + * up the engines and make this app do it's thang. + */ +$app->run(); \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..7bf80b9 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,13 @@ +User-Agent: * +Disallow: /wp-login.php +Disallow: /lostpassword/ +Disallow: ? +Sitemap: https://www.zone-actu.com/post-sitemap1.xml +Sitemap: https://www.zone-actu.com/post-sitemap2.xml +Sitemap: https://www.zone-actu.com/post-sitemap3.xml +Sitemap: https://www.zone-actu.com/post-sitemap4.xml +Sitemap: https://www.zone-actu.com/page-sitemap.xml +Sitemap: https://www.zone-actu.com/category-sitemap.xml +Sitemap: https://www.zone-actu.com/post_tag-sitemap.xml +Sitemap: https://www.zone-actu.com/post_format-sitemap.xml +Sitemap: https://www.zone-actu.com/news-sitemap.xml \ No newline at end of file diff --git a/rewrite.php b/rewrite.php new file mode 100644 index 0000000..3b8845b --- /dev/null +++ b/rewrite.php @@ -0,0 +1,29 @@ +publicDirectory, '/'); + +// Path to the front controller +define('FCPATH', realpath($public).DIRECTORY_SEPARATOR); + +// Ensure the current directory is pointing to the front controller's directory +chdir('public'); + +$app = require rtrim($paths->systemDirectory,'/ ').'/bootstrap.php'; + +// Grab our Console +$console = new \CodeIgniter\CLI\Console($app); + +// We want errors to be shown when using it from the CLI. +error_reporting(-1); +ini_set('display_errors', 1); + +// Show basic information before we do anything else. +$console->showHeader(); + +// fire off the command the main framework. +$console->run(); diff --git a/system/.htaccess b/system/.htaccess new file mode 100644 index 0000000..3462048 --- /dev/null +++ b/system/.htaccess @@ -0,0 +1,6 @@ + + Require all denied + + + Deny from all + diff --git a/system/API/ResponseTrait.php b/system/API/ResponseTrait.php new file mode 100644 index 0000000..f6b2410 --- /dev/null +++ b/system/API/ResponseTrait.php @@ -0,0 +1,367 @@ + 201, + 'deleted' => 200, + 'invalid_request' => 400, + 'unsupported_response_type' => 400, + 'invalid_scope' => 400, + 'temporarily_unavailable' => 400, + 'invalid_grant' => 400, + 'invalid_credentials' => 400, + 'invalid_refresh' => 400, + 'no_data' => 400, + 'invalid_data' => 400, + 'access_denied' => 401, + 'unauthorized' => 401, + 'invalid_client' => 401, + 'forbidden' => 403, + 'resource_not_found' => 404, + 'not_acceptable' => 406, + 'resource_exists' => 409, + 'conflict' => 409, + 'resource_gone' => 410, + 'payload_too_large' => 413, + 'unsupported_media_type' => 415, + 'too_many_requests' => 429, + 'server_error' => 500, + 'unsupported_grant_type' => 501, + 'not_implemented' => 501, + ]; + + //-------------------------------------------------------------------- + + /** + * Provides a single, simple method to return an API response, formatted + * to match the requested format, with proper content-type and status code. + * + * @param null $data + * @param int $status + * @param string $message + * + * @return mixed + */ + public function respond($data = null, int $status = null, string $message = '') + { + // If data is null and status code not provided, exit and bail + if ($data === null && $status === null) + { + $status = 404; + + // Create the output var here in case of $this->response([]); + $output = null; + } // If data is null but status provided, keep the output empty. + elseif ($data === null && is_numeric($status)) + { + $output = null; + } + else + { + $status = empty($status) ? 200 : $status; + $output = $this->format($data); + } + + return $this->response->setBody($output) + ->setStatusCode($status, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used for generic failures that no custom methods exist for. + * + * @param string|array $messages + * @param int|null $status HTTP status code + * @param string|null $code Custom, API-specific, error code + * @param string $customMessage + * + * @return mixed + */ + public function fail($messages, int $status = 400, string $code = null, string $customMessage = '') + { + if ( ! is_array($messages)) + { + $messages = [$messages]; + } + + $response = [ + 'status' => $status, + 'error' => $code === null ? $status : $code, + 'messages' => $messages, + ]; + + return $this->respond($response, $status, $customMessage); + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + // Response Helpers + //-------------------------------------------------------------------- + + /** + * Used after successfully creating a new resource. + * + * @param mixed $data Data. + * @param string $message Message. + * + * @return mixed + */ + public function respondCreated($data = null, string $message = '') + { + return $this->respond($data, $this->codes['created'], $message); + } + + //-------------------------------------------------------------------- + + /** + * Used after a resource has been successfully deleted. + * + * @param mixed $data Data. + * @param string $message Message. + * + * @return mixed + */ + public function respondDeleted($data = null, string $message = '') + { + return $this->respond($data, $this->codes['deleted'], $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when the client is either didn't send authorization information, + * or had bad authorization credentials. User is encouraged to try again + * with the proper information. + * + * @param string $description + * @param string $code + * @param string $message + * + * @return mixed + */ + public function failUnauthorized(string $description, string $code = null, string $message = '') + { + return $this->fail($description, $this->codes['unauthorized'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when access is always denied to this resource and no amount + * of trying again will help. + * + * @param string $description + * @param string $code + * @param string $message + * + * @return mixed + */ + public function failForbidden(string $description, string $code = null, string $message = '') + { + return $this->fail($description, $this->codes['forbidden'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when a specified resource cannot be found. + * + * @param string $description + * @param string $code + * @param string $message + * + * @return mixed + */ + public function failNotFound(string $description, string $code = null, string $message = '') + { + return $this->fail($description, $this->codes['resource_not_found'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when the data provided by the client cannot be validated. + * + * @param string $description + * @param string $code + * @param string $message + * + * @return mixed + */ + public function failValidationError(string $description, string $code = null, string $message = '') + { + return $this->fail($description, $this->codes['invalid_data'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Use when trying to create a new resource and it already exists. + * + * @param string $description + * @param string $code + * @param string $message + * + * @return mixed + */ + public function failResourceExists(string $description, string $code = null, string $message = '') + { + return $this->fail($description, $this->codes['resource_exists'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Use when a resource was previously deleted. This is different than + * Not Found, because here we know the data previously existed, but is now gone, + * where Not Found means we simply cannot find any information about it. + * + * @param string $description + * @param string $code + * @param string $message + * + * @return mixed + */ + public function failResourceGone(string $description, string $code = null, string $message = '') + { + return $this->fail($description, $this->codes['resource_gone'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when the user has made too many requests for the resource recently. + * + * @param string $description + * @param string $code + * @param string $message + * + * @return mixed + */ + public function failTooManyRequests(string $description, string $code = null, string $message = '') + { + return $this->fail($description, $this->codes['too_many_requests'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when there is a server error. + * + * @param string $description The error message to show the user. + * @param string|null $code A custom, API-specific, error code. + * @param string $message A custom "reason" message to return. + * + * @return Response The value of the Response's send() method. + */ + public function failServerError(string $description, string $code = null, string $message = ''): Response + { + return $this->fail($description, $this->codes['server_error'], $code, $message); + } + + //-------------------------------------------------------------------- + // Utility Methods + //-------------------------------------------------------------------- + + /** + * Handles formatting a response. Currently makes some heavy assumptions + * and needs updating! :) + * + * @param null $data + * + * @return null|string + */ + protected function format($data = null) + { + // If the data is a string, there's not much we can do to it... + if (is_string($data)) + { + // The content type should be text/... and not application/... + $contentType = $this->response->getHeaderLine('Content-Type'); + $contentType = str_replace('application/json', 'text/html', $contentType); + $contentType = str_replace('application/', 'text/', $contentType); + $this->response->setContentType($contentType); + + return $data; + } + + // if we don't have a formatter, make one + if ( ! isset($this->formatter)) + { + $config = new Format(); + + // Determine correct response type through content negotiation + $format = $this->request->negotiate('media', $config->supportedResponseFormats, true); + + $this->response->setContentType($format); + + // if no formatter, use the default + $this->formatter = $config->getFormatter($format); + } + + // Recursively convert objects into associative arrays + $data = json_decode(json_encode($data), true); + + return $this->formatter->format($data); + } + +} diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php new file mode 100644 index 0000000..33cba5e --- /dev/null +++ b/system/Autoloader/Autoloader.php @@ -0,0 +1,371 @@ + [ + * 'Foo\Bar' => '/path/to/packages/foo-bar' + * ], + * 'classmap' => [ + * 'MyClass' => '/path/to/class/file.php' + * ] + * ]; + * + * Example: + * + * register(); + * + * @package CodeIgniter\Autoloader + */ +class Autoloader +{ + + /** + * Stores namespaces as key, and path as values. + * + * @var array + */ + protected $prefixes = []; + + /** + * Stores class name as key, and path as values. + * + * @var array + */ + protected $classmap = []; + + //-------------------------------------------------------------------- + + /** + * Reads in the configuration array (described above) and stores + * the valid parts that we'll need. + * + * @param \Config\Autoload $config + */ + public function initialize(\Config\Autoload $config) + { + // We have to have one or the other, though we don't enforce the need + // to have both present in order to work. + if (empty($config->psr4) && empty($config->classmap)) + { + throw new \InvalidArgumentException('Config array must contain either the \'psr4\' key or the \'classmap\' key.'); + } + + if (isset($config->psr4)) + { + $this->prefixes = $config->psr4; + } + + if (isset($config->classmap)) + { + $this->classmap = $config->classmap; + } + + unset($config); + } + + //-------------------------------------------------------------------- + + /** + * Register the loader with the SPL autoloader stack. + * + * @codeCoverageIgnore + */ + public function register() + { + // Since the default file extensions are searched + // in order of .inc then .php, but we always use .php, + // put the .php extension first to eek out a bit + // better performance. + // http://php.net/manual/en/function.spl-autoload.php#78053 + spl_autoload_extensions('.php,.inc'); + + // Prepend the PSR4 autoloader for maximum performance. + spl_autoload_register([$this, 'loadClass'], true, true); + + // Now prepend another loader for the files in our class map. + $config = is_array($this->classmap) ? $this->classmap : []; + + spl_autoload_register(function ($class) use ($config) { + if ( ! array_key_exists($class, $config)) + { + return false; + } + + include_once $config[$class]; + }, true, // Throw exception + true // Prepend + ); + } + + //-------------------------------------------------------------------- + + /** + * Registers a namespace with the autoloader. + * + * @param string $namespace + * @param string $path + * + * @return Autoloader + */ + public function addNamespace($namespace, $path) + { + if (isset($this->prefixes[$namespace])) + { + if (is_string($this->prefixes[$namespace])) + { + $this->prefixes[$namespace] = [$this->prefixes[$namespace]]; + } + + $this->prefixes[$namespace] = array_merge($this->prefixes[$namespace], [$path]); + } + else + { + $this->prefixes[$namespace] = [$path]; + } + + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Removes a single namespace from the psr4 settings. + * + * @param string $namespace + * + * @return Autoloader + */ + public function removeNamespace($namespace) + { + unset($this->prefixes[$namespace]); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Loads the class file for a given class name. + * + * @param string $class The fully qualified class name. + * + * @return mixed The mapped file on success, or boolean false + * on failure. + */ + public function loadClass($class) + { + $class = trim($class, '\\'); + $class = str_ireplace('.php', '', $class); + + $mapped_file = $this->loadInNamespace($class); + + // Nothing? One last chance by looking + // in common CodeIgniter folders. + if ( ! $mapped_file) + { + $mapped_file = $this->loadLegacy($class); + } + + return $mapped_file; + } + + //-------------------------------------------------------------------- + + /** + * Loads the class file for a given class name. + * + * @param string $class The fully-qualified class name + * + * @return mixed The mapped file name on success, or boolean false on fail + */ + protected function loadInNamespace($class) + { + if (strpos($class, '\\') === false) + { + return false; + } + + foreach ($this->prefixes as $namespace => $directories) + { + if (is_string($directories)) + { + $directories = [$directories]; + } + + foreach ($directories as $directory) + { + $directory = rtrim($directory, '/'); + + if (strpos($class, $namespace) === 0) + { + $filePath = $directory . str_replace('\\', '/', substr($class, strlen($namespace))) . '.php'; + $filename = $this->requireFile($filePath); + + if ($filename) + { + return $filename; + } + } + } + } + + // never found a mapped file + return false; + } + + //-------------------------------------------------------------------- + + /** + * Attempts to load the class from common locations in previous + * version of CodeIgniter, namely 'application/libraries', and + * 'application/Models'. + * + * @param string $class The class name. This typically should NOT have a namespace. + * + * @return mixed The mapped file name on success, or boolean false on failure + */ + protected function loadLegacy($class) + { + // If there is a namespace on this class, then + // we cannot load it from traditional locations. + if (strpos($class, '\\') !== false) + { + return false; + } + + $paths = [ + APPPATH . 'Controllers/', + APPPATH . 'Libraries/', + APPPATH . 'Models/', + ]; + + $class = str_replace('\\', '/', $class) . '.php'; + + foreach ($paths as $path) + { + if ($file = $this->requireFile($path . $class)) + { + return $file; + } + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * A central way to require a file is loaded. Split out primarily + * for testing purposes. + * + * @codeCoverageIgnore + * + * @param string $file + * + * @return bool + */ + protected function requireFile($file) + { + $file = $this->sanitizeFilename($file); + + if (file_exists($file)) + { + require_once $file; + + return $file; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Sanitizes a filename, replacing spaces with dashes. + * + * Removes special characters that are illegal in filenames on certain + * operating systems and special characters requiring special escaping + * to manipulate at the command line. Replaces spaces and consecutive + * dashes with a single dash. Trim period, dash and underscore from beginning + * and end of filename. + * + * @param string $filename + * + * @return string The sanitized filename + */ + public function sanitizeFilename(string $filename): string + { + // Only allow characters deemed safe for POSIX portable filenames. + // Plus the forward slash for directory separators since this might + // be a path. + // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_278 + // Modified to allow backslash and colons for on Windows machines. + $filename = preg_replace('/[^a-zA-Z0-9\s\/\-\_\.\:\\\\]/', '', $filename); + + // Clean up our filename edges. + $filename = trim($filename, '.-_'); + + return $filename; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php new file mode 100644 index 0000000..597c33c --- /dev/null +++ b/system/Autoloader/FileLocator.php @@ -0,0 +1,313 @@ +namespaces = $autoload->psr4; + + unset($autoload); + + // Always keep the Application directory as a "package". + array_unshift($this->namespaces, APPPATH); + } + + //-------------------------------------------------------------------- + + /** + * Attempts to locate a file by examining the name for a namespace + * and looking through the PSR-4 namespaced files that we know about. + * + * @param string $file The namespaced file to locate + * @param string $folder The folder within the namespace that we should look for the file. + * @param string $ext The file extension the file should have. + * + * @return string The path to the file if found, or an empty string. + */ + public function locateFile(string $file, string $folder = null, string $ext = 'php'): string + { + // Ensure the extension is on the filename + $file = strpos($file, '.' . $ext) !== false ? $file : $file . '.' . $ext; + + // Clean the folder name from the filename + if ( ! empty($folder)) + { + $file = str_replace($folder . '/', '', $file); + } + + // No namespaceing? Try the application folder. + if (strpos($file, '\\') === false) + { + return $this->legacyLocate($file, $folder); + } + + // Standardize slashes to handle nested directories. + $file = str_replace('/', '\\', $file); + + $segments = explode('\\', $file); + + // The first segment will be empty if a slash started the filename. + if (empty($segments[0])) + unset($segments[0]); + + $path = ''; + $prefix = ''; + $filename = ''; + + while ( ! empty($segments)) + { + $prefix .= empty($prefix) ? ucfirst(array_shift($segments)) : '\\' . ucfirst(array_shift($segments)); + + if ( ! array_key_exists($prefix, $this->namespaces)) + { + continue; + } + + $path = $this->namespaces[$prefix] . '/'; + $filename = implode('/', $segments); + break; + } + + // IF we have a folder name, then the calling function + // expects this file to be within that folder, like 'Views', + // or 'libraries'. + // @todo Allow it to check with and without the nested folder. + if ( ! empty($folder) && strpos($filename, $folder) === false) + { + $filename = $folder . '/' . $filename; + } + + $path .= $filename; + + if ( ! $this->requireFile($path)) + { + $path = ''; + } + + return $path; + } + + //-------------------------------------------------------------------- + + /** + * Searches through all of the defined namespaces looking for a file. + * Returns an array of all found locations for the defined file. + * + * Example: + * + * $locator->search('Config/Routes.php'); + * // Assuming PSR4 namespaces include foo and bar, might return: + * [ + * 'application/modules/foo/Config/Routes.php', + * 'application/modules/bar/Config/Routes.php', + * ] + * + * @param string $path + * @param string $ext + * + * @return array + */ + public function search(string $path, string $ext = 'php'): array + { + $foundPaths = []; + + // Ensure the extension is on the filename + $path = strpos($path, '.' . $ext) !== false ? $path : $path . '.' . $ext; + + foreach ($this->namespaces as $name => $folder) + { + $folder = rtrim($folder, '/') . '/'; + + if (file_exists($folder . $path)) + { + $foundPaths[] = $folder . $path; + } + } + + // Remove any duplicates + $foundPaths = array_unique($foundPaths); + + return $foundPaths; + } + + //-------------------------------------------------------------------- + + /** + * Attempts to load a file and instantiate a new class by looking + * at its full path and comparing that to our existing psr4 namespaces + * in Autoloader config file. + * + * @param string $path + * + * @return string|void + */ + public function findQualifiedNameFromPath(string $path) + { + $path = realpath($path); + + if ( ! $path) + { + return; + } + + foreach ($this->namespaces as $namespace => $nsPath) + { + $nsPath = realpath($nsPath); + if (is_numeric($namespace) || empty($nsPath)) + continue; + + if (mb_strpos($path, $nsPath) === 0) + { + $className = '\\' . $namespace . '\\' . + ltrim(str_replace('/', '\\', mb_substr($path, mb_strlen($nsPath))), '\\'); + // Remove the file extension (.php) + $className = mb_substr($className, 0, -4); + + return $className; + } + } + } + + //-------------------------------------------------------------------- + + /** + * Scans the defined namespaces, returning a list of all files + * that are contained within the subpath specifed by $path. + * + * @param string $path + * + * @return array + */ + public function listFiles(string $path): array + { + if (empty($path)) + return []; + + $files = []; + helper('filesystem'); + + foreach ($this->namespaces as $namespace => $nsPath) + { + $fullPath = realpath(rtrim($nsPath, '/') . '/' . $path); + + if ( ! is_dir($fullPath)) + continue; + + $tempFiles = get_filenames($fullPath, true); + //CLI::newLine($tempFiles); + + if (! empty($tempFiles)) + $files = array_merge($files, $tempFiles); + } + + return $files; + } + + /** + * Checks the application folder to see if the file can be found. + * Only for use with filenames that DO NOT include namespacing. + * + * @param string $file + * @param string|null $folder + * + * @return string + * @internal param string $ext + * + */ + protected function legacyLocate(string $file, string $folder = null): string + { + $paths = [APPPATH, BASEPATH]; + + foreach ($paths as $path) + { + $path .= empty($folder) ? $file : $folder . '/' . $file; + + if ($this->requireFile($path) === true) + { + return $path; + } + } + + return ''; + } + + //-------------------------------------------------------------------- + + /** + * Checks to see if a file exists on the file system. This is split + * out to it's own method to make testing simpler. + * + * @codeCoverageIgnore + * @param string $path + * + * @return bool + */ + protected function requireFile(string $path): bool + { + return file_exists($path); + } + + //-------------------------------------------------------------------- +} diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php new file mode 100644 index 0000000..745c254 --- /dev/null +++ b/system/CLI/BaseCommand.php @@ -0,0 +1,241 @@ +logger = $logger; + $this->commands = $commands; + } + + //-------------------------------------------------------------------- + + abstract public function run(array $params); + + //-------------------------------------------------------------------- + + /** + * Can be used by a command to run other commands. + * + * @param string $command + * @param array $params + * + * @return mixed + */ + protected function call(string $command, array $params = []) + { + // The CommandRunner will grab the first element + // for the command name. + array_unshift($params, $command); + + return $this->commands->index($params); + } + + //-------------------------------------------------------------------- + + /** + * A simple method to display an error with line/file, + * in child commands. + * + * @param \Exception $e + */ + protected function showError(\Exception $e) + { + CLI::newLine(); + CLI::error($e->getMessage()); + CLI::write($e->getFile() . ' - ' . $e->getLine()); + CLI::newLine(); + } + + //-------------------------------------------------------------------- + + /** + * Makes it simple to access our protected properties. + * + * @param string $key + * + * @return mixed + */ + public function __get(string $key) + { + if (isset($this->$key)) + { + return $this->$key; + } + } + + //-------------------------------------------------------------------- + + /** + * show Help include (usage,arguments,description,options) + */ + public function showHelp() + { + // 4 spaces insted of tab + $tab = " "; + CLI::write(lang('CLI.helpDescription'), 'yellow'); + CLI::write($tab . $this->description); + CLI::newLine(); + + CLI::write(lang('CLI.helpUsage'), 'yellow'); + $usage = empty($this->usage) ? $this->name . " [arguments]" : $this->usage; + CLI::write($tab . $usage); + CLI::newLine(); + + $pad = max($this->getPad($this->options, 6), $this->getPad($this->arguments, 6)); + + if ( ! empty($this->arguments)) + { + CLI::write(lang('CLI.helpArguments'), 'yellow'); + foreach ($this->arguments as $argument => $description) + { + CLI::write($tab . CLI::color(str_pad($argument, $pad), 'green') . $description, 'yellow'); + } + CLI::newLine(); + } + + if ( ! empty($this->options)) + { + CLI::write(lang('CLI.helpOptions'), 'yellow'); + foreach ($this->options as $option => $description) + { + CLI::write($tab . CLI::color(str_pad($option, $pad), 'green') . $description, 'yellow'); + } + CLI::newLine(); + } + } + + //-------------------------------------------------------------------- + + /** + * Get pad for $key => $value array output + * + * @param array $array + * @param int $pad + * + * @return int + */ + public function getPad($array, int $pad) + { + $max = 0; + foreach ($array as $key => $value) + { + $max = max($max, strlen($key)); + } + return $max + $pad; + } + + //-------------------------------------------------------------------- +} diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php new file mode 100644 index 0000000..7319668 --- /dev/null +++ b/system/CLI/CLI.php @@ -0,0 +1,773 @@ + '0;30', + 'dark_gray' => '1;30', + 'blue' => '0;34', + 'dark_blue' => '1;34', + 'light_blue' => '1;34', + 'green' => '0;32', + 'light_green' => '1;32', + 'cyan' => '0;36', + 'light_cyan' => '1;36', + 'red' => '0;31', + 'light_red' => '1;31', + 'purple' => '0;35', + 'light_purple' => '1;35', + 'light_yellow' => '0;33', + 'yellow' => '1;33', + 'light_gray' => '0;37', + 'white' => '1;37', + ]; + + /** + * Background color list + * @var array + */ + protected static $background_colors = [ + 'black' => '40', + 'red' => '41', + 'green' => '42', + 'yellow' => '43', + 'blue' => '44', + 'magenta' => '45', + 'cyan' => '46', + 'light_gray' => '47', + ]; + + /** + * List of array segments. + * @var array + */ + protected static $segments = []; + /** + * @var array + */ + protected static $options = []; + + //-------------------------------------------------------------------- + + /** + * Static "constructor". + */ + public static function init() + { + // Readline is an extension for PHP that makes interactivity with PHP + // much more bash-like. + // http://www.php.net/manual/en/readline.installation.php + static::$readline_support = extension_loaded('readline'); + + static::parseCommandLine(); + + static::$initialized = true; + } + + //-------------------------------------------------------------------- + + /** + * Get input from the shell, using readline or the standard STDIN + * + * Named options must be in the following formats: + * php index.php user -v --v -name=John --name=John + * + * @param string $prefix + * + * @return string + */ + public static function input(string $prefix = null): string + { + if (static::$readline_support) + { + return readline($prefix); + } + + echo $prefix; + + return fgets(STDIN); + } + + //-------------------------------------------------------------------- + + /** + * Asks the user for input. This can have either 1 or 2 arguments. + * + * Usage: + * + * // Waits for any key press + * CLI::prompt(); + * + * // Takes any input + * $color = CLI::prompt('What is your favorite color?'); + * + * // Takes any input, but offers default + * $color = CLI::prompt('What is your favourite color?', 'white'); + * + * // Will only accept the options in the array + * $ready = CLI::prompt('Are you ready?', array('y','n')); + * + * @return string The user input + */ + public static function prompt(): string + { + $args = func_get_args(); + + $options = []; + $output = ''; + $default = null; + + // How many we got + $arg_count = count($args); + + // Is the last argument a boolean? True means required + $required = end($args) === true; + + // Reduce the argument count if required was passed, we don't care about that anymore + $required === true && -- $arg_count; + + // This method can take a few crazy combinations of arguments, so lets work it out + switch ($arg_count) + { + case 2: + + // E.g: $ready = CLI::prompt('Are you ready?', array('y','n')); + if (is_array($args[1])) + { + list($output, $options) = $args; + } + + // E.g: $color = CLI::prompt('What is your favourite color?', 'white'); + elseif (is_string($args[1])) + { + list($output, $default) = $args; + } + + break; + + case 1: + + // No question (probably been asked already) so just show options + // E.g: $ready = CLI::prompt(array('y','n')); + if (is_array($args[0])) + { + $options = $args[0]; + } + + // Question without options + // E.g: $ready = CLI::prompt('What did you do today?'); + elseif (is_string($args[0])) + { + $output = $args[0]; + } + + break; + } + + // If a question has been asked with the read + if ($output !== '') + { + $extra_output = ''; + + if ($default !== null) + { + $extra_output = ' [ Default: "' . $default . '" ]'; + } + elseif (! empty($options)) + { + $extra_output = ' [ ' . implode(', ', $options) . ' ]'; + } + + fwrite(STDOUT, $output . $extra_output . ': '); + } + + // Read the input from keyboard. + $input = trim(static::input()) ?: $default; + + // No input provided and we require one (default will stop this being called) + if (empty($input) && $required === true) + { + static::write('This is required.'); + static::newLine(); + + $input = forward_static_call_array([__CLASS__, 'prompt'], $args); + } + + // If options are provided and the choice is not in the array, tell them to try again + if ( ! empty($options) && ! in_array($input, $options)) + { + static::write('This is not a valid option. Please try again.'); + static::newLine(); + + $input = forward_static_call_array([__CLASS__, 'prompt'], $args); + } + + return empty($input) ? '' : $input; + } + + //-------------------------------------------------------------------- + + /** + * Outputs a string to the cli. + * + * @param string $text The text to output + * @param string $foreground + * @param string $background + */ + public static function write(string $text = '', string $foreground = null, string $background = null) + { + if ($foreground || $background) + { + $text = static::color($text, $foreground, $background); + } + + fwrite(STDOUT, $text . PHP_EOL); + } + + //-------------------------------------------------------------------- + + /** + * Outputs an error to the CLI using STDERR instead of STDOUT + * + * @param string|array $text The text to output, or array of errors + * @param string $foreground + * @param string $background + */ + public static function error(string $text, string $foreground = 'light_red', string $background = null) + { + if ($foreground || $background) + { + $text = static::color($text, $foreground, $background); + } + + fwrite(STDERR, $text . PHP_EOL); + } + + //-------------------------------------------------------------------- + + /** + * Beeps a certain number of times. + * + * @param int $num The number of times to beep + */ + public static function beep(int $num = 1) + { + echo str_repeat("\x07", $num); + } + + //-------------------------------------------------------------------- + + /** + * Waits a certain number of seconds, optionally showing a wait message and + * waiting for a key press. + * + * @param int $seconds Number of seconds + * @param bool $countdown Show a countdown or not + */ + public static function wait(int $seconds, bool $countdown = false) + { + if ($countdown === true) + { + $time = $seconds; + + while ($time > 0) + { + fwrite(STDOUT, $time . '... '); + sleep(1); + $time --; + } + static::write(); + } + else + { + if ($seconds > 0) + { + sleep($seconds); + } + else + { + static::write(static::$wait_msg); + static::input(); + } + } + } + + //-------------------------------------------------------------------- + + /** + * if operating system === windows + */ + public static function isWindows() + { + return 'win' === strtolower(substr(php_uname("s"), 0, 3)); + } + + //-------------------------------------------------------------------- + + /** + * Enter a number of empty lines + * + * @param int $num Number of lines to output + * + * @return void + */ + public static function newLine(int $num = 1) + { + // Do it once or more, write with empty string gives us a new line + for ($i = 0; $i < $num; $i ++ ) + { + static::write(''); + } + } + + //-------------------------------------------------------------------- + + /** + * Clears the screen of output + * + * @return void + */ + public static function clearScreen() + { + static::isWindows() + + // Windows is a bit crap at this, but their terminal is tiny so shove this in + ? static::newLine(40) + + // Anything with a flair of Unix will handle these magic characters + : fwrite(STDOUT, chr(27) . "[H" . chr(27) . "[2J"); + } + + //-------------------------------------------------------------------- + + /** + * Returns the given text with the correct color codes for a foreground and + * optionally a background color. + * + * @param string $text The text to color + * @param string $foreground The foreground color + * @param string $background The background color + * @param string $format Other formatting to apply. Currently only 'underline' is understood + * + * @return string The color coded string + */ + public static function color(string $text, string $foreground, string $background = null, string $format = null) + { + if (static::isWindows() && ! isset($_SERVER['ANSICON'])) + { + return $text; + } + + if ( ! array_key_exists($foreground, static::$foreground_colors)) + { + throw new \RuntimeException('Invalid CLI foreground color: ' . $foreground); + } + + if ($background !== null && ! array_key_exists($background, static::$background_colors)) + { + throw new \RuntimeException('Invalid CLI background color: ' . $background); + } + + $string = "\033[" . static::$foreground_colors[$foreground] . "m"; + + if ($background !== null) + { + $string .= "\033[" . static::$background_colors[$background] . "m"; + } + + if ($format === 'underline') + { + $string .= "\033[4m"; + } + + $string .= $text . "\033[0m"; + + return $string; + } + + //-------------------------------------------------------------------- + + /** + * Attempts to determine the width of the viewable CLI window. + * This only works on *nix-based systems, so return a sane default + * for Windows environments. + * + * @param int $default + * + * @return int + */ + public static function getWidth(int $default = 80): int + { + if (static::isWindows()) + { + return $default; + } + + return (int) shell_exec('tput cols'); + } + + //-------------------------------------------------------------------- + + /** + * Attempts to determine the height of the viewable CLI window. + * This only works on *nix-based systems, so return a sane default + * for Windows environments. + * + * @param int $default + * + * @return int + */ + public static function getHeight(int $default = 32): int + { + if (static::isWindows()) + { + return $default; + } + + return (int) shell_exec('tput lines'); + } + + //-------------------------------------------------------------------- + + /** + * Displays a progress bar on the CLI. You must call it repeatedly + * to update it. Set $thisStep = false to erase the progress bar. + * + * @param int $thisStep + * @param int $totalSteps + */ + public static function showProgress($thisStep = 1, int $totalSteps = 10) + { + static $inProgress = false; + + // restore cursor position when progress is continuing. + if ($inProgress !== false && $inProgress <= $thisStep) + { + fwrite(STDOUT, "\033[1A"); + } + $inProgress = $thisStep; + + if ($thisStep !== false) + { + // Don't allow div by zero or negative numbers.... + $thisStep = abs($thisStep); + $totalSteps = $totalSteps < 1 ? 1 : $totalSteps; + + $percent = intval(($thisStep / $totalSteps) * 100); + $step = (int) round($percent / 10); + + // Write the progress bar + fwrite(STDOUT, "[\033[32m" . str_repeat('#', $step) . str_repeat('.', 10 - $step) . "\033[0m]"); + // Textual representation... + fwrite(STDOUT, sprintf(" %3d%% Complete", $percent) . PHP_EOL); + } + else + { + fwrite(STDOUT, "\007"); + } + } + + //-------------------------------------------------------------------- + + /** + * Takes a string and writes it to the command line, wrapping to a maximum + * width. If no maximum width is specified, will wrap to the window's max + * width. + * + * If an int is passed into $pad_left, then all strings after the first + * will padded with that many spaces to the left. Useful when printing + * short descriptions that need to start on an existing line. + * + * @param string $string + * @param int $max + * @param int $pad_left + * + * @return string + */ + public static function wrap(string $string = null, int $max = 0, int $pad_left = 0): string + { + if (empty($string)) + { + return ''; + } + + if ($max == 0) + { + $max = CLI::getWidth(); + } + + if (CLI::getWidth() < $max) + { + $max = CLI::getWidth(); + } + + $max = $max - $pad_left; + + $lines = wordwrap($string, $max); + + if ($pad_left > 0) + { + $lines = explode(PHP_EOL, $lines); + + $first = true; + + array_walk($lines, function (&$line, $index) use ($pad_left, &$first) { + if ( ! $first) + { + $line = str_repeat(" ", $pad_left) . $line; + } + else + { + $first = false; + } + }); + + $lines = implode(PHP_EOL, $lines); + } + + return $lines; + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + // Command-Line 'URI' support + //-------------------------------------------------------------------- + + /** + * Parses the command line it was called from and collects all + * options and valid segments. + * + * I tried to use getopt but had it fail occassionally to find any + * options but argc has always had our back. We don't have all of the power + * of getopt but this does us just fine. + */ + protected static function parseCommandLine() + { + $optionsFound = false; + + for ($i = 1; $i < $_SERVER['argc']; $i ++ ) + { + // If there's no '-' at the beginning of the argument + // then add it to our segments. + if ( ! $optionsFound && mb_strpos($_SERVER['argv'][$i], '-') === false) + { + static::$segments[] = $_SERVER['argv'][$i]; + continue; + } + + // We set $optionsFound here so that we know to + // skip the next argument since it's likely the + // value belonging to this option. + $optionsFound = true; + + if (mb_substr($_SERVER['argv'][$i], 0, 1) != '-') + { + continue; + } + + $arg = str_replace('-', '', $_SERVER['argv'][$i]); + $value = null; + + // if the next item doesn't have a dash it's a value. + if (isset($_SERVER['argv'][$i + 1]) && mb_substr($_SERVER['argv'][$i + 1], 0, 1) != '-') + { + $value = $_SERVER['argv'][$i + 1]; + $i ++; + } + + static::$options[$arg] = $value; + + // Reset $optionsFound so it can collect segments + // past any options. + $optionsFound = false; + } + } + + //-------------------------------------------------------------------- + + /** + * Returns the command line string portions of the arguments, minus + * any options, as a string. This is used to pass along to the main + * CodeIgniter application. + * + * @return string + */ + public static function getURI() + { + return implode('/', static::$segments); + } + + //-------------------------------------------------------------------- + + /** + * Returns an individual segment. + * + * This ignores any options that might have been dispersed between + * valid segments in the command: + * + * // segment(3) is 'three', not '-f' or 'anOption' + * > php spark one two -f anOption three + * + * @param int $index + * + * @return mixed|null + */ + public static function getSegment(int $index) + { + if ( ! isset(static::$segments[$index - 1])) + { + return null; + } + + return static::$segments[$index - 1]; + } + + //-------------------------------------------------------------------- + + /** + * Gets a single command-line option. Returns TRUE if the option + * exists, but doesn't have a value, and is simply acting as a flag. + * + * @param string $name + * + * @return bool|mixed|null + */ + public static function getOption(string $name) + { + if ( ! array_key_exists($name, static::$options)) + { + return null; + } + + // If the option didn't have a value, simply return TRUE + // so they know it was set, otherwise return the actual value. + $val = static::$options[$name] === null ? true : static::$options[$name]; + + return $val; + } + + //-------------------------------------------------------------------- + + /** + * Returns the raw array of options found. + * + * @return array + */ + public static function getOptions() + { + return static::$options; + } + + //-------------------------------------------------------------------- + + /** + * Returns the options a string, suitable for passing along on + * the CLI to other commands. + * + * @return string + */ + public static function getOptionString(): string + { + if (empty(static::$options)) + { + return ''; + } + + $out = ''; + + foreach (static::$options as $name => $value) + { + // If there's a space, we need to group + // so it will pass correctly. + if (mb_strpos($value, ' ') !== false) + { + $value = '"' . $value . '"'; + } + + $out .= "-{$name} $value "; + } + + return $out; + } + + //-------------------------------------------------------------------- +} + +// Ensure the class is initialized. +CLI::init(); diff --git a/system/CLI/CommandRunner.php b/system/CLI/CommandRunner.php new file mode 100644 index 0000000..f4cc320 --- /dev/null +++ b/system/CLI/CommandRunner.php @@ -0,0 +1,181 @@ +index($params); + } + + //-------------------------------------------------------------------- + + + /** + * @param array $params + * + * @return mixed + */ + public function index(array $params) + { + $command = array_shift($params); + + $this->createCommandList(); + + if (is_null($command)) + { + $command = 'help'; + } + + return $this->runCommand($command, $params); + } + + //-------------------------------------------------------------------- + + /** + * Actually runs the command. + * + * @param string $command + * @param array $params + * + * @return mixed + */ + protected function runCommand(string $command, array $params) + { + if ( ! isset($this->commands[$command])) + { + CLI::error('Command \'' . $command . '\' not found'); + CLI::newLine(); + return; + } + + // The file would have already been loaded during the + // createCommandList function... + $className = $this->commands[$command]['class']; + $class = new $className($this->logger, $this); + + return $class->run($params); + } + + //-------------------------------------------------------------------- + + /** + * Scans all Commands directories and prepares a list + * of each command with it's group and file. + * + * @return null|void + */ + protected function createCommandList() + { + $files = service('locator')->listFiles("Commands/"); + + // If no matching command files were found, bail + if (empty($files)) + { + return; + } + + // Loop over each file checking to see if a command with that + // alias exists in the class. If so, return it. Otherwise, try the next. + foreach ($files as $file) + { + $className = service('locator')->findQualifiedNameFromPath($file); + + if (empty($className) || ! class_exists($className)) + { + continue; + } + + $class = new $className($this->logger, $this); + + // Store it! + if ($class->group !== null) + { + $this->commands[$class->name] = [ + 'class' => $className, + 'file' => $file, + 'group' => $class->group, + 'description' => $class->description + ]; + } + + $class = null; + unset($class); + } + + asort($this->commands); + } + + //-------------------------------------------------------------------- + + /** + * Allows access to the current commands that have been found. + * + * @return array + */ + public function getCommands() + { + return $this->commands; + } + + //-------------------------------------------------------------------- +} diff --git a/system/CLI/Console.php b/system/CLI/Console.php new file mode 100644 index 0000000..92043e9 --- /dev/null +++ b/system/CLI/Console.php @@ -0,0 +1,94 @@ +app = $app; + } + + //-------------------------------------------------------------------- + + /** + * Runs the current command discovered on the CLI. + */ + public function run() + { + $path = CLI::getURI() ?: 'list'; + + // Set the path for the application to route to. + $this->app->setPath("ci{$path}"); + + return $this->app->run(); + } + + //-------------------------------------------------------------------- + + /** + * Displays basic information about the Console. + */ + public function showHeader() + { + CLI::newLine(1); + + CLI::write(CLI::color('CodeIgniter CLI Tool', 'green') + . ' - Version ' . CodeIgniter::CI_VERSION + . ' - Server-Time: ' . date('Y-m-d H:i:sa')); + + CLI::newLine(1); + } + + //-------------------------------------------------------------------- +} diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php new file mode 100644 index 0000000..1209164 --- /dev/null +++ b/system/CodeIgniter.php @@ -0,0 +1,913 @@ +startTime = microtime(true); + $this->startMemory = memory_get_usage(true); + $this->config = $config; + } + + //-------------------------------------------------------------------- + + /** + * Handles some basic app and environment setup. + */ + public function initialize() + { + // Set default timezone on the server + date_default_timezone_set($this->config->appTimezone ?? 'UTC'); + + // Setup Exception Handling + Services::exceptions() + ->initialize(); + + $this->loadEnvironment(); + $this->detectEnvironment(); + $this->bootstrapEnvironment(); + + if (CI_DEBUG) + { + require_once BASEPATH . 'ThirdParty/Kint/kint.php'; + } + } + + //-------------------------------------------------------------------- + + /** + * Launch the application! + * + * This is "the loop" if you will. The main entry point into the script + * that gets the required class instances, fires off the filters, + * tries to route the response, loads the controller and generally + * makes all of the pieces work together. + * + * @param \CodeIgniter\Router\RouteCollectionInterface $routes + */ + public function run(RouteCollectionInterface $routes = null) + { + $this->startBenchmark(); + + $this->getRequestObject(); + $this->getResponseObject(); + + $this->forceSecureAccess(); + + $this->spoofRequestMethod(); + + Events::trigger('pre_system'); + + // Check for a cached page. Execution will stop + // if the page has been cached. + $cacheConfig = new Cache(); + $this->displayCache($cacheConfig); + + try + { + $this->handleRequest($routes, $cacheConfig); + } catch (Router\RedirectException $e) + { + $logger = Services::logger(); + $logger->info('REDIRECTED ROUTE at ' . $e->getMessage()); + + // If the route is a 'redirect' route, it throws + // the exception with the $to as the message + $this->response->redirect($e->getMessage(), 'auto', $e->getCode()); + $this->callExit(EXIT_SUCCESS); + } + catch (PageNotFoundException $e) + { + $this->display404errors($e); + } + } + + //-------------------------------------------------------------------- + + /** + * Handles the main request logic and fires the controller. + * + * @param \CodeIgniter\Router\RouteCollectionInterface $routes + * @param $cacheConfig + */ + protected function handleRequest(RouteCollectionInterface $routes = null, $cacheConfig) + { + $this->tryToRouteIt($routes); + + // Run "before" filters + $filters = Services::filters(); + $uri = $this->request instanceof CLIRequest ? $this->request->getPath() : $this->request->uri->getPath(); + + $filters->run($uri, 'before'); + + $returned = $this->startController(); + + // Closure controller has run in startController(). + if ( ! is_callable($this->controller)) + { + $controller = $this->createController(); + + // Is there a "post_controller_constructor" event? + Events::trigger('post_controller_constructor'); + + $returned = $this->runController($controller); + } + else + { + $this->benchmark->stop('controller_constructor'); + $this->benchmark->stop('controller'); + } + + // Handle any redirects + if ($returned instanceof RedirectResponse) + { + $this->callExit(EXIT_SUCCESS); + } + + // If $returned is a string, then the controller output something, + // probably a view, instead of echoing it directly. Send it along + // so it can be used with the output. + $this->gatherOutput($cacheConfig, $returned); + + // Run "after" filters + $response = $filters->run($uri, 'after'); + + if ($response instanceof Response) + { + $this->response = $response; + } + + // Save our current URI as the previous URI in the session + // for safer, more accurate use with `previous_url()` helper function. + $this->storePreviousURL($this->request->uri ?? $uri); + + unset($uri); + + $this->sendResponse(); + + //-------------------------------------------------------------------- + // Is there a post-system event? + //-------------------------------------------------------------------- + Events::trigger('post_system'); + } + + //-------------------------------------------------------------------- + + /** + * You can load different configurations depending on your + * current environment. Setting the environment also influences + * things like logging and error reporting. + * + * This can be set to anything, but default usage is: + * + * development + * testing + * production + */ + protected function detectEnvironment() + { + // running under Continuous Integration server? + if (getenv('CI') !== false) + { + define('ENVIRONMENT', 'testing'); + } + else + { + define('ENVIRONMENT', $_SERVER['CI_ENVIRONMENT'] ?? 'production'); + } + } + + //-------------------------------------------------------------------- + + /** + * Load any custom boot files based upon the current environment. + * + * If no boot file exists, we shouldn't continue because something + * is wrong. At the very least, they should have error reporting setup. + */ + protected function bootstrapEnvironment() + { + if (file_exists(APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php')) + { + require_once APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; + } + else + { + header('HTTP/1.1 503 Service Unavailable.', true, 503); + echo 'The application environment is not set correctly.'; + exit(1); // EXIT_ERROR + } + } + + //-------------------------------------------------------------------- + + /** + * Loads any custom server config values from the .env file. + */ + protected function loadEnvironment() + { + // Load environment settings from .env files + // into $_SERVER and $_ENV + require BASEPATH . 'Config/DotEnv.php'; + + $env = new DotEnv(ROOTPATH); + $env->load(); + } + + //-------------------------------------------------------------------- + + /** + * Start the Benchmark + * + * The timer is used to display total script execution both in the + * debug toolbar, and potentially on the displayed page. + */ + protected function startBenchmark() + { + $this->startTime = microtime(true); + + $this->benchmark = Services::timer(); + $this->benchmark->start('total_execution', $this->startTime); + $this->benchmark->start('bootstrap'); + } + + //-------------------------------------------------------------------- + + /** + * Get our Request object, (either IncomingRequest or CLIRequest) + * and set the server protocol based on the information provided + * by the server. + */ + protected function getRequestObject() + { + if (is_cli()) + { + $this->request = Services::clirequest($this->config); + } + else + { + $this->request = Services::request($this->config); + $this->request->setProtocolVersion($_SERVER['SERVER_PROTOCOL']); + } + } + + //-------------------------------------------------------------------- + + /** + * Get our Response object, and set some default values, including + * the HTTP protocol version and a default successful response. + */ + protected function getResponseObject() + { + $this->response = Services::response($this->config); + + if ( ! is_cli()) + { + $this->response->setProtocolVersion($this->request->getProtocolVersion()); + } + + // Assume success until proven otherwise. + $this->response->setStatusCode(200); + } + + //-------------------------------------------------------------------- + + /** + * Force Secure Site Access? If the config value 'forceGlobalSecureRequests' + * is true, will enforce that all requests to this site are made through + * HTTPS. Will redirect the user to the current page with HTTPS, as well + * as set the HTTP Strict Transport Security header for those browsers + * that support it. + * + * @param int $duration How long the Strict Transport Security + * should be enforced for this URL. + */ + protected function forceSecureAccess($duration = 31536000) + { + if ($this->config->forceGlobalSecureRequests !== true) + { + return; + } + + force_https($duration, $this->request, $this->response); + } + + //-------------------------------------------------------------------- + + /** + * Determines if a response has been cached for the given URI. + * + * @param \Config\Cache $config + * + * @throws \Exception + * + * @return bool + */ + public function displayCache($config) + { + if ($cachedResponse = cache()->get($this->generateCacheName($config))) + { + $cachedResponse = unserialize($cachedResponse); + if ( ! is_array($cachedResponse) || ! isset($cachedResponse['output']) || ! isset($cachedResponse['headers'])) + { + throw new \Exception("Error unserializing page cache"); + } + + $headers = $cachedResponse['headers']; + $output = $cachedResponse['output']; + + // Clear all default headers + foreach ($this->response->getHeaders() as $key => $val) + { + $this->response->removeHeader($key); + } + + // Set cached headers + foreach ($headers as $name => $value) + { + $this->response->setHeader($name, $value); + } + + $output = $this->displayPerformanceMetrics($output); + $this->response->setBody($output)->send(); + $this->callExit(EXIT_SUCCESS); + }; + } + + //-------------------------------------------------------------------- + + /** + * Tells the app that the final output should be cached. + * + * @param int $time + * + * @return $this + */ + public static function cache(int $time) + { + self::$cacheTTL = (int) $time; + } + + //-------------------------------------------------------------------- + + /** + * Caches the full response from the current request. Used for + * full-page caching for very high performance. + * + * @param \Config\Cache $config + */ + public function cachePage(Cache $config) + { + $headers = []; + foreach ($this->response->getHeaders() as $header) + { + $headers[$header->getName()] = $header->getValueLine(); + } + + return cache()->save( + $this->generateCacheName($config), serialize(['headers' => $headers, 'output' => $this->output]), self::$cacheTTL + ); + } + + //-------------------------------------------------------------------- + + /** + * Returns an array with our basic performance stats collected. + * + * @return array + */ + public function getPerformanceStats() + { + return [ + 'startTime' => $this->startTime, + 'totalTime' => $this->totalTime, + 'startMemory' => $this->startMemory + ]; + } + + //-------------------------------------------------------------------- + + /** + * Generates the cache name to use for our full-page caching. + * + * @param $config + * + * @return string + */ + protected function generateCacheName($config): string + { + if (is_cli()) + { + return md5($this->request->getPath()); + } + + $uri = $this->request->uri; + + if ($config->cacheQueryString) + { + $name = URI::createURIString( + $uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery() + ); + } + else + { + $name = URI::createURIString( + $uri->getScheme(), $uri->getAuthority(), $uri->getPath() + ); + } + + return md5($name); + } + + //-------------------------------------------------------------------- + + /** + * Replaces the memory_usage and elapsed_time tags. + * + * @param string $output + * + * @return string + */ + public function displayPerformanceMetrics(string $output): string + { + $this->totalTime = $this->benchmark->getElapsedTime('total_execution'); + + $output = str_replace('{elapsed_time}', $this->totalTime, $output); + + return $output; + } + + //-------------------------------------------------------------------- + + /** + * Try to Route It - As it sounds like, works with the router to + * match a route against the current URI. If the route is a + * "redirect route", will also handle the redirect. + * + * @param RouteCollectionInterface $routes An collection interface to use in place + * of the config file. + */ + protected function tryToRouteIt(RouteCollectionInterface $routes = null) + { + if (empty($routes) || ! $routes instanceof RouteCollectionInterface) + { + require APPPATH . 'Config/Routes.php'; + } + + // $routes is defined in Config/Routes.php + $this->router = Services::router($routes); + + $path = $this->determinePath(); + + $this->benchmark->stop('bootstrap'); + $this->benchmark->start('routing'); + + ob_start(); + + $this->controller = $this->router->handle($path); + $this->method = $this->router->methodName(); + + // If a {locale} segment was matched in the final route, + // then we need to set the correct locale on our Request. + if ($this->router->hasLocale()) + { + $this->request->setLocale($this->router->getLocale()); + } + + $this->benchmark->stop('routing'); + } + + //-------------------------------------------------------------------- + + /** + * Determines the path to use for us to try to route to, based + * on user input (setPath), or the CLI/IncomingRequest path. + */ + protected function determinePath() + { + if ( ! empty($this->path)) + { + return $this->path; + } + + return is_cli() ? $this->request->getPath() : $this->request->uri->getPath(); + } + + //-------------------------------------------------------------------- + + /** + * Allows the request path to be set from outside the class, + * instead of relying on CLIRequest or IncomingRequest for the path. + * + * This is primarily used by the Console. + * + * @param string $path + * + * @return $this + */ + public function setPath(string $path) + { + $this->path = $path; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Now that everything has been setup, this method attempts to run the + * controller method and make the script go. If it's not able to, will + * show the appropriate Page Not Found error. + */ + protected function startController() + { + $this->benchmark->start('controller'); + $this->benchmark->start('controller_constructor'); + + // Is it routed to a Closure? + if (is_object($this->controller) && (get_class($this->controller) == 'Closure')) + { + $controller = $this->controller; + return $controller(...$this->router->params()); + } + else + { + // No controller specified - we don't know what to do now. + if (empty($this->controller)) + { + throw new PageNotFoundException('Controller is empty.'); + } + else + { + // Try to autoload the class + if ( ! class_exists($this->controller, true) || $this->method[0] === '_') + { + throw new PageNotFoundException('Controller or its method is not found.'); + } + else if ( ! method_exists($this->controller, '_remap') && + ! is_callable([$this->controller, $this->method], false) + ) + { + throw new PageNotFoundException('Controller method is not found.'); + } + } + } + } + + //-------------------------------------------------------------------- + + /** + * Instantiates the controller class. + * + * @return mixed + */ + protected function createController() + { + $class = new $this->controller($this->request, $this->response); + + $this->benchmark->stop('controller_constructor'); + + return $class; + } + + //-------------------------------------------------------------------- + + /** + * Runs the controller, allowing for _remap methods to function. + * + * @param mixed $class + * + * @return mixed + */ + protected function runController($class) + { + if (method_exists($class, '_remap')) + { + $output = $class->_remap($this->method, ...$this->router->params()); + } + else + { + $output = $class->{$this->method}(...$this->router->params()); + } + + $this->benchmark->stop('controller'); + + return $output; + } + + //-------------------------------------------------------------------- + + /** + * Displays a 404 Page Not Found error. If set, will try to + * call the 404Override controller/method that was set in routing config. + * + * @param PageNotFoundException $e + */ + protected function display404errors(PageNotFoundException $e) + { + // Is there a 404 Override available? + if ($override = $this->router->get404Override()) + { + if ($override instanceof \Closure) + { + echo $override(); + } + else if (is_array($override)) + { + $this->benchmark->start('controller'); + $this->benchmark->start('controller_constructor'); + + $this->controller = $override[0]; + $this->method = $override[1]; + + unset($override); + + $controller = $this->createController(); + $this->runController($controller); + } + + $this->gatherOutput(); + $this->sendResponse(); + + return; + } + + // Display 404 Errors + $this->response->setStatusCode(404); + + if (ENVIRONMENT !== 'testing') + { + if (ob_get_level() > 0) + { + ob_end_flush(); + } + } + else + { + // When testing, one is for phpunit, another is for test case. + if (ob_get_level() > 2) + { + ob_end_flush(); + } + } + + throw new PageNotFoundException(lang('HTTP.pageNotFound')); + } + + //-------------------------------------------------------------------- + + /** + * Gathers the script output from the buffer, replaces some execution + * time tag in the output and displays the debug toolbar, if required. + * + * @param null $cacheConfig + * @param null $returned + */ + protected function gatherOutput($cacheConfig = null, $returned = null) + { + $this->output = ob_get_contents(); + ob_end_clean(); + + // If the controller returned a response object, + // we need to grab the body from it so it can + // be added to anything else that might have been + // echoed already. + // We also need to save the instance locally + // so that any status code changes, etc, take place. + if ($returned instanceof Response) + { + $this->response = $returned; + $returned = $returned->getBody(); + } + + if (is_string($returned)) + { + $this->output .= $returned; + } + + // Cache it without the performance metrics replaced + // so that we can have live speed updates along the way. + if (self::$cacheTTL > 0) + { + $this->cachePage($cacheConfig); + } + + $this->output = $this->displayPerformanceMetrics($this->output); + + $this->response->setBody($this->output); + } + + //-------------------------------------------------------------------- + + /** + * If we have a session object to use, store the current URI + * as the previous URI. This is called just prior to sending the + * response to the client, and will make it available next request. + * + * This helps provider safer, more reliable previous_url() detection. + * + * @param \CodeIgniter\HTTP\URI $uri + */ + public function storePreviousURL($uri) + { + // This is mainly needed during testing... + if (is_string($uri)) + { + $uri = new URI($uri); + } + + if (isset($_SESSION)) + { + $_SESSION['_ci_previous_url'] = (string) $uri; + } + } + + //-------------------------------------------------------------------- + + /** + * Modifies the Request Object to use a different method if a POST + * variable called _method is found. + * + * Does not work on CLI commands. + */ + public function spoofRequestMethod() + { + if (is_cli()) + return; + + // Only works with POSTED forms + if ($this->request->getMethod() !== 'post') + return; + + $method = $this->request->getPost('_method'); + + if (empty($method)) + return; + + $this->request = $this->request->setMethod($method); + } + + /** + * Sends the output of this request back to the client. + * This is what they've been waiting for! + */ + protected function sendResponse() + { + $this->response->send(); + } + + //-------------------------------------------------------------------- + + /** + * Exits the application, setting the exit code for CLI-based applications + * that might be watching. + * + * Made into a separate method so that it can be mocked during testing + * without actually stopping script execution. + * + * @param $code + */ + protected function callExit($code) + { + exit($code); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Commands/Database/CreateMigration.php b/system/Commands/Database/CreateMigration.php new file mode 100644 index 0000000..c02ac64 --- /dev/null +++ b/system/Commands/Database/CreateMigration.php @@ -0,0 +1,178 @@ + 'The migration file name' + ]; + + /** + * the Command's Options + * + * @var array + */ + protected $options = [ + '-n' => 'Set migration namespace' + ]; + + /** + * Creates a new migration file with the current timestamp. + * + * @todo Have this check the settings and see what type of file it should create (timestamp or sequential) + * + * @param array $params + */ + public function run(array $params = []) + { + + $name = array_shift($params); + + if (empty($name)) + { + $name = CLI::prompt(lang('Migrations.migNameMigration')); + } + + if (empty($name)) + { + CLI::error(lang('Migrations.migBadCreateName')); + return; + } + $namespace = CLI::getOption('n'); + $homepath = APPPATH; + + if ( ! empty($ns)) + { + // Get all namespaces form PSR4 paths. + $config = new Autoload(); + $namespaces = $config->psr4; + + foreach ($namespaces as $namespace => $path) + { + + if ($namespace == $ns) + { + $homepath = realpath($path); + break; + } + } + } + else + { + $ns = "App"; + } + + $path = $homepath . '/Database/Migrations/' . date('YmdHis_') . $name . '.php'; + + $template = << 'Set database group', + ]; + + /** + * Migrates us up or down to the version specified as $currentVersion + * in the migrations config file. + * + * @param array $params + */ + public function run(array $params = []) + { + $runner = Services::migrations(); + + CLI::write(lang('Migrations.migToVersion'), 'yellow'); + + $group = CLI::getOption('g'); + try + { + $runner->current($group); + $messages = $runner->getCliMessages(); + foreach ($messages as $message) + { + CLI::write($message); + } + + CLI::write('Done'); + + } catch (\Exception $e) + { + $this->showError($e); + } + + + } + +} diff --git a/system/Commands/Database/MigrateLatest.php b/system/Commands/Database/MigrateLatest.php new file mode 100644 index 0000000..08258df --- /dev/null +++ b/system/Commands/Database/MigrateLatest.php @@ -0,0 +1,136 @@ + 'Set migration namespace', + '-g' => 'Set database group', + '-all' => 'Set latest for all namespace, will ignore (-n) option', + ]; + + /** + * Ensures that all migrations have been run. + * + * @param array $params + */ + public function run(array $params = []) + { + $runner = Services::migrations(); + + CLI::write(lang('Migrations.migToLatest'), 'yellow'); + + $namespace = CLI::getOption('n'); + $group = CLI::getOption('g'); + + try + { + if ( ! is_null(CLI::getOption('all'))) + { + $runner->latestAll($group); + } + else + { + $runner->latest($namespace, $group); + } + $messages = $runner->getCliMessages(); + foreach ($messages as $message) + { + CLI::write($message); + } + + CLI::write('Done'); + + } catch (\Exception $e) + { + $this->showError($e); + } + + + } + +} diff --git a/system/Commands/Database/MigrateRefresh.php b/system/Commands/Database/MigrateRefresh.php new file mode 100644 index 0000000..7e4c35a --- /dev/null +++ b/system/Commands/Database/MigrateRefresh.php @@ -0,0 +1,108 @@ + 'Set migration namespace', + '-g' => 'Set database group', + '-all' => 'Set latest for all namespace, will ignore (-n) option' + ]; + + /** + * Does a rollback followed by a latest to refresh the current state + * of the database. + * + * @param array $params + */ + public function run(array $params = []) + { + $this->call('migrate:rollback'); + $this->call('migrate:latest'); + } + +} diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php new file mode 100644 index 0000000..a7b81b9 --- /dev/null +++ b/system/Commands/Database/MigrateRollback.php @@ -0,0 +1,154 @@ + 'Set migration namespace', + '-g' => 'Set database group', + '-all' => 'Set latest for all namespace, will ignore (-n) option', + ]; + + /** + * Runs all of the migrations in reverse order, until they have + * all been un-applied. + * + * @param array $params + */ + public function run(array $params = []) + { + $runner = Services::migrations(); + + CLI::write(lang('Migrations.migRollingBack'), 'yellow'); + $group = CLI::getOption('g'); + if ( ! is_null($group)) + { + $runner->setGroup($group); + } + try + { + if (is_null(CLI::getOption('all'))) + { + $namespace = CLI::getOption('n'); + $runner->version(0, $namespace); + } + else + { + // Get all namespaces form PSR4 paths. + $config = new Autoload(); + $namespaces = $config->psr4; + foreach ($namespaces as $namespace => $path) + { + $runner->setNamespace($namespace); + $migrations = $runner->findMigrations(); + if (empty($migrations)) + { + continue; + } + $runner->version(0, $namespace, $group); + } + } + $messages = $runner->getCliMessages(); + foreach ($messages as $message) + { + CLI::write($message); + } + + CLI::write('Done'); + + } catch (\Exception $e) + { + $this->showError($e); + } + + + } + +} diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php new file mode 100644 index 0000000..00352fd --- /dev/null +++ b/system/Commands/Database/MigrateStatus.php @@ -0,0 +1,165 @@ + 'Set database group', + ]; + + /** + * Displays a list of all migrations and whether they've been run or not. + * + * @param array $params + */ + public function run(array $params = []) + { + $runner = Services::migrations(); + + if ( ! is_null(CLI::getOption('g'))) + { + $runner->setGroup(CLI::getOption('g')); + } + + // Get all namespaces form PSR4 paths. + $config = new Autoload(); + $namespaces = $config->psr4; + + // Loop for all $namespaces + foreach ($namespaces as $namespace => $path) + { + + $runner->setNamespace($namespace); + $migrations = $runner->findMigrations(); + $history = $runner->getHistory(); + + if (empty($migrations)) + { + CLI::error("$namespace: " . lang('Migrations.migNoneFound')); + continue; + } + + ksort($migrations); + + CLI::newLine(1); + + CLI::write(lang('Migrations.migHistoryFor') . "$namespace: ", 'green'); + + CLI::newLine(1); + + $max = 0; + foreach ($migrations as $version => $migration) + { + $file = substr($migration->name, strpos($migration->name, $version . '_')); + $migrations[$version]->name = $file; + + $max = max($max, strlen($file)); + } + + CLI::write(str_pad(lang('Migrations.filename'), $max + 6) . lang('Migrations.migOn'), 'yellow'); + + + foreach ($migrations as $version => $migration) + { + $date = ''; + foreach ($history as $row) + { + if ($row['version'] != $version) + { + continue; + } + + $date = date("Y-m-d H:i:s", $row['time']); + } + CLI::write(str_pad($migration->name, $max + 6) . ($date ? $date : '---')); + } + } + } + +} diff --git a/system/Commands/Database/MigrateVersion.php b/system/Commands/Database/MigrateVersion.php new file mode 100644 index 0000000..c3e28e7 --- /dev/null +++ b/system/Commands/Database/MigrateVersion.php @@ -0,0 +1,137 @@ + 'The version number to migrate', + ]; + + /** + * the Command's Options + * + * @var array + */ + protected $options = [ + '-n' => 'Set migration namespace', + '-g' => 'Set database group', + ]; + + /** + * Migrates the database up or down to get to the specified version. + * + * @param array $params + */ + public function run(array $params = []) + { + $runner = Services::migrations(); + + // Get the version number + $version = array_shift($params); + + if (is_null($version)) + { + $version = CLI::prompt(lang('Migrations.version')); + } + + if (is_null($version)) + { + CLI::error(lang('Migrations.invalidVersion')); + exit(); + } + + CLI::write(sprintf(lang('Migrations.migToVersionPH'), $version), 'yellow'); + + $namespace = CLI::getOption('n'); + $group = CLI::getOption('g'); + try + { + $runner->version($version, $namespace, $group); + CLI::write('Done'); + } catch (\Exception $e) + { + $this->showError($e); + } + + + } + +} diff --git a/system/Commands/Database/Seed.php b/system/Commands/Database/Seed.php new file mode 100644 index 0000000..1b85455 --- /dev/null +++ b/system/Commands/Database/Seed.php @@ -0,0 +1,128 @@ + 'The seeder name to run' + ]; + + /** + * the Command's Options + * + * @var array + */ + protected $options = []; + + /** + * Runs all of the migrations in reverse order, until they have + * all been un-applied. + * + * @param array $params + */ + public function run(array $params = []) + { + $seeder = new Seeder(new \Config\Database()); + + $seedName = array_shift($params); + + if (empty($seedName)) + { + $seedName = CLI::prompt(lang('Migrations.migSeeder'), 'DatabaseSeeder'); + } + + if (empty($seedName)) + { + CLI::error(lang('Migrations.migMissingSeeder')); + return; + } + + try + { + $seeder->call($seedName); + } catch (\Exception $e) + { + $this->showError($e); + } + } + +} diff --git a/system/Commands/Help.php b/system/Commands/Help.php new file mode 100644 index 0000000..4099a89 --- /dev/null +++ b/system/Commands/Help.php @@ -0,0 +1,118 @@ + 'The command name [default: "help"]' + ]; + + /** + * the Command's Options + * + * @var array + */ + protected $options = []; + + //-------------------------------------------------------------------- + + /** + * Displays the help for the spark cli script itself. + * + * @param array $params + */ + public function run(array $params) + { + $command = array_shift($params); + if (is_null($command)) + { + $command = 'help'; + } + + $commands = $this->commands->getCommands(); + $class = new $commands[$command]['class']($this->logger, $this->commands); + + $class->showHelp(); + } + +} diff --git a/system/Commands/ListCommands.php b/system/Commands/ListCommands.php new file mode 100644 index 0000000..afcee40 --- /dev/null +++ b/system/Commands/ListCommands.php @@ -0,0 +1,202 @@ +commands->getCommands(); + + $this->describeCommands($commands); + + CLI::newLine(); + } + + //-------------------------------------------------------------------- + + /** + * Displays the commands on the CLI. + * + * @param array $commands + */ + protected function describeCommands(array $commands = []) + { + arsort($commands); + + $names = array_keys($commands); + $descs = array_column($commands, 'description'); + $groups = array_column($commands, 'group'); + $lastGroup = ''; + + // Pad each item to the same length + $names = $this->padArray($names, 2, 2); + $countNames = count($names); + for ($i = 0; $i < $countNames; $i ++ ) + { + $lastGroup = $this->describeGroup($groups[$i], $lastGroup); + + $out = CLI::color($names[$i], 'yellow'); + + if (isset($descs[$i])) + { + $out .= CLI::wrap($descs[$i], 125, strlen($names[$i])); + } + + CLI::write($out); + } + } + + //-------------------------------------------------------------------- + + /** + * Outputs the description, if necessary. + * + * @param string $new + * @param string $old + * + * @return string + */ + protected function describeGroup(string $new, string $old) + { + if ($new == $old) + { + return $old; + } + + CLI::newLine(); + CLI::write($new); + + return $new; + } + + //-------------------------------------------------------------------- + + /** + * Returns a new array where all of the string elements have + * been padding with trailing spaces to be the same length. + * + * @param array $array + * @param int $extra // How many extra spaces to add at the end + * @param int $indent + * + * @return array + */ + protected function padArray($array, $extra = 2, $indent = 0) + { + $max = max(array_map('strlen', $array)) + $extra + $indent; + + foreach ($array as &$item) + { + $item = str_repeat(' ', $indent) . $item; + $item = str_pad($item, $max); + } + + return $array; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Commands/Sessions/CreateMigration.php b/system/Commands/Sessions/CreateMigration.php new file mode 100644 index 0000000..c8d7417 --- /dev/null +++ b/system/Commands/Sessions/CreateMigration.php @@ -0,0 +1,126 @@ + 'Set migration namespace', + '-g' => 'Set database group', + '-t' => 'Set table name', + ]; + + /** + * Creates a new migration file with the current timestamp. + * + * @param array $params + */ + public function run(array $params = []) + { + $config = new App(); + + $tableName = CLI::getOption('t') ?? $config->sessionSavePath ?? 'ci_sessions'; + + $path = APPPATH . 'Database/Migrations/' . date('YmdHis_') . 'create_' . $tableName . '_table' . '.php'; + + $data = [ + 'namespace' => CLI::getOption('n') ?? APP_NAMESPACE ?? 'App', + 'DBGroup' => CLI::getOption('g'), + 'tableName' => $tableName, + 'matchIP' => $config->sessionMatchIP ?? false, + ]; + + $template = view('\CodeIgniter\Commands\Sessions\Views\migration.tpl', $data); + $template = str_replace('@php', '\Database\Migrations; + +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014-2017 British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author CodeIgniter Dev Team + * @copyright 2014-2017 British Columbia Institute of Technology (https://bcit.ca/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ + +use CodeIgniter\Database\Migration; + +class Migration_create__table extends Migration +{ + + protected $DBGroup = ''; + + + public function up() + { + $this->forge->addField([ + 'id' => [ + 'type' => 'VARCHAR', + 'constraint' => 128, + 'null' => false + ], + 'ip_address' => [ + 'type' => 'VARCHAR', + 'constraint' => 45, + 'null' => false + ], + 'timestamp' => [ + 'type' => 'INT', + 'constraint' => 10, + 'unsigned' => true, + 'null' => false, + 'default' => 0 + ], + 'data' => [ + 'type' => 'TEXT', + 'null' => false, + 'default' => '' + ], + ]); + + $this->forge->addKey(['id', 'ip_address'], true); + + $this->forge->addKey('id', true); + + $this->forge->addKey('timestamp'); + $this->forge->createTable('', true); + } + + //-------------------------------------------------------------------- + + public function down() + { + $this->forge->dropTable('', true); + } +} diff --git a/system/Common.php b/system/Common.php new file mode 100644 index 0000000..f5dd1c3 --- /dev/null +++ b/system/Common.php @@ -0,0 +1,929 @@ +save('foo', 'bar'); + * $foo = cache('bar'); + * + * @param string|null $key + * + * @return \CodeIgniter\Cache\CacheInterface|mixed + */ + function cache(string $key = null) + { + $cache = \Config\Services::cache(); + + // No params - return cache object + if (is_null($key)) + { + return $cache; + } + + // Still here? Retrieve the value. + return $cache->get($key); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('view')) +{ + + /** + * Grabs the current RendererInterface-compatible class + * and tells it to render the specified view. Simply provides + * a convenience method that can be used in Controllers, + * libraries, and routed closures. + * + * NOTE: Does not provide any escaping of the data, so that must + * all be handled manually by the developer. + * + * @param string $name + * @param array $data + * @param array $options Unused - reserved for third-party extensions. + * + * @return string + */ + function view(string $name, array $data = [], array $options = []) + { + /** + * @var CodeIgniter\View\View $renderer + */ + $renderer = Services::renderer(); + + $saveData = null; + if (array_key_exists('saveData', $options) && $options['saveData'] === true) + { + $saveData = (bool) $options['saveData']; + unset($options['saveData']); + } + + return $renderer->setData($data, 'raw') + ->render($name, $options, $saveData); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('view_cell')) +{ + + /** + * View cells are used within views to insert HTML chunks that are managed + * by other classes. + * + * @param string $library + * @param null $params + * @param int $ttl + * @param string|null $cacheName + * + * @return string + */ + function view_cell(string $library, $params = null, int $ttl = 0, string $cacheName = null) + { + return Services::viewcell() + ->render($library, $params, $ttl, $cacheName); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('env')) +{ + + /** + * Allows user to retrieve values from the environment + * variables that have been set. Especially useful for + * retrieving values set from the .env file for + * use in config files. + * + * @param string $key + * @param null $default + * + * @return mixed + */ + function env(string $key, $default = null) + { + $value = getenv($key); + if ($value === false) + { + $value = $_ENV[$key] ?? $_SERVER[$key] ?? false; + } + + // Not found? Return the default value + if ($value === false) + { + return $default; + } + + // Handle any boolean values + switch (strtolower($value)) + { + case 'true': + return true; + case 'false': + return false; + case 'empty': + return ''; + case 'null': + return; + } + + return $value; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('esc')) +{ + + /** + * Performs simple auto-escaping of data for security reasons. + * Might consider making this more complex at a later date. + * + * If $data is a string, then it simply escapes and returns it. + * If $data is an array, then it loops over it, escaping each + * 'value' of the key/value pairs. + * + * Valid context values: html, js, css, url, attr, raw, null + * + * @param string|array $data + * @param string $context + * @param string $encoding + * + * @return string|array + */ + function esc($data, $context = 'html', $encoding = null) + { + if (is_array($data)) + { + foreach ($data as $key => &$value) + { + $value = esc($value, $context); + } + } + + if (is_string($data)) + { + $context = strtolower($context); + + // Provide a way to NOT escape data since + // this could be called automatically by + // the View library. + if (empty($context) || $context == 'raw') + { + return $data; + } + + if ( ! in_array($context, ['html', 'js', 'css', 'url', 'attr'])) + { + throw new \InvalidArgumentException('Invalid escape context provided.'); + } + + if ($context == 'attr') + { + $method = 'escapeHtmlAttr'; + } + else + { + $method = 'escape' . ucfirst($context); + } + + // @todo Optimize this to only load a single instance during page request. + $escaper = new \Zend\Escaper\Escaper($encoding); + + $data = $escaper->$method($data); + } + + return $data; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('session')) +{ + + /** + * A convenience method for accessing the session instance, + * or an item that has been set in the session. + * + * Examples: + * session()->set('foo', 'bar'); + * $foo = session('bar'); + * + * @param string $val + * + * @return \CodeIgniter\Session\Session|mixed|null + */ + function session($val = null) + { + // Returning a single item? + if (is_string($val)) + { + return $_SESSION[$val] ?? null; + } + + return \Config\Services::session(); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('timer')) +{ + + /** + * A convenience method for working with the timer. + * If no parameter is passed, it will return the timer instance, + * otherwise will start or stop the timer intelligently. + * + * @param string|null $name + * + * @return \CodeIgniter\Debug\Timer|mixed + */ + function timer(string $name = null) + { + $timer = \Config\Services::timer(); + + if (empty($name)) + { + return $timer; + } + + if ($timer->has($name)) + { + return $timer->stop($name); + } + + return $timer->start($name); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('service')) +{ + + /** + * Allows cleaner access to the Services Config file. + * Always returns a SHARED instance of the class, so + * calling the function multiple times should always + * return the same instance. + * + * These are equal: + * - $timer = service('timer') + * - $timer = \CodeIgniter\Config\Services::timer(); + * + * @param string $name + * @param array ...$params + * + * @return mixed + */ + function service(string $name, ...$params) + { + return Services::$name(...$params); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('single_service')) +{ + + /** + * Allow cleaner access to a Service. + * Always returns a new instance of the class. + * + * @param string $name + * @param array|null $params + * + * @return mixed + */ + function single_service(string $name, ...$params) + { + // Ensure it's NOT a shared instance + array_push($params, false); + + return Services::$name(...$params); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('lang')) +{ + + /** + * A convenience method to translate a string and format it + * with the intl extension's MessageFormatter object. + * + * @param string $line + * @param array $args + * @param string $locale + * + * @return string + */ + function lang(string $line, array $args = [], string $locale = null) + { + return Services::language($locale) + ->getLine($line, $args); + } + +} + +//-------------------------------------------------------------------- + + +if ( ! function_exists('log_message')) +{ + + /** + * A convenience/compatibility method for logging events through + * the Log system. + * + * Allowed log levels are: + * - emergency + * - alert + * - critical + * - error + * - warning + * - notice + * - info + * - debug + * + * @param string $level + * @param string $message + * @param array|null $context + * + * @return mixed + */ + function log_message(string $level, string $message, array $context = []) + { + // When running tests, we want to always ensure that the + // TestLogger is running, which provides utilities for + // for asserting that logs were called in the test code. + if (ENVIRONMENT == 'testing') + { + $logger = new \CodeIgniter\Log\TestLogger(new \Config\Logger()); + + return $logger->log($level, $message, $context); + } + + return Services::logger(true) + ->log($level, $message, $context); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('is_cli')) +{ + + /** + * Is CLI? + * + * Test to see if a request was made from the command line. + * + * @return bool + */ + function is_cli() + { + return (PHP_SAPI === 'cli' || defined('STDIN')); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('route_to')) +{ + + /** + * Given a controller/method string and any params, + * will attempt to build the relative URL to the + * matching route. + * + * NOTE: This requires the controller/method to + * have a route defined in the routes Config file. + * + * @param string $method + * @param array ...$params + * + * @return false|string + */ + function route_to(string $method, ...$params): string + { + $routes = Services::routes(); + + return $routes->reverseRoute($method, ...$params); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('remove_invisible_characters')) +{ + + /** + * Remove Invisible Characters + * + * This prevents sandwiching null characters + * between ascii characters, like Java\0script. + * + * @param string $str + * @param bool $url_encoded + * + * @return string + */ + function remove_invisible_characters($str, $url_encoded = true) + { + $non_displayables = []; + + // every control character except newline (dec 10), + // carriage return (dec 13) and horizontal tab (dec 09) + if ($url_encoded) + { + $non_displayables[] = '/%0[0-8bcef]/'; // url encoded 00-08, 11, 12, 14, 15 + $non_displayables[] = '/%1[0-9a-f]/'; // url encoded 16-31 + } + + $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127 + + do + { + $str = preg_replace($non_displayables, '', $str, -1, $count); + } while ($count); + + return $str; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('helper')) +{ + + /** + * Loads a helper file into memory. Supports namespaced helpers, + * both in and out of the 'helpers' directory of a namespaced directory. + * + * @param string|array $filenames + */ + function helper($filenames) + { + $loader = Services::locator(true); + + if ( ! is_array($filenames)) + { + $filenames = [$filenames]; + } + + foreach ($filenames as $filename) + { + if (strpos($filename, '_helper') === false) + { + $filename .= '_helper'; + } + + $path = $loader->locateFile($filename, 'Helpers'); + + if ( ! empty($path)) + { + include_once $path; + } + } + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('app_timezone')) +{ + + /** + * Returns the timezone the application has been set to display + * dates in. This might be different than the timezone set + * at the server level, as you often want to stores dates in UTC + * and convert them on the fly for the user. + * + * @return string + */ + function app_timezone() + { + $config = new \Config\App(); + + return $config->appTimezone; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('csrf_token')) +{ + + /** + * Returns the CSRF token name. + * Can be used in Views when building hidden inputs manually, + * or used in javascript vars when using APIs. + * + * @return string + */ + function csrf_token() + { + $config = new \Config\App(); + + return $config->CSRFTokenName; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('csrf_hash')) +{ + + /** + * Returns the current hash value for the CSRF protection. + * Can be used in Views when building hidden inputs manually, + * or used in javascript vars for API usage. + * + * @return string + */ + function csrf_hash() + { + $security = Services::security(null, true); + + return $security->getCSRFHash(); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('csrf_field')) +{ + + /** + * Generates a hidden input field for use within manually generated forms. + * + * @return string + */ + function csrf_field() + { + return ''; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('force_https')) +{ + + /** + * Used to force a page to be accessed in via HTTPS. + * Uses a standard redirect, plus will set the HSTS header + * for modern browsers that support, which gives best + * protection against man-in-the-middle attacks. + * + * @see https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security + * + * @param int $duration How long should the SSL header be set for? (in seconds) + * Defaults to 1 year. + * @param RequestInterface $request + * @param ResponseInterface $response + */ + function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null) + { + if (is_null($request)) + { + $request = Services::request(null, true); + } + if (is_null($response)) + { + $response = Services::response(null, true); + } + + if ($request->isSecure()) + { + return; + } + + // If the session library is loaded, we should regenerate + // the session ID for safety sake. + if (class_exists('Session', false)) + { + Services::session(null, true) + ->regenerate(); + } + + $uri = $request->uri; + $uri->setScheme('https'); + + $uri = \CodeIgniter\HTTP\URI::createURIString( + $uri->getScheme(), $uri->getAuthority(true), $uri->getPath(), // Absolute URIs should use a "/" for an empty path + $uri->getQuery(), $uri->getFragment() + ); + + // Set an HSTS header + $response->setHeader('Strict-Transport-Security', 'max-age=' . $duration); + $response->redirect($uri); + exit(); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('redirect')) +{ + + /** + * Convenience method that works with the current global $request and + * $router instances to redirect using named/reverse-routed routes + * to determine the URL to go to. If nothing is found, will treat + * as a traditional redirect and pass the string in, letting + * $response->redirect() determine the correct method and code. + * + * If more control is needed, you must use $response->redirect explicitly. + * + * @param string $uri + * + * @return \CodeIgniter\HTTP\RedirectResponse + */ + function redirect(string $uri=null) + { + $response = Services::redirectResponse(null, true); + + if (! empty($uri)) + { + return $response->to($uri); + } + + return $response; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('stringify_attributes')) +{ + + /** + * Stringify attributes for use in HTML tags. + * + * Helper function used to convert a string, array, or object + * of attributes to a string. + * + * @param mixed $attributes string, array, object + * @param bool $js + * + * @return string + */ + function stringify_attributes($attributes, $js = false): string + { + $atts = ''; + + if (empty($attributes)) + { + return $atts; + } + + if (is_string($attributes)) + { + return ' ' . $attributes; + } + + $attributes = (array) $attributes; + + foreach ($attributes as $key => $val) + { + $atts .= ($js) ? $key . '=' . esc($val, 'js') . ',' : ' ' . $key . '="' . esc($val, 'attr') . '"'; + } + + return rtrim($atts, ','); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('is_really_writable')) +{ + + /** + * Tests for file writability + * + * is_writable() returns TRUE on Windows servers when you really can't write to + * the file, based on the read-only attribute. is_writable() is also unreliable + * on Unix servers if safe_mode is on. + * + * @link https://bugs.php.net/bug.php?id=54709 + * + * @param string $file + * + * @return bool + */ + function is_really_writable($file) + { + // If we're on a Unix server with safe_mode off we call is_writable + if (DIRECTORY_SEPARATOR === '/' || ! ini_get('safe_mode')) + { + return is_writable($file); + } + + /* For Windows servers and safe_mode "on" installations we'll actually + * write a file then read it. Bah... + */ + if (is_dir($file)) + { + $file = rtrim($file, '/') . '/' . bin2hex(random_bytes(16)); + if (($fp = @fopen($file, 'ab')) === false) + { + return false; + } + + fclose($fp); + @chmod($file, 0777); + @unlink($file); + + return true; + } + elseif ( ! is_file($file) OR ( $fp = @fopen($file, 'ab')) === false) + { + return false; + } + + fclose($fp); + + return true; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('slash_item')) +{ + + //Unlike CI3, this function is placed here because + //it's not a config, or part of a config. + /** + * Fetch a config file item with slash appended (if not empty) + * + * @param string $item Config item name + * + * @return string|null The configuration item or NULL if + * the item doesn't exist + */ + function slash_item($item) + { + $config = new \Config\App(); + $configItem = $config->{$item}; + + if ( ! isset($configItem) || empty(trim($configItem))) + { + return $configItem; + } + + return rtrim($configItem, '/') . '/'; + } + +} +//-------------------------------------------------------------------- + +if ( ! function_exists('function_usable')) +{ + + /** + * Function usable + * + * Executes a function_exists() check, and if the Suhosin PHP + * extension is loaded - checks whether the function that is + * checked might be disabled in there as well. + * + * This is useful as function_exists() will return FALSE for + * functions disabled via the *disable_functions* php.ini + * setting, but not for *suhosin.executor.func.blacklist* and + * *suhosin.executor.disable_eval*. These settings will just + * terminate script execution if a disabled function is executed. + * + * The above described behavior turned out to be a bug in Suhosin, + * but even though a fix was commited for 0.9.34 on 2012-02-12, + * that version is yet to be released. This function will therefore + * be just temporary, but would probably be kept for a few years. + * + * @link http://www.hardened-php.net/suhosin/ + * @param string $function_name Function to check for + * @return bool TRUE if the function exists and is safe to call, + * FALSE otherwise. + */ + function function_usable($function_name) + { + static $_suhosin_func_blacklist; + + if (function_exists($function_name)) + { + if ( ! isset($_suhosin_func_blacklist)) + { + $_suhosin_func_blacklist = extension_loaded('suhosin') ? explode(',', trim(ini_get('suhosin.executor.func.blacklist'))) : []; + } + + return ! in_array($function_name, $_suhosin_func_blacklist, TRUE); + } + + return FALSE; + } + +} + +//-------------------------------------------------------------------- + +if (! function_exists('dd')) +{ + /** + * Prints a Kint debug report and exits. + * + * @param array ...$vars + */ + function dd(...$vars) + { + Kint::dump(...$vars); + exit; + } +} diff --git a/system/ComposerScripts.php b/system/ComposerScripts.php new file mode 100644 index 0000000..6ef50ea --- /dev/null +++ b/system/ComposerScripts.php @@ -0,0 +1,206 @@ +getFileName(); + } + + //-------------------------------------------------------------------- + + /** + * A recursive remove directory method. + * + * @param $dir + */ + protected static function removeDir($dir) + { + if (is_dir($dir)) + { + $objects = scandir($dir); + foreach ($objects as $object) + { + if ($object != "." && $object != "..") + { + if (filetype($dir."/".$object) == "dir") + { + static::removeDir($dir."/".$object); + } + else + { + unlink($dir."/".$object); + } + } + } + reset($objects); + rmdir($dir); + } + } + + /** + * Moves the Zend Escaper files into our base repo so that it's + * available for packaged releases where the users don't user Composer. + */ + public static function moveEscaper() + { + if (class_exists('\\Zend\\Escaper\\Escaper') && file_exists(self::getClassFilePath('\\Zend\\Escaper\\Escaper'))) + { + $base = static::$basePath.'ZendEscaper'; + + foreach ([$base, $base.'/Exception'] as $path) + { + if (! is_dir($path)) + { + mkdir($path, 0755); + } + } + + $files = [ + self::getClassFilePath('\\Zend\\Escaper\\Exception\\ExceptionInterface') => $base.'/Exception/ExceptionInterface.php', + self::getClassFilePath('\\Zend\\Escaper\\Exception\\InvalidArgumentException') => $base.'/Exception/InvalidArgumentException.php', + self::getClassFilePath('\\Zend\\Escaper\\Exception\\RuntimeException') => $base.'/Exception/RuntimeException.php', + self::getClassFilePath('\\Zend\\Escaper\\Escaper') => $base.'/Escaper.php', + ]; + + foreach ($files as $source => $dest) + { + if (! self::moveFile($source, $dest)) + { + die('Error moving: '.$source); + } + } + } + } + + //-------------------------------------------------------------------- + + /** + * Moves the Kint file into our base repo so that it's + * available for packaged releases where the users don't user Composer. + */ + public static function moveKint() + { + $filename = 'vendor/kint-php/kint/build/kint-aante-light.php'; + + if (file_exists($filename)) + { + $base = static::$basePath.'Kint'; + + // Remove the contents of the previous Kint folder, if any. + if (is_dir($base)) + { + static::removeDir($base); + } + + // Create Kint if it doesn't exist already + if (! is_dir($base)) + { + mkdir($base, 0755); + } + + if (! self::moveFile($filename, $base.'/kint.php')) + { + die('Error moving: '.$filename); + } + } + } +} diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php new file mode 100644 index 0000000..016ed5e --- /dev/null +++ b/system/Config/AutoloadConfig.php @@ -0,0 +1,193 @@ + SYSPATH + * `]; + */ + $this->psr4 = [ + 'CodeIgniter' => realpath(BASEPATH) + ]; + + if (isset($_SERVER['CI_ENVIRONMENT']) && $_SERVER['CI_ENVIRONMENT'] === 'testing') + { + $this->psr4['Tests\Support'] = BASEPATH . '../tests/_support/'; + } + + /** + * ------------------------------------------------------------------- + * Class Map + * ------------------------------------------------------------------- + * The class map provides a map of class names and their exact + * location on the drive. Classes loaded in this manner will have + * slightly faster performance because they will not have to be + * searched for within one or more directories as they would if they + * were being autoloaded through a namespace. + * + * Prototype: + * + * $Config['classmap'] = [ + * 'MyClass' => '/path/to/class/file.php' + * ]; + */ + $this->classmap = [ + 'CodeIgniter\CodeIgniter' => BASEPATH . 'CodeIgniter.php', + 'CodeIgniter\CLI\CLI' => BASEPATH . 'CLI/CLI.php', + 'CodeIgniter\Loader' => BASEPATH . 'Loader.php', + 'CodeIgniter\Cache\CacheFactory' => BASEPATH . 'Cache/CacheFactory.php', + 'CodeIgniter\Cache\CacheInterface' => BASEPATH . 'Cache/CacheInterface.php', + 'CodeIgniter\Cache\Handlers\DummyHandler' => BASEPATH . 'Cache/Handlers/DummyHandler.php', + 'CodeIgniter\Cache\Handlers\FileHandler' => BASEPATH . 'Cache/Handlers/FileHandler.php', + 'CodeIgniter\Cache\Handlers\MemcachedHandler' => BASEPATH . 'Cache/Handlers/MemcachedHandler.php', + 'CodeIgniter\Cache\Handlers\PredisHandler' => BASEPATH . 'Cache/Handlers/PredisHandler.php', + 'CodeIgniter\Cache\Handlers\RedisHandler' => BASEPATH . 'Cache/Handlers/RedisHandler.php', + 'CodeIgniter\Cache\Handlers\WincacheHandler' => BASEPATH . 'Cache/Handlers/WincacheHandler.php', + 'CodeIgniter\Controller' => BASEPATH . 'Controller.php', + 'CodeIgniter\Config\AutoloadConfig' => BASEPATH . 'Config/Autoload.php', + 'CodeIgniter\Config\BaseConfig' => BASEPATH . 'Config/BaseConfig.php', + 'CodeIgniter\Config\Database' => BASEPATH . 'Config/Database.php', + 'CodeIgniter\Config\Database\Connection' => BASEPATH . 'Config/Database/Connection.php', + 'CodeIgniter\Config\Database\Connection\MySQLi' => BASEPATH . 'Config/Database/Connection/MySQLi.php', + 'CodeIgniter\Config\DotEnv' => BASEPATH . 'Config/DotEnv.php', + 'CodeIgniter\Database\BaseBuilder' => BASEPATH . 'Database/BaseBuilder.php', + 'CodeIgniter\Database\BaseConnection' => BASEPATH . 'Database/BaseConnection.php', + 'CodeIgniter\Database\BaseResult' => BASEPATH . 'Database/BaseResult.php', + 'CodeIgniter\Database\Config' => BASEPATH . 'Database/Config.php', + 'CodeIgniter\Database\ConnectionInterface' => BASEPATH . 'Database/ConnectionInterface.php', + 'CodeIgniter\Database\Database' => BASEPATH . 'Database/Database.php', + 'CodeIgniter\Database\Query' => BASEPATH . 'Database/Query.php', + 'CodeIgniter\Database\QueryInterface' => BASEPATH . 'Database/QueryInterface.php', + 'CodeIgniter\Database\ResultInterface' => BASEPATH . 'Database/ResultInterface.php', + 'CodeIgniter\Database\Migration' => BASEPATH . 'Database/Migration.php', + 'CodeIgniter\Database\MigrationRunner' => BASEPATH . 'Database/MigrationRunner.php', + 'CodeIgniter\Debug\Exceptions' => BASEPATH . 'Debug/Exceptions.php', + 'CodeIgniter\Debug\Timer' => BASEPATH . 'Debug/Timer.php', + 'CodeIgniter\Debug\Iterator' => BASEPATH . 'Debug/Iterator.php', + 'CodeIgniter\Encryption\Encryption' => BASEPATH . 'Encryption/Encryption.php', + 'CodeIgniter\Encryption\EncrypterInterface' => BASEPATH . 'Encryption/EncrypterInterface.php', + 'CodeIgniter\Encryption\Handlers\BaseHandler' => BASEPATH . 'Encryption/Handlers/BaseHandler.php', + 'CodeIgniter\Encryption\Handlers\OpenSSLHandler' => BASEPATH . 'Encryption/Handlers/OpenSSLHandler.php', + 'CodeIgniter\Events\Events' => BASEPATH . 'Events/Events.php', + 'CodeIgniter\HTTP\CLIRequest' => BASEPATH . 'HTTP/CLIRequest.php', + 'CodeIgniter\HTTP\ContentSecurityPolicy' => BASEPATH . 'HTTP/ContentSecurityPolicy.php', + 'CodeIgniter\HTTP\CURLRequest' => BASEPATH . 'HTTP/CURLRequest.php', + 'CodeIgniter\HTTP\IncomingRequest' => BASEPATH . 'HTTP/IncomingRequest.php', + 'CodeIgniter\HTTP\Message' => BASEPATH . 'HTTP/Message.php', + 'CodeIgniter\HTTP\Negotiate' => BASEPATH . 'HTTP/Negotiate.php', + 'CodeIgniter\HTTP\Request' => BASEPATH . 'HTTP/Request.php', + 'CodeIgniter\HTTP\RequestInterface' => BASEPATH . 'HTTP/RequestInterface.php', + 'CodeIgniter\HTTP\Response' => BASEPATH . 'HTTP/Response.php', + 'CodeIgniter\HTTP\ResponseInterface' => BASEPATH . 'HTTP/ResponseInterface.php', + 'CodeIgniter\HTTP\URI' => BASEPATH . 'HTTP/URI.php', + 'CodeIgniter\Log\Logger' => BASEPATH . 'Log/Logger.php', + 'Psr\Log\LoggerAwareInterface' => BASEPATH . 'ThirdParty/PSR/Log/LoggerAwareInterface.php', + 'Psr\Log\LoggerAwareTrait' => BASEPATH . 'ThirdParty/PSR/Log/LoggerAwareTrait.php', + 'Psr\Log\LoggerInterface' => BASEPATH . 'ThirdParty/PSR/Log/LoggerInterface.php', + 'Psr\Log\LogLevel' => BASEPATH . 'ThirdParty/PSR/Log/LogLevel.php', + 'CodeIgniter\Log\Handlers\BaseHandler' => BASEPATH . 'Log/Handlers/BaseHandler.php', + 'CodeIgniter\Log\Handlers\ChromeLoggerHandler' => BASEPATH . 'Log/Handlers/ChromeLoggerHandler.php', + 'CodeIgniter\Log\Handlers\FileHandler' => BASEPATH . 'Log/Handlers/FileHandler.php', + 'CodeIgniter\Log\Handlers\HandlerInterface' => BASEPATH . 'Log/Handlers/HandlerInterface.php', + 'CodeIgniter\Router\RouteCollection' => BASEPATH . 'Router/RouteCollection.php', + 'CodeIgniter\Router\RouteCollectionInterface' => BASEPATH . 'Router/RouteCollectionInterface.php', + 'CodeIgniter\Router\Router' => BASEPATH . 'Router/Router.php', + 'CodeIgniter\Router\RouterInterface' => BASEPATH . 'Router/RouterInterface.php', + 'CodeIgniter\Security\Security' => BASEPATH . 'Security/Security.php', + 'CodeIgniter\Session\Session' => BASEPATH . 'Session/Session.php', + 'CodeIgniter\Session\SessionInterface' => BASEPATH . 'Session/SessionInterface.php', + 'CodeIgniter\Session\Handlers\BaseHandler' => BASEPATH . 'Session/Handlers/BaseHandler.php', + 'CodeIgniter\Session\Handlers\FileHandler' => BASEPATH . 'Session/Handlers/FileHandler.php', + 'CodeIgniter\Session\Handlers\MemcachedHandler' => BASEPATH . 'Session/Handlers/MemcachedHandler.php', + 'CodeIgniter\Session\Handlers\RedisHandler' => BASEPATH . 'Session/Handlers/RedisHandler.php', + 'CodeIgniter\View\RendererInterface' => BASEPATH . 'View/RendererInterface.php', + 'CodeIgniter\View\View' => BASEPATH . 'View/View.php', + 'CodeIgniter\View\Parser' => BASEPATH . 'View/Parser.php', + 'CodeIgniter\View\Cell' => BASEPATH . 'View/Cell.php', + 'Zend\Escaper\Escaper' => BASEPATH . 'ThirdParty/ZendEscaper/Escaper.php', + 'CodeIgniter\Log\TestLogger' => BASEPATH . '../tests/_support/Log/TestLogger.php', + 'CIDatabaseTestCase' => BASEPATH . '../tests/_support/CIDatabaseTestCase.php' + ]; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Config/BaseConfig.php b/system/Config/BaseConfig.php new file mode 100644 index 0000000..3ec1c1e --- /dev/null +++ b/system/Config/BaseConfig.php @@ -0,0 +1,187 @@ +$property)) + { + foreach ($this->$property as $key => $val) + { + if ($value = $this->getEnvValue("{$property}.{$key}", $prefix, $shortPrefix)) + { + if (is_null($value)) + continue; + + if ($value === 'false') + $value = false; + elseif ($value === 'true') + $value = true; + + $this->$property[$key] = $value; + } + } + } + else + { + if (($value = $this->getEnvValue($property, $prefix, $shortPrefix)) !== false) + { + if (is_null($value)) + continue; + + if ($value === 'false') + $value = false; + elseif ($value === 'true') + $value = true; + + $this->$property = $value; + } + } + } + + $this->registerProperties(); + } + + //-------------------------------------------------------------------- + + /** + * Retrieve an environment-specific configuration setting + * + * @param string $property + * @param string $prefix + * @param string $shortPrefix + * + * @return mixed + */ + protected function getEnvValue(string $property, string $prefix, string $shortPrefix) + { + $shortPrefix = ltrim( $shortPrefix, '\\' ); + + switch (true) { + case array_key_exists( "{$shortPrefix}.{$property}", $_ENV ): + return $_ENV["{$shortPrefix}.{$property}"]; + break; + case array_key_exists( "{$shortPrefix}.{$property}", $_SERVER ): + return $_SERVER["{$shortPrefix}.{$property}"]; + break; + case array_key_exists( "{$prefix}.{$property}", $_ENV ): + return $_ENV["{$prefix}.{$property}"]; + break; + case array_key_exists( "{$prefix}.{$property}", $_SERVER ): + return $_SERVER["{$prefix}.{$property}"]; + break; + default: + $value = getenv( $property ); + return $value === false ? null : $value; + } + } + + //-------------------------------------------------------------------- + + /** + * Provides external libraries a simple way to register one or more + * options into a config file. + */ + protected function registerProperties() + { + if (empty($this->registrars)) + return; + + $shortName = (new \ReflectionClass($this))->getShortName(); + + // Check the registrar class for a method named after this class' shortName + foreach ($this->registrars as $callable) + { + // ignore non-applicable registrars + if ( ! method_exists($callable, $shortName)) + continue; + + $properties = $callable::$shortName(); + + if ( ! is_array($properties)) + { + throw new \RuntimeException('Registrars must return an array of properties and their values.'); + } + + foreach ($properties as $property => $value) + { + if (is_array($this->$property) && is_array($value)) + { + $this->$property = array_merge($this->$property, $value); + } + else + { + $this->$property = $value; + } + } + } + } + + //-------------------------------------------------------------------- +} diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php new file mode 100644 index 0000000..46081fc --- /dev/null +++ b/system/Config/DotEnv.php @@ -0,0 +1,299 @@ +path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file; + } + + //-------------------------------------------------------------------- + + /** + * The main entry point, will load the .env file and process it + * so that we end up with all settings in the PHP environment vars + * (i.e. getenv(), $_ENV, and $_SERVER) + * + * @return bool + */ + public function load() + { + // We don't want to enforce the presence of a .env file, + // they should be optional. + if ( ! is_file($this->path)) + { + return false; + } + + // Ensure file is readable + if ( ! is_readable($this->path)) + { + throw new \InvalidArgumentException("The .env file is not readable: {$this->path}"); + } + + $lines = file($this->path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + foreach ($lines as $line) + { + // Is it a comment? + if (strpos(trim($line), '#') === 0) + { + continue; + } + + // If there is an equal sign, then we know we + // are assigning a variable. + if (strpos($line, '=') !== false) + { + $this->setVariable($line); + } + } + + } + + //-------------------------------------------------------------------- + + /** + * Sets the variable into the environment. Will parse the string + * first to look for {name}={value} pattern, ensure that nested + * variables are handled, and strip it of single and double quotes. + * + * @param string $name + * @param string $value + */ + protected function setVariable(string $name, string $value = '') + { + list($name, $value) = $this->normaliseVariable($name, $value); + + if ( ! getenv($name, true)) + putenv("$name=$value"); + if (empty($_ENV[$name])) + $_ENV[$name] = $value; + if (empty($_SERVER[$name])) + $_SERVER[$name] = $value; + } + + //-------------------------------------------------------------------- + + /** + * Parses for assignment, cleans the $name and $value, and ensures + * that nested variables are handled. + * + * @param string $name + * @param string $value + * + * @return array + */ + public function normaliseVariable(string $name, string $value = ''): array + { + // Split our compound string into it's parts. + if (strpos($name, '=') !== false) + { + list($name, $value) = explode('=', $name, 2); + } + + $name = trim($name); + $value = trim($value); + + // Sanitize the name + $name = str_replace(['export', '\'', '"'], '', $name); + + // Sanitize the value + $value = $this->sanitizeValue($value); + + $value = $this->resolveNestedVariables($value); + + return [$name, $value]; + } + + //-------------------------------------------------------------------- + + /** + * Strips quotes from the environment variable value. + * + * This was borrowed from the excellent phpdotenv with very few changes. + * https://github.com/vlucas/phpdotenv + * + * @param string $value + * + * @return string + * @throws \InvalidArgumentException + */ + protected function sanitizeValue(string $value): string + { + if ( ! $value) + { + return $value; + } + + // Does it begin with a quote? + if (strpbrk($value[0], '"\'') !== false) + { + // value starts with a quote + $quote = $value[0]; + $regexPattern = sprintf( + '/^ + %1$s # match a quote at the start of the value + ( # capturing sub-pattern used + (?: # we do not need to capture this + [^%1$s\\\\] # any character other than a quote or backslash + |\\\\\\\\ # or two backslashes together + |\\\\%1$s # or an escaped quote e.g \" + )* # as many characters that match the previous rules + ) # end of the capturing sub-pattern + %1$s # and the closing quote + .*$ # and discard any string after the closing quote + /mx', $quote + ); + $value = preg_replace($regexPattern, '$1', $value); + $value = str_replace("\\$quote", $quote, $value); + $value = str_replace('\\\\', '\\', $value); + } + else + { + $parts = explode(' #', $value, 2); + + $value = trim($parts[0]); + + // Unquoted values cannot contain whitespace + if (preg_match('/\s+/', $value) > 0) + { + throw new \InvalidArgumentException('.env values containing spaces must be surrounded by quotes.'); + } + } + + return $value; + } + + //-------------------------------------------------------------------- + + /** + * Resolve the nested variables. + * + * Look for ${varname} patterns in the variable value and replace with an existing + * environment variable. + * + * This was borrowed from the excellent phpdotenv with very few changes. + * https://github.com/vlucas/phpdotenv + * + * @param $value + * + * @return string + */ + protected function resolveNestedVariables(string $value): string + { + if (strpos($value, '$') !== false) + { + $loader = $this; + + $value = preg_replace_callback( + '/\${([a-zA-Z0-9_]+)}/', function ($matchedPatterns) use ($loader) { + $nestedVariable = $loader->getVariable($matchedPatterns[1]); + + if (is_null($nestedVariable)) + { + return $matchedPatterns[0]; + } + else + { + return $nestedVariable; + } + }, $value + ); + } + + return $value; + } + + //-------------------------------------------------------------------- + + /** + * Search the different places for environment variables and return first value found. + * + * This was borrowed from the excellent phpdotenv with very few changes. + * https://github.com/vlucas/phpdotenv + * + * @param string $name + * + * @return string|null + */ + protected function getVariable(string $name) + { + switch (true) + { + case array_key_exists($name, $_ENV): + return $_ENV[$name]; + break; + case array_key_exists($name, $_SERVER): + return $_SERVER[$name]; + break; + default: + $value = getenv($name); + + // switch getenv default to null + return $value === false ? null : $value; + } + } + + //-------------------------------------------------------------------- +} diff --git a/system/Config/ForeignCharacters.php b/system/Config/ForeignCharacters.php new file mode 100644 index 0000000..1d58a77 --- /dev/null +++ b/system/Config/ForeignCharacters.php @@ -0,0 +1,141 @@ + 'ae', + '/ö|Å“/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ãœ/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Ã|Â|Ã|Ä|Ã…|Ǻ|Ä€|Ä‚|Ä„|Ç|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|Ð/' => 'A', + '/à|á|â|ã|Ã¥|Ç»|Ä|ă|Ä…|ÇŽ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a', + '/Б/' => 'B', + '/б/' => 'b', + '/Ç|Ć|Ĉ|ÄŠ|ÄŒ/' => 'C', + '/ç|ć|ĉ|Ä‹|Ä/' => 'c', + '/Д/' => 'D', + '/д/' => 'd', + '/Ã|ÄŽ|Ä|Δ/' => 'Dj', + '/ð|Ä|Ä‘|δ/' => 'dj', + '/È|É|Ê|Ë|Ä’|Ä”|Ä–|Ę|Äš|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E', + '/è|é|ê|ë|Ä“|Ä•|Ä—|Ä™|Ä›|έ|ε|ẽ|ẻ|ẹ|á»|ế|á»…|ể|ệ|е|Ñ/' => 'e', + '/Ф/' => 'F', + '/Ñ„/' => 'f', + '/Äœ|Äž|Ä |Ä¢|Γ|Г|Ò/' => 'G', + '/Ä|ÄŸ|Ä¡|Ä£|γ|г|Ò‘/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/Ä¥|ħ/' => 'h', + '/ÃŒ|Ã|ÃŽ|Ã|Ĩ|Ī|Ĭ|Ç|Ä®|Ä°|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I', + '/ì|í|î|ï|Ä©|Ä«|Ä­|Ç|į|ı|η|ή|ί|ι|ÏŠ|ỉ|ị|и|Ñ‹|Ñ—/' => 'i', + '/Ä´/' => 'J', + '/ĵ/' => 'j', + '/Ķ|Κ|К/' => 'K', + '/Ä·|κ|к/' => 'k', + '/Ĺ|Ä»|Ľ|Ä¿|Å|Λ|Л/' => 'L', + '/ĺ|ļ|ľ|Å€|Å‚|λ|л/' => 'l', + '/Ðœ/' => 'M', + '/м/' => 'm', + '/Ñ|Ń|Å…|Ň|Î|Ð/' => 'N', + '/ñ|Å„|ņ|ň|ʼn|ν|н/' => 'n', + '/Ã’|Ó|Ô|Õ|ÅŒ|ÅŽ|Ç‘|Å|Æ |Ø|Ǿ|Ο|ÎŒ|Ω|Î|Ỏ|Ọ|á»’|á»|á»–|á»”|Ộ|Ờ|Ớ|á» |Ở|Ợ|О/' => 'O', + '/ò|ó|ô|õ|Å|Å|Ç’|Å‘|Æ¡|ø|Ç¿|º|ο|ÏŒ|ω|ÏŽ|á»|á»|ồ|ố|á»—|ổ|á»™|á»|á»›|ỡ|ở|ợ|о/' => 'o', + '/П/' => 'P', + '/п/' => 'p', + '/Å”|Å–|Ř|Ρ|Р/' => 'R', + '/Å•|Å—|Å™|Ï|Ñ€/' => 'r', + '/Åš|Åœ|Åž|Ș|Å |Σ|С/' => 'S', + '/Å›|Å|ÅŸ|È™|Å¡|Å¿|σ|Ï‚|Ñ/' => 's', + '/Èš|Å¢|Ť|Ŧ|Ï„|Т/' => 'T', + '/È›|Å£|Å¥|ŧ|Ñ‚/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Å®|Å°|Ų|Ư|Ç“|Ç•|Ç—|Ç™|Ç›|Ũ|Ủ|Ụ|Ừ|Ứ|á»®|Ử|á»°|У/' => 'U', + '/ù|ú|û|Å©|Å«|Å­|ů|ű|ų|Æ°|Ç”|Ç–|ǘ|Çš|Çœ|Ï…|Ï|Ï‹|ủ|ụ|ừ|ứ|ữ|á»­|á»±|у/' => 'u', + '/Ƴ|ÉŽ|á»´|Ẏ|Ó²|Ó®|ÐŽ|Ã|Ÿ|Ŷ|Î¥|ÎŽ|Ϋ|Ỳ|Ỹ|Ỷ|á»´|Й/' => 'Y', + '/ẙ|Ê|Æ´|É|ỵ|áº|Ó³|Ó¯|Ñž|ý|ÿ|Å·|ỳ|ỹ|á»·|ỵ|й/' => 'y', + '/Ð’/' => 'V', + '/в/' => 'v', + '/Å´/' => 'W', + '/ŵ/' => 'w', + '/Ź|Å»|Ž|Ζ|З/' => 'Z', + '/ź|ż|ž|ζ|з/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/' => 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Å’/' => 'OE', + '/Æ’/' => 'f', + '/ξ/' => 'ks', + '/Ï€/' => 'p', + '/β/' => 'v', + '/μ/' => 'm', + '/ψ/' => 'ps', + '/Ð/' => 'Yo', + '/Ñ‘/' => 'yo', + '/Є/' => 'Ye', + '/Ñ”/' => 'ye', + '/Ї/' => 'Yi', + '/Ж/' => 'Zh', + '/ж/' => 'zh', + '/Ð¥/' => 'Kh', + '/Ñ…/' => 'kh', + '/Ц/' => 'Ts', + '/ц/' => 'ts', + '/Ч/' => 'Ch', + '/ч/' => 'ch', + '/Ш/' => 'Sh', + '/ш/' => 'sh', + '/Щ/' => 'Shch', + '/щ/' => 'shch', + '/Ъ|ÑŠ|Ь|ÑŒ/' => '', + '/Ю/' => 'Yu', + '/ÑŽ/' => 'yu', + '/Я/' => 'Ya', + '/Ñ/' => 'ya' + ]; + +} diff --git a/system/Config/Routes.php b/system/Config/Routes.php new file mode 100644 index 0000000..e6fb3f4 --- /dev/null +++ b/system/Config/Routes.php @@ -0,0 +1,53 @@ +cli('migrations/(:segment)/(:segment)', '\CodeIgniter\Commands\MigrationsCommand::$1/$2'); +$routes->cli('migrations/(:segment)', '\CodeIgniter\Commands\MigrationsCommand::$1'); +$routes->cli('migrations', '\CodeIgniter\Commands\MigrationsCommand::index'); + +// CLI Catchall - uses a _remap to +$routes->cli('ci(:any)', '\CodeIgniter\CLI\CommandRunner::index/$1'); + diff --git a/system/Config/Services.php b/system/Config/Services.php new file mode 100644 index 0000000..8a0f7d9 --- /dev/null +++ b/system/Config/Services.php @@ -0,0 +1,901 @@ +withFile($path)->rotate(90)->save(); + * + * @param string $handler + * @param mixed $config + * @param bool $getShared + * + * @return \CodeIgniter\Images\Handlers\BaseHandler + */ + public static function image(string $handler = null, $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('image', $handler, $config); + } + + if (empty($config)) + { + $config = new \Config\Images(); + } + + $handler = is_null($handler) ? $config->defaultHandler : $handler; + + $class = $config->handlers[$handler]; + + return new $class($config); + } + + //-------------------------------------------------------------------- + + /** + * The Iterator class provides a simple way of looping over a function + * and timing the results and memory usage. Used when debugging and + * optimizing applications. + * + * @param bool $getShared + * + * @return \CodeIgniter\Debug\Iterator + */ + public static function iterator($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('iterator'); + } + + return new \CodeIgniter\Debug\Iterator(); + } + + //-------------------------------------------------------------------- + + /** + * Responsible for loading the language string translations. + * + * @param string $locale + * @param bool $getShared + * + * @return \CodeIgniter\Language\Language + */ + public static function language(string $locale = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('language', $locale); + } + + $locale = ! empty($locale) ? $locale : self::request()->getLocale(); + + return new \CodeIgniter\Language\Language($locale); + } + + //-------------------------------------------------------------------- + + /** + * The file locator provides utility methods for looking for non-classes + * within namespaced folders, as well as convenience methods for + * loading 'helpers', and 'libraries'. + * + * @param bool $getShared + * + * @return \CodeIgniter\Autoloader\FileLocator + */ + public static function locator($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('locator'); + } + + return new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload()); + } + + //-------------------------------------------------------------------- + + /** + * The Logger class is a PSR-3 compatible Logging class that supports + * multiple handlers that process the actual logging. + * + * @param bool $getShared + * + * @return \CodeIgniter\Log\Logger + */ + public static function logger($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('logger'); + } + + return new \CodeIgniter\Log\Logger(new \Config\Logger()); + } + + //-------------------------------------------------------------------- + + + /** + * @param \CodeIgniter\Config\BaseConfig $config + * @param \CodeIgniter\Database\ConnectionInterface $db + * @param bool $getShared + * + * @return \CodeIgniter\Database\MigrationRunner + */ + public static function migrations(BaseConfig $config = null, ConnectionInterface $db = null, bool $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('migrations', $config, $db); + } + + $config = empty($config) ? new \Config\Migrations() : $config; + + return new MigrationRunner($config, $db); + } + + //-------------------------------------------------------------------- + + /** + * The Negotiate class provides the content negotiation features for + * working the request to determine correct language, encoding, charset, + * and more. + * + * @param \CodeIgniter\HTTP\RequestInterface $request + * @param bool $getShared + * + * @return \CodeIgniter\HTTP\Negotiate + */ + public static function negotiator(\CodeIgniter\HTTP\RequestInterface $request = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('negotiator', $request); + } + + if (is_null($request)) + { + $request = self::request(); + } + + return new \CodeIgniter\HTTP\Negotiate($request); + } + + //-------------------------------------------------------------------- + + + /** + * @param mixed $config + * @param \CodeIgniter\View\RendererInterface $view + * @param bool $getShared + * + * @return \CodeIgniter\Pager\Pager + */ + public static function pager($config = null, RendererInterface $view = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('pager', $config, $view); + } + + if (empty($config)) + { + $config = new \Config\Pager(); + } + + if ( ! $view instanceof RendererInterface) + { + $view = self::renderer(); + } + + return new \CodeIgniter\Pager\Pager($config, $view); + } + + //-------------------------------------------------------------------- + + /** + * The Parser is a simple template parser. + * + * @param string $viewPath + * @param mixed $config + * @param bool $getShared + * + * @return \CodeIgniter\View\Parser + */ + public static function parser($viewPath = APPPATH . 'Views/', $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('parser', $viewPath, $config); + } + + if (is_null($config)) + { + $config = new \Config\View(); + } + + return new \CodeIgniter\View\Parser($config, $viewPath, self::locator(true), CI_DEBUG, self::logger(true)); + } + + //-------------------------------------------------------------------- + + /** + * The Renderer class is the class that actually displays a file to the user. + * The default View class within CodeIgniter is intentionally simple, but this + * service could easily be replaced by a template engine if the user needed to. + * + * @param string $viewPath + * @param mixed $config + * @param bool $getShared + * + * @return \CodeIgniter\View\View + */ + public static function renderer($viewPath = APPPATH . 'Views/', $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('renderer', $viewPath, $config); + } + + if (is_null($config)) + { + $config = new \Config\View(); + } + + return new \CodeIgniter\View\View($config, $viewPath, self::locator(true), CI_DEBUG, self::logger(true)); + } + + //-------------------------------------------------------------------- + + /** + * The Request class models an HTTP request. + * + * @param \Config\App $config + * @param bool $getShared + * + * @return \CodeIgniter\HTTP\IncomingRequest + */ + public static function request(\Config\App $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('request', $config); + } + + if ( ! is_object($config)) + { + $config = new \Config\App(); + } + + return new \CodeIgniter\HTTP\IncomingRequest( + $config, new \CodeIgniter\HTTP\URI() + ); + } + + //-------------------------------------------------------------------- + + /** + * The Response class models an HTTP response. + * + * @param \Config\App $config + * @param bool $getShared + * + * @return \CodeIgniter\HTTP\Response + */ + public static function response(\Config\App $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('response', $config); + } + + if ( ! is_object($config)) + { + $config = new \Config\App(); + } + + return new \CodeIgniter\HTTP\Response($config); + } + + //-------------------------------------------------------------------- + + /** + * The Redirect class provides nice way of working with redirects. + * + * @param \Config\App $config + * @param bool $getShared + * + * @return \CodeIgniter\HTTP\Response + */ + public static function redirectResponse(\Config\App $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('redirectResponse', $config); + } + + if ( ! is_object($config)) + { + $config = new \Config\App(); + } + + return new \CodeIgniter\HTTP\RedirectResponse($config); + } + + //-------------------------------------------------------------------- + + /** + * The Routes service is a class that allows for easily building + * a collection of routes. + * + * @param bool $getShared + * + * @return \CodeIgniter\Router\RouteCollection + */ + public static function routes($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('routes'); + } + + return new \CodeIgniter\Router\RouteCollection(self::locator()); + } + + //-------------------------------------------------------------------- + + /** + * The Router class uses a RouteCollection's array of routes, and determines + * the correct Controller and Method to execute. + * + * @param \CodeIgniter\Router\RouteCollectionInterface $routes + * @param bool $getShared + * + * @return \CodeIgniter\Router\Router + */ + public static function router(\CodeIgniter\Router\RouteCollectionInterface $routes = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('router', $routes); + } + + if (empty($routes)) + { + $routes = self::routes(true); + } + + return new \CodeIgniter\Router\Router($routes); + } + + //-------------------------------------------------------------------- + + /** + * The Security class provides a few handy tools for keeping the site + * secure, most notably the CSRF protection tools. + * + * @param \Config\App $config + * @param bool $getShared + * + * @return \CodeIgniter\Security\Security + */ + public static function security(\Config\App $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('security', $config); + } + + if ( ! is_object($config)) + { + $config = new \Config\App(); + } + + return new \CodeIgniter\Security\Security($config); + } + + //-------------------------------------------------------------------- + + /** + * @param \Config\App $config + * @param bool $getShared + * + * @return \CodeIgniter\Session\Session + */ + public static function session(\Config\App $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('session', $config); + } + + if ( ! is_object($config)) + { + $config = new \Config\App(); + } + + $logger = self::logger(true); + + $driverName = $config->sessionDriver; + $driver = new $driverName($config); + $driver->setLogger($logger); + + $session = new \CodeIgniter\Session\Session($driver, $config); + $session->setLogger($logger); + + return $session; + } + + //-------------------------------------------------------------------- + + /** + * The Throttler class provides a simple method for implementing + * rate limiting in your applications. + * + * @param bool $getShared + * + * @return \CodeIgniter\Throttle\Throttler + */ + public static function throttler($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('throttler'); + } + + return new \CodeIgniter\Throttle\Throttler(self::cache()); + } + + //-------------------------------------------------------------------- + + /** + * The Timer class provides a simple way to Benchmark portions of your + * application. + * + * @param bool $getShared + * + * @return \CodeIgniter\Debug\Timer + */ + public static function timer($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('timer'); + } + + return new \CodeIgniter\Debug\Timer(); + } + + //-------------------------------------------------------------------- + + /** + * @param \Config\App $config + * @param bool $getShared + * + * @return \CodeIgniter\Debug\Toolbar + */ + public static function toolbar(\Config\App $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('toolbar', $config); + } + + if ( ! is_object($config)) + { + $config = new \Config\App(); + } + + return new \CodeIgniter\Debug\Toolbar($config); + } + + //-------------------------------------------------------------------- + + /** + * The URI class provides a way to model and manipulate URIs. + * + * @param string $uri + * @param bool $getShared + * + * @return \CodeIgniter\HTTP\URI + */ + public static function uri($uri = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('uri', $uri); + } + + return new \CodeIgniter\HTTP\URI($uri); + } + + //-------------------------------------------------------------------- + + /** + * The Validation class provides tools for validating input data. + * + * @param \Config\Validation $config + * @param bool $getShared + * + * @return \CodeIgniter\Validation\Validation + */ + public static function validation(\Config\Validation $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('validation', $config); + } + + if (is_null($config)) + { + $config = new \Config\Validation(); + } + + return new \CodeIgniter\Validation\Validation($config, self::renderer()); + } + + //-------------------------------------------------------------------- + + /** + * View cells are intended to let you insert HTML into view + * that has been generated by any callable in the system. + * + * @param bool $getShared + * + * @return \CodeIgniter\View\Cell + */ + public static function viewcell($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('viewcell'); + } + + return new \CodeIgniter\View\Cell(self::cache()); + } + + //-------------------------------------------------------------------- + + /** + * The Typography class provides a way to format text in semantically relevant ways. + * + * @param bool $getShared + * + * @return \CodeIgniter\Typography\Typography + */ + public static function typography($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('typography'); + } + + return new \CodeIgniter\Typography\Typography(); + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + // Utility Methods - DO NOT EDIT + //-------------------------------------------------------------------- + + /** + * Returns a shared instance of any of the class' services. + * + * $key must be a name matching a service. + * + * @param string $key + * @param array ...$params + * + * @return mixed + */ + protected static function getSharedInstance(string $key, ...$params) + { + // Returns mock if exists + if (isset(static::$mocks[$key])) + { + return static::$mocks[$key]; + } + + if ( ! isset(static::$instances[$key])) + { + // Make sure $getShared is false + array_push($params, false); + + static::$instances[$key] = static::$key(...$params); + } + + return static::$instances[$key]; + } + + //-------------------------------------------------------------------- + + /** + * Provides the ability to perform case-insensitive calling of service + * names. + * + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public static function __callStatic(string $name, array $arguments) + { + $name = strtolower($name); + + if (method_exists(__CLASS__, $name)) + { + return Services::$name(...$arguments); + } + } + + //-------------------------------------------------------------------- + + /** + * Reset shared instances and mocks for testing. + */ + public static function reset() + { + static::$mocks = []; + + static::$instances = []; + } + + //-------------------------------------------------------------------- + + /** + * Inject mock object for testing. + * + * @param string $name + * @param $mock + */ + public static function injectMock(string $name, $mock) + { + $name = strtolower($name); + static::$mocks[$name] = $mock; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Config/View.php b/system/Config/View.php new file mode 100644 index 0000000..f6be94f --- /dev/null +++ b/system/Config/View.php @@ -0,0 +1,79 @@ + '\abs', + 'capitalize' => '\CodeIgniter\View\Filters::capitalize', + 'date' => '\CodeIgniter\View\Filters::date', + 'date_modify' => '\CodeIgniter\View\Filters::date_modify', + 'default' => '\CodeIgniter\View\Filters::default', + 'esc' => '\CodeIgniter\View\Filters::esc', + 'excerpt' => '\CodeIgniter\View\Filters::excerpt', + 'highlight' => '\CodeIgniter\View\Filters::highlight', + 'highlight_code' => '\CodeIgniter\View\Filters::highlight_code', + 'limit_words' => '\CodeIgniter\View\Filters::limit_words', + 'limit_chars' => '\CodeIgniter\View\Filters::limit_chars', + 'lower' => '\strtolower', + 'nl2br' => '\CodeIgniter\View\Filters::nl2br', + 'number_format' => '\number_format', + 'prose' => '\CodeIgniter\View\Filters::prose', + 'round' => '\CodeIgniter\View\Filters::round', + 'strip_tags' => '\strip_tags', + 'title' => '\CodeIgniter\View\Filters::title', + 'upper' => '\strtoupper', + ]; + protected $corePlugins = [ + 'current_url' => '\CodeIgniter\View\Plugins::currentURL', + 'previous_url' => '\CodeIgniter\View\Plugins::previousURL', + 'mailto' => '\CodeIgniter\View\Plugins::mailto', + 'safe_mailto' => '\CodeIgniter\View\Plugins::safeMailto', + 'lang' => '\CodeIgniter\View\Plugins::lang', + 'validation_errors' => '\CodeIgniter\View\Plugins::validationErrors', + ]; + + public function __construct() + { + $this->filters = array_merge($this->filters, $this->coreFilters); + $this->plugins = array_merge($this->plugins, $this->corePlugins); + + parent::__construct(); + } + +} diff --git a/system/Controller.php b/system/Controller.php new file mode 100644 index 0000000..3f8a2b7 --- /dev/null +++ b/system/Controller.php @@ -0,0 +1,193 @@ +request = $request; + + $this->response = $response; + + $this->logger = is_null($logger) ? Services::logger(true) : $logger; + + $this->logger->info('Controller "' . get_class($this) . '" loaded.'); + + if ($this->forceHTTPS > 0) + { + $this->forceHTTPS($this->forceHTTPS); + } + + $this->loadHelpers(); + } + + //-------------------------------------------------------------------- + + /** + * A convenience method to use when you need to ensure that a single + * method is reached only via HTTPS. If it isn't, then a redirect + * will happen back to this method and HSTS header will be sent + * to have modern browsers transform requests automatically. + * + * @param int $duration The number of seconds this link should be + * considered secure for. Only with HSTS header. + * Default value is 1 year. + */ + public function forceHTTPS(int $duration = 31536000) + { + force_https($duration, $this->request, $this->response); + } + + //-------------------------------------------------------------------- + + /** + * Provides a simple way to tie into the main CodeIgniter class + * and tell it how long to cache the current page for. + * + * @param int $time + */ + public function cachePage(int $time) + { + CodeIgniter::cache($time); + } + + //-------------------------------------------------------------------- + + /** + * Handles "auto-loading" helper files. + */ + protected function loadHelpers() + { + if (empty($this->helpers)) + return; + + foreach ($this->helpers as $helper) + { + helper($helper); + } + } + + //-------------------------------------------------------------------- + + /** + * A shortcut to performing validation on $_POST input. If validation + * is not successful, a $errors property will be set on this class. + * + * @param $rules + * @param array|null $messages + * + * @return bool + */ + public function validate($rules, array $messages = []): bool + { + $this->validator = Services::validation(); + + $success = $this->validator->withRequest($this->request) + ->setRules($rules, $messages) + ->run(); + + return $success; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php new file mode 100644 index 0000000..c616c39 --- /dev/null +++ b/system/Database/BaseBuilder.php @@ -0,0 +1,2849 @@ +db = $db; + + $this->from($tableName); + + if (! empty($options)) + { + foreach ($options as $key => $value) + { + $this->$key = $value; + } + } + } + + //-------------------------------------------------------------------- + + /** + * Returns an array of bind values and their + * named parameters for binding in the Query object later. + * + * @return array + */ + public function getBinds(): array + { + return $this->binds; + } + + //-------------------------------------------------------------------- + + /** + * Select + * + * Generates the SELECT portion of the query + * + * @param string|array $select + * @param mixed $escape + * + * @return BaseBuilder + */ + public function select($select = '*', $escape = null) + { + if (is_string($select)) + { + $select = explode(',', $select); + } + + // If the escape value was not set, we will base it on the global setting + is_bool($escape) || $escape = $this->db->protectIdentifiers; + + foreach ($select as $val) + { + $val = trim($val); + + if ($val !== '') + { + $this->QBSelect[] = $val; + $this->QBNoEscape[] = $escape; + } + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Select Max + * + * Generates a SELECT MAX(field) portion of a query + * + * @param string $select The field + * @param string $alias An alias + * + * @return BaseBuilder + */ + public function selectMax($select = '', $alias = '') + { + return $this->maxMinAvgSum($select, $alias, 'MAX'); + } + + //-------------------------------------------------------------------- + + /** + * Select Min + * + * Generates a SELECT MIN(field) portion of a query + * + * @param string $select The field + * @param string $alias An alias + * + * @return BaseBuilder + */ + public function selectMin($select = '', $alias = '') + { + return $this->maxMinAvgSum($select, $alias, 'MIN'); + } + + //-------------------------------------------------------------------- + + /** + * Select Average + * + * Generates a SELECT AVG(field) portion of a query + * + * @param string $select The field + * @param string $alias An alias + * + * @return BaseBuilder + */ + public function selectAvg($select = '', $alias = '') + { + return $this->maxMinAvgSum($select, $alias, 'AVG'); + } + + //-------------------------------------------------------------------- + + /** + * Select Sum + * + * Generates a SELECT SUM(field) portion of a query + * + * @param string $select The field + * @param string $alias An alias + * + * @return BaseBuilder + */ + public function selectSum($select = '', $alias = '') + { + return $this->maxMinAvgSum($select, $alias, 'SUM'); + } + + //-------------------------------------------------------------------- + + /** + * SELECT [MAX|MIN|AVG|SUM]() + * + * @used-by selectMax() + * @used-by selectMin() + * @used-by selectAvg() + * @used-by selectSum() + * + * @param string $select Field name + * @param string $alias + * @param string $type + * + * @return BaseBuilder + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + protected function maxMinAvgSum($select = '', $alias = '', $type = 'MAX') + { + if ( ! is_string($select) || $select === '') + { + throw new DatabaseException('The query you submitted is not valid.'); + } + + $type = strtoupper($type); + + if ( ! in_array($type, ['MAX', 'MIN', 'AVG', 'SUM'])) + { + throw new DatabaseException('Invalid function type: ' . $type); + } + + if ($alias === '') + { + $alias = $this->createAliasFromTable(trim($select)); + } + + $sql = $type . '(' . $this->db->protectIdentifiers(trim($select)) . ') AS ' . $this->db->escapeIdentifiers(trim($alias)); + + $this->QBSelect[] = $sql; + $this->QBNoEscape[] = null; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Determines the alias name based on the table + * + * @param string $item + * + * @return string + */ + protected function createAliasFromTable($item) + { + if (strpos($item, '.') !== false) + { + $item = explode('.', $item); + + return end($item); + } + + return $item; + } + + //-------------------------------------------------------------------- + + /** + * DISTINCT + * + * Sets a flag which tells the query string compiler to add DISTINCT + * + * @param bool $val + * + * @return BaseBuilder + */ + public function distinct($val = true) + { + $this->QBDistinct = is_bool($val) ? $val : true; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * From + * + * Generates the FROM portion of the query + * + * @param mixed $from can be a string or array + * @param bool $overwrite Should we remove the first table existing? + * + * @return BaseBuilder + */ + public function from($from, $overwrite = false) + { + if ($overwrite === true) + { + $this->QBFrom = []; + $this->QBAliasedTables = []; + } + + foreach ((array) $from as $val) + { + if (strpos($val, ',') !== false) + { + foreach (explode(',', $val) as $v) + { + $v = trim($v); + $this->trackAliases($v); + + $this->QBFrom[] = $v = $this->db->protectIdentifiers($v, true, null, false); + } + } + else + { + $val = trim($val); + + // Extract any aliases that might exist. We use this information + // in the protectIdentifiers to know whether to add a table prefix + $this->trackAliases($val); + + $this->QBFrom[] = $this->db->protectIdentifiers($val, true, null, false); + } + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * JOIN + * + * Generates the JOIN portion of the query + * + * @param string $table + * @param string $cond The join condition + * @param string $type The type of join + * @param string $escape Whether not to try to escape identifiers + * + * @return BaseBuilder + */ + public function join($table, $cond, $type = '', $escape = null) + { + if ($type !== '') + { + $type = strtoupper(trim($type)); + + if ( ! in_array($type, ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'], true)) + { + $type = ''; + } + else + { + $type .= ' '; + } + } + + // Extract any aliases that might exist. We use this information + // in the protectIdentifiers to know whether to add a table prefix + $this->trackAliases($table); + + is_bool($escape) || $escape = $this->db->protectIdentifiers; + + if ( ! $this->hasOperator($cond)) + { + $cond = ' USING (' . ($escape ? $this->db->escapeIdentifiers($cond) : $cond) . ')'; + } + elseif ($escape === false) + { + $cond = ' ON ' . $cond; + } + else + { + // Split multiple conditions + if (preg_match_all('/\sAND\s|\sOR\s/i', $cond, $joints, PREG_OFFSET_CAPTURE)) + { + $conditions = []; + $joints = $joints[0]; + array_unshift($joints, ['', 0]); + + for ($i = count($joints) - 1, $pos = strlen($cond); $i >= 0; $i -- ) + { + $joints[$i][1] += strlen($joints[$i][0]); // offset + $conditions[$i] = substr($cond, $joints[$i][1], $pos - $joints[$i][1]); + $pos = $joints[$i][1] - strlen($joints[$i][0]); + $joints[$i] = $joints[$i][0]; + } + } + else + { + $conditions = [$cond]; + $joints = ['']; + } + + $cond = ' ON '; + for ($i = 0, $c = count($conditions); $i < $c; $i ++ ) + { + $operator = $this->getOperator($conditions[$i]); + $cond .= $joints[$i]; + $cond .= preg_match("/(\(*)?([\[\]\w\.'-]+)" . preg_quote($operator) . "(.*)/i", $conditions[$i], $match) ? $match[1] . $this->db->protectIdentifiers($match[2]) . $operator . $this->db->protectIdentifiers($match[3]) : $conditions[$i]; + } + } + + // Do we want to escape the table name? + if ($escape === true) + { + $table = $this->db->protectIdentifiers($table, true, null, false); + } + + // Assemble the JOIN statement + $this->QBJoin[] = $join = $type . 'JOIN ' . $table . $cond; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * WHERE + * + * Generates the WHERE portion of the query. + * Separates multiple calls with 'AND'. + * + * @param mixed $key + * @param mixed $value + * @param bool $escape + * + * @return BaseBuilder + */ + public function where($key, $value = null, $escape = null) + { + return $this->whereHaving('QBWhere', $key, $value, 'AND ', $escape); + } + + //-------------------------------------------------------------------- + + /** + * OR WHERE + * + * Generates the WHERE portion of the query. + * Separates multiple calls with 'OR'. + * + * @param mixed $key + * @param mixed $value + * @param bool $escape + * + * @return BaseBuilder + */ + public function orWhere($key, $value = null, $escape = null) + { + return $this->whereHaving('QBWhere', $key, $value, 'OR ', $escape); + } + + //-------------------------------------------------------------------- + + /** + * WHERE, HAVING + * + * @used-by where() + * @used-by orWhere() + * @used-by having() + * @used-by orHaving() + * + * @param string $qb_key 'QBWhere' or 'QBHaving' + * @param mixed $key + * @param mixed $value + * @param string $type + * @param bool $escape + * + * @return BaseBuilder + */ + protected function whereHaving($qb_key, $key, $value = null, $type = 'AND ', $escape = null) + { + if ( ! is_array($key)) + { + $key = [$key => $value]; + } + + // If the escape value was not set will base it on the global setting + is_bool($escape) || $escape = $this->db->protectIdentifiers; + + foreach ($key as $k => $v) + { + $prefix = empty($this->$qb_key) ? $this->groupGetType('') : $this->groupGetType($type); + + if ($v !== null) + { + $op = $this->getOperator($k); + $k = trim(str_replace($op, '', $k)); + + $bind = $this->setBind($k, $v); + + if (empty($op)) + { + $k .= ' ='; + } + else + { + $k .= $op; + } + } + elseif ( ! $this->hasOperator($k)) + { + // value appears not to have been set, assign the test to IS NULL + $k .= ' IS NULL'; + } + elseif (preg_match('/\s*(!?=|<>|IS(?:\s+NOT)?)\s*$/i', $k, $match, PREG_OFFSET_CAPTURE)) + { + $k = substr($k, 0, $match[0][1]) . ($match[1][0] === '=' ? ' IS NULL' : ' IS NOT NULL'); + } + + $v = ! is_null($v) ? " :$bind:" : $v; + + $this->{$qb_key}[] = ['condition' => $prefix . $k . $v, 'escape' => $escape]; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * WHERE IN + * + * Generates a WHERE field IN('item', 'item') SQL query, + * joined with 'AND' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * + * @return BaseBuilder + */ + public function whereIn($key = null, $values = null, $escape = null) + { + return $this->_whereIn($key, $values, false, 'AND ', $escape); + } + + //-------------------------------------------------------------------- + + /** + * OR WHERE IN + * + * Generates a WHERE field IN('item', 'item') SQL query, + * joined with 'OR' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * + * @return BaseBuilder + */ + public function orWhereIn($key = null, $values = null, $escape = null) + { + return $this->_whereIn($key, $values, false, 'OR ', $escape); + } + + //-------------------------------------------------------------------- + + /** + * WHERE NOT IN + * + * Generates a WHERE field NOT IN('item', 'item') SQL query, + * joined with 'AND' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * + * @return BaseBuilder + */ + public function whereNotIn($key = null, $values = null, $escape = null) + { + return $this->_whereIn($key, $values, true, 'AND ', $escape); + } + + //-------------------------------------------------------------------- + + /** + * OR WHERE NOT IN + * + * Generates a WHERE field NOT IN('item', 'item') SQL query, + * joined with 'OR' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * + * @return BaseBuilder + */ + public function orWhereNotIn($key = null, $values = null, $escape = null) + { + return $this->_whereIn($key, $values, true, 'OR ', $escape); + } + + //-------------------------------------------------------------------- + + /** + * Internal WHERE IN + * + * @used-by WhereIn() + * @used-by orWhereIn() + * @used-by whereNotIn() + * @used-by orWhereNotIn() + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $not If the statement would be IN or NOT IN + * @param string $type + * @param bool $escape + * + * @return BaseBuilder + */ + protected function _whereIn($key = null, $values = null, $not = false, $type = 'AND ', $escape = null) + { + if ($key === null OR $values === null) + { + return $this; + } + + if ( ! is_array($values)) + { + $values = [$values]; + } + + is_bool($escape) || $escape = $this->db->protectIdentifiers; + + $ok = $key; + + if ($escape === true) + { + $key = $this->db->protectIdentifiers($key); + } + + $not = ($not) ? ' NOT' : ''; + + $where_in = array_values($values); + $this->binds[$ok] = $where_in; + + $prefix = empty($this->QBWhere) ? $this->groupGetType('') : $this->groupGetType($type); + + $where_in = [ + 'condition' => $prefix . $key . $not . " IN :{$ok}:", + 'escape' => false, + ]; + + $this->QBWhere[] = $where_in; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * LIKE + * + * Generates a %LIKE% portion of the query. + * Separates multiple calls with 'AND'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search + * + * @return BaseBuilder + */ + public function like($field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false) + { + return $this->_like($field, $match, 'AND ', $side, '', $escape, $insensitiveSearch); + } + + //-------------------------------------------------------------------- + + /** + * NOT LIKE + * + * Generates a NOT LIKE portion of the query. + * Separates multiple calls with 'AND'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search + * + * @return BaseBuilder + */ + public function notLike($field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false) + { + return $this->_like($field, $match, 'AND ', $side, 'NOT', $escape, $insensitiveSearch); + } + + //-------------------------------------------------------------------- + + /** + * OR LIKE + * + * Generates a %LIKE% portion of the query. + * Separates multiple calls with 'OR'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search + * + * @return BaseBuilder + */ + public function orLike($field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false) + { + return $this->_like($field, $match, 'OR ', $side, '', $escape, $insensitiveSearch); + } + + //-------------------------------------------------------------------- + + /** + * OR NOT LIKE + * + * Generates a NOT LIKE portion of the query. + * Separates multiple calls with 'OR'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search + * + * @return BaseBuilder + */ + public function orNotLike($field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false) + { + return $this->_like($field, $match, 'OR ', $side, 'NOT', $escape, $insensitiveSearch); + } + + //-------------------------------------------------------------------- + + /** + * Internal LIKE + * + * @used-by like() + * @used-by orLike() + * @used-by notLike() + * @used-by orNotLike() + * + * @param mixed $field + * @param string $match + * @param string $type + * @param string $side + * @param string $not + * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search + * + * @return BaseBuilder + */ + protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $escape = null, $insensitiveSearch = false) + { + if ( ! is_array($field)) + { + $field = [$field => $match]; + } + + $escape = is_bool($escape) ? $escape : $this->db->protectIdentifiers; + + // lowercase $side in case somebody writes e.g. 'BEFORE' instead of 'before' (doh) + $side = strtolower($side); + + foreach ($field as $k => $v) + { + $prefix = empty($this->QBWhere) ? $this->groupGetType('') : $this->groupGetType($type); + + if ($insensitiveSearch === true) + { + $v = strtolower($v); + } + + if ($side === 'none') + { + $bind = $this->setBind($k, $v); + } + elseif ($side === 'before') + { + $bind = $this->setBind($k, "%$v"); + } + elseif ($side === 'after') + { + $bind = $this->setBind($k, "$v%"); + } + else + { + $bind = $this->setBind($k, "%$v%"); + } + + $like_statement = $this->_like_statement($prefix, $k, $not, $bind, $insensitiveSearch); + + // some platforms require an escape sequence definition for LIKE wildcards + if ($escape === true && $this->db->likeEscapeStr !== '') + { + $like_statement .= sprintf($this->db->likeEscapeStr, $this->db->likeEscapeChar); + } + + $this->QBWhere[] = ['condition' => $like_statement, 'escape' => $escape]; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Platform independent LIKE statement builder. + * + * @param string|null $prefix + * @param string $column + * @param string|null $not + * @param string $bind + * @param bool $insensitiveSearch + * + * @return string $like_statement + */ + public function _like_statement(string $prefix = null, string $column, string $not = null, string $bind, bool $insensitiveSearch = false): string + { + $like_statement = "{$prefix} {$column} {$not} LIKE :{$bind}:"; + + if ($insensitiveSearch === true) + { + $like_statement = "{$prefix} LOWER({$column}) {$not} LIKE :{$bind}:"; + } + + return $like_statement; + } + + //-------------------------------------------------------------------- + + /** + * Starts a query group. + * + * @param string $not (Internal use only) + * @param string $type (Internal use only) + * + * @return BaseBuilder + */ + public function groupStart($not = '', $type = 'AND ') + { + $type = $this->groupGetType($type); + + $this->QBWhereGroupStarted = true; + $prefix = empty($this->QBWhere) ? '' : $type; + $where = [ + 'condition' => $prefix . $not . str_repeat(' ', ++ $this->QBWhereGroupCount) . ' (', + 'escape' => false, + ]; + + $this->QBWhere[] = $where; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Starts a query group, but ORs the group + * + * @return BaseBuilder + */ + public function orGroupStart() + { + return $this->groupStart('', 'OR '); + } + + //-------------------------------------------------------------------- + + /** + * Starts a query group, but NOTs the group + * + * @return BaseBuilder + */ + public function notGroupStart() + { + return $this->groupStart('NOT ', 'AND '); + } + + //-------------------------------------------------------------------- + + /** + * Starts a query group, but OR NOTs the group + * + * @return BaseBuilder + */ + public function orNotGroupStart() + { + return $this->groupStart('NOT ', 'OR '); + } + + //-------------------------------------------------------------------- + + /** + * Ends a query group + * + * @return BaseBuilder + */ + public function groupEnd() + { + $this->QBWhereGroupStarted = false; + $where = [ + 'condition' => str_repeat(' ', $this->QBWhereGroupCount -- ) . ')', + 'escape' => false, + ]; + + $this->QBWhere[] = $where; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Group_get_type + * + * @used-by groupStart() + * @used-by _like() + * @used-by whereHaving() + * @used-by _whereIn() + * + * @param string $type + * + * @return string + */ + protected function groupGetType($type) + { + if ($this->QBWhereGroupStarted) + { + $type = ''; + $this->QBWhereGroupStarted = false; + } + + return $type; + } + + //-------------------------------------------------------------------- + + /** + * GROUP BY + * + * @param string $by + * @param bool $escape + * + * @return BaseBuilder + */ + public function groupBy($by, $escape = null) + { + is_bool($escape) || $escape = $this->db->protectIdentifiers; + + if (is_string($by)) + { + $by = ($escape === true) ? explode(',', $by) : [$by]; + } + + foreach ($by as $val) + { + $val = trim($val); + + if ($val !== '') + { + $val = ['field' => $val, 'escape' => $escape]; + + $this->QBGroupBy[] = $val; + } + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * HAVING + * + * Separates multiple calls with 'AND'. + * + * @param string $key + * @param string $value + * @param bool $escape + * + * @return BaseBuilder + */ + public function having($key, $value = null, $escape = null) + { + return $this->whereHaving('QBHaving', $key, $value, 'AND ', $escape); + } + + //-------------------------------------------------------------------- + + /** + * OR HAVING + * + * Separates multiple calls with 'OR'. + * + * @param string $key + * @param string $value + * @param bool $escape + * + * @return BaseBuilder + */ + public function orHaving($key, $value = null, $escape = null) + { + return $this->whereHaving('QBHaving', $key, $value, 'OR ', $escape); + } + + //-------------------------------------------------------------------- + + /** + * ORDER BY + * + * @param string $orderby + * @param string $direction ASC, DESC or RANDOM + * @param bool $escape + * + * @return BaseBuilder + */ + public function orderBy($orderby, $direction = '', $escape = null) + { + $direction = strtoupper(trim($direction)); + + if ($direction === 'RANDOM') + { + $direction = ''; + + // Do we have a seed value? + $orderby = ctype_digit((string) $orderby) ? sprintf($this->randomKeyword[1], $orderby) : $this->randomKeyword[0]; + } + elseif (empty($orderby)) + { + return $this; + } + elseif ($direction !== '') + { + $direction = in_array($direction, ['ASC', 'DESC'], true) ? ' ' . $direction : ''; + } + + is_bool($escape) || $escape = $this->db->protectIdentifiers; + + if ($escape === false) + { + $qb_orderby[] = ['field' => $orderby, 'direction' => $direction, 'escape' => false]; + } + else + { + $qb_orderby = []; + foreach (explode(',', $orderby) as $field) + { + $qb_orderby[] = ($direction === '' && + preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE)) ? [ + 'field' => ltrim(substr($field, 0, $match[0][1])), + 'direction' => ' ' . $match[1][0], + 'escape' => true, + ] : ['field' => trim($field), 'direction' => $direction, 'escape' => true]; + } + } + + $this->QBOrderBy = array_merge($this->QBOrderBy, $qb_orderby); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * LIMIT + * + * @param int $value LIMIT value + * @param int $offset OFFSET value + * + * @return BaseBuilder + */ + public function limit(int $value = null, int $offset = 0) + { + if ( ! is_null($value)) + { + $this->QBLimit = $value; + } + + if ( ! empty($offset)) + { + $this->QBOffset = $offset; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Sets the OFFSET value + * + * @param int $offset OFFSET value + * + * @return BaseBuilder + */ + public function offset($offset) + { + if ( ! empty($offset)) + { + $this->QBOffset = (int) $offset; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * LIMIT string + * + * Generates a platform-specific LIMIT clause. + * + * @param string $sql SQL Query + * + * @return string + */ + protected function _limit($sql) + { + return $sql . ' LIMIT ' . ($this->QBOffset ? $this->QBOffset . ', ' : '') . $this->QBLimit; + } + + //-------------------------------------------------------------------- + + /** + * The "set" function. + * + * Allows key/value pairs to be set for insert(), update() or replace(). + * + * @param string|array|object $key Field name, or an array of field/value pairs + * @param string $value Field value, if $key is a single field + * @param bool $escape Whether to escape values and identifiers + * + * @return BaseBuilder + */ + public function set($key, $value = '', $escape = null) + { + $key = $this->objectToArray($key); + + if ( ! is_array($key)) + { + $key = [$key => $value]; + } + + $escape = is_bool($escape) ? $escape : $this->db->protectIdentifiers; + + foreach ($key as $k => $v) + { + if ($escape) + { + $bind = $this->setBind($k, $v); + $this->QBSet[$this->db->protectIdentifiers($k, false, $escape)] = ":$bind:"; + } + else + { + $this->QBSet[$this->db->protectIdentifiers($k, false, $escape)] = $v; + } + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Get SELECT query string + * + * Compiles a SELECT query string and returns the sql. + * + * @param bool $reset TRUE: resets QB values; FALSE: leave QB values alone + * + * @return string + */ + public function getCompiledSelect($reset = true) + { + $select = $this->compileSelect(); + + if ($reset === true) + { + $this->resetSelect(); + } + + return $select; + } + + //-------------------------------------------------------------------- + + /** + * Get + * + * Compiles the select statement based on the other functions called + * and runs the query + * + * @param int $limit The limit clause + * @param int $offset The offset clause + * @param bool $returnSQL If true, returns the generate SQL, otherwise executes the query. + * + * @return ResultInterface + */ + public function get(int $limit = null, int $offset = 0, $returnSQL = false) + { + if ( ! is_null($limit)) + { + $this->limit($limit, $offset); + } + $result = $returnSQL ? $this->getCompiledSelect() : $this->db->query($this->compileSelect(), $this->binds); + + $this->resetSelect(); + + return $result; + } + + //-------------------------------------------------------------------- + + /** + * "Count All" query + * + * Generates a platform-specific query string that counts all records in + * the specified database + * + * @param bool $test Are we running automated tests? + * + * @return int + */ + public function countAll($test = false) + { + $table = $this->QBFrom[0]; + + $sql = $this->countString . $this->db->escapeIdentifiers('numrows') . ' FROM ' . + $this->db->protectIdentifiers($table, true, null, false); + + if ($test) + { + return $sql; + } + + $query = $this->db->query($sql); + if (empty($query->getResult())) + { + return 0; + } + + $query = $query->getRow(); + $this->resetSelect(); + + return (int) $query->numrows; + } + + //-------------------------------------------------------------------- + + /** + * "Count All Results" query + * + * Generates a platform-specific query string that counts all records + * returned by an Query Builder query. + * + * @param bool $reset + * @param bool $test The reset clause + * + * @return int + */ + public function countAllResults($reset = true, $test = false) + { + // ORDER BY usage is often problematic here (most notably + // on Microsoft SQL Server) and ultimately unnecessary + // for selecting COUNT(*) ... + if ( ! empty($this->QBOrderBy)) + { + $orderby = $this->QBOrderBy; + $this->QBOrderBy = null; + } + + $sql = ($this->QBDistinct === true) ? $this->countString . $this->db->protectIdentifiers('numrows') . "\nFROM (\n" . + $this->compileSelect() . "\n) CI_count_all_results" : $this->compileSelect($this->countString . $this->db->protectIdentifiers('numrows')); + + if ($test) + { + return $sql; + } + + $result = $this->db->query($sql, $this->binds); + + if ($reset === true) + { + $this->resetSelect(); + } + // If we've previously reset the QBOrderBy values, get them back + elseif ( ! isset($this->QBOrderBy)) + { + $this->QBOrderBy = $orderby; + } + + $row = (! $result instanceof ResultInterface) + ? null + : $result->getRow(); + + if (empty($row)) + { + return 0; + } + + return (int) $row->numrows; + } + + //-------------------------------------------------------------------- + + /** + * Get_Where + * + * Allows the where clause, limit and offset to be added directly + * + * @param string $where + * @param int $limit + * @param int $offset + * + * @return ResultInterface + */ + public function getWhere($where = null, $limit = null, $offset = null) + { + if ($where !== null) + { + $this->where($where); + } + + if ( ! empty($limit)) + { + $this->limit($limit, $offset); + } + + $result = $this->db->query($this->compileSelect(), $this->binds); + $this->resetSelect(); + + return $result; + } + + //-------------------------------------------------------------------- + + /** + * Insert_Batch + * + * Compiles batch insert strings and runs the queries + * + * @param array $set An associative array of insert values + * @param bool $escape Whether to escape values and identifiers + * + * @param int $batch_size + * @param bool $testing + * + * @return int Number of rows inserted or FALSE on failure + * @throws DatabaseException + */ + public function insertBatch($set = null, $escape = null, $batch_size = 100, $testing = false) + { + if ($set === null) + { + if (empty($this->QBSet)) + { + if (CI_DEBUG) + { + throw new DatabaseException('You must use the "set" method to update an entry.'); + } + + return false; + } + } + else + { + if (empty($set)) + { + if (CI_DEBUG) + { + throw new DatabaseException('insertBatch() called with no data'); + } + + return false; + } + + $this->setInsertBatch($set, '', $escape); + } + + $table = $this->QBFrom[0]; + + // Batch this baby + $affected_rows = 0; + for ($i = 0, $total = count($this->QBSet); $i < $total; $i += $batch_size) + { + $sql = $this->_insertBatch($this->db->protectIdentifiers($table, true, $escape, false), $this->QBKeys, array_slice($this->QBSet, $i, $batch_size)); + + if ($testing) + { + ++ $affected_rows; + } + else + { + $this->db->query($sql, $this->binds); + $affected_rows += $this->db->affectedRows(); + } + } + + if ( ! $testing) + { + $this->resetWrite(); + } + + return $affected_rows; + } + + //-------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * + * @return string + */ + protected function _insertBatch($table, $keys, $values) + { + return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); + } + + //-------------------------------------------------------------------- + + /** + * The "setInsertBatch" function. Allows key/value pairs to be set for batch inserts + * + * @param mixed $key + * @param string $value + * @param bool $escape + * + * @return BaseBuilder + */ + public function setInsertBatch($key, $value = '', $escape = null) + { + $key = $this->batchObjectToArray($key); + + if ( ! is_array($key)) + { + $key = [$key => $value]; + } + + $escape = is_bool($escape) ? $escape : $this->db->protectIdentifiers; + + $keys = array_keys($this->objectToArray(current($key))); + sort($keys); + + foreach ($key as $row) + { + $row = $this->objectToArray($row); + if (count(array_diff($keys, array_keys($row))) > 0 || count(array_diff(array_keys($row), $keys)) > 0) + { + // batch function above returns an error on an empty array + $this->QBSet[] = []; + + return; + } + + ksort($row); // puts $row in the same order as our keys + + $clean = []; + foreach ($row as $k => $value) + { + $clean[] = ':' . $this->setBind($k, $value) .':'; + } + + $row = $clean; + + $this->QBSet[] = '(' . implode(',', $row) . ')'; + } + + foreach ($keys as $k) + { + $this->QBKeys[] = $this->db->protectIdentifiers($k, false, $escape); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Get INSERT query string + * + * Compiles an insert query and returns the sql + * + * @param bool $reset TRUE: reset QB values; FALSE: leave QB values alone + * + * @return string + */ + public function getCompiledInsert($reset = true) + { + if ($this->validateInsert() === false) + { + return false; + } + + $sql = $this->_insert( + $this->db->protectIdentifiers( + $this->QBFrom[0], true, null, false + ), array_keys($this->QBSet), array_values($this->QBSet) + ); + + if ($reset === true) + { + $this->resetWrite(); + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Insert + * + * Compiles an insert string and runs the query + * + * @param array $set An associative array of insert values + * @param bool $escape Whether to escape values and identifiers + * @param bool $test Used when running tests + * + * @return bool TRUE on success, FALSE on failure + */ + public function insert($set = null, $escape = null, $test = false) + { + if ($set !== null) + { + $this->set($set, '', $escape); + } + + if ($this->validateInsert() === false) + { + return false; + } + + $sql = $this->_insert( + $this->db->protectIdentifiers( + $this->QBFrom[0], true, $escape, false + ), array_keys($this->QBSet), array_values($this->QBSet) + ); + + if ($test === false) + { + $this->resetWrite(); + + return $this->db->query($sql, $this->binds); + } + } + + //-------------------------------------------------------------------- + + /** + * Validate Insert + * + * This method is used by both insert() and getCompiledInsert() to + * validate that the there data is actually being set and that table + * has been chosen to be inserted into. + * + * @return string + * @throws DatabaseException + */ + protected function validateInsert() + { + if (empty($this->QBSet)) + { + if (CI_DEBUG) + { + throw new DatabaseException('You must use the "set" method to update an entry.'); + } + + return false; + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @param string $table The table name + * @param array $keys The insert keys + * @param array $unescapedKeys The insert values + * + * @return string + */ + protected function _insert($table, array $keys, array $unescapedKeys) + { + return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; + } + + //-------------------------------------------------------------------- + + /** + * Replace + * + * Compiles an replace into string and runs the query + * + * @param array $set An associative array of insert values + * @param bool $returnSQL + * + * @return bool TRUE on success, FALSE on failure + * @throws DatabaseException + */ + public function replace($set = null, $returnSQL = false) + { + if ($set !== null) + { + $this->set($set); + } + + if (empty($this->QBSet)) + { + if (CI_DEBUG) + { + throw new DatabaseException('You must use the "set" method to update an entry.'); + } + return false; + } + + $table = $this->QBFrom[0]; + + $sql = $this->_replace($table, array_keys($this->QBSet), array_values($this->QBSet)); + + $this->resetWrite(); + + return $returnSQL ? $sql : $this->db->query($sql, $this->binds); + } + + //-------------------------------------------------------------------- + + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @param string $table The table name + * @param array $keys The insert keys + * @param array $values The insert values + * + * @return string + */ + protected function _replace($table, $keys, $values) + { + return 'REPLACE INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $values) . ')'; + } + + //-------------------------------------------------------------------- + + /** + * FROM tables + * + * Groups tables in FROM clauses if needed, so there is no confusion + * about operator precedence. + * + * Note: This is only used (and overridden) by MySQL and CUBRID. + * + * @return string + */ + protected function _fromTables() + { + return implode(', ', $this->QBFrom); + } + + //-------------------------------------------------------------------- + + /** + * Get UPDATE query string + * + * Compiles an update query and returns the sql + * + * @param bool $reset TRUE: reset QB values; FALSE: leave QB values alone + * + * @return string + */ + public function getCompiledUpdate($reset = true) + { + if ($this->validateUpdate() === false) + { + return false; + } + + $sql = $this->_update($this->QBFrom[0], $this->QBSet); + + if ($reset === true) + { + $this->resetWrite(); + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * UPDATE + * + * Compiles an update string and runs the query. + * + * @param array $set An associative array of update values + * @param mixed $where + * @param int $limit + * @param bool $test Are we testing the code? + * + * @return bool TRUE on success, FALSE on failure + */ + public function update($set = null, $where = null, int $limit = null, $test = false) + { + if ($set !== null) + { + $this->set($set); + } + + if ($this->validateUpdate() === false) + { + return false; + } + + if ($where !== null) + { + $this->where($where); + } + + if ( ! empty($limit)) + { + $this->limit($limit); + } + + $sql = $this->_update($this->QBFrom[0], $this->QBSet); + + if ( ! $test) + { + $this->resetWrite(); + + if ($this->db->query($sql, $this->binds)) + { + return true; + } + + return false; + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table the Table name + * @param array $values the Update data + * + * @return string + */ + protected function _update($table, $values) + { + foreach ($values as $key => $val) + { + $valstr[] = $key . ' = ' . $val; + } + + return 'UPDATE ' . $table . ' SET ' . implode(', ', $valstr) + . $this->compileWhereHaving('QBWhere') + . $this->compileOrderBy() + . ($this->QBLimit ? $this->_limit(' ') : ''); + } + + //-------------------------------------------------------------------- + + /** + * Validate Update + * + * This method is used by both update() and getCompiledUpdate() to + * validate that data is actually being set and that a table has been + * chosen to be update. + * + * + * @return bool + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + protected function validateUpdate() + { + if (empty($this->QBSet)) + { + if (CI_DEBUG) + { + throw new DatabaseException('You must use the "set" method to update an entry.'); + } + + return false; + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Update_Batch + * + * Compiles an update string and runs the query + * + * @param array $set An associative array of update values + * @param string $index The where key + * @param int $batch_size The size of the batch to run + * @param bool $returnSQL True means SQL is returned, false will execute the query + * + * @return mixed Number of rows affected or FALSE on failure + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function updateBatch($set = null, $index = null, $batch_size = 100, $returnSQL = false) + { + if ($index === null) + { + if (CI_DEBUG) + { + throw new DatabaseException('You must specify an index to match on for batch updates.'); + } + + return false; + } + + if ($set === null) + { + if (empty($this->QBSet)) + { + if (CI_DEBUG) + { + throw new DatabaseException('You must use the "set" method to update an entry.'); + } + return false; + } + } + else + { + if (empty($set)) + { + if (CI_DEBUG) + { + throw new DatabaseException('updateBatch() called with no data'); + } + return false; + } + + $this->setUpdateBatch($set, $index); + } + + $table = $this->QBFrom[0]; + + // Batch this baby + $affected_rows = 0; + $savedSQL = []; + for ($i = 0, $total = count($this->QBSet); $i < $total; $i += $batch_size) + { + $sql = $this->_updateBatch($table, array_slice($this->QBSet, $i, $batch_size), $this->db->protectIdentifiers($index) + ); + + if ($returnSQL) + { + $savedSQL[] = $sql; + } + else + { + $this->db->query($sql, $this->binds); + $affected_rows += $this->db->affectedRows(); + } + + $this->QBWhere = []; + } + + $this->resetWrite(); + + return $returnSQL ? $savedSQL : $affected_rows; + } + + //-------------------------------------------------------------------- + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @param string $table Table name + * @param array $values Update data + * @param string $index WHERE key + * + * @return string + */ + protected function _updateBatch($table, $values, $index) + { + $ids = []; + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field !== $index) + { + $final[$field][] = 'WHEN ' . $index . ' = ' . $val[$index] . ' THEN ' . $val[$field]; + } + } + } + + $cases = ''; + foreach ($final as $k => $v) + { + $cases .= $k . " = CASE \n" + . implode("\n", $v) . "\n" + . 'ELSE ' . $k . ' END, '; + } + + $this->where($index . ' IN(' . implode(',', $ids) . ')', null, false); + + return 'UPDATE ' . $table . ' SET ' . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere'); + } + + //-------------------------------------------------------------------- + + /** + * The "setUpdateBatch" function. Allows key/value pairs to be set for batch updating + * + * @param array $key + * @param string $index + * @param bool $escape + * + * @return BaseBuilder + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function setUpdateBatch($key, $index = '', $escape = null) + { + $key = $this->batchObjectToArray($key); + + if ( ! is_array($key)) + { + // @todo error + } + + is_bool($escape) || $escape = $this->db->protectIdentifiers; + + foreach ($key as $k => $v) + { + $index_set = false; + $clean = []; + foreach ($v as $k2 => $v2) + { + if ($k2 === $index) + { + $index_set = true; + } + + $bind = $this->setBind($k2, $v2); + + $clean[$this->db->protectIdentifiers($k2, false, $escape)] = ":$bind:"; + } + + if ($index_set === false) + { + throw new DatabaseException('One or more rows submitted for batch updating is missing the specified index.'); + } + + $this->QBSet[] = $clean; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Empty Table + * + * Compiles a delete string and runs "DELETE FROM table" + * + * @param bool $test + * @return bool TRUE on success, FALSE on failure + */ + public function emptyTable($test = false) + { + $table = $this->QBFrom[0]; + + $sql = $this->_delete($table); + + if ($test) + { + return $sql; + } + + $this->resetWrite(); + + return $this->db->query($sql); + } + + //-------------------------------------------------------------------- + + /** + * Truncate + * + * Compiles a truncate string and runs the query + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @param bool $test Whether we're in test mode or not. + * + * @return bool TRUE on success, FALSE on failure + */ + public function truncate($test = false) + { + $table = $this->QBFrom[0]; + + $sql = $this->_truncate($table); + + if ($test === true) + { + return $sql; + } + + $this->resetWrite(); + + return $this->db->query($sql); + } + + //-------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the truncate() command, + * then this method maps to 'DELETE FROM table' + * + * @param string $table The table name + * + * @return string + */ + protected function _truncate($table) + { + return 'TRUNCATE ' . $table; + } + + //-------------------------------------------------------------------- + + /** + * Get DELETE query string + * + * Compiles a delete query string and returns the sql + * + * @param bool $reset TRUE: reset QB values; FALSE: leave QB values alone + * + * @return string + */ + public function getCompiledDelete($reset = true) + { + $table = $this->QBFrom[0]; + + $this->returnDeleteSQL = true; + $sql = $this->delete($table, '', null, $reset); + $this->returnDeleteSQL = false; + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Delete + * + * Compiles a delete string and runs the query + * + * @param mixed $where The where clause + * @param mixed $limit The limit clause + * @param bool $reset_data + * @param bool $returnSQL + * + * @return mixed + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function delete($where = '', $limit = null, $reset_data = true, $returnSQL = false) + { + $table = $this->db->protectIdentifiers($this->QBFrom[0], true, null, false); + + if ($where !== '') + { + $this->where($where); + } + + if (empty($this->QBWhere)) + { + if (CI_DEBUG) + { + throw new DatabaseException('Deletes are not allowed unless they contain a "where" or "like" clause.'); + } + + return false; + } + + $sql = $this->_delete($table); + + if ( ! empty($limit)) + { + $this->QBLimit = $limit; + } + + if ( ! empty($this->QBLimit)) + { + $sql = $this->_limit($sql); + } + + if ($reset_data) + { + $this->resetWrite(); + } + + return ($returnSQL === true) ? $sql : $this->db->query($sql, $this->binds); + } + + //-------------------------------------------------------------------- + + /** + * Increments a numeric column by the specified value. + * + * @param string $column + * @param int $value + * + * @return bool + */ + public function increment(string $column, int $value = 1) + { + $column = $this->db->protectIdentifiers($column); + + $sql = $this->_update($this->QBFrom[0], [$column => "{$column} + {$value}"]); + + return $this->db->query($sql, $this->binds); + } + + //-------------------------------------------------------------------- + + /** + * Decrements a numeric column by the specified value. + * + * @param string $column + * @param int $value + * + * @return bool + */ + public function decrement(string $column, int $value = 1) + { + $column = $this->db->protectIdentifiers($column); + + $sql = $this->_update($this->QBFrom[0], [$column => "{$column}-{$value}"]); + + return $this->db->query($sql, $this->binds); + } + + //-------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table The table name + * + * @return string + */ + protected function _delete($table) + { + return 'DELETE FROM ' . $table . $this->compileWhereHaving('QBWhere') + . ($this->QBLimit ? ' LIMIT ' . $this->QBLimit : ''); + } + + //-------------------------------------------------------------------- + + /** + * Track Aliases + * + * Used to track SQL statements written with aliased tables. + * + * @param string $table The table to inspect + * + * @return string + */ + protected function trackAliases($table) + { + if (is_array($table)) + { + foreach ($table as $t) + { + $this->trackAliases($t); + } + + return; + } + + // Does the string contain a comma? If so, we need to separate + // the string into discreet statements + if (strpos($table, ',') !== false) + { + return $this->trackAliases(explode(',', $table)); + } + + // if a table alias is used we can recognize it by a space + if (strpos($table, ' ') !== false) + { + // if the alias is written with the AS keyword, remove it + $table = preg_replace('/\s+AS\s+/i', ' ', $table); + + // Grab the alias + $table = trim(strrchr($table, ' ')); + + // Store the alias, if it doesn't already exist + if ( ! in_array($table, $this->QBAliasedTables)) + { + $this->QBAliasedTables[] = $table; + } + } + } + + //-------------------------------------------------------------------- + + /** + * Compile the SELECT statement + * + * Generates a query string based on which functions were used. + * Should not be called directly. + * + * @param bool $select_override + * + * @return string + */ + protected function compileSelect($select_override = false) + { + // Write the "select" portion of the query + if ($select_override !== false) + { + $sql = $select_override; + } + else + { + $sql = ( ! $this->QBDistinct) ? 'SELECT ' : 'SELECT DISTINCT '; + + if (empty($this->QBSelect)) + { + $sql .= '*'; + } + else + { + // Cycle through the "select" portion of the query and prep each column name. + // The reason we protect identifiers here rather than in the select() function + // is because until the user calls the from() function we don't know if there are aliases + foreach ($this->QBSelect as $key => $val) + { + $no_escape = isset($this->QBNoEscape[$key]) ? $this->QBNoEscape[$key] : null; + $this->QBSelect[$key] = $this->db->protectIdentifiers($val, false, $no_escape); + } + + $sql .= implode(', ', $this->QBSelect); + } + } + + // Write the "FROM" portion of the query + if (! empty($this->QBFrom)) + { + $sql .= "\nFROM " . $this->_fromTables(); + } + + // Write the "JOIN" portion of the query + if (! empty($this->QBJoin)) + { + $sql .= "\n" . implode("\n", $this->QBJoin); + } + + $sql .= $this->compileWhereHaving('QBWhere') + . $this->compileGroupBy() + . $this->compileWhereHaving('QBHaving') + . $this->compileOrderBy(); // ORDER BY + // LIMIT + if ($this->QBLimit) + { + return $this->_limit($sql . "\n"); + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Compile WHERE, HAVING statements + * + * Escapes identifiers in WHERE and HAVING statements at execution time. + * + * Required so that aliases are tracked properly, regardless of whether + * where(), orWhere(), having(), orHaving are called prior to from(), + * join() and prefixTable is added only if needed. + * + * @param string $qb_key 'QBWhere' or 'QBHaving' + * + * @return string SQL statement + */ + protected function compileWhereHaving($qb_key) + { + if (! empty($this->$qb_key)) + { + for ($i = 0, $c = count($this->$qb_key); $i < $c; $i ++ ) + { + // Is this condition already compiled? + if (is_string($this->{$qb_key}[$i])) + { + continue; + } + elseif ($this->{$qb_key}[$i]['escape'] === false) + { + $this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition']; + continue; + } + + // Split multiple conditions + $conditions = preg_split( + '/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i', $this->{$qb_key}[$i]['condition'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY + ); + + for ($ci = 0, $cc = count($conditions); $ci < $cc; $ci ++ ) + { + if (($op = $this->getOperator($conditions[$ci])) === false + OR ! preg_match('/^(\(?)(.*)(' . preg_quote($op, '/') . ')\s*(.*(? '(test <= foo)', /* the whole thing */ + // 1 => '(', /* optional */ + // 2 => 'test', /* the field name */ + // 3 => ' <= ', /* $op */ + // 4 => 'foo', /* optional, if $op is e.g. 'IS NULL' */ + // 5 => ')' /* optional */ + // ); + + if ( ! empty($matches[4])) + { +// $this->isLiteral($matches[4]) OR $matches[4] = $this->db->protectIdentifiers(trim($matches[4])); + $matches[4] = ' ' . $matches[4]; + } + + $conditions[$ci] = $matches[1] . $this->db->protectIdentifiers(trim($matches[2])) + . ' ' . trim($matches[3]) . $matches[4] . $matches[5]; + } + + $this->{$qb_key}[$i] = implode('', $conditions); + } + + return ($qb_key === 'QBHaving' ? "\nHAVING " : "\nWHERE ") + . implode("\n", $this->$qb_key); + } + + return ''; + } + + //-------------------------------------------------------------------- + + /** + * Compile GROUP BY + * + * Escapes identifiers in GROUP BY statements at execution time. + * + * Required so that aliases are tracked properly, regardless of wether + * groupBy() is called prior to from(), join() and prefixTable is added + * only if needed. + * + * @return string SQL statement + */ + protected function compileGroupBy() + { + if (! empty($this->QBGroupBy)) + { + for ($i = 0, $c = count($this->QBGroupBy); $i < $c; $i ++ ) + { + // Is it already compiled? + if (is_string($this->QBGroupBy[$i])) + { + continue; + } + + $this->QBGroupBy[$i] = ($this->QBGroupBy[$i]['escape'] === false OR + $this->isLiteral($this->QBGroupBy[$i]['field'])) ? $this->QBGroupBy[$i]['field'] : $this->db->protectIdentifiers($this->QBGroupBy[$i]['field']); + } + + return "\nGROUP BY " . implode(', ', $this->QBGroupBy); + } + + return ''; + } + + //-------------------------------------------------------------------- + + /** + * Compile ORDER BY + * + * Escapes identifiers in ORDER BY statements at execution time. + * + * Required so that aliases are tracked properly, regardless of wether + * orderBy() is called prior to from(), join() and prefixTable is added + * only if needed. + * + * @return string SQL statement + */ + protected function compileOrderBy() + { + if (is_array($this->QBOrderBy) && ! empty($this->QBOrderBy)) + { + for ($i = 0, $c = count($this->QBOrderBy); $i < $c; $i ++ ) + { + if ($this->QBOrderBy[$i]['escape'] !== false && ! $this->isLiteral($this->QBOrderBy[$i]['field'])) + { + $this->QBOrderBy[$i]['field'] = $this->db->protectIdentifiers($this->QBOrderBy[$i]['field']); + } + + $this->QBOrderBy[$i] = $this->QBOrderBy[$i]['field'] . $this->QBOrderBy[$i]['direction']; + } + + return $this->QBOrderBy = "\nORDER BY " . implode(', ', $this->QBOrderBy); + } + elseif (is_string($this->QBOrderBy)) + { + return $this->QBOrderBy; + } + + return ''; + } + + //-------------------------------------------------------------------- + + /** + * Object to Array + * + * Takes an object as input and converts the class variables to array key/vals + * + * @param object $object + * + * @return array + */ + protected function objectToArray($object) + { + if ( ! is_object($object)) + { + return $object; + } + + $array = []; + foreach (get_object_vars($object) as $key => $val) + { + // There are some built in keys we need to ignore for this conversion + if ( ! is_object($val) && ! is_array($val) && $key !== '_parent_name') + { + $array[$key] = $val; + } + } + + return $array; + } + + //-------------------------------------------------------------------- + + /** + * Object to Array + * + * Takes an object as input and converts the class variables to array key/vals + * + * @param object $object + * + * @return array + */ + protected function batchObjectToArray($object) + { + if ( ! is_object($object)) + { + return $object; + } + + $array = []; + $out = get_object_vars($object); + $fields = array_keys($out); + + foreach ($fields as $val) + { + // There are some built in keys we need to ignore for this conversion + if ($val !== '_parent_name') + { + $i = 0; + foreach ($out[$val] as $data) + { + $array[$i ++][$val] = $data; + } + } + } + + return $array; + } + + //-------------------------------------------------------------------- + + /** + * Is literal + * + * Determines if a string represents a literal value or a field name + * + * @param string $str + * + * @return bool + */ + protected function isLiteral($str) + { + $str = trim($str); + + if (empty($str) || ctype_digit($str) || (string) (float) $str === $str || + in_array(strtoupper($str), ['TRUE', 'FALSE'], true) + ) + { + return true; + } + + static $_str; + + if (empty($_str)) + { + $_str = ($this->db->escapeChar !== '"') ? ['"', "'"] : ["'"]; + } + + return in_array($str[0], $_str, true); + } + + //-------------------------------------------------------------------- + + /** + * Reset Query Builder values. + * + * Publicly-visible method to reset the QB values. + * + * @return BaseBuilder + */ + public function resetQuery() + { + $this->resetSelect(); + $this->resetWrite(); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Resets the query builder values. Called by the get() function + * + * @param array $qb_reset_items An array of fields to reset + */ + protected function resetRun($qb_reset_items) + { + foreach ($qb_reset_items as $item => $default_value) + { + $this->$item = $default_value; + } + } + + //-------------------------------------------------------------------- + + /** + * Resets the query builder values. Called by the get() function + */ + protected function resetSelect() + { + $this->resetRun([ + 'QBSelect' => [], + 'QBJoin' => [], + 'QBWhere' => [], + 'QBGroupBy' => [], + 'QBHaving' => [], + 'QBOrderBy' => [], + 'QBAliasedTables' => [], + 'QBNoEscape' => [], + 'QBDistinct' => false, + 'QBLimit' => false, + 'QBOffset' => false, + ]); + } + + //-------------------------------------------------------------------- + + /** + * Resets the query builder "write" values. + * + * Called by the insert() update() insertBatch() updateBatch() and delete() functions + */ + protected function resetWrite() + { + $this->resetRun([ + 'QBSet' => [], + 'QBJoin' => [], + 'QBWhere' => [], + 'QBOrderBy' => [], + 'QBKeys' => [], + 'QBLimit' => false, + ]); + } + + //-------------------------------------------------------------------- + + /** + * Tests whether the string has an SQL operator + * + * @param string $str + * + * @return bool + */ + protected function hasOperator($str) + { + return (bool) preg_match('/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i', trim($str)); + } + + // -------------------------------------------------------------------- + + /** + * Returns the SQL string operator + * + * @param string $str + * + * @return string + */ + protected function getOperator($str) + { + static $_operators; + + if (empty($_operators)) + { + $_les = ($this->db->likeEscapeStr !== '') ? '\s+' . preg_quote(trim(sprintf($this->db->likeEscapeStr, $this->db->likeEscapeChar)), '/') : ''; + $_operators = [ + '\s*(?:<|>|!)?=\s*', // =, <=, >=, != + '\s*<>?\s*', // <, <> + '\s*>\s*', // > + '\s+IS NULL', // IS NULL + '\s+IS NOT NULL', // IS NOT NULL + '\s+EXISTS\s*\(.*\)', // EXISTS(sql) + '\s+NOT EXISTS\s*\(.*\)', // NOT EXISTS(sql) + '\s+BETWEEN\s+', // BETWEEN value AND value + '\s+IN\s*\(.*\)', // IN(list) + '\s+NOT IN\s*\(.*\)', // NOT IN (list) + '\s+LIKE\s+\S.*(' . $_les . ')?', // LIKE 'expr'[ ESCAPE '%s'] + '\s+NOT LIKE\s+\S.*(' . $_les . ')?' // NOT LIKE 'expr'[ ESCAPE '%s'] + ]; + } + + return preg_match('/' . implode('|', $_operators) . '/i', $str, $match) ? $match[0] : false; + } + + // -------------------------------------------------------------------- + + /** + * Stores a bind value after ensuring that it's unique. + * + * @param string $key + * @param null $value + * + * @return string + */ + protected function setBind(string $key, $value = null) + { + if ( ! array_key_exists($key, $this->binds)) + { + $this->binds[$key] = $value; + + return $key; + } + + $count = 0; + + while (array_key_exists($key . $count, $this->binds)) + { + ++ $count; + } + + $this->binds[$key . $count] = $value; + + return $key . $count; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php new file mode 100644 index 0000000..5aaa252 --- /dev/null +++ b/system/Database/BaseConnection.php @@ -0,0 +1,1700 @@ + $value) + { + $this->$key = $value; + } + } + + //-------------------------------------------------------------------- + + /** + * Initializes the database connection/settings. + * + * @return mixed + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function initialize() + { + /* If an established connection is available, then there's + * no need to connect and select the database. + * + * Depending on the database driver, conn_id can be either + * boolean TRUE, a resource or an object. + */ + if ($this->connID) + { + return; + } + + //-------------------------------------------------------------------- + + $this->connectTime = microtime(true); + + // Connect to the database and set the connection ID + $this->connID = $this->connect($this->pConnect); + + // No connection resource? Check if there is a failover else throw an error + if ( ! $this->connID) + { + // Check if there is a failover set + if ( ! empty($this->failover) && is_array($this->failover)) + { + // Go over all the failovers + foreach ($this->failover as $failover) + { + // Replace the current settings with those of the failover + foreach ($failover as $key => $val) + { + $this->$key = $val; + } + + // Try to connect + $this->connID = $this->connect($this->pConnect); + + // If a connection is made break the foreach loop + if ($this->connID) + { + break; + } + } + } + + // We still don't have a connection? + if ( ! $this->connID) + { + throw new DatabaseException('Unable to connect to the database.'); + } + } + + $this->connectDuration = microtime(true) - $this->connectTime; + } + + //-------------------------------------------------------------------- + + /** + * Connect to the database. + * + * @param bool $persistent + * @return mixed + */ + abstract public function connect($persistent = false); + + //-------------------------------------------------------------------- + + /** + * Close the database connection. + */ + public function close() + { + if ($this->connID) + { + $this->_close(); + $this->connID = FALSE; + } + } + + //-------------------------------------------------------------------- + + /** + * Platform dependent way method for closing the connection. + * + * @return mixed + */ + abstract protected function _close(); + + //-------------------------------------------------------------------- + + /** + * Create a persistent database connection. + * + * @return mixed + */ + public function persistentConnect() + { + return $this->connect(true); + } + + //-------------------------------------------------------------------- + + /** + * Keep or establish the connection if no queries have been sent for + * a length of time exceeding the server's idle timeout. + * + * @return mixed + */ + abstract public function reconnect(); + + //-------------------------------------------------------------------- + + /** + * Returns the actual connection object. If both a 'read' and 'write' + * connection has been specified, you can pass either term in to + * get that connection. If you pass either alias in and only a single + * connection is present, it must return the sole connection. + * + * @param string|null $alias + * + * @return mixed + */ + public function getConnection(string $alias = null) + { + //@todo work with read/write connections + return $this->connID; + } + + //-------------------------------------------------------------------- + + /** + * Select a specific database table to use. + * + * @param string $databaseName + * + * @return mixed + */ + abstract public function setDatabase(string $databaseName); + + //-------------------------------------------------------------------- + + /** + * Returns the name of the current database being used. + * + * @return string + */ + public function getDatabase(): string + { + return empty($this->database) ? '' : $this->database; + } + + //-------------------------------------------------------------------- + + /** + * Returns the last error encountered by this connection. + * + * @return mixed + */ + public function getError() + { + + } + + //-------------------------------------------------------------------- + + /** + * The name of the platform in use (MySQLi, mssql, etc) + * + * @return mixed + */ + public function getPlatform() + { + return $this->DBDriver; + } + + //-------------------------------------------------------------------- + + /** + * Returns a string containing the version of the database being used. + * + * @return mixed + */ + abstract public function getVersion(); + + //-------------------------------------------------------------------- + + /** + * Executes the query against the database. + * + * @param $sql + * + * @return mixed + */ + abstract protected function execute($sql); + + //-------------------------------------------------------------------- + + /** + * Orchestrates a query against the database. Queries must use + * Database\Statement objects to store the query and build it. + * This method works with the cache. + * + * Should automatically handle different connections for read/write + * queries if needed. + * + * @param string $sql + * @param array ...$binds + * @param string $queryClass + * @return BaseResult|Query|false + */ + public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Database\\Query') + { + if (empty($this->connID)) + { + $this->initialize(); + } + + $resultClass = str_replace('Connection', 'Result', get_class($this)); + + /** + * @var Query $query + */ + $query = new $queryClass($this); + + $query->setQuery($sql, $binds); + + if ( ! empty($this->swapPre) && ! empty($this->DBPrefix)) + { + $query->swapPrefix($this->DBPrefix, $this->swapPre); + } + + $startTime = microtime(true); + + // Always save the last query so we can use + // the getLastQuery() method. + $this->lastQuery = $query; + + // Run the query for real + if ( ! $this->pretend && false === ($this->resultID = $this->simpleQuery($query->getQuery()))) + { + $query->setDuration($startTime, $startTime); + + // This will trigger a rollback if transactions are being used + if ($this->_trans_depth !== 0) + { + $this->_trans_status = false; + } + + // @todo deal with errors + + if ($this->DBDebug) + { + // We call this function in order to roll-back queries + // if transactions are enabled. If we don't call this here + // the error message will trigger an exit, causing the + // transactions to remain in limbo. + while ($this->_trans_depth !== 0) + { + $transDepth = $this->_trans_depth; + $this->transComplete(); + + if ($transDepth === $this->_trans_depth) + { + // @todo log + // log_message('error', 'Database: Failure during an automated transaction commit/rollback!'); + break; + } + } + + // display the errors.... + // @todo display the error... + + return false; + } + + if ( ! $this->pretend) + { + // Let others do something with this query. + Events::trigger('DBQuery', $query); + } + + return new $resultClass($this->connID, $this->resultID); + } + + $query->setDuration($startTime); + + if ( ! $this->pretend) + { + // Let others do somethign with this query + Events::trigger('DBQuery', $query); + } + + // If $pretend is true, then we just want to return + // the actual query object here. There won't be + // any results to return. + return $this->pretend ? $query : new $resultClass($this->connID, $this->resultID); + } + + //-------------------------------------------------------------------- + + /** + * Performs a basic query against the database. No binding or caching + * is performed, nor are transactions handled. Simply takes a raw + * query string and returns the database-specific result id. + * + * @param string $sql + * + * @return mixed + */ + public function simpleQuery(string $sql) + { + if (empty($this->connID)) + { + $this->initialize(); + } + + return $this->execute($sql); + } + + //-------------------------------------------------------------------- + + /** + * Disable Transactions + * + * This permits transactions to be disabled at run-time. + */ + public function transOff() + { + $this->trans_enabled = FALSE; + } + + //-------------------------------------------------------------------- + + /** + * Enable/disable Transaction Strict Mode + * + * When strict mode is enabled, if you are running multiple groups of + * transactions, if one group fails all subsequent groups will be + * rolled back. + * + * If strict mode is disabled, each group is treated autonomously, + * meaning a failure of one group will not affect any others + * + * @param bool $mode = true + * + * @return $this + */ + public function transStrict(bool $mode = true) + { + $this->trans_strict = $mode; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Start Transaction + * + * @param bool $test_mode = FALSE + * @return bool + */ + public function transStart($test_mode = false) + { + if ( ! $this->trans_enabled) + { + return false; + } + + return $this->transBegin($test_mode); + } + + //-------------------------------------------------------------------- + + /** + * Complete Transaction + * + * @return bool + */ + public function transComplete() + { + if ( ! $this->trans_enabled) + { + return false; + } + + // The query() function will set this flag to FALSE in the event that a query failed + if ($this->_trans_status === false OR $this->_trans_failure === true) + { + $this->transRollback(); + + // If we are NOT running in strict mode, we will reset + // the _trans_status flag so that subsequent groups of + // transactions will be permitted. + if ($this->trans_strict === false) + { + $this->_trans_status = true; + } + + // log_message('debug', 'DB Transaction Failure'); + return FALSE; + } + + return $this->transCommit(); + } + + //-------------------------------------------------------------------- + + /** + * Lets you retrieve the transaction flag to determine if it has failed + * + * @return bool + */ + public function transStatus(): bool + { + return $this->_trans_status; + } + + //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @param bool $test_mode + * @return bool + */ + public function transBegin(bool $test_mode = false): bool + { + if ( ! $this->trans_enabled) + { + return false; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 0) + { + $this->_trans_depth ++; + return true; + } + + if (empty($this->connID)) + { + $this->initialize(); + } + + // Reset the transaction failure flag. + // If the $test_mode flag is set to TRUE transactions will be rolled back + // even if the queries produce a successful result. + $this->_trans_failure = ($test_mode === true); + + if ($this->_transBegin()) + { + $this->_trans_depth ++; + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + public function transCommit(): bool + { + if ( ! $this->trans_enabled || $this->_trans_depth === 0) + { + return false; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 1 || $this->_transCommit()) + { + $this->_trans_depth --; + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + public function transRollback(): bool + { + if ( ! $this->trans_enabled OR $this->_trans_depth === 0) + { + return false; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 1 OR $this->_transRollback()) + { + $this->_trans_depth --; + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + abstract protected function _transBegin(): bool; + + //-------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + abstract protected function _transCommit(): bool; + + //-------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + abstract protected function _transRollback(): bool; + + //-------------------------------------------------------------------- + + /** + * Returns an instance of the query builder for this connection. + * + * @param string $tableName + * + * @return BaseBuilder + * @throws DatabaseException + */ + public function table($tableName) + { + if (empty($tableName)) + { + throw new DatabaseException('You must set the database table to be used with your query.'); + } + + $className = str_replace('Connection', 'Builder', get_class($this)); + + return new $className($tableName, $this); + } + + //-------------------------------------------------------------------- + + /** + * Creates a prepared statement with the database that can then + * be used to execute multiple statements against. Within the + * closure, you would build the query in any normal way, though + * the Query Builder is the expected manner. + * + * Example: + * $stmt = $db->prepare(function($db) + * { + * return $db->table('users') + * ->where('id', 1) + * ->get(); + * }) + * + * @param \Closure $func + * @param array $options Passed to the prepare() method + * + * @return BasePreparedQuery|null + */ + public function prepare(\Closure $func, array $options = []) + { + $this->pretend(true); + + $sql = $func($this); + + $this->pretend(false); + + if ($sql instanceof QueryInterface) + { + $sql = $sql->getOriginalQuery(); + } + + $class = str_ireplace('Connection', 'PreparedQuery', get_class($this)); + /** + * @var BasePreparedQuery $class + */ + $class = new $class($this); + + return $class->prepare($sql, $options); + } + + //-------------------------------------------------------------------- + + /** + * Returns the last query's statement object. + * + * @return mixed + */ + public function getLastQuery() + { + return $this->lastQuery; + } + + //-------------------------------------------------------------------- + + /** + * Returns a string representation of the last query's statement object. + * + * @return string + */ + public function showLastQuery() + { + return (string) $this->lastQuery; + } + + //-------------------------------------------------------------------- + + /** + * Returns the time we started to connect to this database in + * seconds with microseconds. + * + * Used by the Debug Toolbar's timeline. + * + * @return float + */ + public function getConnectStart() + { + return $this->connectTime; + } + + //-------------------------------------------------------------------- + + /** + * Returns the number of seconds with microseconds that it took + * to connect to the database. + * + * Used by the Debug Toolbar's timeline. + * + * @param int $decimals + * + * @return mixed + */ + public function getConnectDuration($decimals = 6) + { + return number_format($this->connectDuration, $decimals); + } + + //-------------------------------------------------------------------- + + /** + * Protect Identifiers + * + * This function is used extensively by the Query Builder class, and by + * a couple functions in this class. + * It takes a column or table name (optionally with an alias) and inserts + * the table prefix onto it. Some logic is necessary in order to deal with + * column names that include the path. Consider a query like this: + * + * SELECT hostname.database.table.column AS c FROM hostname.database.table + * + * Or a query with aliasing: + * + * SELECT m.member_id, m.member_name FROM members AS m + * + * Since the column name can include up to four segments (host, DB, table, column) + * or also have an alias prefix, we need to do a bit of work to figure this out and + * insert the table prefix (if it exists) in the proper position, and escape only + * the correct identifiers. + * + * @param string|array + * @param bool + * @param mixed + * @param bool + * + * @return string|array + */ + public function protectIdentifiers($item, $prefixSingle = false, $protectIdentifiers = null, $fieldExists = true) + { + if ( ! is_bool($protectIdentifiers)) + { + $protectIdentifiers = $this->protectIdentifiers; + } + + if (is_array($item)) + { + $escaped_array = []; + foreach ($item as $k => $v) + { + $escaped_array[$this->protectIdentifiers($k)] = $this->protectIdentifiers($v, $prefixSingle, $protectIdentifiers, $fieldExists); + } + + return $escaped_array; + } + + // This is basically a bug fix for queries that use MAX, MIN, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. There's probably a more graceful + // way to deal with this, but I'm not thinking of it -- Rick + // + // Added exception for single quotes as well, we don't want to alter + // literal strings. -- Narf + if (strcspn($item, "()'") !== strlen($item)) + { + return $item; + } + + // Convert tabs or multiple spaces into single spaces + $item = preg_replace('/\s+/', ' ', trim($item)); + + // If the item has an alias declaration we remove it and set it aside. + // Note: strripos() is used in order to support spaces in table names + if ($offset = strripos($item, ' AS ')) + { + $alias = ($protectIdentifiers) ? substr($item, $offset, 4) . $this->escapeIdentifiers(substr($item, $offset + 4)) : substr($item, $offset); + $item = substr($item, 0, $offset); + } + elseif ($offset = strrpos($item, ' ')) + { + $alias = ($protectIdentifiers) ? ' ' . $this->escapeIdentifiers(substr($item, $offset + 1)) : substr($item, $offset); + $item = substr($item, 0, $offset); + } + else + { + $alias = ''; + } + + // Break the string apart if it contains periods, then insert the table prefix + // in the correct location, assuming the period doesn't indicate that we're dealing + // with an alias. While we're at it, we will escape the components + if (strpos($item, '.') !== false) + { + $parts = explode('.', $item); + + // Does the first segment of the exploded item match + // one of the aliases previously identified? If so, + // we have nothing more to do other than escape the item + // + // NOTE: The ! empty() condition prevents this method + // from breaking when QB isn't enabled. + if ( ! empty($this->qb_aliased_tables) && in_array($parts[0], $this->qb_aliased_tables)) + { + if ($protectIdentifiers === true) + { + foreach ($parts as $key => $val) + { + if ( ! in_array($val, $this->reservedIdentifiers)) + { + $parts[$key] = $this->escapeIdentifiers($val); + } + } + + $item = implode('.', $parts); + } + + return $item . $alias; + } + + // Is there a table prefix defined in the config file? If not, no need to do anything + if ($this->DBPrefix !== '') + { + // We now add the table prefix based on some logic. + // Do we have 4 segments (hostname.database.table.column)? + // If so, we add the table prefix to the column name in the 3rd segment. + if (isset($parts[3])) + { + $i = 2; + } + // Do we have 3 segments (database.table.column)? + // If so, we add the table prefix to the column name in 2nd position + elseif (isset($parts[2])) + { + $i = 1; + } + // Do we have 2 segments (table.column)? + // If so, we add the table prefix to the column name in 1st segment + else + { + $i = 0; + } + + // This flag is set when the supplied $item does not contain a field name. + // This can happen when this function is being called from a JOIN. + if ($fieldExists === false) + { + $i ++; + } + + // Verify table prefix and replace if necessary + if ($this->swapPre !== '' && strpos($parts[$i], $this->swapPre) === 0) + { + $parts[$i] = preg_replace('/^' . $this->swapPre . '(\S+?)/', $this->DBPrefix . '\\1', $parts[$i]); + } + // We only add the table prefix if it does not already exist + elseif (strpos($parts[$i], $this->DBPrefix) !== 0) + { + $parts[$i] = $this->DBPrefix . $parts[$i]; + } + + // Put the parts back together + $item = implode('.', $parts); + } + + if ($protectIdentifiers === true) + { + $item = $this->escapeIdentifiers($item); + } + + return $item . $alias; + } + + // In some cases, especially 'from', we end up running through + // protect_identifiers twice. This algorithm won't work when + // it contains the escapeChar so strip it out. + $item = trim($item, $this->escapeChar); + + // Is there a table prefix? If not, no need to insert it + if ($this->DBPrefix !== '') + { + // Verify table prefix and replace if necessary + if ($this->swapPre !== '' && strpos($item, $this->swapPre) === 0) + { + $item = preg_replace('/^' . $this->swapPre . '(\S+?)/', $this->DBPrefix . '\\1', $item); + } + // Do we prefix an item with no segments? + elseif ($prefixSingle === true && strpos($item, $this->DBPrefix) !== 0) + { + $item = $this->DBPrefix . $item; + } + } + + if ($protectIdentifiers === true && ! in_array($item, $this->reservedIdentifiers)) + { + $item = $this->escapeIdentifiers($item); + } + + return $item . $alias; + } + + //-------------------------------------------------------------------- + + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @param mixed + * + * @return mixed + */ + public function escapeIdentifiers($item) + { + if ($this->escapeChar === '' OR empty($item) OR in_array($item, $this->reservedIdentifiers)) + { + return $item; + } + elseif (is_array($item)) + { + foreach ($item as $key => $value) + { + $item[$key] = $this->escapeIdentifiers($value); + } + + return $item; + } + // Avoid breaking functions and literal values inside queries + elseif (ctype_digit($item) OR $item[0] === "'" OR ( $this->escapeChar !== '"' && $item[0] === '"') OR + strpos($item, '(') !== false + ) + { + return $item; + } + + static $preg_ec = []; + + if (empty($preg_ec)) + { + if (is_array($this->escapeChar)) + { + $preg_ec = [ + preg_quote($this->escapeChar[0], '/'), + preg_quote($this->escapeChar[1], '/'), + $this->escapeChar[0], + $this->escapeChar[1], + ]; + } + else + { + $preg_ec[0] = $preg_ec[1] = preg_quote($this->escapeChar, '/'); + $preg_ec[2] = $preg_ec[3] = $this->escapeChar; + } + } + + foreach ($this->reservedIdentifiers as $id) + { + if (strpos($item, '.' . $id) !== false) + { + return preg_replace('/' . $preg_ec[0] . '?([^' . $preg_ec[1] . '\.]+)' . $preg_ec[1] . '?\./i', $preg_ec[2] . '$1' . $preg_ec[3] . '.', $item); + } + } + + return preg_replace('/' . $preg_ec[0] . '?([^' . $preg_ec[1] . '\.]+)' . $preg_ec[1] . '?(\.)?/i', $preg_ec[2] . '$1' . $preg_ec[3] . '$2', $item); + } + + //-------------------------------------------------------------------- + + /** + * DB Prefix + * + * Prepends a database prefix if one exists in configuration + * + * @param string $table the table + * + * @return string + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function prefixTable($table = '') + { + if ($table === '') + { + throw new DatabaseException('A table name is required for that operation.'); + } + + return $this->DBPrefix . $table; + } + + //-------------------------------------------------------------------- + + /** + * Set DB Prefix + * + * Set's the DB Prefix to something new without needing to reconnect + * + * @param string $prefix The prefix + * + * @return string + */ + public function setPrefix($prefix = '') + { + return $this->DBPrefix = $prefix; + } + + //-------------------------------------------------------------------- + + /** + * Returns the total number of rows affected by this query. + * + * @return mixed + */ + abstract public function affectedRows(): int; + + //-------------------------------------------------------------------- + + /** + * "Smart" Escape String + * + * Escapes data based on type. + * Sets boolean and null types + * + * @param $str + * + * @return mixed + */ + public function escape($str) + { + if (is_array($str)) + { + $str = array_map([&$this, 'escape'], $str); + + return $str; + } + else if (is_string($str) OR ( is_object($str) && method_exists($str, '__toString'))) + { + return "'" . $this->escapeString($str) . "'"; + } + else if (is_bool($str)) + { + return ($str === false) ? 0 : 1; + } + else if (is_numeric($str) && $str < 0) + { + return "'{$str}'"; + } + else if ($str === null) + { + return 'NULL'; + } + + return $str; + } + + //-------------------------------------------------------------------- + + /** + * Escape String + * + * @param string|string[] $str Input string + * @param bool $like Whether or not the string will be used in a LIKE condition + * @return string + */ + public function escapeString($str, $like = FALSE) + { + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = $this->escapeString($val, $like); + } + + return $str; + } + + $str = $this->_escapeString($str); + + // escape LIKE condition wildcards + if ($like === true) + { + return str_replace( + [$this->likeEscapeChar, '%', '_'], [$this->likeEscapeChar . $this->likeEscapeChar, $this->likeEscapeChar . '%', $this->likeEscapeChar . '_'], $str + ); + } + + return $str; + } + + //-------------------------------------------------------------------- + + /** + * Escape LIKE String + * + * Calls the individual driver for platform + * specific escaping for LIKE conditions + * + * @param string|string[] + * @return mixed + */ + public function escapeLikeString($str) + { + return $this->escapeString($str, TRUE); + } + + //-------------------------------------------------------------------- + + /** + * Platform independent string escape. + * + * Will likely be overridden in child classes. + * + * @param string $str + * + * @return string + */ + protected function _escapeString(string $str): string + { + return str_replace("'", "''", remove_invisible_characters($str, false)); + } + + //-------------------------------------------------------------------- + + /** + * This function enables you to call PHP database functions that are not natively included + * in CodeIgniter, in a platform independent manner. + * + * @param string $functionName + * @param array ...$params + * + * @return bool + * @throws DatabaseException + */ + public function callFunction(string $functionName, ...$params) + { + $driver = ($this->DBDriver === 'postgre' ? 'pg' : strtolower($this->DBDriver)) . '_'; + + if (FALSE === strpos($driver, $functionName)) + { + $functionName = $driver . $functionName; + } + + if ( ! function_exists($functionName)) + { + if ($this->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + + return false; + } + + return $functionName(...$params); + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + // META Methods + //-------------------------------------------------------------------- + + /** + * Returns an array of table names + * + * @param bool $constrain_by_prefix = FALSE + * @return bool|array + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function listTables($constrain_by_prefix = FALSE) + { + // Is there a cached result? + if (isset($this->dataCache['table_names']) && count($this->dataCache['table_names'])) + { + return $this->dataCache['table_names']; + } + + if (FALSE === ($sql = $this->_listTables($constrain_by_prefix))) + { + if ($this->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + return false; + } + + $this->dataCache['table_names'] = []; + $query = $this->query($sql); + + foreach ($query->getResultArray() as $row) + { + // Do we know from which column to get the table name? + if ( ! isset($key)) + { + if (isset($row['table_name'])) + { + $key = 'table_name'; + } + elseif (isset($row['TABLE_NAME'])) + { + $key = 'TABLE_NAME'; + } + else + { + /* We have no other choice but to just get the first element's key. + * Due to array_shift() accepting its argument by reference, if + * E_STRICT is on, this would trigger a warning. So we'll have to + * assign it first. + */ + $key = array_keys($row); + $key = array_shift($key); + } + } + + $this->dataCache['table_names'][] = $row[$key]; + } + + return $this->dataCache['table_names']; + } + + //-------------------------------------------------------------------- + + /** + * Determine if a particular table exists + * + * @param string $table_name + * @return bool + */ + public function tableExists($table_name) + { + return in_array($this->protectIdentifiers($table_name, TRUE, FALSE, FALSE), $this->listTables()); + } + + //-------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * @param string $table Table name + * + * @return array|false + * @throws DatabaseException + */ + public function getFieldNames($table) + { + // Is there a cached result? + if (isset($this->dataCache['field_names'][$table])) + { + return $this->dataCache['field_names'][$table]; + } + + if (empty($this->connID)) + { + $this->initialize(); + } + + if (FALSE === ($sql = $this->_listColumns($table))) + { + if ($this->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + return false; + } + + $query = $this->query($sql); + $this->dataCache['field_names'][$table] = []; + + foreach ($query->getResultArray() as $row) + { + // Do we know from where to get the column's name? + if ( ! isset($key)) + { + if (isset($row['column_name'])) + { + $key = 'column_name'; + } + elseif (isset($row['COLUMN_NAME'])) + { + $key = 'COLUMN_NAME'; + } + else + { + // We have no other choice but to just get the first element's key. + $key = key($row); + } + } + + $this->dataCache['field_names'][$table][] = $row[$key]; + } + + return $this->dataCache['field_names'][$table]; + } + + //-------------------------------------------------------------------- + + /** + * Determine if a particular field exists + * + * @param string + * @param string + * @return bool + */ + public function fieldExists($fieldName, $tableName) + { + return in_array($fieldName, $this->getFieldNames($tableName)); + } + + //-------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table the table name + * @return array|false + */ + public function getFieldData(string $table) + { + $fields = $this->_fieldData($this->protectIdentifiers($table, true, false, false)); + + return $fields ?? false; + } + + //-------------------------------------------------------------------- + + /** + * Returns an object with key data + * + * @param string $table the table name + * @return array + */ + public function getIndexData(string $table) + { + $fields = $this->_indexData($this->protectIdentifiers($table, true, false, false)); + + return $fields ?? false; + } + + //-------------------------------------------------------------------- + + /** + * Returns an object with foreign key data + * + * @param string $table the table name + * @return array + */ + public function getForeignKeyData(string $table) + { + $fields = $this->_foreignKeyData($this->protectIdentifiers($table, true, false, false)); + + return $fields ?? false; + } + + //-------------------------------------------------------------------- + + /** + * Allows the engine to be set into a mode where queries are not + * actually executed, but they are still generated, timed, etc. + * + * This is primarily used by the prepared query functionality. + * + * @param bool $pretend + * + * @return $this + */ + public function pretend(bool $pretend = true) + { + $this->pretend = $pretend; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the last error code and message. + * + * Must return an array with keys 'code' and 'message': + * + * return ['code' => null, 'message' => null); + * + * @return array + */ + abstract public function error(); + + //-------------------------------------------------------------------- + + /** + * Insert ID + * + * @return int + */ + abstract public function insertID(); + + //-------------------------------------------------------------------- + + /** + * Generates the SQL for listing tables in a platform-dependent manner. + * + * @param bool $constrainByPrefix + * + * @return string + */ + abstract protected function _listTables($constrainByPrefix = false): string; + + //-------------------------------------------------------------------- + + /** + * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string $table + * + * @return string + */ + abstract protected function _listColumns(string $table = ''): string; + + //-------------------------------------------------------------------- +} diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php new file mode 100644 index 0000000..25f160a --- /dev/null +++ b/system/Database/BasePreparedQuery.php @@ -0,0 +1,260 @@ +db = & $db; + } + + //-------------------------------------------------------------------- + + /** + * Prepares the query against the database, and saves the connection + * info necessary to execute the query later. + * + * NOTE: This version is based on SQL code. Child classes should + * override this method. + * + * @param string $sql + * @param array $options Passed to the connection's prepare statement. + * @param string $queryClass + * + * @return mixed + */ + public function prepare(string $sql, array $options = [], $queryClass = 'CodeIgniter\\Database\\Query') + { + // We only supports positional placeholders (?) + // in order to work with the execute method below, so we + // need to replace our named placeholders (:name) + $sql = preg_replace('/:[^\s,)]+/', '?', $sql); + + /** + * @var \CodeIgniter\Database\Query $query + */ + $query = new $queryClass($this->db); + + $query->setQuery($sql); + + if ( ! empty($this->db->swapPre) && ! empty($this->db->DBPrefix)) + { + $query->swapPrefix($this->db->DBPrefix, $this->db->swapPre); + } + + $this->query = $query; + + return $this->_prepare($query->getOriginalQuery(), $options); + } + + //-------------------------------------------------------------------- + + /** + * The database-dependent portion of the prepare statement. + * + * @param string $sql + * @param array $options Passed to the connection's prepare statement. + * + * @return mixed + */ + abstract public function _prepare(string $sql, array $options = []); + + //-------------------------------------------------------------------- + + /** + * Takes a new set of data and runs it against the currently + * prepared query. Upon success, will return a Results object. + * + * @param array $data + * + * @return ResultInterface + */ + public function execute(...$data) + { + // Execute the Query. + $startTime = microtime(true); + + $result = $this->_execute($data); + + // Update our query object + $query = clone $this->query; + $query->setBinds($data); + + $query->setDuration($startTime); + + // Let others do something with this query + Events::trigger('DBQuery', $query); + + // Return a result object + $resultClass = str_replace('PreparedQuery', 'Result', get_class($this)); + + $resultID = $this->_getResult(); + + return new $resultClass($this->db->connID, $resultID); + } + + //-------------------------------------------------------------------- + + /** + * The database dependant version of the execute method. + * + * @param array $data + * + * @return ResultInterface + */ + abstract public function _execute($data); + + //-------------------------------------------------------------------- + + /** + * Returns the result object for the prepared query. + * + * @return mixed + */ + abstract public function _getResult(); + + //-------------------------------------------------------------------- + + /** + * Explicity closes the statement. + */ + public function close() + { + if ( ! is_object($this->statement)) + { + return; + } + + $this->statement->close(); + } + + //-------------------------------------------------------------------- + + /** + * Returns the SQL that has been prepared. + * + * @return string + */ + public function getQueryString(): string + { + if ( ! $this->query instanceof QueryInterface) + { + throw new \BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.'); + } + + return $this->query->getQuery(); + } + + //-------------------------------------------------------------------- + + /** + * A helper to determine if any error exists. + * + * @return bool + */ + public function hasError() + { + return ! empty($this->errorString); + } + + //-------------------------------------------------------------------- + + /** + * Returns the error code created while executing this statement. + * + * @return int + */ + public function getErrorCode(): int + { + return $this->errorCode; + } + + //-------------------------------------------------------------------- + + /** + * Returns the error message created while executing this statement. + * + * @return string + */ + public function getErrorMessage(): string + { + return $this->errorString; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/BaseResult.php b/system/Database/BaseResult.php new file mode 100644 index 0000000..7812954 --- /dev/null +++ b/system/Database/BaseResult.php @@ -0,0 +1,617 @@ +connID = $connID; + $this->resultID = $resultID; + } + + //-------------------------------------------------------------------- + + /** + * Retrieve the results of the query. Typically an array of + * individual data rows, which can be either an 'array', an + * 'object', or a custom class name. + * + * @param string $type The row type. Either 'array', 'object', or a class name to use + * + * @return mixed + */ + public function getResult($type = 'object'): array + { + if ($type === 'array') + { + return $this->getResultArray(); + } + elseif ($type === 'object') + { + return $this->getResultObject(); + } + + return $this->getCustomResultObject($type); + } + + //-------------------------------------------------------------------- + + /** + * Returns the results as an array of custom objects. + * + * @param string $className The name of the class to use. + * + * @return mixed + */ + public function getCustomResultObject(string $className) + { + if (isset($this->customResultObject[$className])) + { + return $this->customResultObject[$className]; + } + elseif ( ! $this->resultID OR $this->numRows === 0) + { + return []; + } + + // Don't fetch the result set again if we already have it + $_data = null; + if (($c = count($this->resultArray)) > 0) + { + $_data = 'result_array'; + } + elseif (($c = count($this->resultObject)) > 0) + { + $_data = 'result_object'; + } + + if ($_data !== null) + { + for ($i = 0; $i < $c; $i ++ ) + { + $this->customResultObject[$className][$i] = new $className(); + + foreach ($this->{$_data}[$i] as $key => $value) + { + $this->customResultObject[$className][$i]->$key = $value; + } + } + + return $this->customResultObject[$className]; + } + + is_null($this->rowData) OR $this->dataSeek(0); + $this->customResultObject[$className] = []; + + while ($row = $this->fetchObject($className)) + { + $this->customResultObject[$className][] = $row; + } + + return $this->customResultObject[$className]; + } + + //-------------------------------------------------------------------- + + /** + * Returns the results as an array of arrays. + * + * If no results, an empty array is returned. + * + * @return array + */ + public function getResultArray(): array + { + if (! empty($this->resultArray)) + { + return $this->resultArray; + } + + // In the event that query caching is on, the result_id variable + // will not be a valid resource so we'll simply return an empty + // array. + if ( ! $this->resultID OR $this->numRows === 0) + { + return []; + } + + if (($c = count($this->resultObject)) > 0) + { + for ($i = 0; $i < $c; $i ++ ) + { + $this->resultArray[$i] = (array) $this->resultObject[$i]; + } + + return $this->resultArray; + } + + is_null($this->rowData) OR $this->dataSeek(0); + while ($row = $this->fetchAssoc()) + { + $this->resultArray[] = $row; + } + + return $this->resultArray; + } + + //-------------------------------------------------------------------- + + /** + * Returns the results as an array of objects. + * + * If no results, an empty array is returned. + * + * @return array + */ + public function getResultObject(): array + { + if (! empty($this->resultObject)) + { + return $this->resultObject; + } + + // In the event that query caching is on, the result_id variable + // will not be a valid resource so we'll simply return an empty + // array. + if ( ! $this->resultID OR $this->numRows === 0) + { + return []; + } + + if (($c = count($this->resultArray)) > 0) + { + for ($i = 0; $i < $c; $i ++ ) + { + $this->resultObject[$i] = (object) $this->resultArray[$i]; + } + + return $this->resultObject; + } + + is_null($this->rowData) OR $this->dataSeek(0); + while ($row = $this->fetchObject()) + { + $this->resultObject[] = $row; + } + + return $this->resultObject; + } + + //-------------------------------------------------------------------- + + /** + * Wrapper object to return a row as either an array, an object, or + * a custom class. + * + * If row doesn't exist, returns null. + * + * @param int $n The index of the results to return + * @param string $type The type of result object. 'array', 'object' or class name. + * + * @return mixed + */ + public function getRow($n = 0, $type = 'object') + { + if ( ! is_numeric($n)) + { + // We cache the row data for subsequent uses + is_array($this->rowData) OR $this->row_data = $this->getRowArray(0); + + // array_key_exists() instead of isset() to allow for NULL values + if (empty($this->rowData) OR ! array_key_exists($n, $this->rowData)) + { + return null; + } + + return $this->rowData[$n]; + } + + if ($type === 'object') + { + return $this->getRowObject($n); + } + elseif ($type === 'array') + { + return $this->getRowArray($n); + } + else + { + return $this->getCustomRowObject($n, $type); + } + } + + //-------------------------------------------------------------------- + + /** + * Returns a row as a custom class instance. + * + * If row doesn't exists, returns null. + * + * @param int $n + * @param string $className + * + * @return mixed + */ + public function getCustomRowObject($n, string $className) + { + isset($this->customResultObject[$className]) OR $this->customResultObject($className); + + if (empty($this->customResultObject[$className])) + { + return null; + } + + if ($n !== $this->currentRow && isset($this->customResultObject[$className][$n])) + { + $this->current_row = $n; + } + + return $this->customResultObject[$className][$this->currentRow]; + } + + //-------------------------------------------------------------------- + + /** + * Returns a single row from the results as an array. + * + * If row doesn't exist, returns null. + * + * @param int $n + * + * @return mixed + */ + public function getRowArray($n = 0) + { + $result = $this->getResultArray(); + if (empty($result)) + { + return null; + } + + if ($n !== $this->currentRow && isset($result[$n])) + { + $this->currentRow = $n; + } + + return $result[$this->currentRow]; + } + + //-------------------------------------------------------------------- + + /** + * Returns a single row from the results as an object. + * + * If row doesn't exist, returns null. + * + * @param int $n + * + * @return mixed + */ + public function getRowObject($n = 0) + { + $result = $this->getResultObject(); + if (empty($result)) + { + return null; + } + + if ($n !== $this->customResultObject && isset($result[$n])) + { + $this->currentRow = $n; + } + + return $result[$this->currentRow]; + } + + //-------------------------------------------------------------------- + + /** + * Assigns an item into a particular column slot. + * + * @param $key + * @param null $value + * + * @return mixed + */ + public function setRow($key, $value = null) + { + // We cache the row data for subsequent uses + if ( ! is_array($this->rowData)) + { + $this->row_data = $this->getRowArray(0); + } + + if (is_array($key)) + { + foreach ($key as $k => $v) + { + $this->rowData[$k] = $v; + } + + return; + } + + if ($key !== '' && $value !== null) + { + $this->rowData[$key] = $value; + } + } + + //-------------------------------------------------------------------- + + /** + * Returns the "first" row of the current results. + * + * @param string $type + * + * @return mixed + */ + public function getFirstRow($type = 'object') + { + $result = $this->getResult($type); + + return (empty($result)) ? null : $result[0]; + } + + //-------------------------------------------------------------------- + + /** + * Returns the "last" row of the current results. + * + * @param string $type + * + * @return mixed + */ + public function getLastRow($type = 'object') + { + $result = $this->getResult($type); + + return (empty($result)) ? null : $result[count($result) - 1]; + } + + //-------------------------------------------------------------------- + + /** + * Returns the "next" row of the current results. + * + * @param string $type + * + * @return mixed + */ + public function getNextRow($type = 'object') + { + $result = $this->getResult($type); + if (empty($result)) + { + return null; + } + + return isset($result[$this->currentRow + 1]) ? $result[++ $this->currentRow] : null; + } + + //-------------------------------------------------------------------- + + /** + * Returns the "previous" row of the current results. + * + * @param string $type + * + * @return mixed + */ + public function getPreviousRow($type = 'object') + { + $result = $this->getResult($type); + if (empty($result)) + { + return null; + } + + if (isset($result[$this->currentRow - 1])) + { + -- $this->currentRow; + } + + return $result[$this->currentRow]; + } + + //-------------------------------------------------------------------- + + /** + * Returns an unbuffered row and move the pointer to the next row. + * + * @param string $type + * + * @return mixed + */ + public function getUnbufferedRow($type = 'object') + { + if ($type === 'array') + { + return $this->fetchAssoc(); + } + elseif ($type === 'object') + { + return $this->fetchObject(); + } + + return $this->fetchObject($type); + } + + //-------------------------------------------------------------------- + + /** + * Gets the number of fields in the result set. + * + * @return int + */ + abstract public function getFieldCount(): int; + + //-------------------------------------------------------------------- + + /** + * Generates an array of column names in the result set. + * + * @return array + */ + abstract public function getFieldNames(): array; + + //-------------------------------------------------------------------- + + /** + * Generates an array of objects representing field meta-data. + * + * @return array + */ + abstract public function getFieldData(): array; + + //-------------------------------------------------------------------- + + /** + * Frees the current result. + * + * @return mixed + */ + abstract public function freeResult(); + + //-------------------------------------------------------------------- + + /** + * Moves the internal pointer to the desired offset. This is called + * internally before fetching results to make sure the result set + * starts at zero. + * + * @param int $n + * + * @return mixed + */ + abstract public function dataSeek($n = 0); + + //-------------------------------------------------------------------- + + /** + * Returns the result set as an array. + * + * Overridden by driver classes. + * + * @return array + */ + abstract protected function fetchAssoc(); + + //-------------------------------------------------------------------- + + /** + * Returns the result set as an object. + * + * Overridden by child classes. + * + * @param string $className + * + * @return object + */ + abstract protected function fetchObject($className = 'stdClass'); + + //-------------------------------------------------------------------- +} diff --git a/system/Database/BaseUtils.php b/system/Database/BaseUtils.php new file mode 100644 index 0000000..b821140 --- /dev/null +++ b/system/Database/BaseUtils.php @@ -0,0 +1,443 @@ +db = & $db; + } + + //-------------------------------------------------------------------- + + /** + * List databases + * + * @return array|bool + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function listDatabases() + { + // Is there a cached result? + if (isset($this->db->dataCache['db_names'])) + { + return $this->db->dataCache['db_names']; + } + elseif ($this->listDatabases === FALSE) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('Unsupported feature of the database platform you are using.'); + } + return false; + } + + $this->db->dataCache['db_names'] = []; + + $query = $this->db->query($this->listDatabases); + if ($query === FALSE) + { + return $this->db->dataCache['db_names']; + } + + for ($i = 0, $query = $query->getResultArray(), $c = count($query); $i < $c; $i ++ ) + { + $this->db->dataCache['db_names'][] = current($query[$i]); + } + + return $this->db->dataCache['db_names']; + } + + //-------------------------------------------------------------------- + + /** + * Determine if a particular database exists + * + * @param string $database_name + * @return bool + */ + public function databaseExists($database_name) + { + return in_array($database_name, $this->listDatabases()); + } + + //-------------------------------------------------------------------- + + /** + * Optimize Table + * + * @param string $table_name + * @return bool|mixed + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function optimizeTable($table_name) + { + if ($this->optimizeTable === FALSE) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('Unsupported feature of the database platform you are using.'); + } + return false; + } + + $query = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($table_name))); + if ($query !== FALSE) + { + $query = $query->getResultArray(); + return current($query); + } + + return FALSE; + } + + //-------------------------------------------------------------------- + + /** + * Optimize Database + * + * @return mixed + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function optimizeDatabase() + { + if ($this->optimizeTable === FALSE) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('Unsupported feature of the database platform you are using.'); + } + return false; + } + + $result = []; + foreach ($this->db->listTables() as $table_name) + { + $res = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($table_name))); + if (is_bool($res)) + { + return $res; + } + + // Build the result array... + $res = $res->getResultArray(); + $res = current($res); + $key = str_replace($this->db->database . '.', '', current($res)); + $keys = array_keys($res); + unset($res[$keys[0]]); + + $result[$key] = $res; + } + + return $result; + } + + //-------------------------------------------------------------------- + + /** + * Repair Table + * + * @param string $table_name + * @return mixed + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function repairTable($table_name) + { + if ($this->repairTable === FALSE) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('Unsupported feature of the database platform you are using.'); + } + return false; + } + + $query = $this->db->query(sprintf($this->repairTable, $this->db->escapeIdentifiers($table_name))); + if (is_bool($query)) + { + return $query; + } + + $query = $query->getResultArray(); + return current($query); + } + + //-------------------------------------------------------------------- + + /** + * Generate CSV from a query result object + * + * @param ResultInterface $query Query result object + * @param string $delim Delimiter (default: ,) + * @param string $newline Newline character (default: \n) + * @param string $enclosure Enclosure (default: ") + * + * @return string + */ + public function getCSVFromResult(ResultInterface $query, $delim = ',', $newline = "\n", $enclosure = '"') + { + $out = ''; + // First generate the headings from the table column names + foreach ($query->getFieldNames() as $name) + { + $out .= $enclosure . str_replace($enclosure, $enclosure . $enclosure, $name) . $enclosure . $delim; + } + + $out = substr($out, 0, -strlen($delim)) . $newline; + + // Next blast through the result array and build out the rows + while ($row = $query->getUnbufferedRow('array')) + { + $line = []; + foreach ($row as $item) + { + $line[] = $enclosure . str_replace($enclosure, $enclosure . $enclosure, $item) . $enclosure; + } + $out .= implode($delim, $line) . $newline; + } + + return $out; + } + + //-------------------------------------------------------------------- + + /** + * Generate XML data from a query result object + * + * @param ResultInterface $query Query result object + * @param array $params Any preferences + * + * @return string + */ + public function getXMLFromResult(ResultInterface $query, $params = []) + { + // Set our default values + foreach (['root' => 'root', 'element' => 'element', 'newline' => "\n", 'tab' => "\t"] as $key => $val) + { + if ( ! isset($params[$key])) + { + $params[$key] = $val; + } + } + + // Create variables for convenience + extract($params); + + // Load the xml helper +// get_instance()->load->helper('xml'); + // Generate the result + $xml = '<' . $root . '>' . $newline; + while ($row = $query->getUnbufferedRow()) + { + $xml .= $tab . '<' . $element . '>' . $newline; + foreach ($row as $key => $val) + { + $xml .= $tab . $tab . '<' . $key . '>' . xml_convert($val) . '' . $newline; + } + $xml .= $tab . '' . $newline; + } + + return $xml . '' . $newline; + } + + //-------------------------------------------------------------------- + + /** + * Database Backup + * + * @param array $params + * @return mixed + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function backup($params = []) + { + // If the parameters have not been submitted as an + // array then we know that it is simply the table + // name, which is a valid short cut. + if (is_string($params)) + { + $params = ['tables' => $params]; + } + + // Set up our default preferences + $prefs = [ + 'tables' => [], + 'ignore' => [], + 'filename' => '', + 'format' => 'gzip', // gzip, zip, txt + 'add_drop' => TRUE, + 'add_insert' => TRUE, + 'newline' => "\n", + 'foreign_key_checks' => TRUE + ]; + + // Did the user submit any preferences? If so set them.... + if (! empty($params)) + { + foreach ($prefs as $key => $val) + { + if (isset($params[$key])) + { + $prefs[$key] = $params[$key]; + } + } + } + + // Are we backing up a complete database or individual tables? + // If no table names were submitted we'll fetch the entire table list + if (empty($prefs['tables'])) + { + $prefs['tables'] = $this->db->listTables(); + } + + // Validate the format + if ( ! in_array($prefs['format'], ['gzip', 'zip', 'txt'], TRUE)) + { + $prefs['format'] = 'txt'; + } + + // Is the encoder supported? If not, we'll either issue an + // error or use plain text depending on the debug settings + if (($prefs['format'] === 'gzip' && ! function_exists('gzencode')) + OR ( $prefs['format'] === 'zip' && ! function_exists('gzcompress'))) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('The file compression format you chose is not supported by your server.'); + } + + $prefs['format'] = 'txt'; + } + + // Was a Zip file requested? + if ($prefs['format'] === 'zip') + { + // Set the filename if not provided (only needed with Zip files) + if ($prefs['filename'] === '') + { + $prefs['filename'] = (count($prefs['tables']) === 1 ? $prefs['tables'] : $this->db->database) + . date('Y-m-d_H-i', time()) . '.sql'; + } + else + { + // If they included the .zip file extension we'll remove it + if (preg_match('|.+?\.zip$|', $prefs['filename'])) + { + $prefs['filename'] = str_replace('.zip', '', $prefs['filename']); + } + + // Tack on the ".sql" file extension if needed + if ( ! preg_match('|.+?\.sql$|', $prefs['filename'])) + { + $prefs['filename'] .= '.sql'; + } + } + + // Load the Zip class and output it +// $CI =& get_instance(); +// $CI->load->library('zip'); +// $CI->zip->add_data($prefs['filename'], $this->_backup($prefs)); +// return $CI->zip->get_zip(); + } + elseif ($prefs['format'] === 'txt') // Was a text file requested? + { + return $this->_backup($prefs); + } + elseif ($prefs['format'] === 'gzip') // Was a Gzip file requested? + { + return gzencode($this->_backup($prefs)); + } + + return; + } + + //-------------------------------------------------------------------- + + /** + * Platform dependent version of the backup function. + * + * @param array|null $prefs + * + * @return mixed + */ + abstract public function _backup(array $prefs = null); + + //-------------------------------------------------------------------- +} diff --git a/system/Database/Config.php b/system/Database/Config.php new file mode 100644 index 0000000..b282987 --- /dev/null +++ b/system/Database/Config.php @@ -0,0 +1,232 @@ +defaultGroup; + } + + if (is_string($group) && ! isset($config->$group) && $group != 'custom') + { + throw new \InvalidArgumentException($group . ' is not a valid database connection group.'); + } + + if ($getShared && isset(self::$instances[$group])) + { + return self::$instances[$group]; + } + + self::ensureFactory(); + + if (isset($config->$group)) + { + $config = $config->$group; + } + + $connection = self::$factory->load($config, $group); + + self::$instances[$group] = & $connection; + + return $connection; + } + + //-------------------------------------------------------------------- + + /** + * Returns an array of all db connections currently made. + * + * @return array + */ + public static function getConnections() + { + return self::$instances; + } + + //-------------------------------------------------------------------- + + /** + * Loads and returns an instance of the Forge for the specified + * database group, and loads the group if it hasn't been loaded yet. + * + * @param string|null $group + * + * @return Forge + */ + public static function forge(string $group = null) + { + $config = new \Config\Database(); + + self::ensureFactory(); + + if (empty($group)) + { + $group = ENVIRONMENT == 'testing' ? 'tests' : $config->defaultGroup; + } + + if ( ! isset($config->$group)) + { + throw new \InvalidArgumentException($group . ' is not a valid database connection group.'); + } + + if ( ! isset(self::$instances[$group])) + { + $db = self::connect($group); + } + else + { + $db = self::$instances[$group]; + } + + return self::$factory->loadForge($db); + } + + //-------------------------------------------------------------------- + + /** + * Returns a new instance of the Database Utilities class. + * + * @param string|null $group + * + * @return BaseUtils + */ + public static function utils(string $group = null) + { + $config = new \Config\Database(); + + self::ensureFactory(); + + if (empty($group)) + { + $group = $config->defaultGroup; + } + + if ( ! isset($config->group)) + { + throw new \InvalidArgumentException($group . ' is not a valid database connection group.'); + } + + if ( ! isset(self::$instances[$group])) + { + $db = self::connect($group); + } + else + { + $db = self::$instances[$group]; + } + + return self::$factory->loadUtils($db); + } + + //-------------------------------------------------------------------- + + /** + * Returns a new instance of the Database Seeder. + * + * @param string|null $group + * + * @return Seeder + */ + public static function seeder(string $group = null) + { + $config = new \Config\Database(); + + return new Seeder($config, self::connect($group)); + } + + //-------------------------------------------------------------------- + + /** + * Ensures the database Connection Manager/Factory is loaded and ready to use. + */ + protected static function ensureFactory() + { + if (self::$factory instanceof \CodeIgniter\Database\Database) + { + return; + } + + self::$factory = new \CodeIgniter\Database\Database(); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/ConnectionInterface.php b/system/Database/ConnectionInterface.php new file mode 100644 index 0000000..67ade6d --- /dev/null +++ b/system/Database/ConnectionInterface.php @@ -0,0 +1,222 @@ +connections[$alias] = $class; + + return $this->connections[$alias]; + } + + //-------------------------------------------------------------------- + + /** + * Creates a new Forge instance for the current database type. + * + * @param ConnectionInterface|BaseConnection $db + * + * @return mixed + */ + public function loadForge(ConnectionInterface $db) + { + $className = strpos($db->DBDriver, '\\') === false ? '\CodeIgniter\Database\\' . $db->DBDriver . '\\Forge' : $db->DBDriver . '\\Connection'; + + // Make sure a connection exists + if ( ! $db->connID) + { + $db->initialize(); + } + + $class = new $className($db); + + return $class; + } + + //-------------------------------------------------------------------- + + /** + * Loads the Database Utilities class. + * + * @param ConnectionInterface|BaseConnection $db + * + * @return mixed + */ + public function loadUtils(ConnectionInterface $db) + { + $className = strpos($db->DBDriver, '\\') === false ? '\CodeIgniter\Database\\' . $db->DBDriver . '\\Utils' : $db->DBDriver . '\\Utils'; + + // Make sure a connection exists + if ( ! $db->connID) + { + $db->initialize(); + } + + $class = new $className($db); + + return $class; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/Exceptions/DatabaseException.php b/system/Database/Exceptions/DatabaseException.php new file mode 100644 index 0000000..5249755 --- /dev/null +++ b/system/Database/Exceptions/DatabaseException.php @@ -0,0 +1,48 @@ +db = & $db; + } + + //-------------------------------------------------------------------- + + /** + * Provides access to the forge's current database connection. + * + * @return ConnectionInterface + */ + public function getConnection() + { + return $this->db; + } + + //-------------------------------------------------------------------- + + /** + * Create database + * + * @param string $db_name + * + * @return bool + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function createDatabase($db_name) + { + if ($this->createDatabaseStr === false) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + + return false; + } + elseif ( ! $this->db->query(sprintf($this->createDatabaseStr, $db_name, $this->db->charset, $this->db->DBCollat)) + ) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('Unable to drop the specified database.'); + } + + return false; + } + + if ( ! empty($this->db->dataCache['db_names'])) + { + $this->db->dataCache['db_names'][] = $db_name; + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Drop database + * + * @param string $db_name + * + * @return bool + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function dropDatabase($db_name) + { + if ($this->dropDatabaseStr === false) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + + return false; + } + elseif ( ! $this->db->query(sprintf($this->dropDatabaseStr, $db_name))) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('Unable to drop the specified database.'); + } + + return false; + } + + if ( ! empty($this->db->dataCache['db_names'])) + { + $key = array_search(strtolower($db_name), array_map('strtolower', $this->db->dataCache['db_names']), true); + if ($key !== false) + { + unset($this->db->dataCache['db_names'][$key]); + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Add Key + * + * @param string $key + * @param bool $primary + * + * @return Forge + */ + public function addKey($key, $primary = false) + { + if ($primary === true) + { + foreach ((array) $key as $one) + { + $this->primaryKeys[] = $one; + } + } + else + { + $this->keys[] = $key; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Add Field + * + * @param array $field + * + * @return Forge + */ + public function addField($field) + { + if (is_string($field)) + { + if ($field === 'id') + { + $this->addField([ + 'id' => [ + 'type' => 'INT', + 'constraint' => 9, + 'auto_increment' => true, + ], + ]); + $this->addKey('id', true); + } + else + { + if (strpos($field, ' ') === false) + { + throw new \InvalidArgumentException('Field information is required for that operation.'); + } + + $this->fields[] = $field; + } + } + + if (is_array($field)) + { + $this->fields = array_merge($this->fields, $field); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Add Foreign Key + * + * @param array $field + * + * @return \CodeIgniter\Database\Forge + */ + public function addForeignKey($fieldName= '',$tableName = '', $tableField = '', $onUpdate = false, $onDelete = false) + { + + if( ! isset($this->fields[$fieldName])) + { + throw new \RuntimeException('Field "'.$fieldName.'" not exist'); + } + + $this->foreignKeys[$fieldName] = [ + 'table' => $tableName, + 'field' => $tableField, + 'onDelete' => $onDelete, + 'onUpdate' => $onUpdate + ]; + + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Foreign Key Drop + * + * @param string $table Table name + * @param string $foreign_name Foreign name + * + * @return bool + */ + public function dropForeignKey($table, $foreign_name) + { + + $sql = sprintf($this->dropConstraintStr,$this->db->escapeIdentifiers($this->db->DBPrefix.$table),$this->db->escapeIdentifiers($this->db->DBPrefix.$foreign_name)); + + if ($sql === false) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + + return false; + } + + return $this->db->query($sql); + } + + //-------------------------------------------------------------------- + + /** + * Create Table + * + * @param string $table Table name + * @param bool $if_not_exists Whether to add IF NOT EXISTS condition + * @param array $attributes Associative array of table attributes + * + * @return bool + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function createTable($table, $if_not_exists = false, array $attributes = []) + { + if ($table === '') + { + throw new \InvalidArgumentException('A table name is required for that operation.'); + } + else + { + $table = $this->db->DBPrefix . $table; + } + + if (count($this->fields) === 0) + { + throw new \RuntimeException('Field information is required.'); + } + + $sql = $this->_createTable($table, $if_not_exists, $attributes); + + if (is_bool($sql)) + { + $this->_reset(); + if ($sql === false) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + + return false; + } + } + + if (($result = $this->db->query($sql)) !== false) + { + empty($this->db->dataCache['table_names']) OR $this->db->dataCache['table_names'][] = $table; + + // Most databases don't support creating indexes from within the CREATE TABLE statement + if ( ! empty($this->keys)) + { + for ($i = 0, $sqls = $this->_processIndexes($table), $c = count($sqls); $i < $c; $i ++ ) + { + $this->db->query($sqls[$i]); + } + } + } + + $this->_reset(); + + return $result; + } + + //-------------------------------------------------------------------- + + /** + * Create Table + * + * @param string $table Table name + * @param bool $if_not_exists Whether to add 'IF NOT EXISTS' condition + * @param array $attributes Associative array of table attributes + * + * @return mixed + */ + protected function _createTable($table, $if_not_exists, $attributes) + { + // For any platforms that don't support Create If Not Exists... + if ($if_not_exists === true && $this->createTableIfStr === false) + { + if ($this->db->tableExists($table)) + { + return true; + } + else + { + $if_not_exists = false; + } + } + + $sql = ($if_not_exists) ? sprintf($this->createTableIfStr, $this->db->escapeIdentifiers($table)) : 'CREATE TABLE'; + + $columns = $this->_processFields(true); + for ($i = 0, $c = count($columns); $i < $c; $i ++ ) + { + $columns[$i] = ($columns[$i]['_literal'] !== false) ? "\n\t" . $columns[$i]['_literal'] : "\n\t" . $this->_processColumn($columns[$i]); + } + + $columns = implode(',', $columns); + + $columns .= $this->_processPrimaryKeys($table); + $columns .= $this->_processForeignKeys($table); + + // Are indexes created from within the CREATE TABLE statement? (e.g. in MySQL) + if ($this->createTableKeys === true) + { + $columns .= $this->_processIndexes($table); + } + + // createTableStr will usually have the following format: "%s %s (%s\n)" + $sql = sprintf($this->createTableStr . '%s', $sql, $this->db->escapeIdentifiers($table), $columns, $this->_createTableAttributes($attributes)); + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * CREATE TABLE attributes + * + * @param array $attributes Associative array of table attributes + * + * @return string + */ + protected function _createTableAttributes($attributes) + { + $sql = ''; + + foreach (array_keys($attributes) as $key) + { + if (is_string($key)) + { + $sql .= ' ' . strtoupper($key) . ' ' . $attributes[$key]; + } + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Drop Table + * + * @param string $table_name Table name + * @param bool $if_exists Whether to add an IF EXISTS condition + * @param bool $cascade Whether to add an CASCADE condition + * + * @return mixed + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function dropTable($table_name, $if_exists = false, $cascade = false) + { + if ($table_name === '') + { + if ($this->db->DBDebug) + { + throw new DatabaseException('A table name is required for that operation.'); + } + + return false; + } + + + // If the prefix is already starting the table name, remove it... + if (! empty($this->db->DBPrefix) && strpos($table_name, $this->db->DBPrefix) === 0) + { + $table_name = substr($table_name, strlen($this->db->DBPrefix)); + } + + if (($query = $this->_dropTable($this->db->DBPrefix . $table_name, $if_exists, $cascade)) === true) + { + return true; + } + + $query = $this->db->query($query); + + // Update table list cache + if ($query && ! empty($this->db->dataCache['table_names'])) + { + $key = array_search(strtolower($this->db->DBPrefix . $table_name), array_map('strtolower', $this->db->dataCache['table_names']), true); + if ($key !== false) + { + unset($this->db->dataCache['table_names'][$key]); + } + } + + return $query; + } + + //-------------------------------------------------------------------- + + /** + * Drop Table + * + * Generates a platform-specific DROP TABLE string + * + * @param string $table Table name + * @param bool $if_exists Whether to add an IF EXISTS condition + * @param bool $cascade Whether to add an CASCADE condition + * + * @return string + */ + protected function _dropTable($table, $if_exists, $cascade) + { + $sql = 'DROP TABLE'; + + if ($if_exists) + { + if ($this->dropTableIfStr === false) + { + if ( ! $this->db->tableExists($table)) + { + return true; + } + } + else + { + $sql = sprintf($this->dropTableIfStr, $this->db->escapeIdentifiers($table)); + } + } + + $sql = $sql . ' ' . $this->db->escapeIdentifiers($table); + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Rename Table + * + * @param string $table_name Old table name + * @param string $new_table_name New table name + * + * @return mixed + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function renameTable($table_name, $new_table_name) + { + if ($table_name === '' OR $new_table_name === '') + { + throw new \InvalidArgumentException('A table name is required for that operation.'); + } + elseif ($this->renameTableStr === false) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + + return false; + } + + $result = $this->db->query(sprintf($this->renameTableStr, $this->db->escapeIdentifiers($this->db->DBPrefix . $table_name), $this->db->escapeIdentifiers($this->db->DBPrefix . $new_table_name)) + ); + + if ($result && ! empty($this->db->dataCache['table_names'])) + { + $key = array_search(strtolower($this->db->DBPrefix . $table_name), array_map('strtolower', $this->db->dataCache['table_names']), true); + if ($key !== false) + { + $this->db->dataCache['table_names'][$key] = $this->db->DBPrefix . $new_table_name; + } + } + + return $result; + } + + //-------------------------------------------------------------------- + + /** + * Column Add + * + * @param string $table Table name + * @param array $field Column definition + * + * @return bool + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function addColumn($table, $field) + { + // Work-around for literal column definitions + is_array($field) OR $field = [$field]; + + foreach (array_keys($field) as $k) + { + $this->addField([$k => $field[$k]]); + } + + $sqls = $this->_alterTable('ADD', $this->db->DBPrefix . $table, $this->_processFields()); + $this->_reset(); + if ($sqls === false) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + + return false; + } + + for ($i = 0, $c = count($sqls); $i < $c; $i ++ ) + { + if ($this->db->query($sqls[$i]) === false) + { + return false; + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Column Drop + * + * @param string $table Table name + * @param string $column_name Column name + * + * @return bool + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function dropColumn($table, $column_name) + { + $sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $column_name); + if ($sql === false) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + + return false; + } + + return $this->db->query($sql); + } + + //-------------------------------------------------------------------- + + /** + * Column Modify + * + * @param string $table Table name + * @param string $field Column definition + * + * @return bool + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function modifyColumn($table, $field) + { + // Work-around for literal column definitions + is_array($field) OR $field = [$field]; + + foreach (array_keys($field) as $k) + { + $this->addField([$k => $field[$k]]); + } + + if (count($this->fields) === 0) + { + throw new \RuntimeException('Field information is required'); + } + + $sqls = $this->_alterTable('CHANGE', $this->db->DBPrefix . $table, $this->_processFields()); + $this->_reset(); + if ($sqls === false) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('This feature is not available for the database you are using.'); + } + + return false; + } + + for ($i = 0, $c = count($sqls); $i < $c; $i ++ ) + { + if ($this->db->query($sqls[$i]) === false) + { + return false; + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * + * @return string|string[] + */ + protected function _alterTable($alter_type, $table, $field) + { + $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table) . ' '; + + // DROP has everything it needs now. + if ($alter_type === 'DROP') + { + return $sql . 'DROP COLUMN ' . $this->db->escapeIdentifiers($field); + } + + $sql .= ($alter_type === 'ADD') ? 'ADD ' : $alter_type . ' COLUMN '; + + $sqls = []; + for ($i = 0, $c = count($field); $i < $c; $i ++ ) + { + $sqls[] = $sql + . ($field[$i]['_literal'] !== false ? $field[$i]['_literal'] : $this->_processColumn($field[$i])); + } + + return $sqls; + } + + //-------------------------------------------------------------------- + + /** + * Process fields + * + * @param bool $create_table + * + * @return array + */ + protected function _processFields($create_table = false) + { + $fields = []; + + foreach ($this->fields as $key => $attributes) + { + if (is_int($key) && ! is_array($attributes)) + { + $fields[] = ['_literal' => $attributes]; + continue; + } + + $attributes = array_change_key_case($attributes, CASE_UPPER); + + if ($create_table === true && empty($attributes['TYPE'])) + { + continue; + } + + isset($attributes['TYPE']) && $this->_attributeType($attributes); + + $field = [ + 'name' => $key, + 'new_name' => isset($attributes['NAME']) ? $attributes['NAME'] : null, + 'type' => isset($attributes['TYPE']) ? $attributes['TYPE'] : null, + 'length' => '', + 'unsigned' => '', + 'null' => '', + 'unique' => '', + 'default' => '', + 'auto_increment' => '', + '_literal' => false, + ]; + + isset($attributes['TYPE']) && $this->_attributeUnsigned($attributes, $field); + + if ($create_table === false) + { + if (isset($attributes['AFTER'])) + { + $field['after'] = $attributes['AFTER']; + } + elseif (isset($attributes['FIRST'])) + { + $field['first'] = (bool) $attributes['FIRST']; + } + } + + $this->_attributeDefault($attributes, $field); + + if (isset($attributes['NULL'])) + { + if ($attributes['NULL'] === true) + { + $field['null'] = empty($this->null) ? '' : ' ' . $this->null; + } + else + { + $field['null'] = ' NOT NULL'; + } + } + elseif ($create_table === true) + { + $field['null'] = ' NOT NULL'; + } + + $this->_attributeAutoIncrement($attributes, $field); + $this->_attributeUnique($attributes, $field); + + if (isset($attributes['COMMENT'])) + { + $field['comment'] = $this->db->escape($attributes['COMMENT']); + } + + if (isset($attributes['TYPE']) && ! empty($attributes['CONSTRAINT'])) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'ENUM': + case 'SET': + $attributes['CONSTRAINT'] = $this->db->escape($attributes['CONSTRAINT']); + $field['length'] = is_array($attributes['CONSTRAINT']) ? "('" . implode("','", $attributes['CONSTRAINT']) . "')" : '(' . $attributes['CONSTRAINT'] . ')'; + break; + default: + $field['length'] = is_array($attributes['CONSTRAINT']) ? '(' . implode(',', $attributes['CONSTRAINT']) . ')' : '(' . $attributes['CONSTRAINT'] . ')'; + break; + } + } + + $fields[] = $field; + } + + return $fields; + } + + //-------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * + * @return string + */ + protected function _processColumn($field) + { + return $this->db->escapeIdentifiers($field['name']) + . ' ' . $field['type'] . $field['length'] + . $field['unsigned'] + . $field['default'] + . $field['null'] + . $field['auto_increment'] + . $field['unique']; + } + + //-------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * + * @return void + */ + protected function _attributeType(&$attributes) + { + // Usually overridden by drivers + } + + //-------------------------------------------------------------------- + + /** + * Field attribute UNSIGNED + * + * Depending on the unsigned property value: + * + * - TRUE will always set $field['unsigned'] to 'UNSIGNED' + * - FALSE will always set $field['unsigned'] to '' + * - array(TYPE) will set $field['unsigned'] to 'UNSIGNED', + * if $attributes['TYPE'] is found in the array + * - array(TYPE => UTYPE) will change $field['type'], + * from TYPE to UTYPE in case of a match + * + * @param array &$attributes + * @param array &$field + * + * @return void + */ + protected function _attributeUnsigned(&$attributes, &$field) + { + if (empty($attributes['UNSIGNED']) OR $attributes['UNSIGNED'] !== true) + { + return; + } + + // Reset the attribute in order to avoid issues if we do type conversion + $attributes['UNSIGNED'] = false; + + if (is_array($this->unsigned)) + { + foreach (array_keys($this->unsigned) as $key) + { + if (is_int($key) && strcasecmp($attributes['TYPE'], $this->unsigned[$key]) === 0) + { + $field['unsigned'] = ' UNSIGNED'; + + return; + } + elseif (is_string($key) && strcasecmp($attributes['TYPE'], $key) === 0) + { + $field['type'] = $key; + + return; + } + } + + return; + } + + $field['unsigned'] = ($this->unsigned === true) ? ' UNSIGNED' : ''; + } + + //-------------------------------------------------------------------- + + /** + * Field attribute DEFAULT + * + * @param array &$attributes + * @param array &$field + * + * @return void + */ + protected function _attributeDefault(&$attributes, &$field) + { + if ($this->default === false) + { + return; + } + + if (array_key_exists('DEFAULT', $attributes)) + { + if ($attributes['DEFAULT'] === null) + { + $field['default'] = empty($this->null) ? '' : $this->default . $this->null; + + // Override the NULL attribute if that's our default + $attributes['NULL'] = true; + $field['null'] = empty($this->null) ? '' : ' ' . $this->null; + } + else + { + $field['default'] = $this->default . $this->db->escape($attributes['DEFAULT']); + } + } + } + + //-------------------------------------------------------------------- + + /** + * Field attribute UNIQUE + * + * @param array &$attributes + * @param array &$field + * + * @return void + */ + protected function _attributeUnique(&$attributes, &$field) + { + if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === true) + { + $field['unique'] = ' UNIQUE'; + } + } + + //-------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * + * @return void + */ + protected function _attributeAutoIncrement(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true && + stripos($field['type'], 'int') !== false + ) + { + $field['auto_increment'] = ' AUTO_INCREMENT'; + } + } + + //-------------------------------------------------------------------- + + /** + * Process primary keys + * + * @param string $table Table name + * + * @return string + */ + protected function _processPrimaryKeys($table) + { + $sql = ''; + + for ($i = 0, $c = count($this->primaryKeys); $i < $c; $i ++ ) + { + if ( ! isset($this->fields[$this->primaryKeys[$i]])) + { + unset($this->primaryKeys[$i]); + } + } + + if (count($this->primaryKeys) > 0) + { + $sql .= ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers('pk_' . $table) + . ' PRIMARY KEY(' . implode(', ', $this->db->escapeIdentifiers($this->primaryKeys)) . ')'; + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table + * + * @return array + */ + protected function _processIndexes($table) + { + $sqls = []; + + for ($i = 0, $c = count($this->keys); $i < $c; $i ++ ) + { + $this->keys[$i] = (array) $this->keys[$i]; + + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2 ++ ) + { + if ( ! isset($this->fields[$this->keys[$i][$i2]])) + { + unset($this->keys[$i][$i2]); + } + } + if (count($this->keys[$i]) <= 0) + { + continue; + } + + $sqls[] = 'CREATE INDEX ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i])) + . ' ON ' . $this->db->escapeIdentifiers($table) + . ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');'; + } + + return $sqls; + } + + //-------------------------------------------------------------------- + /** + * Process foreign keys + * + * @param string $table Table name + * + * @return string + */ + protected function _processForeignKeys($table) { + $sql = ''; + + $allowActions = array('CASCADE','SET NULL','NO ACTION','RESTRICT','SET DEFAULT'); + + if (count($this->foreignKeys) > 0){ + foreach ($this->foreignKeys as $field => $fkey) { + $name_index = $table.'_'.$field.'_foreign'; + + $sql .= ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers($name_index) + . ' FOREIGN KEY(' . $this->db->escapeIdentifiers($field) . ') REFERENCES '.$this->db->escapeIdentifiers($this->db->DBPrefix.$fkey['table']).' ('.$this->db->escapeIdentifiers($fkey['field']).')'; + + if($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions)){ + $sql .= " ON DELETE ".$fkey['onDelete']; + } + + if($fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $allowActions)){ + $sql .= " ON UPDATE ".$fkey['onDelete']; + } + + } + } + + return $sql; + } + //-------------------------------------------------------------------- + + /** + * Reset + * + * Resets table creation vars + * + * @return void + */ + protected function _reset() + { + $this->fields = $this->keys = $this->primaryKeys = []; + } + +} diff --git a/system/Database/Migration.php b/system/Database/Migration.php new file mode 100644 index 0000000..b4f82d1 --- /dev/null +++ b/system/Database/Migration.php @@ -0,0 +1,104 @@ +forge = ! is_null($forge) ? $forge : \Config\Database::forge($this->DBGroup); + + $this->db = $this->forge->getConnection(); + } + + //-------------------------------------------------------------------- + + /** + * Returns the database group name this migration uses. + * + * @return string + */ + public function getDBGroup() + { + return $this->DBGroup; + } + + //-------------------------------------------------------------------- + + /** + * Perform a migration step. + */ + abstract public function up(); + + //-------------------------------------------------------------------- + + /** + * Revert a migration step. + */ + abstract public function down(); + + //-------------------------------------------------------------------- +} diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php new file mode 100644 index 0000000..6032735 --- /dev/null +++ b/system/Database/MigrationRunner.php @@ -0,0 +1,724 @@ +enabled = $config->enabled ?? false; + $this->type = $config->type ?? 'timestamp'; + $this->table = $config->table ?? 'migrations'; + $this->currentVersion = $config->currentVersion ?? 0; + + // Default name space is the app namespace + $this->namespace = APP_NAMESPACE; + + // get default database group + $config = new \Config\Database(); + $this->group = $config->defaultGroup; + unset($config); + + if (empty($this->table)) + { + throw new ConfigException(lang('Migrations.migMissingTable')); + } + + if ( ! in_array($this->type, ['sequential', 'timestamp'])) + { + throw new ConfigException(lang('Migrations.migInvalidType') . $this->type); + } + + // Migration basename regex + $this->regex = ($this->type === 'timestamp') ? '/^\d{14}_(\w+)$/' : '/^\d{3}_(\w+)$/'; + + // If no db connection passed in, use + // default database group. + $this->db = ! empty($db) ? $db : \Config\Database::connect(); + + $this->ensureTable(); + } + + //-------------------------------------------------------------------- + + /** + * Migrate to a schema version + * + * Calls each migration step required to get to the schema version of + * choice + * + * @param string $targetVersion Target schema version + * @param string $namespace + * @param string $group + * + * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure + * @throws ConfigException + */ + public function version(string $targetVersion, $namespace = null, $group = null) + { + if ( ! $this->enabled) + { + throw new ConfigException(lang('Migrations.migDisabled')); + } + // Set Namespace if not null + if ( ! is_null($namespace)) + { + $this->setNamespace($namespace); + } + + // Set database group if not null + if ( ! is_null($group)) + { + $this->setGroup($group); + } + + $migrations = $this->findMigrations(); + + if (empty($migrations)) + { + return true; + } + + // Get Namespace current version + // Note: We use strings, so that timestamp versions work on 32-bit systems + $currentVersion = $this->getVersion(); + if ($targetVersion > $currentVersion) + { + // Moving Up + $method = 'up'; + ksort($migrations); + } + else + { + // Moving Down, apply in reverse order + $method = 'down'; + krsort($migrations); + } + + // Check Migration consistency + $this->CheckMigrations($migrations, $method, $targetVersion); + + // loop migration for each namespace (module) + foreach ($migrations as $version => $migration) + { + + // Only include migrations within the scoop + if (($method === 'up' && $version > $currentVersion && $version <= $targetVersion) OR ( $method === 'down' && $version <= $currentVersion && $version > $targetVersion) + ) + { + + include_once $migration->path; + // Get namespaced class name + $class = $this->namespace . '\Database\Migrations\Migration_' . ($migration->name); + + $this->setName($migration->name); + + // Validate the migration file structure + if ( ! class_exists($class, false)) + { + throw new \RuntimeException(sprintf(lang('Migrations.migClassNotFound'), $class)); + } + + // Forcing migration to selected database group + $instance = new $class(\Config\Database::forge($this->group)); + + if ( ! is_callable([$instance, $method])) + { + throw new \RuntimeException(sprintf(lang('Migrations.migMissingMethod'), $method)); + } + + $instance->{$method}(); + if ($method === 'up') + { + $this->addHistory($migration->version); + } + elseif ($method === 'down') + { + $this->removeHistory($migration->version); + } + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Sets the schema to the latest migration + * + * @param string $namespace + * @param string $group + * + * @return mixed Current version string on success, FALSE on failure + */ + public function latest($namespace = null, $group = null) + { + + // Set Namespace if not null + if ( ! is_null($namespace)) + { + $this->setNamespace($namespace); + } + // Set database group if not null + if ( ! is_null($group)) + { + $this->setGroup($group); + } + + $migrations = $this->findMigrations(); + + $lastMigration = end($migrations)->version ?? 0; + + // Calculate the last migration step from existing migration + // filenames and proceed to the standard version migration + return $this->version($lastMigration); + } + + //-------------------------------------------------------------------- + + /** + * Sets the schema to the latest migration for all namespaces + * + * @param string $group + * + * @return bool + */ + public function latestAll($group = null) + { + // Set database group if not null + if ( ! is_null($group)) + { + $this->setGroup($group); + } + + // Get all namespaces form PSR4 paths. + $config = new Autoload(); + $namespaces = $config->psr4; + + foreach ($namespaces as $namespace => $path) + { + + $this->setNamespace($namespace); + $migrations = $this->findMigrations(); + + if (empty($migrations)) + { + continue; + } + + $lastMigration = end($migrations)->version; + // No New migrations to add + if ($lastMigration == $this->getVersion()) + { + continue; + } + + // Calculate the last migration step from existing migration + // filenames and proceed to the standard version migration + $this->version($lastMigration); + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Sets the (APP_NAMESPACE) schema to $currentVersion in migration config file + * + * @param string $group + * + * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure + */ + public function current($group = null) + { + // Set database group if not null + if ( ! is_null($group)) + { + $this->setGroup($group); + } + + return $this->version($this->currentVersion); + } + + //-------------------------------------------------------------------- + + /** + * Retrieves list of available migration scripts + * + * @return array list of migrations as $version for one namespace + */ + public function findMigrations() + { + $migrations = []; + // Get namespace location form PSR4 paths. + $config = new Autoload(); + + $location = $config->psr4[$this->namespace]; + + // Setting migration directories. + $dir = rtrim($location, '/') . '/Database/Migrations/'; + + // Load all *_*.php files in the migrations path + foreach (glob($dir . '*_*.php') as $file) + { + $name = basename($file, '.php'); + // Filter out non-migration files + if (preg_match($this->regex, $name)) + { + // Create migration object using stdClass + $migration = new \stdClass(); + // Get migration version number + $migration->version = $this->getMigrationNumber($name); + $migration->name = $this->getMigrationName($name); + $migration->path = $file; + + // Add to migrations[version] + $migrations[$migration->version] = $migration; + } + } + + return $migrations; + } + + //-------------------------------------------------------------------- + + /** + * checks if the list of available migration scripts list are consistent + * if sequential check if no gaps and check if all consistent with migrations table if downgrading + * if timestamp check if consistent with migrations table if downgrading + * + * @param array $migrations + * @param string $method + * @param int $targetversion + * + * @return bool + */ + protected function CheckMigrations($migrations, $method, $targetversion) + { + // Check if no migrations found + if (empty($migrations)) + { + if ($this->silent) + { + return false; + } + throw new \RuntimeException(lang('Migrations.migEmpty')); + } + + // Check if $targetversion file is found + if ($targetversion != 0 && ! array_key_exists($targetversion, $migrations)) + { + if ($this->silent) + { + return false; + } + throw new \RuntimeException(lang('Migrations.migNotFound') . $targetversion); + } + + ksort($migrations); + + if ($method === 'down') + { + $history_migrations = $this->getHistory($this->group); + $history_size = count($history_migrations) - 1; + } + // Check for sequence gaps + $loop = 0; + foreach ($migrations as $migration) + { + if ($this->type === 'sequential' && abs($migration->version - $loop) > 1) + { + throw new \RuntimeException(lang('Migration.migGap') . " " . $migration->version); + } + // Check if all old migration files are all available to do downgrading + if ($method === 'down') + { + if ($loop <= $history_size && $history_migrations[$loop]['version'] != $migration->version) + { + throw new \RuntimeException(lang('Migration.migGap') . " " . $migration->version); + } + } + $loop ++; + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Set namespace. + * Allows other scripts to modify on the fly as needed. + * + * @param string $namespace + * + * @return MigrationRunner + */ + public function setNamespace(string $namespace) + { + $this->namespace = $namespace; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Set database Group. + * Allows other scripts to modify on the fly as needed. + * + * @param string $group + * + * @return MigrationRunner + */ + public function setGroup(string $group) + { + $this->group = $group; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Set migration Name. + * + * @param string $name + */ + public function setName(string $name) + { + $this->name = $name; + } + + //-------------------------------------------------------------------- + + /** + * Grabs the full migration history from the database. + * + * @param string $group + * + * @return array + */ + public function getHistory($group = 'default') + { + $query = $this->db->table($this->table) + ->where('group', $group) + ->where('namespace', $this->namespace) + ->orderBy('version', 'ASC') + ->get(); + + if ( ! $query) + { + return []; + } + + return $query->getResultArray(); + } + + //-------------------------------------------------------------------- + + /** + * If $silent == true, then will not throw exceptions and will + * attempt to continue gracefully. + * + * @param bool $silent + * + * @return MigrationRunner + */ + public function setSilent(bool $silent) + { + $this->silent = $silent; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Extracts the migration number from a filename + * + * @param string $migration + * + * @return string Numeric portion of a migration filename + */ + protected function getMigrationNumber($migration) + { + return sscanf($migration, '%[0-9]+', $number) ? $number : '0'; + } + + //-------------------------------------------------------------------- + + /** + * Extracts the migration class name from a filename + * + * @param string $migration + * + * @return string text portion of a migration filename + */ + protected function getMigrationName($migration) + { + $parts = explode('_', $migration); + array_shift($parts); + + return implode('_', $parts); + } + + //-------------------------------------------------------------------- + + /** + * Retrieves current schema version + * + * @return string Current migration version + */ + protected function getVersion() + { + $row = $this->db->table($this->table) + ->select('version') + ->where('group', $this->group) + ->where('namespace', $this->namespace) + ->orderBy('version', 'DESC') + ->get(); + + return $row && ! is_null($row->getRow()) ? $row->getRow()->version : '0'; + } + + //-------------------------------------------------------------------- + + /** + * Retrieves current schema version + * + * @return string Current migration version + */ + public function getCliMessages() + { + + return $this->cliMessages; + } + + //-------------------------------------------------------------------- + + /** + * Stores the current schema version. + * + * @param string $version + * + * @internal param string $migration Migration reached + * + */ + protected function addHistory($version) + { + $this->db->table($this->table) + ->insert([ + 'version' => $version, + 'name' => $this->name, + 'group' => $this->group, + 'namespace' => $this->namespace, + 'time' => time(), + ]); + if (is_cli()) + { + $this->cliMessages[] = "\t" . CLI::color(lang('Migrations.migAdded'), 'yellow') . "($this->namespace) " . $version . '_' . $this->name; + } + } + + //-------------------------------------------------------------------- + + /** + * Removes a single history + * + * @param string $version + */ + protected function removeHistory($version) + { + $this->db->table($this->table) + ->where('version', $version) + ->where('group', $this->group) + ->where('namespace', $this->namespace) + ->delete(); + if (is_cli()) + { + $this->cliMessages[] = "\t" . CLI::color(lang('Migrations.migRemoved'), 'yellow') . "($this->namespace) " . $version . '_' . $this->name; + } + } + + //-------------------------------------------------------------------- + + /** + * Ensures that we have created our migrations table + * in the database. + */ + protected function ensureTable() + { + if ($this->db->tableExists($this->table)) + { + return; + } + + $forge = \Config\Database::forge(); + + $forge->addField([ + 'version' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => false, + ], + 'name' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => false, + ], + 'group' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => false, + ], + 'namespace' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => false, + ], + 'time' => [ + 'type' => 'INT', + 'constraint' => 11, + 'null' => false, + ], + ]); + + $forge->createTable($this->table, true); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/MySQLi/Builder.php b/system/Database/MySQLi/Builder.php new file mode 100644 index 0000000..d713a52 --- /dev/null +++ b/system/Database/MySQLi/Builder.php @@ -0,0 +1,53 @@ +hostname[0] === '/') + { + $hostname = null; + $port = null; + $socket = $this->hostname; + } + else + { + $hostname = ($persistent === true) ? 'p:' . $this->hostname : $this->hostname; + $port = empty($this->port) ? null : $this->port; + $socket = null; + } + + $client_flags = ($this->compress === true) ? MYSQLI_CLIENT_COMPRESS : 0; + $this->mysqli = mysqli_init(); + + mysqli_report(MYSQLI_REPORT_ALL & ~MYSQLI_REPORT_INDEX); + + $this->mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 10); + + if (isset($this->strictOn)) + { + if ($this->strictOn) + { + $this->mysqli->options(MYSQLI_INIT_COMMAND, 'SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")'); + } + else + { + $this->mysqli->options(MYSQLI_INIT_COMMAND, 'SET SESSION sql_mode = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + @@sql_mode, + "STRICT_ALL_TABLES,", ""), + ",STRICT_ALL_TABLES", ""), + "STRICT_ALL_TABLES", ""), + "STRICT_TRANS_TABLES,", ""), + ",STRICT_TRANS_TABLES", ""), + "STRICT_TRANS_TABLES", "")' + ); + } + } + + if (is_array($this->encrypt)) + { + $ssl = []; + empty($this->encrypt['ssl_key']) OR $ssl['key'] = $this->encrypt['ssl_key']; + empty($this->encrypt['ssl_cert']) OR $ssl['cert'] = $this->encrypt['ssl_cert']; + empty($this->encrypt['ssl_ca']) OR $ssl['ca'] = $this->encrypt['ssl_ca']; + empty($this->encrypt['ssl_capath']) OR $ssl['capath'] = $this->encrypt['ssl_capath']; + empty($this->encrypt['ssl_cipher']) OR $ssl['cipher'] = $this->encrypt['ssl_cipher']; + + if ( ! empty($ssl)) + { + if (isset($this->encrypt['ssl_verify'])) + { + if ($this->encrypt['ssl_verify']) + { + defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT') && + $this->mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true); + } + // Apparently (when it exists), setting MYSQLI_OPT_SSL_VERIFY_SERVER_CERT + // to FALSE didn't do anything, so PHP 5.6.16 introduced yet another + // constant ... + // + // https://secure.php.net/ChangeLog-5.php#5.6.16 + // https://bugs.php.net/bug.php?id=68344 + elseif (defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT')) + { + $this->mysqli->options(MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT, true); + } + } + + $client_flags |= MYSQLI_CLIENT_SSL; + $this->mysqli->ssl_set( + isset($ssl['key']) ? $ssl['key'] : null, isset($ssl['cert']) ? $ssl['cert'] : null, isset($ssl['ca']) ? $ssl['ca'] : null, isset($ssl['capath']) ? $ssl['capath'] : null, isset($ssl['cipher']) ? $ssl['cipher'] : null + ); + } + } + + if ($this->mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, $socket, $client_flags) + ) + { + // Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails + if ( + ($client_flags & MYSQLI_CLIENT_SSL) && version_compare($this->mysqli->client_info, '5.7.3', '<=') && empty($this->mysqli->query("SHOW STATUS LIKE 'ssl_cipher'") + ->fetch_object()->Value) + ) + { + $this->mysqli->close(); + $message = 'MySQLi was configured for an SSL connection, but got an unencrypted connection instead!'; + log_message('error', $message); + + if ($this->DBDebug) + { + throw new DatabaseException($message); + } + return false; + } + + if ( ! $this->mysqli->set_charset($this->charset)) + { + log_message('error', "Database: Unable to set the configured connection charset ('{$this->charset}')."); + $this->mysqli->close(); + + if ($this->db->debug) + { + throw new DatabaseException('Unable to set client connection character set: ' . $this->charset); + } + return false; + } + + return $this->mysqli; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Keep or establish the connection if no queries have been sent for + * a length of time exceeding the server's idle timeout. + * + * @return void + */ + public function reconnect() + { + $this->close(); + $this->initialize(); + } + + //-------------------------------------------------------------------- + + /** + * Close the database connection. + */ + protected function _close() + { + $this->connID->close(); + } + + //-------------------------------------------------------------------- + + /** + * Select a specific database table to use. + * + * @param string $databaseName + * + * @return mixed + */ + public function setDatabase(string $databaseName) + { + if ($databaseName === '') + { + $databaseName = $this->database; + } + + if ($this->connID->select_db($databaseName)) + { + $this->database = $databaseName; + + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Returns a string containing the version of the database being used. + * + * @return mixed + */ + public function getVersion() + { + if (isset($this->dataCache['version'])) + { + return $this->dataCache['version']; + } + + if (empty($this->mysqli)) + { + $this->initialize(); + } + + return $this->dataCache['version'] = $this->mysqli->server_info; + } + + //-------------------------------------------------------------------- + + /** + * Executes the query against the database. + * + * @param string $sql + * + * @return mixed + */ + public function execute($sql) + { + return $this->connID->query($this->prepQuery($sql)); + } + + //-------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @param string $sql an SQL query + * + * @return string + */ + protected function prepQuery($sql) + { + // mysqli_affected_rows() returns 0 for "DELETE FROM TABLE" queries. This hack + // modifies the query so that it a proper number of affected rows is returned. + if ($this->deleteHack === true && preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql)) + { + return trim($sql) . ' WHERE 1=1'; + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Returns the total number of rows affected by this query. + * + * @return mixed + */ + public function affectedRows(): int + { + return $this->connID->affected_rows; + } + + //-------------------------------------------------------------------- + + /** + * Platform-dependant string escape + * + * @param string $str + * @return string + */ + protected function _escapeString(string $str): string + { + if (is_bool($str)) + { + return $str; + } + + if (! $this->connID) + { + $this->initialize(); + } + + return $this->connID->real_escape_string($str); + } + + //-------------------------------------------------------------------- + + /** + * Generates the SQL for listing tables in a platform-dependent manner. + * + * @param bool $prefixLimit + * + * @return string + */ + protected function _listTables($prefixLimit = false): string + { + $sql = 'SHOW TABLES FROM ' . $this->escapeIdentifiers($this->database); + + if ($prefixLimit !== FALSE && $this->DBPrefix !== '') + { + return $sql . " LIKE '" . $this->escapeLikeStr($this->DBPrefix) . "%'"; + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string $table + * + * @return string + */ + protected function _listColumns(string $table = ''): string + { + return 'SHOW COLUMNS FROM ' . $this->protectIdentifiers($table, TRUE, NULL, FALSE); + } + + //-------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function _fieldData(string $table) + { + if (($query = $this->query('SHOW COLUMNS FROM ' . $this->protectIdentifiers($table, TRUE, NULL, FALSE))) === FALSE) + { + return FALSE; + } + $query = $query->getResultObject(); + + $retval = []; + for ($i = 0, $c = count($query); $i < $c; $i ++ ) + { + $retval[$i] = new \stdClass(); + $retval[$i]->name = $query[$i]->Field; + + sscanf($query[$i]->Type, '%[a-z](%d)', $retval[$i]->type, $retval[$i]->max_length + ); + + $retval[$i]->default = $query[$i]->Default; + $retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI'); + } + + return $retval; + } + + //-------------------------------------------------------------------- + + /** + * Returns an object with index data + * + * @param string $table + * @return array + */ + public function _indexData(string $table) + { + if (($query = $this->query('SHOW CREATE TABLE ' . $this->protectIdentifiers($table, TRUE, NULL, FALSE))) === FALSE) + { + return FALSE; + } + $row = $query->getRowArray(); + if ( ! $row) + { + return FALSE; + } + + $retval = []; + foreach (explode("\n", $row['Create Table']) as $line) + { + $line = trim($line); + if (strpos($line, 'PRIMARY KEY') === 0) + { + $obj = new \stdClass(); + $obj->name = 'PRIMARY KEY'; + $_fields = explode(',', preg_replace('/^.*\((.+)\).*$/', '$1', $line)); + $obj->fields = array_map(function($v) { + return trim($v, '`'); + }, $_fields); + + $retval[] = $obj; + } + elseif (strpos($line, 'UNIQUE KEY') === 0 || strpos($line, 'KEY') === 0) + { + if (preg_match('/KEY `([^`]+)` \((.+)\)/', $line, $matches)) + { + $obj = new \stdClass(); + $obj->name = $matches[1]; + $obj->fields = array_map(function($v) { + return trim($v, '`'); + }, explode(',', $matches[2])); + + $retval[] = $obj; + } + else + { + throw new \LogicException('parsing key string failed.'); + } + } + } + + return $retval; + } + + //-------------------------------------------------------------------- + + /** + * Returns an object with Foreign key data + * + * @param string $table + * @return array + */ + public function _foreignKeyData(string $table) + { + $sql = ' + SELECT + tc.CONSTRAINT_NAME, + tc.TABLE_NAME, + rc.REFERENCED_TABLE_NAME + FROM information_schema.TABLE_CONSTRAINTS AS tc + INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS rc + ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + WHERE + tc.CONSTRAINT_TYPE = '.$this->escape('FOREIGN KEY').' AND + tc.TABLE_SCHEMA = '.$this->escape($this->database).' AND + tc.TABLE_NAME = '.$this->escape($table); + + if (($query = $this->query($sql)) === false) + { + return false; + } + $query = $query->getResultObject(); + + $retval = []; + foreach ($query as $row) + { + $obj = new \stdClass(); + $obj->constraint_name = $row->CONSTRAINT_NAME; + $obj->table_name = $row->TABLE_NAME; + $obj->foreign_table_name = $row->REFERENCED_TABLE_NAME; + + $retval[] = $obj; + } + + return $retval; + } + + //-------------------------------------------------------------------- + + /** + * Returns the last error code and message. + * + * Must return an array with keys 'code' and 'message': + * + * return ['code' => null, 'message' => null); + * + * @return array + */ + public function error() + { + if ( ! empty($this->mysqli->connect_errno)) + { + return [ + 'code' => $this->mysqli->connect_errno, + 'message' => $this->_mysqli->connect_error + ]; + } + + return ['code' => $this->connID->errno, 'message' => $this->connID->error]; + } + + //-------------------------------------------------------------------- + + /** + * Insert ID + * + * @return int + */ + public function insertID() + { + return $this->connID->insert_id; + } + + //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _transBegin(): bool + { + $this->connID->autocommit(false); + + return $this->connID->begin_transaction(); + } + + //-------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _transCommit(): bool + { + if ($this->connID->commit()) + { + $this->connID->autocommit(true); + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _transRollback(): bool + { + if ($this->connID->rollback()) + { + $this->connID->autocommit(true); + return true; + } + + return false; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/MySQLi/Forge.php b/system/Database/MySQLi/Forge.php new file mode 100644 index 0000000..7909369 --- /dev/null +++ b/system/Database/MySQLi/Forge.php @@ -0,0 +1,243 @@ +db->charset) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET')) + { + $sql .= ' DEFAULT CHARACTER SET = ' . $this->db->charset; + } + + if ( ! empty($this->db->DBCollat) && ! strpos($sql, 'COLLATE')) + { + $sql .= ' COLLATE = ' . $this->db->DBCollat; + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alterTable($alter_type, $table, $field) + { + if ($alter_type === 'DROP') + { + return parent::_alterTable($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); + for ($i = 0, $c = count($field); $i < $c; $i ++ ) + { + if ($field[$i]['_literal'] !== FALSE) + { + $field[$i] = ($alter_type === 'ADD') ? "\n\tADD " . $field[$i]['_literal'] : "\n\tMODIFY " . $field[$i]['_literal']; + } + else + { + if ($alter_type === 'ADD') + { + $field[$i]['_literal'] = "\n\tADD "; + } + else + { + $field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; + } + + $field[$i] = $field[$i]['_literal'] . $this->_processColumn($field[$i]); + } + } + + return [$sql . implode(',', $field)]; + } + + //-------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _processColumn($field) + { + $extra_clause = isset($field['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($field['after']) : ''; + + if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE) + { + $extra_clause = ' FIRST'; + } + + return $this->db->escapeIdentifiers($field['name']) + . (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name'])) + . ' ' . $field['type'] . $field['length'] + . $field['unsigned'] + . $field['null'] + . $field['default'] + . $field['auto_increment'] + . $field['unique'] + . (empty($field['comment']) ? '' : ' COMMENT ' . $field['comment']) + . $extra_clause; + } + + //-------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table (ignored) + * @return string + */ + protected function _processIndexes($table) + { + $sql = ''; + + for ($i = 0, $c = count($this->keys); $i < $c; $i ++ ) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2 ++ ) + { + if ( ! isset($this->fields[$this->keys[$i][$i2]])) + { + unset($this->keys[$i][$i2]); + continue; + } + } + } + elseif ( ! isset($this->fields[$this->keys[$i]])) + { + unset($this->keys[$i]); + continue; + } + + is_array($this->keys[$i]) OR $this->keys[$i] = [$this->keys[$i]]; + + $sql .= ",\n\tKEY " . $this->db->escapeIdentifiers(implode('_', $this->keys[$i])) + . ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ')'; + } + + $this->keys = []; + + return $sql; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php new file mode 100644 index 0000000..d14d0b8 --- /dev/null +++ b/system/Database/MySQLi/PreparedQuery.php @@ -0,0 +1,130 @@ +statement = $this->db->mysqli->prepare($sql)) + { + $this->errorCode = $this->db->mysqli->errno; + $this->errorString = $this->db->mysqli->error; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Takes a new set of data and runs it against the currently + * prepared query. Upon success, will return a Results object. + * + * @param array $data + * + * @return \CodeIgniter\Database\ResultInterface + */ + public function _execute($data) + { + if (is_null($this->statement)) + { + throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + } + + // First off -bind the parameters + $bindTypes = ''; + + // Determine the type string + foreach ($data as $item) + { + if (is_integer($item)) + { + $bindTypes .= 'i'; + } + elseif (is_numeric($item)) + { + $bindTypes .= 'd'; + } + else + { + $bindTypes .= 's'; + } + } + + // Bind it + $this->statement->bind_param($bindTypes, ...$data); + + $success = $this->statement->execute(); + + return $success; + } + + //-------------------------------------------------------------------- + + /** + * Returns the result object for the prepared query. + * + * @return mixed + */ + public function _getResult() + { + return $this->statement->get_result(); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/MySQLi/Result.php b/system/Database/MySQLi/Result.php new file mode 100644 index 0000000..b740bb5 --- /dev/null +++ b/system/Database/MySQLi/Result.php @@ -0,0 +1,162 @@ +resultID->field_count; + } + + //-------------------------------------------------------------------- + + /** + * Generates an array of column names in the result set. + * + * @return array + */ + public function getFieldNames(): array + { + $fieldNames = []; + $this->resultID->field_seek(0); + while ($field = $this->resultID->fetch_field()) + { + $fieldNames[] = $field->name; + } + + return $fieldNames; + } + + //-------------------------------------------------------------------- + + /** + * Generates an array of objects representing field meta-data. + * + * @return array + */ + public function getFieldData(): array + { + $retval = []; + $fieldData = $this->resultID->fetch_fields(); + + for ($i = 0, $c = count($fieldData); $i < $c; $i ++ ) + { + $retval[$i] = new \stdClass(); + $retval[$i]->name = $fieldData[$i]->name; + $retval[$i]->type = $fieldData[$i]->type; + $retval[$i]->max_length = $fieldData[$i]->max_length; + $retval[$i]->primary_key = (int) ($fieldData[$i]->flags & 2); + $retval[$i]->default = $fieldData[$i]->def; + } + + return $retval; + } + + //-------------------------------------------------------------------- + + /** + * Frees the current result. + */ + public function freeResult() + { + if (is_object($this->resultID)) + { + $this->resultID->free(); + $this->resultID = false; + } + } + + //-------------------------------------------------------------------- + + /** + * Moves the internal pointer to the desired offset. This is called + * internally before fetching results to make sure the result set + * starts at zero. + * + * @param int $n + * + * @return mixed + */ + public function dataSeek($n = 0) + { + return $this->resultID->data_seek($n); + } + + //-------------------------------------------------------------------- + + /** + * Returns the result set as an array. + * + * Overridden by driver classes. + * + * @return array + */ + protected function fetchAssoc() + { + return $this->resultID->fetch_assoc(); + } + + //-------------------------------------------------------------------- + + /** + * Returns the result set as an object. + * + * Overridden by child classes. + * + * @param string $className + * + * @return object + */ + protected function fetchObject($className = 'stdClass') + { + return $this->resultID->fetch_object($className); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php new file mode 100644 index 0000000..d27e59f --- /dev/null +++ b/system/Database/Postgre/Builder.php @@ -0,0 +1,361 @@ + 1 ? "0.{$orderby}" : $orderby); + } + + if (is_float($orderby)) + { + $this->db->simpleQuery("SET SEED {$orderby}"); + } + + $orderby = $this->randomKeyword[0]; + $direction = ''; + $escape = false; + } + + return parent::orderBy($orderby, $direction, $escape); + } + + //-------------------------------------------------------------------- + + /** + * Increments a numeric column by the specified value. + * + * @param string $column + * @param int $value + * + * @return bool + */ + public function increment(string $column, int $value = 1) + { + $column = $this->db->protectIdentifiers($column); + + $sql = $this->_update($this->QBFrom[0], [$column => "to_number({$column}, '9999999') + {$value}"]); + + return $this->db->query($sql, $this->binds); + } + + //-------------------------------------------------------------------- + + /** + * Decrements a numeric column by the specified value. + * + * @param string $column + * @param int $value + * + * @return bool + */ + public function decrement(string $column, int $value = 1) + { + $column = $this->db->protectIdentifiers($column); + + $sql = $this->_update($this->QBFrom[0], [$column => "to_number({$column}, '9999999') - {$value}"]); + + return $this->db->query($sql, $this->binds); + } + + //-------------------------------------------------------------------- + + /** + * Replace + * + * Compiles an replace into string and runs the query. + * Because PostgreSQL doesn't support the replace into command, + * we simply do a DELETE and an INSERT on the first key/value + * combo, assuming that it's either the primary key or a unique key. + * + * @param array $set An associative array of insert values + * @param bool $returnSQL + * + * @return bool TRUE on success, FALSE on failure + * @throws DatabaseException + * @internal param true $bool returns the generated SQL, false executes the query. + * + */ + public function replace($set = null, $returnSQL = false) + { + if ($set !== null) + { + $this->set($set); + } + + if (count($this->QBSet) === 0) + { + if (CI_DEBUG) + { + throw new DatabaseException('You must use the "set" method to update an entry.'); + } + return false; + } + + $table = $this->QBFrom[0]; + + $set = $this->binds; + $keys = array_keys($set); + $values = array_values($set); + + $builder = $this->db->table($table); + $exists = $builder->where("$keys[0] = $values[0]", null, false)->get()->getFirstRow(); + + if (empty($exists)) + { + $result = $builder->insert($set); + } + else + { + array_pop($set); + $result = $builder->update($set, "$keys[0] = $values[0]"); + } + + unset($builder); + $this->resetWrite(); + + return $result; + } + + //-------------------------------------------------------------------- + + /** + * Delete + * + * Compiles a delete string and runs the query + * + * @param string $where + * @param null $limit + * @param bool $reset_data + * @param bool $returnSQL + * + * @return mixed + * @throws DatabaseException + * @internal param the $mixed where clause + * @internal param the $mixed limit clause + * @internal param $bool + * + */ + public function delete($where = '', $limit = null, $reset_data = true, $returnSQL = false) + { + if ( ! empty($limit) || ! empty($this->QBLimit)) + { + throw new DatabaseException('PostgreSQL does not allow LIMITs on DELETE queries.'); + } + + return parent::delete($where, $limit, $reset_data, $returnSQL); + } + + //-------------------------------------------------------------------- + + /** + * LIMIT string + * + * Generates a platform-specific LIMIT clause. + * + * @param string $sql SQL Query + * + * @return string + */ + protected function _limit($sql) + { + return $sql . ' LIMIT ' . $this->QBLimit . ($this->QBOffset ? " OFFSET {$this->QBOffset}" : ''); + } + + //-------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * + * @return string + * @throws DatabaseException + * @internal param the $string table name + * @internal param the $array update data + * + */ + protected function _update($table, $values) + { + if ( ! empty($this->QBLimit)) + { + throw new DatabaseException('Postgres does not support LIMITs with UPDATE queries.'); + } + + $this->QBOrderBy = []; + return parent::_update($table, $values); + } + + //-------------------------------------------------------------------- + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @param string $table Table name + * @param array $values Update data + * @param string $index WHERE key + * + * @return string + */ + protected function _updateBatch($table, $values, $index) + { + $ids = []; + foreach ($values as $key => $val) + { + $ids[] = $val[$index]; + + foreach (array_keys($val) as $field) + { + if ($field !== $index) + { + $final[$field][] = "WHEN {$val[$index]} THEN {$val[$field]}"; + } + } + } + + $cases = ''; + foreach ($final as $k => $v) + { + $cases .= "{$k} = (CASE {$index}\n" + . implode("\n", $v) + . "\nELSE {$k} END), "; + } + + $this->where("{$index} IN(" . implode(',', $ids) . ')', null, false); + + return "UPDATE {$table} SET " . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere'); + } + + //-------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table The table name + * + * @return string + */ + protected function _delete($table) + { + $this->QBLimit = false; + return parent::_delete($table); + } + + //-------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the truncate() command, + * then this method maps to 'DELETE FROM table' + * + * @param string $table The table name + * + * @return string + */ + protected function _truncate($table) + { + return 'TRUNCATE ' . $table . ' RESTART IDENTITY'; + } + + //-------------------------------------------------------------------- + + /** + * Platform independent LIKE statement builder. + * + * In PostgreSQL, the ILIKE operator will perform case insensitive + * searches according to the current locale. + * + * @see https://www.postgresql.org/docs/9.2/static/functions-matching.html + * + * @param string|null $prefix + * @param string $column + * @param string|null $not + * @param string $bind + * @param bool $insensitiveSearch + * + * @return string $like_statement + */ + public function _like_statement(string $prefix = null, string $column, string $not = null, string $bind, bool $insensitiveSearch = false): string + { + $op = $insensitiveSearch === true ? 'ILIKE' : 'LIKE'; + + return "{$prefix} {$column} {$not} {$op} :{$bind}:"; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php new file mode 100644 index 0000000..7238c52 --- /dev/null +++ b/system/Database/Postgre/Connection.php @@ -0,0 +1,570 @@ +DSN)) + { + $this->buildDSN(); + } + + // Strip pgsql if exists + if (mb_strpos($this->DSN, 'pgsql:') === 0) + { + $this->DSN = mb_substr($this->DSN, 6); + } + + // Convert semicolons to spaces. + $this->DSN = str_replace(';', ' ', $this->DSN); + + $this->connID = $persistent === true ? pg_pconnect($this->DSN) : pg_connect($this->DSN); + + if ($this->connID !== false) + { + if ($persistent === true && pg_connection_status($this->connID) === PGSQL_CONNECTION_BAD && pg_ping($this->connID) === false + ) + { + return false; + } + + empty($this->schema) or $this->simpleQuery("SET search_path TO {$this->schema},public"); + + if ($this->setClientEncoding($this->charset) === false) + { + return false; + } + } + + return $this->connID; + } + + //-------------------------------------------------------------------- + + /** + * Keep or establish the connection if no queries have been sent for + * a length of time exceeding the server's idle timeout. + * + * @return mixed + */ + public function reconnect() + { + if (pg_ping($this->connID) === false) + { + $this->connID = false; + } + } + + //-------------------------------------------------------------------- + + /** + * Close the database connection. + */ + protected function _close() + { + pg_close($this->connID); + } + + //-------------------------------------------------------------------- + + /** + * Select a specific database table to use. + * + * @param string $databaseName + * + * @return mixed + */ + public function setDatabase(string $databaseName) + { + return false; + } + + //-------------------------------------------------------------------- + + /** + * Returns a string containing the version of the database being used. + * + * @return mixed + */ + public function getVersion() + { + if (isset($this->dataCache['version'])) + { + return $this->dataCache['version']; + } + + if ( ! $this->connID or ( $pgVersion = pg_version($this->connID)) === false) + { + $this->initialize(); + } + + return isset($pgVersion['server']) ? $this->dataCache['version'] = $pgVersion['server'] : false; + } + + //-------------------------------------------------------------------- + + /** + * Executes the query against the database. + * + * @param string $sql + * + * @return resource + */ + public function execute($sql) + { + return pg_query($this->connID, $sql); + } + + //-------------------------------------------------------------------- + + /** + * Returns the total number of rows affected by this query. + * + * @return mixed + */ + public function affectedRows(): int + { + return pg_affected_rows($this->resultID); + } + + //-------------------------------------------------------------------- + + /** + * "Smart" Escape String + * + * Escapes data based on type + * + * @param string $str + * @return mixed + */ + public function escape($str) + { + if (! $this->connID) + { + $this->initialize(); + } + + if (is_string($str) OR ( is_object($str) && method_exists($str, '__toString'))) + { + return pg_escape_literal($this->connID, $str); + } + elseif (is_bool($str)) + { + return $str ? 'TRUE' : 'FALSE'; + } + + return parent::escape($str); + } + + //-------------------------------------------------------------------- + + /** + * Platform-dependant string escape + * + * @param string $str + * @return string + */ + protected function _escapeString(string $str): string + { + if (! $this->connID) + { + $this->initialize(); + } + + return pg_escape_string($this->connID, $str); + } + + //-------------------------------------------------------------------- + + /** + * Generates the SQL for listing tables in a platform-dependent manner. + * + * @param bool $prefixLimit + * + * @return string + */ + protected function _listTables($prefixLimit = false): string + { + $sql = 'SELECT "table_name" FROM "information_schema"."tables" WHERE "table_schema" = \'' . $this->schema . "'"; + + if ($prefixLimit !== false && $this->DBPrefix !== '') + { + return $sql . ' AND "table_name" LIKE \'' + . $this->escapeLikeString($this->DBPrefix) . "%' " + . sprintf($this->likeEscapeStr, $this->likeEscapeChar); + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string $table + * + * @return string + */ + protected function _listColumns(string $table = ''): string + { + return 'SELECT "column_name" + FROM "information_schema"."columns" + WHERE LOWER("table_name") = ' + . $this->escape($this->DBPrefix.strtolower($table)); + } + + //-------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function _fieldData(string $table) + { + $sql = 'SELECT "column_name", "data_type", "character_maximum_length", "numeric_precision", "column_default" + FROM "information_schema"."columns" + WHERE LOWER("table_name") = ' + . $this->escape(strtolower($table)); + + if (($query = $this->query($sql)) === false) + { + return false; + } + $query = $query->getResultObject(); + + $retval = []; + for ($i = 0, $c = count($query); $i < $c; $i ++ ) + { + $retval[$i] = new \stdClass(); + $retval[$i]->name = $query[$i]->column_name; + $retval[$i]->type = $query[$i]->data_type; + $retval[$i]->default = $query[$i]->column_default; + $retval[$i]->max_length = $query[$i]->character_maximum_length > 0 ? $query[$i]->character_maximum_length : $query[$i]->numeric_precision; + } + + return $retval; + } + + //-------------------------------------------------------------------- + + /** + * Returns an object with index data + * + * @param string $table + * @return array + */ + public function _indexData(string $table) + { + $sql = 'SELECT "indexname", "indexdef" + FROM "pg_indexes" + WHERE LOWER("tablename") = ' . $this->escape(strtolower($table)) . ' + AND "schemaname" = ' . $this->escape('public'); + + if (($query = $this->query($sql)) === false) + { + return false; + } + $query = $query->getResultObject(); + + $retval = []; + foreach ($query as $row) + { + $obj = new \stdClass(); + $obj->name = $row->indexname; + $_fields = explode(',', preg_replace('/^.*\((.+?)\)$/', '$1', trim($row->indexdef))); + $obj->fields = array_map(function($v) { + return trim($v); + }, $_fields); + + $retval[] = $obj; + } + + return $retval; + } + + //-------------------------------------------------------------------- + +/** + * Returns an object with Foreign key data + * + * @param string $table + * @return array + */ + public function _foreignKeyData(string $table) + { + $sql = 'SELECT + tc.constraint_name, tc.table_name, kcu.column_name, + ccu.table_name AS foreign_table_name, + ccu.column_name AS foreign_column_name + FROM information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu + ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = '.$this->escape('FOREIGN KEY').' AND tc.table_name = '.$this->escape($table); + + if (($query = $this->query($sql)) === false) + { + return false; + } + $query = $query->getResultObject(); + + $retval = []; + foreach ($query as $row) + { + $obj = new \stdClass(); + $obj->constraint_name = $row->constraint_name; + $obj->table_name = $row->table_name; + $obj->foreign_table_name = $row->foreign_table_name; + + $retval[] = $obj; + } + + return $retval; + } + + //-------------------------------------------------------------------- + + /** + * Returns the last error code and message. + * + * Must return an array with keys 'code' and 'message': + * + * return ['code' => null, 'message' => null); + * + * @return array + */ + public function error() + { + return [ + 'code' => '', + 'message' => pg_last_error($this->connID) + ]; + } + + //-------------------------------------------------------------------- + + /** + * Insert ID + * + * @return int + */ + public function insertID() + { + $v = pg_version($this->connID); + // 'server' key is only available since PostgreSQL 7.4 + $v = isset($v['server']) ? $v['server'] : 0; + + $table = func_num_args() > 0 ? func_get_arg(0) : null; + $column = func_num_args() > 1 ? func_get_arg(1) : null; + + if ($table === null && $v >= '8.1') + { + $sql = 'SELECT LASTVAL() AS ins_id'; + } + elseif ($table !== null) + { + if ($column !== null && $v >= '8.0') + { + $sql = "SELECT pg_get_serial_sequence('{$table}', '{$column}') AS seq"; + $query = $this->query($sql); + $query = $query->getRow(); + $seq = $query->seq; + } + else + { + // seq_name passed in table parameter + $seq = $table; + } + + $sql = "SELECT CURRVAL('{$seq}') AS ins_id"; + } + else + { + return pg_last_oid($this->resultID); + } + + $query = $this->query($sql); + $query = $query->getRow(); + return (int) $query->ins_id; + } + + //-------------------------------------------------------------------- + + /** + * Build a DSN from the provided parameters + * + * @return void + */ + protected function buildDSN() + { + $this->DSN === '' or $this->DSN = ''; + + // If UNIX sockets are used, we shouldn't set a port + if (strpos($this->hostname, '/') !== false) + { + $this->port = ''; + } + + $this->hostname === '' or $this->DSN = "host={$this->hostname} "; + + if ( ! empty($this->port) && ctype_digit($this->port)) + { + $this->DSN .= "port={$this->port} "; + } + + if ($this->username !== '') + { + $this->DSN .= "user={$this->username} "; + + // An empty password is valid! + // password must be set to null to ignore it. + + $this->password === null or $this->DSN .= "password='{$this->password}' "; + } + + $this->database === '' or $this->DSN .= "dbname={$this->database} "; + + // We don't have these options as elements in our standard configuration + // array, but they might be set by parse_url() if the configuration was + // provided via string> Example: + // + // postgre://username:password@localhost:5432/database?connect_timeout=5&sslmode=1 + foreach (['connect_timeout', 'options', 'sslmode', 'service'] as $key) + { + if (isset($this->{$key}) && is_string($this->{$key}) && $this->{$key} !== '') + { + $this->DSN .= "{$key}='{$this->{$key}}' "; + } + } + + $this->DSN = rtrim($this->DSN); + } + + //-------------------------------------------------------------------- + + /** + * Set client encoding + * + * @param string $charset The client encoding to which the data will be converted. + * @return bool + */ + protected function setClientEncoding($charset) + { + return pg_set_client_encoding($this->connID, $charset) === 0; + } + + //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _transBegin(): bool + { + return (bool) pg_query($this->connID, 'BEGIN'); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _transCommit(): bool + { + return (bool) pg_query($this->connID, 'COMMIT'); + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _transRollback(): bool + { + return (bool) pg_query($this->connID, 'ROLLBACK'); + } + + // -------------------------------------------------------------------- +} diff --git a/system/Database/Postgre/Forge.php b/system/Database/Postgre/Forge.php new file mode 100644 index 0000000..06fa0ff --- /dev/null +++ b/system/Database/Postgre/Forge.php @@ -0,0 +1,238 @@ + 'INTEGER', + 'SMALLINT' => 'INTEGER', + 'INT' => 'BIGINT', + 'INT4' => 'BIGINT', + 'INTEGER' => 'BIGINT', + 'INT8' => 'NUMERIC', + 'BIGINT' => 'NUMERIC', + 'REAL' => 'DOUBLE PRECISION', + 'FLOAT' => 'DOUBLE PRECISION' + ]; + + /** + * NULL value representation in CREATE/ALTER TABLE statements + * + * @var string + */ + protected $_null = 'NULL'; + + //-------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * + * @return string|array + */ + protected function _alterTable($alter_type, $table, $field) + { + if (in_array($alter_type, ['DROP', 'ADD'], true)) + { + return parent::_alterTable($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); + $sqls = []; + for ($i = 0, $c = count($field); $i < $c; $i ++ ) + { + if ($field[$i]['_literal'] !== false) + { + return false; + } + + if (version_compare($this->db->getVersion(), '8', '>=') && isset($field[$i]['type'])) + { + $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) + . " TYPE {$field[$i]['type']}{$field[$i]['length']}"; + } + + if ( ! empty($field[$i]['default'])) + { + $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) + . " SET DEFAULT {$field[$i]['default']}"; + } + + if (isset($field[$i]['null'])) + { + $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) + . ($field[$i]['null'] === true ? ' DROP' : ' SET') . ' NOT NULL'; + } + + if ( ! empty($field[$i]['new_name'])) + { + $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) + . ' TO ' . $this->db->escapeIdentifiers($field[$i]['new_name']); + } + + if ( ! empty($field[$i]['comment'])) + { + $sqls[] = 'COMMENT ON COLUMN' . $this->db->escapeIdentifiers($table) + . '.' . $this->db->escapeIdentifiers($field[$i]['name']) + . " IS {$field[$i]['comment']}"; + } + } + + return $sqls; + } + + //-------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _processColumn($field) + { + return $this->db->escapeIdentifiers($field['name']) + . ' ' . $field['type'] . $field['length'] + . $field['default'] + . $field['null'] + . $field['auto_increment'] + . $field['unique']; + } + + + //-------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * + * @return void + */ + protected function _attributeType(&$attributes) + { + // Reset field lengths for data types that don't support it + if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== false) + { + $attributes['CONSTRAINT'] = null; + } + + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = false; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = false; + return; + case 'DATETIME': + $attributes['TYPE'] = 'TIMESTAMP'; + default: + return; + } + } + + //-------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * + * @return void + */ + protected function _attributeAutoIncrement(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true) + { + $field['type'] = $field['type'] === 'NUMERIC' ? 'BIGSERIAL' : 'SERIAL'; + } + } + + //-------------------------------------------------------------------- + + /** + * Drop Table + * + * Generates a platform-specific DROP TABLE string + * + * @param string $table Table name + * @param bool $if_exists Whether to add an IF EXISTS condition + * + * @return string + */ + protected function _dropTable($table, $if_exists, $cascade) + { + $sql = parent::_dropTable($table, $if_exists, $cascade); + + if($cascade === true) + { + $sql .= ' CASCADE'; + } + + return $sql; + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Database/Postgre/PreparedQuery.php b/system/Database/Postgre/PreparedQuery.php new file mode 100644 index 0000000..78d2af4 --- /dev/null +++ b/system/Database/Postgre/PreparedQuery.php @@ -0,0 +1,151 @@ +name = mt_rand(1, 10000000000000000); + + $sql = $this->parameterize($sql); + + // Update the query object since the parameters are slightly different + // than what was put in. + $this->query->setQuery($sql); + + if ( ! $this->statement = pg_prepare($this->db->connID, $this->name, $sql)) + { + $this->errorCode = 0; + $this->errorString = pg_last_error($this->db->connID); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Takes a new set of data and runs it against the currently + * prepared query. Upon success, will return a Results object. + * + * @param array $data + * + * @return bool + */ + public function _execute($data) + { + if (is_null($this->statement)) + { + throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + } + + $this->result = pg_execute($this->db->connID, $this->name, $data); + + return (bool) $this->result; + } + + //-------------------------------------------------------------------- + + /** + * Returns the result object for the prepared query. + * + * @return mixed + */ + public function _getResult() + { + return $this->result; + } + + //-------------------------------------------------------------------- + + /** + * Replaces the ? placeholders with $1, $2, etc parameters for use + * within the prepared query. + * + * @param string $sql + * + * @return string + */ + public function parameterize(string $sql): string + { + // Track our current value + $count = 0; + + $sql = preg_replace_callback('/\?/', function($matches) use (&$count) { + $count ++; + return "\${$count}"; + }, $sql); + + return $sql; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/Postgre/Result.php b/system/Database/Postgre/Result.php new file mode 100644 index 0000000..7c071ad --- /dev/null +++ b/system/Database/Postgre/Result.php @@ -0,0 +1,162 @@ +resultID); + } + + //-------------------------------------------------------------------- + + /** + * Generates an array of column names in the result set. + * + * @return array + */ + public function getFieldNames(): array + { + $fieldNames = []; + for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i ++ ) + { + $fieldNames[] = pg_field_name($this->resultID, $i); + } + + return $fieldNames; + } + + //-------------------------------------------------------------------- + + /** + * Generates an array of objects representing field meta-data. + * + * @return array + */ + public function getFieldData(): array + { + $retval = []; + + for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i ++ ) + { + $retval[$i] = new \stdClass(); + $retval[$i]->name = pg_field_name($this->resultID, $i); + $retval[$i]->type = pg_field_type($this->resultID, $i); + $retval[$i]->max_length = pg_field_size($this->resultID, $i); + // $retval[$i]->primary_key = (int)($fieldData[$i]->flags & 2); + // $retval[$i]->default = $fieldData[$i]->def; + } + + return $retval; + } + + //-------------------------------------------------------------------- + + /** + * Frees the current result. + * + * @return mixed + */ + public function freeResult() + { + if (is_resource($this->resultID)) + { + pg_free_result($this->resultID); + $this->resultID = false; + } + } + + //-------------------------------------------------------------------- + + /** + * Moves the internal pointer to the desired offset. This is called + * internally before fetching results to make sure the result set + * starts at zero. + * + * @param int $n + * + * @return mixed + */ + public function dataSeek($n = 0) + { + return pg_result_seek($this->resultID, $n); + } + + //-------------------------------------------------------------------- + + /** + * Returns the result set as an array. + * + * Overridden by driver classes. + * + * @return array + */ + protected function fetchAssoc() + { + return pg_fetch_assoc($this->resultID); + } + + //-------------------------------------------------------------------- + + /** + * Returns the result set as an object. + * + * Overridden by child classes. + * + * @param string $className + * + * @return object + */ + protected function fetchObject($className = 'stdClass') + { + return pg_fetch_object($this->resultID, null, $className); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/Postgre/Utils.php b/system/Database/Postgre/Utils.php new file mode 100644 index 0000000..77662d7 --- /dev/null +++ b/system/Database/Postgre/Utils.php @@ -0,0 +1,74 @@ +db = $db; + } + + //-------------------------------------------------------------------- + + /** + * Sets the raw query string to use for this statement. + * + * @param string $sql + * @param array $binds + * + * @return mixed + */ + public function setQuery(string $sql, $binds = null) + { + $this->originalQueryString = $sql; + + if ( ! is_null($binds)) + { + $this->binds = $binds; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Will store the variables to bind into the query later. + * + * @param array $binds + * + * @return $this + */ + public function setBinds(array $binds) + { + $this->binds = $binds; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the final, processed query string after binding, etal + * has been performed. + * + * @return mixed + */ + public function getQuery(): string + { + if (empty($this->finalQueryString)) + { + $this->finalQueryString = $this->originalQueryString; + } + + $this->compileBinds(); + + return $this->finalQueryString; + } + + //-------------------------------------------------------------------- + + /** + * Records the execution time of the statement using microtime(true) + * for it's start and end values. If no end value is present, will + * use the current time to determine total duration. + * + * @param float $start + * @param float $end + * + * @return mixed + */ + public function setDuration(float $start, float $end = null) + { + $this->startTime = $start; + + if (is_null($end)) + { + $end = microtime(true); + } + + $this->endTime = $end; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the start time in seconds with microseconds. + * + * @param bool $returnRaw + * @param int $decimals + * + * @return mixed + */ + public function getStartTime($returnRaw = false, int $decimals = 6) + { + if ($returnRaw) + { + return $this->startTime; + } + + return number_format($this->startTime, $decimals); + } + + //-------------------------------------------------------------------- + /** + * Returns the duration of this query during execution, or null if + * the query has not been executed yet. + * + * @param int $decimals The accuracy of the returned time. + * + * @return mixed + */ + public function getDuration(int $decimals = 6) + { + return number_format(($this->endTime - $this->startTime), $decimals); + } + + //-------------------------------------------------------------------- + + /** + * Stores the error description that happened for this query. + * + * @param int $code + * @param string $error + * + * @return Query + */ + public function setError(int $code, string $error) + { + $this->errorCode = $code; + $this->errorString = $error; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Reports whether this statement created an error not. + * + * @return bool + */ + public function hasError(): bool + { + return ! empty($this->errorString); + } + + //-------------------------------------------------------------------- + + /** + * Returns the error code created while executing this statement. + * + * @return int + */ + public function getErrorCode(): int + { + return $this->errorCode; + } + + //-------------------------------------------------------------------- + + /** + * Returns the error message created while executing this statement. + * + * @return string + */ + public function getErrorMessage(): string + { + return $this->errorString; + } + + //-------------------------------------------------------------------- + + /** + * Determines if the statement is a write-type query or not. + * + * @return bool + */ + public function isWriteType(): bool + { + return (bool) preg_match( + '/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX)\s/i', $this->originalQueryString); + } + + //-------------------------------------------------------------------- + + /** + * Swaps out one table prefix for a new one. + * + * @param string $orig + * @param string $swap + * + * @return mixed + */ + public function swapPrefix(string $orig, string $swap) + { + $sql = empty($this->finalQueryString) ? $this->originalQueryString : $this->finalQueryString; + + $this->finalQueryString = preg_replace('/(\W)' . $orig . '(\S+?)/', '\\1' . $swap . '\\2', $sql); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the original SQL that was passed into the system. + * + * @return string + */ + public function getOriginalQuery() + { + return $this->originalQueryString; + } + + //-------------------------------------------------------------------- + + /** + * Escapes and inserts any binds into the finalQueryString object. + */ + protected function compileBinds() + { + $sql = $this->finalQueryString; + + $hasNamedBinds = strpos($sql, ':') !== false; + + if (empty($this->binds) || empty($this->bindMarker) || + (strpos($sql, $this->bindMarker) === false && + $hasNamedBinds === false) + ) + { + return; + } + + if ( ! is_array($this->binds)) + { + $binds = [$this->binds]; + $bindCount = 1; + } + else + { + $binds = $this->binds; + $bindCount = count($binds); + } + + // Reverse the binds so that duplicate named binds + // will be processed prior to the original binds. + if ( ! is_numeric(key(array_slice($binds, 0, 1)))) + { + $binds = array_reverse($binds); + } + + // We'll need marker length later + $ml = strlen($this->bindMarker); + + if ($hasNamedBinds) + { + $sql = $this->matchNamedBinds($sql, $binds); + } + else + { + $sql = $this->matchSimpleBinds($sql, $binds, $bindCount, $ml); + } + + $this->finalQueryString = $sql; + } + + //-------------------------------------------------------------------- + + /** + * Match bindings + * @param string $sql + * @param array $binds + * @return string + */ + protected function matchNamedBinds(string $sql, array $binds) + { + $replacers = []; + + foreach ($binds as $placeholder => $value) + { + $escapedValue = $this->db->escape($value); + + // In order to correctly handle backlashes in saved strings + // we will need to preg_quote, so remove the wrapping escape characters + // otherwise it will get escaped. + if (is_array($value)) + { + $escapedValue = '(' . implode(',', $escapedValue) . ')'; + } + + $replacers[":{$placeholder}:"] = $escapedValue; + +// $sql = preg_replace('|:' . $placeholder . '(?!\w)|', $escapedValue, $sql); + } + + $sql = strtr($sql, $replacers); + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Match bindings + * @param string $sql + * @param array $binds + * @param int $bindCount + * @param int $ml + * @return string + */ + protected function matchSimpleBinds(string $sql, array $binds, int $bindCount, int $ml) + { + // Make sure not to replace a chunk inside a string that happens to match the bind marker + if ($c = preg_match_all("/'[^']*'/i", $sql, $matches)) + { + $c = preg_match_all('/' . preg_quote($this->bindMarker, '/') . '/i', str_replace($matches[0], str_replace($this->bindMarker, str_repeat(' ', $ml), $matches[0]), $sql, $c), $matches, PREG_OFFSET_CAPTURE); + + // Bind values' count must match the count of markers in the query + if ($bindCount !== $c) + { + return $sql; + } + } + // Number of binds must match bindMarkers in the string. + else if (($c = preg_match_all('/' . preg_quote($this->bindMarker, '/') . '/i', $sql, $matches, PREG_OFFSET_CAPTURE)) !== $bindCount) + { + return $sql; + } + + do + { + $c --; + $escapedValue = $this->db->escape($binds[$c]); + if (is_array($escapedValue)) + { + $escapedValue = '(' . implode(',', $escapedValue) . ')'; + } + $sql = substr_replace($sql, $escapedValue, $matches[0][$c][1], $ml); + } while ($c !== 0); + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Return text representation of the query + * + * @return mixed|string + */ + public function __toString() + { + return $this->getQuery(); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/QueryInterface.php b/system/Database/QueryInterface.php new file mode 100644 index 0000000..b4b526f --- /dev/null +++ b/system/Database/QueryInterface.php @@ -0,0 +1,155 @@ +seedPath = $config->filesPath ?? APPPATH . 'Database/'; + + if (empty($this->seedPath)) + { + throw new \InvalidArgumentException('Invalid filesPath set in the Config\Database.'); + } + + $this->seedPath = rtrim($this->seedPath, '/') . '/Seeds/'; + + if ( ! is_dir($this->seedPath)) + { + throw new \InvalidArgumentException('Unable to locate the seeds directory. Please check Config\Database::filesPath'); + } + + $this->config = & $config; + + if (is_null($db)) + { + $db = \Config\Database::connect($this->DBGroup); + } + + $this->db = & $db; + } + + //-------------------------------------------------------------------- + + /** + * Loads the specified seeder and runs it. + * + * @param string $class + * + * @throws \InvalidArgumentException + */ + public function call(string $class) + { + if (empty($class)) + { + throw new \InvalidArgumentException('No Seeder was specified.'); + } + + $path = str_replace('.php', '', $class) . '.php'; + + // If we have namespaced class, simply try to load it. + if (strpos($class, '\\') !== false) + { + $seeder = new $class($this->config); + } + // Otherwise, try to load the class manually. + else + { + $path = $this->seedPath . $path; + + if ( ! is_file($path)) + { + throw new \InvalidArgumentException('The specified Seeder is not a valid file: ' . $path); + } + + if ( ! class_exists($class, false)) + { + require $path; + } + + $seeder = new $class($this->config); + } + + $seeder->run(); + + unset($seeder); + + if (is_cli() && ! $this->silent) + { + CLI::write("Seeded: {$class}", 'green'); + } + } + + //-------------------------------------------------------------------- + + /** + * Sets the location of the directory that seed files can be located in. + * + * @param string $path + * + * @return Seeder + */ + public function setPath(string $path) + { + $this->seedPath = rtrim($path, '/') . '/'; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Sets the silent treatment. + * + * @param bool $silent + * + * @return Seeder + */ + public function setSilent(bool $silent) + { + $this->silent = $silent; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Run the database seeds. This is where the magic happens. + * + * Child classes must implement this method and take care + * of inserting their data here. + * + * @return mixed + */ + public function run() + { + + } + + //-------------------------------------------------------------------- +} diff --git a/system/Debug/CustomExceptions.php b/system/Debug/CustomExceptions.php new file mode 100644 index 0000000..26b095a --- /dev/null +++ b/system/Debug/CustomExceptions.php @@ -0,0 +1,165 @@ +ob_level = ob_get_level(); + + $this->viewPath = rtrim($config->errorViewPath, '/ ') . '/'; + + $this->config = $config; + } + + //-------------------------------------------------------------------- + + /** + * Responsible for registering the error, exception and shutdown + * handling of our application. + */ + public function initialize() + { + //Set the Exception Handler + set_exception_handler([$this, 'exceptionHandler']); + + // Set the Error Handler + set_error_handler([$this, 'errorHandler']); + + // Set the handler for shutdown to catch Parse errors + // Do we need this in PHP7? + register_shutdown_function([$this, 'shutdownHandler']); + } + + //-------------------------------------------------------------------- + + /** + * Catches any uncaught errors and exceptions, including most Fatal errors + * (Yay PHP7!). Will log the error, display it if display_errors is on, + * and fire an event that allows custom actions to be taken at this point. + * + * @param \Throwable $exception + */ + public function exceptionHandler(\Throwable $exception) + { + $codes = $this->determineCodes($exception); + $statusCode = $codes[0]; + $exitCode = $codes[1]; + + // Log it + if ($this->config->log === true && ! in_array($statusCode, $this->config->ignoreCodes)) + { + log_message('critical', $exception->getMessage()."\n{trace}", [ + 'trace' => $exception->getTraceAsString() + ]); + } + + if (! is_cli()) + { + $response = Services::response()->setStatusCode($statusCode); + $header = "HTTP/1.1 {$response->getStatusCode()} {$response->getReason()}"; + header($header, true, $statusCode); + } + + $this->render($exception, $statusCode); + + exit($exitCode); + } + + //-------------------------------------------------------------------- + + /** + * Even in PHP7, some errors make it through to the errorHandler, so + * convert these to Exceptions and let the exception handler log it and + * display it. + * + * This seems to be primarily when a user triggers it with trigger_error(). + * + * @param int $severity + * @param string $message + * @param string|null $file + * @param int|null $line + * @param null $context + * + * @throws \ErrorException + */ + public function errorHandler(int $severity, string $message, string $file = null, int $line = null, $context = null) + { + // Convert it to an exception and pass it along. + throw new \ErrorException($message, 0, $severity, $file, $line); + } + + //-------------------------------------------------------------------- + + /** + * Checks to see if any errors have happened during shutdown that + * need to be caught and handle them. + */ + public function shutdownHandler() + { + $error = error_get_last(); + + // If we've got an error that hasn't been displayed, then convert + // it to an Exception and use the Exception handler to display it + // to the user. + if ( ! is_null($error)) + { + // Fatal Error? + if (in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) + { + $this->exceptionHandler(new \ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line'])); + } + } + } + + //-------------------------------------------------------------------- + + /** + * Determines the view to display based on the exception thrown, + * whether an HTTP or CLI request, etc. + * + * @param \Throwable $exception + * @param string $template_path + * + * @return string The path and filename of the view file to use + */ + protected function determineView(\Throwable $exception, string $template_path): string + { + // Production environments should have a custom exception file. + $view = 'production.php'; + $template_path = rtrim($template_path, '/ ') . '/'; + + if (str_ireplace(['off', 'none', 'no', 'false', 'null'], '', ini_get('display_errors'))) + { + $view = 'error_exception.php'; + } + + // 404 Errors + if ($exception instanceof \CodeIgniter\PageNotFoundException) + { + return 'error_404.php'; + } + + // Allow for custom views based upon the status code + else if (is_file($template_path . 'error_' . $exception->getCode() . '.php')) + { + return 'error_' . $exception->getCode() . '.php'; + } + + return $view; + } + + //-------------------------------------------------------------------- + + /** + * Given an exception and status code will display the error to the client. + * + * @param \Throwable $exception + * @param int $statusCode + */ + protected function render(\Throwable $exception, int $statusCode) + { + // Determine directory with views + $path = $this->viewPath; + if (empty($path)) + { + $path = APPPATH . 'Views/errors/'; + } + + $path = is_cli() + ? $path.'cli/' + : $path.'html/'; + + // Determine the vew + $view = $this->determineView($exception, $path); + + // Prepare the vars + $vars = $this->collectVars($exception, $statusCode); + extract($vars); + + // Render it + if (ob_get_level() > $this->ob_level + 1) + { + ob_end_clean(); + } + + ob_start(); + include($path . $view); + $buffer = ob_get_contents(); + ob_end_clean(); + echo $buffer; + } + + //-------------------------------------------------------------------- + + /** + * Gathers the variables that will be made available to the view. + * + * @param \Throwable $exception + * @param int $statusCode + * + * @return array + */ + protected function collectVars(\Throwable $exception, int $statusCode) + { + return [ + 'type' => get_class($exception), + 'code' => $statusCode, + 'message' => $exception->getMessage() ?? '(null)', + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'trace' => $exception->getTrace(), + 'title' => get_class($exception) + ]; + } + + + /** + * Determines the HTTP status code and the exit status code for this request. + * + * @param \Throwable $exception + * + * @return array + */ + protected function determineCodes(\Throwable $exception): array + { + $statusCode = abs($exception->getCode()); + + if ($statusCode < 100 || $statusCode > 599) + { + $exitStatus = $statusCode + EXIT__AUTO_MIN; // 9 is EXIT__AUTO_MIN + if ($exitStatus > EXIT__AUTO_MAX) // 125 is EXIT__AUTO_MAX + { + $exitStatus = EXIT_ERROR; // EXIT_ERROR + } + $statusCode = 500; + } + else + { + $exitStatus = 1; // EXIT_ERROR + } + + return [ + $statusCode ?? 500, + $exitStatus + ]; + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + // Display Methods + //-------------------------------------------------------------------- + + /** + * Clean Path + * + * This makes nicer looking paths for the error output. + * + * @param string $file + * + * @return string + */ + public static function cleanPath($file) + { + if (strpos($file, APPPATH) === 0) + { + $file = 'APPPATH/' . substr($file, strlen(APPPATH)); + } + elseif (strpos($file, BASEPATH) === 0) + { + $file = 'BASEPATH/' . substr($file, strlen(BASEPATH)); + } + elseif (strpos($file, FCPATH) === 0) + { + $file = 'FCPATH/' . substr($file, strlen(FCPATH)); + } + + return $file; + } + + //-------------------------------------------------------------------- + + /** + * Describes memory usage in real-world units. Intended for use + * with memory_get_usage, etc. + * + * @param $bytes + * + * @return string + */ + public static function describeMemory(int $bytes): string + { + if ($bytes < 1024) + { + return $bytes . 'B'; + } + else if ($bytes < 1048576) + { + return round($bytes / 1024, 2) . 'KB'; + } + + return round($bytes / 1048576, 2) . 'MB'; + } + + //-------------------------------------------------------------------- + + /** + * Creates a syntax-highlighted version of a PHP file. + * + * @param $file + * @param $lineNumber + * @param int $lines + * + * @return bool|string + */ + public static function highlightFile($file, $lineNumber, $lines = 15) + { + if (empty($file) || ! is_readable($file)) + { + return false; + } + + // Set our highlight colors: + if (function_exists('ini_set')) + { + ini_set('highlight.comment', '#767a7e; font-style: italic'); + ini_set('highlight.default', '#c7c7c7'); + ini_set('highlight.html', '#06B'); + ini_set('highlight.keyword', '#f1ce61;'); + ini_set('highlight.string', '#869d6a'); + } + + try + { + $source = file_get_contents($file); + } catch (\Throwable $e) + { + return false; + } + + $source = str_replace(["\r\n", "\r"], "\n", $source); + $source = explode("\n", highlight_string($source, true)); + $source = str_replace('
', "\n", $source[1]); + + $source = explode("\n", str_replace("\r\n", "\n", $source)); + + // Get just the part to show + $start = $lineNumber - (int) round($lines / 2); + $start = $start < 0 ? 0 : $start; + + // Get just the lines we need to display, while keeping line numbers... + $source = array_splice($source, $start, $lines, true); + + // Used to format the line number in the source + $format = '% ' . strlen($start + $lines) . 'd'; + + $out = ''; + // Because the highlighting may have an uneven number + // of open and close span tags on one line, we need + // to ensure we can close them all to get the lines + // showing correctly. + $spans = 1; + + foreach ($source as $n => $row) + { + $spans += substr_count($row, ']+>#', $row, $tags); + $out .= sprintf("{$format} %s\n%s", $n + $start + 1, strip_tags($row), implode('', $tags[0]) + ); + } + else + { + $out .= sprintf('' . $format . ' %s', $n + $start + 1, $row) . "\n"; + } + } + + if ($spans > 0) + { + $out .= str_repeat('', $spans); + } + + return '
' . $out . '
'; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Debug/Iterator.php b/system/Debug/Iterator.php new file mode 100644 index 0000000..6fac931 --- /dev/null +++ b/system/Debug/Iterator.php @@ -0,0 +1,170 @@ +tests[$name] = $closure; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Runs through all of the tests that have been added, recording + * time to execute the desired number of iterations, and the approximate + * memory usage used during those iterations. + * + * @param int $iterations + * @param bool $output + * + * @return string + */ + public function run($iterations = 1000, $output = true) + { + foreach ($this->tests as $name => $test) + { + // clear memory before start + gc_collect_cycles(); + + $start = microtime(true); + $start_mem = $max_memory = memory_get_usage(true); + + for ($i = 0; $i < $iterations; $i ++ ) + { + $result = call_user_func($test); + + $max_memory = max($max_memory, memory_get_usage(true)); + + unset($result); + } + + $this->results[$name] = [ + 'time' => microtime(true) - $start, + 'memory' => $max_memory - $start_mem, + 'n' => $iterations, + ]; + } + + if ($output) + { + return $this->getReport(); + } + } + + //-------------------------------------------------------------------- + + /** + * Get results. + * + * @return string + */ + public function getReport() + { + if (empty($this->results)) + { + return 'No results to display.'; + } + + // Template + $tpl = " + + + + + + + + + {rows} + +
TestTimeMemory
"; + + $rows = ""; + + foreach ($this->results as $name => $result) + { + $rows .= " + {$name} + " . number_format($result['time'], 4) . " + {$result['memory']} + "; + } + + $tpl = str_replace('{rows}', $rows, $tpl); + + return $tpl . "
"; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Debug/Timer.php b/system/Debug/Timer.php new file mode 100644 index 0000000..e62b867 --- /dev/null +++ b/system/Debug/Timer.php @@ -0,0 +1,181 @@ +timers[strtolower($name)] = [ + 'start' => ! empty($time) ? $time : microtime(true), + 'end' => null, + ]; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Stops a running timer. + * + * If the timer is not stopped before the timers() method is called, + * it will be automatically stopped at that point. + * + * @param string $name The name of this timer. + * + * @return Timer + */ + public function stop(string $name) + { + $name = strtolower($name); + + if (empty($this->timers[$name])) + { + throw new \RuntimeException('Cannot stop timer: invalid name given.'); + } + + $this->timers[$name]['end'] = microtime(true); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the duration of a recorded timer. + * + * @param string $name The name of the timer. + * @param int $decimals Number of decimal places. + * + * @return null|float Returns null if timer exists by that name. + * Returns a float representing the number of + * seconds elapsed while that timer was running. + */ + public function getElapsedTime(string $name, int $decimals = 4) + { + $name = strtolower($name); + + if (empty($this->timers[$name])) + { + return null; + } + + $timer = $this->timers[$name]; + + if (empty($timer['end'])) + { + $timer['end'] = microtime(true); + } + + return (float) number_format($timer['end'] - $timer['start'], $decimals); + } + + //-------------------------------------------------------------------- + + /** + * Returns the array of timers, with the duration pre-calculated for you. + * + * @param int $decimals Number of decimal places + * + * @return array + */ + public function getTimers(int $decimals = 4) + { + $timers = $this->timers; + + foreach ($timers as &$timer) + { + if (empty($timer['end'])) + { + $timer['end'] = microtime(true); + } + + $timer['duration'] = (float) number_format($timer['end'] - $timer['start'], $decimals); + } + + return $timers; + } + + //-------------------------------------------------------------------- + + /** + * Checks whether or not a timer with the specified name exists. + * + * @param string $name + * + * @return bool + */ + public function has(string $name) + { + return array_key_exists(strtolower($name), $this->timers); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php new file mode 100644 index 0000000..0f1fc0c --- /dev/null +++ b/system/Debug/Toolbar.php @@ -0,0 +1,229 @@ +toolbarCollectors as $collector) + { + if ( ! class_exists($collector)) + { + // @todo Log this! + continue; + } + + $this->collectors[] = new $collector(); + } + } + + //-------------------------------------------------------------------- + + /** + * Run + * + * @param float $startTime + * @param float $totalTime + * @param float $startMemory + * @param \CodeIgniter\HTTP\RequestInterface $request + * @param \CodeIgniter\HTTP\ResponseInterface $response + * + * @return string + */ + public function run($startTime, $totalTime, $startMemory, $request, $response): string + { + $this->startTime = $startTime; + + // Data items used within the view. + $collectors = $this->collectors; + + $totalTime = $totalTime * 1000; + $totalMemory = number_format((memory_get_peak_usage() - $startMemory) / 1048576, 3); + $segmentDuration = $this->roundTo($totalTime / 7, 5); + $segmentCount = (int) ceil($totalTime / $segmentDuration); + $varData = $this->collectVarData(); + + ob_start(); + include(__DIR__ . '/Toolbar/Views/toolbar.tpl.php'); + $output = ob_get_contents(); + ob_end_clean(); + + return $output; + } + + //-------------------------------------------------------------------- + + /** + * Called within the view to display the timeline itself. + * + * @param int $segmentCount + * @param int $segmentDuration + * @return string + */ + protected function renderTimeline(int $segmentCount, int $segmentDuration): string + { + $displayTime = $segmentCount * $segmentDuration; + + $rows = $this->collectTimelineData(); + + $output = ''; + + foreach ($rows as $row) + { + $output .= ""; + $output .= "{$row['name']}"; + $output .= "{$row['component']}"; + $output .= "" . number_format($row['duration'] * 1000, 2) . " ms"; + $output .= ""; + + $offset = ((($row['start'] - $this->startTime) * 1000) / + $displayTime) * 100; + $length = (($row['duration'] * 1000) / $displayTime) * 100; + + $output .= ""; + + $output .= ""; + + $output .= ""; + } + + return $output; + } + + //-------------------------------------------------------------------- + + /** + * Returns a sorted array of timeline data arrays from the collectors. + * + * @return array + */ + protected function collectTimelineData(): array + { + $data = []; + + // Collect it + foreach ($this->collectors as $collector) + { + if ( ! $collector->hasTimelineData()) + { + continue; + } + + $data = array_merge($data, $collector->timelineData()); + } + + // Sort it + + + return $data; + } + + //-------------------------------------------------------------------- + + /** + * Returns an array of data from all of the modules + * that should be displayed in the 'Vars' tab. + * + * @return array + */ + protected function collectVarData()// : array + { + $data = []; + + foreach ($this->collectors as $collector) + { + if ( ! $collector->hasVarData()) + { + continue; + } + + $data = array_merge($data, $collector->getVarData()); + } + + return $data; + } + + //-------------------------------------------------------------------- + + /** + * Rounds a number to the nearest incremental value. + * + * @param float $number + * @param int $increments + * + * @return float + */ + protected function roundTo($number, $increments = 5) + { + $increments = 1 / $increments; + + return (ceil($number * $increments) / $increments); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Debug/Toolbar/Collectors/BaseCollector.php b/system/Debug/Toolbar/Collectors/BaseCollector.php new file mode 100644 index 0000000..df0ca0a --- /dev/null +++ b/system/Debug/Toolbar/Collectors/BaseCollector.php @@ -0,0 +1,281 @@ +title)); + } + + return $this->title; + } + + //-------------------------------------------------------------------- + + /** + * Returns any information that should be shown next to the title. + * + * @return string + */ + public function getTitleDetails(): string + { + return ''; + } + + //-------------------------------------------------------------------- + + /** + * Does this collector need it's own tab? + * + * @return bool + */ + public function hasTabContent(): bool + { + return (bool) $this->hasTabContent; + } + + //-------------------------------------------------------------------- + + /** + * Does this collector have a label? + * + * @return bool + */ + public function hasLabel(): bool + { + return (bool) $this->hasLabel; + } + + //-------------------------------------------------------------------- + + /** + * Does this collector have information for the timeline? + * + * @return bool + */ + public function hasTimelineData(): bool + { + return (bool) $this->hasTimeline; + } + + //-------------------------------------------------------------------- + + /** + * Grabs the data for the timeline, properly formatted, + * or returns an empty array. + * + * @return array + */ + public function timelineData(): array + { + if ( ! $this->hasTimeline) + { + return []; + } + + return $this->formatTimelineData(); + } + + //-------------------------------------------------------------------- + + /** + * Does this Collector have data that should be shown in the + * 'Vars' tab? + * + * @return mixed + */ + public function hasVarData() + { + return (bool) $this->hasVarData; + } + + //-------------------------------------------------------------------- + + /** + * Gets a collection of data that should be shown in the 'Vars' tab. + * The format is an array of sections, each with their own array + * of key/value pairs: + * + * $data = [ + * 'section 1' => [ + * 'foo' => 'bar, + * 'bar' => 'baz' + * ], + * 'section 2' => [ + * 'foo' => 'bar, + * 'bar' => 'baz' + * ], + * ]; + * + * @return null + */ + public function getVarData() + { + return null; + } + + //-------------------------------------------------------------------- + + /** + * Child classes should implement this to return the timeline data + * formatted for correct usage. + * + * Timeline data should be formatted into arrays that look like: + * + * [ + * 'name' => 'Database::Query', + * 'component' => 'Database', + * 'start' => 10 // milliseconds + * 'duration' => 15 // milliseconds + * ] + * + * @return array + */ + protected function formatTimelineData(): array + { + return []; + } + + //-------------------------------------------------------------------- + + /** + * Builds and returns the HTML needed to fill a tab to display + * within the Debug Bar + * + * @return string + */ + public function display(): string + { + return ''; + } + + //-------------------------------------------------------------------- + + /** + * Clean Path + * + * This makes nicer looking paths for the error output. + * + * @param string $file + * + * @return string + */ + public function cleanPath($file) + { + if (strpos($file, APPPATH) === 0) + { + $file = 'APPPATH/' . substr($file, strlen(APPPATH)); + } + elseif (strpos($file, BASEPATH) === 0) + { + $file = 'BASEPATH/' . substr($file, strlen(BASEPATH)); + } + elseif (strpos($file, FCPATH) === 0) + { + $file = 'FCPATH/' . substr($file, strlen(FCPATH)); + } + + return $file; + } + + /** + * Gets the "badge" value for the button. + * + * @param string $value + */ + public function getBadgeValue() + { + return null; + } + +} diff --git a/system/Debug/Toolbar/Collectors/Database.php b/system/Debug/Toolbar/Collectors/Database.php new file mode 100644 index 0000000..f0ddd78 --- /dev/null +++ b/system/Debug/Toolbar/Collectors/Database.php @@ -0,0 +1,217 @@ +connections = \Config\Database::getConnections(); + } + + //-------------------------------------------------------------------- + + /** + * The static method used during Events to collect + * data. + * + * @param \CodeIgniter\Database\Query $query + * + * @internal param $ array \CodeIgniter\Database\Query + */ + public static function collect(Query $query) + { + static::$queries[] = $query; + } + + //-------------------------------------------------------------------- + + /** + * Returns timeline data formatted for the toolbar. + * + * @return array The formatted data or an empty array. + */ + protected function formatTimelineData(): array + { + $data = []; + + foreach ($this->connections as $alias => $connection) + { + // Connection Time + $data[] = [ + 'name' => 'Connecting to Database: "' . $alias . '"', + 'component' => 'Database', + 'start' => $connection->getConnectStart(), + 'duration' => $connection->getConnectDuration() + ]; + } + + foreach (static::$queries as $query) + { + $data[] = [ + 'name' => 'Query', + 'component' => 'Database', + 'start' => $query->getStartTime(true), + 'duration' => $query->getDuration() + ]; + } + + return $data; + } + + //-------------------------------------------------------------------- + + /** + * Returns the HTML to fill the Database tab in the toolbar. + * + * @return string The data formatted for the toolbar. + */ + public function display(): string + { + // Key words we want bolded + $highlight = ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'AND', 'LEFT JOIN', 'ORDER BY', 'GROUP BY', + 'LIMIT', 'INSERT', 'INTO', 'VALUES', 'UPDATE', 'OR ', 'HAVING', 'OFFSET', 'NOT IN', + 'IN', 'LIKE', 'NOT LIKE', 'COUNT', 'MAX', 'MIN', 'ON', 'AS', 'AVG', 'SUM', '(', ')' + ]; + + $parser = \Config\Services::parser(BASEPATH . 'Debug/Toolbar/Views/', null,false); + + $data = [ + 'queries' => [] + ]; + + foreach (static::$queries as $query) + { + $sql = $query->getQuery(); + + foreach ($highlight as $term) + { + $sql = str_replace($term, "{$term}", $sql); + } + + $data['queries'][] = [ + 'duration' => ($query->getDuration(5) * 1000) .' ms', + 'sql' => $sql + ]; + } + + $output = $parser->setData($data) + ->render('_database.tpl'); + + return $output; + } + + //-------------------------------------------------------------------- + + /** + * Gets the "badge" value for the button. + * + * @param string $value + */ + public function getBadgeValue() + { + return count(static::$queries); + } + + //-------------------------------------------------------------------- + + /** + * Information to be displayed next to the title. + * + * @return string The number of queries (in parentheses) or an empty string. + */ + public function getTitleDetails(): string + { + return '(' . count(static::$queries) . ' Queries across ' . count($this->connections) . ' Connection' . + (count($this->connections) > 1 ? 's' : '') . ')'; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Debug/Toolbar/Collectors/Events.php b/system/Debug/Toolbar/Collectors/Events.php new file mode 100644 index 0000000..7275d8e --- /dev/null +++ b/system/Debug/Toolbar/Collectors/Events.php @@ -0,0 +1,171 @@ +viewer = Services::renderer(null, true); + } + + //-------------------------------------------------------------------- + + /** + * Child classes should implement this to return the timeline data + * formatted for correct usage. + * + * @return mixed + */ + protected function formatTimelineData(): array + { + $data = []; + + $rows = $this->viewer->getPerformanceData(); + + foreach ($rows as $name => $info) + { + $data[] = [ + 'name' => 'View: ' . $info['view'], + 'component' => 'Views', + 'start' => $info['start'], + 'duration' => $info['end'] - $info['start'] + ]; + } + + return $data; + } + + //-------------------------------------------------------------------- + + /** + * Returns the HTML to fill the Events tab in the toolbar. + * + * @return string The data formatted for the toolbar. + */ + public function display(): string + { + $parser = \Config\Services::parser(BASEPATH . 'Debug/Toolbar/Views/', null,false); + + $data = [ + 'events' => [] + ]; + + foreach (\CodeIgniter\Events\Events::getPerformanceLogs() as $row) + { + $key = $row['event']; + + if (! array_key_exists($key, $data['events'])) + { + $data['events'][$key] = [ + 'event' => $key, + 'duration' => number_format(($row['end']-$row['start']) * 1000, 2), + 'count' => 1, + ]; + + continue; + } + + $data['events'][$key]['duration'] += number_format(($row['end']-$row['start']) * 1000, 2); + $data['events'][$key]['count']++; + } + + $output = $parser->setData($data) + ->render('_events.tpl'); + + return $output; + } + + //-------------------------------------------------------------------- + + /** + * Gets the "badge" value for the button. + */ + public function getBadgeValue() + { + return count(\CodeIgniter\Events\Events::getPerformanceLogs()); + } +} diff --git a/system/Debug/Toolbar/Collectors/Files.php b/system/Debug/Toolbar/Collectors/Files.php new file mode 100644 index 0000000..ba71b6d --- /dev/null +++ b/system/Debug/Toolbar/Collectors/Files.php @@ -0,0 +1,141 @@ +cleanPath($file); + + if (strpos($path, 'BASEPATH') !== false) + { + $coreFiles[] = [ + 'name' => basename($file), + 'path' => $path + ]; + } + else + { + $userFiles[] = [ + 'name' => basename($file), + 'path' => $path + ]; + } + } + + sort($userFiles); + sort($coreFiles); + + return $parser->setData([ + 'coreFiles' => $coreFiles, + 'userFiles' => $userFiles, + ]) + ->render('_files.tpl'); + } + + //-------------------------------------------------------------------- + + /** + * Displays the number of included files as a badge in the tab button. + * + * @return int + */ + public function getBadgeValue() + { + return count(get_included_files()); + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Debug/Toolbar/Collectors/Logs.php b/system/Debug/Toolbar/Collectors/Logs.php new file mode 100644 index 0000000..48fafd9 --- /dev/null +++ b/system/Debug/Toolbar/Collectors/Logs.php @@ -0,0 +1,97 @@ +logCache; + + if (empty($logs) || ! is_array($logs)) + { + return '

Nothing was logged. If you were expecting logged items, ensure that LoggerConfig file has the correct threshold set.

'; + } + + return $parser->setData([ + 'logs' => $logs + ]) + ->render('_logs.tpl'); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php new file mode 100644 index 0000000..bf19c39 --- /dev/null +++ b/system/Debug/Toolbar/Collectors/Routes.php @@ -0,0 +1,150 @@ +getMatchedRoute(); + + // Get our parameters + $method = is_callable($router->controllerName()) ? new \ReflectionFunction($router->controllerName()) : new \ReflectionMethod($router->controllerName(), $router->methodName()); + $rawParams = $method->getParameters(); + + $params = []; + foreach ($rawParams as $key => $param) + { + $params[] = [ + 'name' => $param->getName(), + 'value' => $router->params()[$key] ?? + "<empty> | default: " . var_export($param->getDefaultValue(), true) + ]; + } + + $matchedRoute = [ + [ + 'directory' => $router->directory(), + 'controller' => $router->controllerName(), + 'method' => $router->methodName(), + 'paramCount' => count($router->params()), + 'truePCount' => count($params), + 'params' => $params ?? [] + ] + ]; + + /* + * Defined Routes + */ + $rawRoutes = $rawRoutes->getRoutes(); + $routes = []; + + foreach ($rawRoutes as $from => $to) + { + $routes[] = [ + 'from' => $from, + 'to' => $to + ]; + } + + return $parser->setData([ + 'matchedRoute' => $matchedRoute, + 'routes' => $routes + ]) + ->render('CodeIgniter\Debug\Toolbar\Views\_routes.tpl'); + } + + //-------------------------------------------------------------------- + + /** + * Returns a count of all the routes in the system. + * + * @return int + */ + public function getBadgeValue() + { + $rawRoutes = Services::routes(true); + + return count($rawRoutes->getRoutes()); + } + +} diff --git a/system/Debug/Toolbar/Collectors/Timers.php b/system/Debug/Toolbar/Collectors/Timers.php new file mode 100644 index 0000000..ba6244a --- /dev/null +++ b/system/Debug/Toolbar/Collectors/Timers.php @@ -0,0 +1,102 @@ +getTimers(6); + + foreach ($rows as $name => $info) + { + if ($name == 'total_execution') + continue; + + $data[] = [ + 'name' => ucwords(str_replace('_', ' ', $name)), + 'component' => 'Timer', + 'start' => $info['start'], + 'duration' => $info['end'] - $info['start'] + ]; + } + + return $data; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Debug/Toolbar/Collectors/Views.php b/system/Debug/Toolbar/Collectors/Views.php new file mode 100644 index 0000000..d635a38 --- /dev/null +++ b/system/Debug/Toolbar/Collectors/Views.php @@ -0,0 +1,176 @@ +viewer = Services::renderer(null, true); + } + + //-------------------------------------------------------------------- + + /** + * Child classes should implement this to return the timeline data + * formatted for correct usage. + * + * @return mixed + */ + protected function formatTimelineData(): array + { + $data = []; + + $rows = $this->viewer->getPerformanceData(); + + foreach ($rows as $name => $info) + { + $data[] = [ + 'name' => 'View: ' . $info['view'], + 'component' => 'Views', + 'start' => $info['start'], + 'duration' => $info['end'] - $info['start'] + ]; + } + + return $data; + } + + //-------------------------------------------------------------------- + + /** + * Gets a collection of data that should be shown in the 'Vars' tab. + * The format is an array of sections, each with their own array + * of key/value pairs: + * + * $data = [ + * 'section 1' => [ + * 'foo' => 'bar, + * 'bar' => 'baz' + * ], + * 'section 2' => [ + * 'foo' => 'bar, + * 'bar' => 'baz' + * ], + * ]; + * + * @return null + */ + public function getVarData() + { + return [ + 'View Data' => $this->viewer->getData() + ]; + } + + //-------------------------------------------------------------------- + + /** + * Returns a count of all views. + * + * @return int + */ + public function getBadgeValue() + { + return count($this->viewer->getPerformanceData()); + } + +} diff --git a/system/Debug/Toolbar/Views/_database.tpl.php b/system/Debug/Toolbar/Views/_database.tpl.php new file mode 100644 index 0000000..ace8da2 --- /dev/null +++ b/system/Debug/Toolbar/Views/_database.tpl.php @@ -0,0 +1,16 @@ + + + + + + + + + {queries} + + + + + {/queries} + +
TimeQuery String
{duration}{! sql !}
diff --git a/system/Debug/Toolbar/Views/_events.tpl.php b/system/Debug/Toolbar/Views/_events.tpl.php new file mode 100644 index 0000000..72be9b9 --- /dev/null +++ b/system/Debug/Toolbar/Views/_events.tpl.php @@ -0,0 +1,18 @@ + + + + + + + + + + {events} + + + + + + {/events} + +
TimeEvent NameTimes Called
{ duration } ms{event}{count}
diff --git a/system/Debug/Toolbar/Views/_files.tpl.php b/system/Debug/Toolbar/Views/_files.tpl.php new file mode 100644 index 0000000..7328e39 --- /dev/null +++ b/system/Debug/Toolbar/Views/_files.tpl.php @@ -0,0 +1,16 @@ + + + {userFiles} + + + + + {/userFiles} + {coreFiles} + + + + + {/coreFiles} + +
{name}{path}
{name}{path}
diff --git a/system/Debug/Toolbar/Views/_logs.tpl.php b/system/Debug/Toolbar/Views/_logs.tpl.php new file mode 100644 index 0000000..66e7f71 --- /dev/null +++ b/system/Debug/Toolbar/Views/_logs.tpl.php @@ -0,0 +1,16 @@ + + + + + + + + + {logs} + + + + + {/logs} + +
SeverityMessage
{level}{msg}
diff --git a/system/Debug/Toolbar/Views/_routes.tpl.php b/system/Debug/Toolbar/Views/_routes.tpl.php new file mode 100644 index 0000000..0988049 --- /dev/null +++ b/system/Debug/Toolbar/Views/_routes.tpl.php @@ -0,0 +1,44 @@ +

Matched Route

+ + + + {matchedRoute} + + + + + + + + + + + + + + + + + {params} + + + + + {/params} + {/matchedRoute} + +
Directory:{directory}
Controller:{controller}
Method:{method}
Params:{paramCount} / {truePCount}
{name}{value}
+ + +

Defined Routes

+ + + + {routes} + + + + + {/routes} + +
{from}{to}
diff --git a/system/Debug/Toolbar/Views/toolbar.css b/system/Debug/Toolbar/Views/toolbar.css new file mode 100644 index 0000000..ccafa74 --- /dev/null +++ b/system/Debug/Toolbar/Views/toolbar.css @@ -0,0 +1,302 @@ +#debug-icon { + position: fixed; + bottom: 0; + left: 0; + width: 36px; + height: 36px; + background: #fff; + border: 1px solid #ddd; + border-radius: .5em; + margin: 5px; + z-index: 10000; + box-shadow: 0 -3px 10px rgba(0, 0, 0, 0.1); + clear: both; + text-align: center; +} + +#debug-icon a svg { + margin: 4px; + max-width: 26px; + max-height: 26px; +} + +#debug-bar a:active, #debug-bar a:link, #debug-bar a:visited { + color: #dd4814; +} + +#debug-bar { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.5; + background: #fff; + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 36px; + z-index: 10000; +} + +#debug-bar h1, +#debug-bar h2, +#debug-bar h3 { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #666; + line-height: 1.5; +} + +#debug-bar p { + font-size: 12px; + margin: 0 0 10px 20px; + padding: 0; +} + +#debug-bar a { + text-decoration: none; +} + +#debug-bar a:hover { + text-decoration: underline; + text-decoration-color: #4e4a4a; +} + +#debug-bar .muted, +#debug-bar .muted td { + color: #bbb; +} + +#debug-bar .toolbar { + display: block; + background: inherit; + overflow: hidden; + overflow-y: auto; + white-space: nowrap; + border: solid #ddd; + border-width: 1px 0; + box-shadow: 0 -3px 10px rgba(0, 0, 0, 0.1); + padding: 5px 12px 5px 20px; /* give room for OS X scrollbar */ + text-align: right; + z-index: 10000; +} + +#debug-icon.fixed-top, +#debug-bar.fixed-top { + top: 0; + bottom: auto; +} + +#debug-icon.fixed-top, +#debug-bar.fixed-top .toolbar { + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); +} + +#debug-bar h1 { + font-size: 16px; + font-weight: 300; + margin: 0 2em 0 0; + padding: 0; + text-align: left; + display: inline-block; + float: left; +} + +#debug-bar h2 { + font-size: 16px; + font-weight: 300; + margin: 0; + padding: 0; +} + +#debug-bar h2 span { + font-size: 13px; +} + +#debug-bar h3 { + text-transform: uppercase; + font-size: 11px; + font-weight: 200; + margin-left: 10pt; +} + +#debug-bar span { + display: inline-block; + font-size: 14px; + padding: .3em 1em .3em; + line-height: 1.0; + vertical-align: baseline; +} + +#debug-bar span .badge { + display: inline-block; + padding: 3px 6px; + font-size: 75%; + font-weight: 500; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 10rem; + background-color: #5bc0de; + margin-left: 0.5em; +} + +#debug-bar table strong { + font-weight: 500; + color: rgba(0, 0, 0, 0.3); +} + +#debug-bar .ci-label { + border-radius: 0.25em; + text-shadow: none; + background-color: #eee; + border: 1px solid #ddd; + margin-left: 0.4em; +} + +#debug-bar .ci-label:hover { + background-color: #eaeaea; + cursor: pointer; +} + +#debug-bar .ci-label a { + display: block; + width: 100%; + color: inherit; + text-decoration: none; +} + +#debug-bar .ci-label.active { + background-color: #ccc; + border-color: #bbb; +} + +#debug-bar .tab { + display: none; + background: inherit; + padding: 1em 2em; + border: solid #ddd; + border-width: 1px 0; + position: fixed; + bottom: 35px; + left: 0; + right: 0; + z-index: 9999; + box-shadow: 0 -3px 10px rgba(0, 0, 0, 0.1); + overflow: hidden; + overflow-y: auto; + max-height: 62%; +} + +#debug-bar.fixed-top .tab { + top: 36px; + bottom: auto; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); +} + +#debug-bar table { + margin: 0 0 10px 20px; + font-size: 13px; + border-collapse: collapse; + width: 100%; +} + +#debug-bar td, +#debug-bar th { + display: table-cell; + text-align: left; +} + +#debug-bar tr { + border: none; +} + +#debug-bar td { + border: none; + padding: 2px 10px 2px 5px; + margin: 0; +} + +#debug-bar th { + padding-bottom: 0.7em; +} + +#debug-bar tr td:first-child { + width: 20%; +} + +#debug-bar tr td:first-child.narrow { + width: 7em; +} + +#debug-bar tr:hover { + background-color: #f3f3f3; +} + +#debug-bar table.timeline { + width: 100%; + margin-left: 0; +} + +#debug-bar table.timeline th { + font-size: 0.7em; + font-weight: 200; + text-align: left; + padding-bottom: 1em; +} + +#debug-bar table.timeline td, +#debug-bar table.timeline th { + border-left: 1px solid #ddd; + padding: 0 1em; + position: relative; +} + +#debug-bar table.timeline tr td:first-child, +#debug-bar table.timeline tr th:first-child { + border-left: 0; + padding-left: 0; +} + +#debug-bar table.timeline td { + padding: 5px; +} + +#debug-bar table.timeline .timer { + position: absolute; + display: inline-block; + padding: 3px; + bottom: 9px; + border-radius: 3px; + background-color: #999; +} + +#debug-bar .route-params, +#debug-bar .route-params-item { + vertical-align: top; +} + +#debug-bar .route-params-item td:first-child { + padding-left: 1em; + text-align: right; + font-style: italic; +} + +.debug-view.show-view { + border: 1px solid #dd4814; + margin: 4px; +} + +.debug-view-path { + background-color: #fdc894; + color: #000; + padding: 2px; + font-family: monospace; + font-size: 11px; + min-height: 16px; + text-align: left; +} + +.show-view .debug-view-path { + display: block !important; +} diff --git a/system/Debug/Toolbar/Views/toolbar.js b/system/Debug/Toolbar/Views/toolbar.js new file mode 100644 index 0000000..97a1b0f --- /dev/null +++ b/system/Debug/Toolbar/Views/toolbar.js @@ -0,0 +1,497 @@ +/* + * Functionality for the CodeIgniter Debug Toolbar. + */ + +var ciDebugBar = { + + toolbar : null, + icon : null, + + //-------------------------------------------------------------------- + + init : function() + { + this.toolbar = document.getElementById('debug-bar'); + this.icon = document.getElementById('debug-icon'); + + ciDebugBar.createListeners(); + ciDebugBar.setToolbarState(); + ciDebugBar.setToolbarPosition(); + ciDebugBar.toogleViewsHints(); + }, + + //-------------------------------------------------------------------- + + createListeners : function() + { + var buttons = [].slice.call(document.querySelectorAll('#debug-bar .ci-label a')); + + for (var i=0; i < buttons.length; i++) + { + buttons[i].addEventListener('click', ciDebugBar.showTab, true); + } + }, + + //-------------------------------------------------------------------- + + showTab: function() + { + // Get the target tab, if any + var tab = document.getElementById(this.getAttribute('data-tab')); + + // If the label have not a tab stops here + if (! tab) { + return; + } + + // Check our current state. + var state = tab.style.display; + + // Hide all tabs + var tabs = document.querySelectorAll('#debug-bar .tab'); + + for (var i=0; i < tabs.length; i++) + { + tabs[i].style.display = 'none'; + } + + // Mark all labels as inactive + var labels = document.querySelectorAll('#debug-bar .ci-label'); + + for (var i=0; i < labels.length; i++) + { + ciDebugBar.removeClass(labels[i], 'active'); + } + + // Show/hide the selected tab + if (state != 'block') + { + tab.style.display = 'block'; + ciDebugBar.addClass(this.parentNode, 'active'); + } + }, + + //-------------------------------------------------------------------- + + addClass : function(el, className) + { + if (el.classList) + { + el.classList.add(className); + } + else + { + el.className += ' ' + className; + } + }, + + //-------------------------------------------------------------------- + + removeClass : function(el, className) + { + if (el.classList) + { + el.classList.remove(className); + } + else + { + el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); + } + + }, + + //-------------------------------------------------------------------- + + /** + * Toggle display of a data table + * @param obj + */ + toggleDataTable : function(obj) + { + if (typeof obj == 'string') + { + obj = document.getElementById(obj + '_table'); + } + + if (obj) + { + obj.style.display = obj.style.display == 'none' ? 'block' : 'none'; + } + }, + + //-------------------------------------------------------------------- + + /** + * Toggle tool bar from full to icon and icon to full + */ + toggleToolbar : function() + { + var open = ciDebugBar.toolbar.style.display != 'none'; + + ciDebugBar.icon.style.display = open == true ? 'inline-block' : 'none'; + ciDebugBar.toolbar.style.display = open == false ? 'inline-block' : 'none'; + + // Remember it for other page loads on this site + ciDebugBar.createCookie('debug-bar-state', '', -1); + ciDebugBar.createCookie('debug-bar-state', open == true ? 'minimized' : 'open' , 365); + }, + + //-------------------------------------------------------------------- + + /** + * Sets the initial state of the toolbar (open or minimized) when + * the page is first loaded to allow it to remember the state between refreshes. + */ + setToolbarState: function() + { + var open = ciDebugBar.readCookie('debug-bar-state'); + + ciDebugBar.icon.style.display = open != 'open' ? 'inline-block' : 'none'; + ciDebugBar.toolbar.style.display = open == 'open' ? 'inline-block' : 'none'; + }, + + //-------------------------------------------------------------------- + + toogleViewsHints: function() + { + var nodeList = []; // [ Element, NewElement( 1 )/OldElement( 0 ) ] + var sortedComments = []; + var comments = []; + + var getComments = function() + { + var nodes = []; + var result = []; + var xpathResults = document.evaluate( "//comment()[starts-with(., ' DEBUG-VIEW')]", document, null, XPathResult.ANY_TYPE, null); + var nextNode = xpathResults.iterateNext(); + while( nextNode ) + { + nodes.push( nextNode ); + nextNode = xpathResults.iterateNext(); + } + + // sort comment by opening and closing tags + for( var i = 0; i < nodes.length; ++i ) + { + // get file path + name to use as key + var path = nodes[i].nodeValue.substring( 18, nodes[i].nodeValue.length - 1 ); + + if( nodes[i].nodeValue[12] === 'S' ) // simple check for start comment + { + // create new entry + result[path] = [ nodes[i], null ]; + } + else if(result[path]) + { + // add to existing entry + result[path][1] = nodes[i]; + } + } + + return result; + }; + + // find node that has TargetNode as parentNode + var getParentNode = function( node, targetNode ) + { + if( node.parentNode === null ) + { + return null; + } + + if( node.parentNode !== targetNode ) + { + return getParentNode( node.parentNode, targetNode ); + } + + return node; + }; + + // define invalid & outer ( also invalid ) elements + const INVALID_ELEMENTS = [ 'NOSCRIPT', 'SCRIPT', 'STYLE' ]; + const OUTER_ELEMENTS = [ 'HTML', 'BODY', 'HEAD' ]; + + var getValidElementInner = function( node, reverse ) + { + // handle invalid tags + if( OUTER_ELEMENTS.indexOf( node.nodeName ) !== -1 ) + { + for( var i = 0; i < document.body.children.length; ++i ) + { + var index = reverse ? document.body.children.length - ( i + 1 ) : i; + var element = document.body.children[index]; + + // skip invalid tags + if( INVALID_ELEMENTS.indexOf( element.nodeName ) !== -1 ) continue; + + return [ element, reverse ]; + } + + return null; + } + + // get to next valid element + while( node !== null && INVALID_ELEMENTS.indexOf( node.nodeName ) !== -1 ) + { + node = reverse ? node.previousElementSibling : node.nextElementSibling; + } + + // return non array if we couldnt find something + if( node === null ) return null; + + return [ node, reverse ]; + }; + + // get next valid element ( to be safe to add divs ) + // @return [ element, skip element ] or null if we couldnt find a valid place + var getValidElement = function( nodeElement ) + { + if (nodeElement) { + if( nodeElement.nextElementSibling !== null ) + { + return getValidElementInner( nodeElement.nextElementSibling, false ) + || getValidElementInner( nodeElement.previousElementSibling, true ); + } + if( nodeElement.previousElementSibling !== null ) + { + return getValidElementInner( nodeElement.previousElementSibling, true ); + } + } + + // something went wrong! -> element is not in DOM + return null; + }; + + function showHints() { + // Had AJAX? Reset view blocks + sortedComments = getComments(); + + for( var key in sortedComments ) + { + var startElement = getValidElement( sortedComments[key][0] ); + var endElement = getValidElement( sortedComments[key][1] ); + + // skip if we couldnt get a valid element + if( startElement === null || endElement === null ) continue; + + // find element which has same parent as startelement + var jointParent = getParentNode( endElement[0], startElement[0].parentNode ); + if( jointParent === null ) + { + // find element which has same parent as endelement + jointParent = getParentNode( startElement[0], endElement[0].parentNode ); + if( jointParent === null ) + { + // both tries failed + continue; + } + else startElement[0] = jointParent; + } + else endElement[0] = jointParent; + + var debugDiv = document.createElement( 'div' ); // holder + var debugPath = document.createElement( 'div' ); // path + var childArray = startElement[0].parentNode.childNodes; // target child array + var parent = startElement[0].parentNode; + var start, end; + + // setup container + debugDiv.classList.add( 'debug-view' ); + debugDiv.classList.add( 'show-view' ); + debugPath.classList.add( 'debug-view-path' ); + debugPath.innerText = key; + debugDiv.appendChild( debugPath ); + + // calc distance between them + // start + for( var i = 0; i < childArray.length; ++i ) + { + // check for comment ( start & end ) -> if its before valid start element + if( childArray[i] === sortedComments[key][1] || + childArray[i] === sortedComments[key][0] || + childArray[i] === startElement[0] ) + { + start = i; + if( childArray[i] === sortedComments[key][0] ) start++; // increase to skip the start comment + break; + } + } + // adjust if we want to skip the start element + if( startElement[1] ) start++; + + // end + for( var i = start; i < childArray.length; ++i ) + { + if( childArray[i] === endElement[0] ) + { + end = i; + // dont break to check for end comment after end valid element + } + else if( childArray[i] === sortedComments[key][1] ) + { + // if we found the end comment, we can break + end = i; + break; + } + } + + // move elements + var number = end - start; + if( endElement[1] ) number++; + for( var i = 0; i < number; ++i ) + { + if( INVALID_ELEMENTS.indexOf( childArray[start] ) !== -1 ) + { + // skip invalid childs that can cause problems if moved + start++; + continue; + } + debugDiv.appendChild( childArray[start] ); + } + + // add container to DOM + nodeList.push( parent.insertBefore( debugDiv, childArray[start] ) ); + } + + ciDebugBar.createCookie('debug-view', 'show', 365); + ciDebugBar.addClass(btn, 'active'); + } + + function hideHints() { + for( var i = 0; i < nodeList.length; ++i ) + { + var index; + + // find index + for( var j = 0; j < nodeList[i].parentNode.childNodes.length; ++j ) + { + if( nodeList[i].parentNode.childNodes[j] === nodeList[i] ) + { + index = j; + break; + } + } + + // move child back + while( nodeList[i].childNodes.length !== 1 ) + { + nodeList[i].parentNode.insertBefore( nodeList[i].childNodes[1], nodeList[i].parentNode.childNodes[index].nextSibling ); + } + + nodeList[i].parentNode.removeChild( nodeList[i] ); + } + nodeList.length = 0; + + ciDebugBar.createCookie('debug-view', '', -1); + ciDebugBar.removeClass(btn, 'active'); + } + + var btn = document.querySelector('[data-tab=ci-views]'); + + // If the Views Collector is inactive stops here + if (! btn) + { + return; + } + + btn = btn.parentNode; + + // Determine Hints state on page load + if (ciDebugBar.readCookie('debug-view')) + { + showHints(); + } + + btn.onclick = function() { + if (ciDebugBar.readCookie('debug-view')) + { + hideHints(); + } + else + { + showHints(); + } + }; + }, + + //-------------------------------------------------------------------- + + setToolbarPosition: function () + { + var btnPosition = document.getElementById('toolbar-position'); + + if (ciDebugBar.readCookie('debug-bar-position') === 'top') + { + ciDebugBar.addClass(ciDebugBar.icon, 'fixed-top'); + ciDebugBar.addClass(ciDebugBar.toolbar, 'fixed-top'); + } + + btnPosition.addEventListener('click', function () { + var position = ciDebugBar.readCookie('debug-bar-position'); + + ciDebugBar.createCookie('debug-bar-position', '', -1); + + if (!position || position === 'bottom') + { + ciDebugBar.createCookie('debug-bar-position', 'top', 365); + ciDebugBar.addClass(ciDebugBar.icon, 'fixed-top'); + ciDebugBar.addClass(ciDebugBar.toolbar, 'fixed-top'); + } + else + { + ciDebugBar.createCookie('debug-bar-position', 'bottom', 365); + ciDebugBar.removeClass(ciDebugBar.icon, 'fixed-top'); + ciDebugBar.removeClass(ciDebugBar.toolbar, 'fixed-top'); + } + }, true); + }, + + //-------------------------------------------------------------------- + + /** + * Helper to create a cookie. + * + * @param name + * @param value + * @param days + */ + createCookie : function(name,value,days) + { + if (days) + { + var date = new Date(); + + date.setTime(date.getTime()+(days*24*60*60*1000)); + + var expires = "; expires="+date.toGMTString(); + } + else + { + var expires = ""; + } + + document.cookie = name+"="+value+expires+"; path=/"; + }, + + //-------------------------------------------------------------------- + + readCookie : function(name) + { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + + for(var i=0;i < ca.length;i++) + { + var c = ca[i]; + while (c.charAt(0)==' ') + { + c = c.substring(1,c.length); + } + if (c.indexOf(nameEQ) == 0) + { + return c.substring(nameEQ.length,c.length); + } + } + return null; + } +}; diff --git a/system/Debug/Toolbar/Views/toolbar.tpl.php b/system/Debug/Toolbar/Views/toolbar.tpl.php new file mode 100644 index 0000000..308d572 --- /dev/null +++ b/system/Debug/Toolbar/Views/toolbar.tpl.php @@ -0,0 +1,253 @@ + + + +
+
+
+

Debug Bar

+ + ms + MB + Timeline + collectors as $c) : ?> + hasTabContent() || $c->hasLabel()) : ?> + + + getTitle()) ?> + getBadgeValue())) : ?> + getBadgeValue() ?> + + + + + + Vars + +
+ + +
+ + + + + + + + + + + + + renderTimeline($segmentCount, $segmentDuration, $totalTime) ?> + +
NAMECOMPONENTDURATION ms
+
+ + + collectors as $c) : ?> + hasTabContent()) : ?> +
+

getTitle()) ?> getTitleDetails()) ?>

+ + display() ?> +
+ + + + +
+ + + $items) : ?> + + +

+
+ + + + + + $value) : ?> + + + + + + +
+ +
+ + +

No data to display.

+ + + + + +

Session User Data

+
+ + + + + + $value) : ?> + + + + + + +
+ +
+ +

No data to display.

+ + +

Session doesn't seem to be active.

+ + +

Request ( isSecure() ? 'HTTPS' : 'HTTP').'/'.$request->getProtocolVersion() ?> )

+ + getGet()) : ?> + +

$_GET

+
+ + + + $value) : ?> + + + + + + +
+ + + getPost()) : ?> + +

$_POST

+
+ + + + $value) : ?> + + + + + + +
+ + + getHeaders()) : ?> + +

Headers

+
+ + + + + $value) : ?> + + + + + + + + + + +
getName()) ?>getValueLine()) ?>
+ + + getCookie()) : ?> + +

Cookies

+
+ + + + $value) : ?> + + + + + + + + + +

Response ( getStatusCode().' - '. esc($response->getReason()) ?> )

+ + getHeaders()) : ?> + +

Headers

+
+ + + + $value) : ?> + + + + + + +
getHeaderLine($header)) ?>
+ +
+ + + diff --git a/system/Encryption/EncrypterInterface.php b/system/Encryption/EncrypterInterface.php new file mode 100644 index 0000000..f3f7a43 --- /dev/null +++ b/system/Encryption/EncrypterInterface.php @@ -0,0 +1,63 @@ + 'OpenSSL', // The PHP extension we plan to use + 'key' => '', // no starting key material + 'cipher' => 'AES-256-CBC', // Encryption cipher + 'digest' => 'SHA512', // HMAC digest algorithm to use + 'encoding' => 'base64', // Base64 encoding + ]; + protected $driver, $key, $cipher, $digest, $base64; + + /** + * Map of drivers to handler classes, in preference order + * + * @var array + */ + protected $drivers = [ + 'OpenSSL', + ]; + + /** + * List of supported HMAC algorithms + * + * name => digest size pairs + * + * @var array + */ + protected $digests = [ + 'SHA224' => 28, + 'SHA256' => 32, + 'SHA384' => 48, + 'SHA512' => 64 + ]; + + /** + * List of acceptable encodings + */ + protected $encodings = ['base64', 'hex']; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @param mixed $params Configuration parameters + * @return void + * + * @throws \CodeIgniter\Encryption\EncryptionException + */ + public function __construct($params = []) + { + $this->logger = \Config\Services::logger(true); + $this->config = array_merge($this->default, (array) new \Config\Encryption()); + + $params = $this->properParams($params); + + // Check for an unknown driver + if (isset($this->drivers[$params['driver']])) + throw new EncryptionException("Unknown handler '" . $params['driver'] . "' cannot be configured."); + + // determine what is installed + $this->handlers = [ + 'OpenSSL' => extension_loaded('openssl'), + ]; + + if ( ! in_array(true, $this->handlers)) + throw new EncryptionException('Unable to find an available encryption handler.'); + + $this->logger->info('Encryption class Initialized'); + } + + /** + * Initialize or re-initialize an encrypter + * + * @param array $params Configuration parameters + * @return \CodeIgniter\Encryption\EncrypterInterface + * + * @throws \CodeIgniter\Encryption\EncryptionException + */ + public function initialize(array $params = []) + { + $params = $this->properParams($params); + + // Insist on a driver + if ( ! isset($params['driver'])) + throw new EncryptionException("No driver requested; Miss Daisy will be so upset!"); + + // Check for an unknown driver + if ( ! in_array($params['driver'], $this->drivers)) + throw new EncryptionException("Unknown handler '" . $params['driver'] . "' cannot be configured."); + + // Check for an unavailable driver + if ( ! $this->handlers[$params['driver']]) + throw new EncryptionException("Driver '" . $params['driver'] . "' is not available."); + + // Check for a bad digest + if ( ! empty($params['digest'])) + if ( ! isset($this->digests[$params['digest']])) + throw new EncryptionException("Unknown digest '" . $params['digest'] . "' specified."); + + // Check for valid encoding + if ( ! empty($param['encoding'])) + if ( ! in_array($params['encoding'], $this->encodings)) + throw new EncryptionException("Unknown encoding '" . $params['encoding'] . "' specified."); + + // Derive a secret key for the encrypter + $hmacKey = strcmp(phpversion(), '7.1.2') >= 0 ? \hash_hkdf($this->digest, $params['key']) : $this->hkdf($params['key'], $this->digest); + $params['secret'] = bin2hex($hmacKey); + + $handlerName = 'CodeIgniter\\Encryption\\Handlers\\' . $this->driver . 'Handler'; + $this->encrypter = new $handlerName($params); + return $this->encrypter; + } + + /** + * Determine proper parameters + * + * @param array|object $params + * + * @return array|null + */ + protected function properParams($params = null) + { + // use existing config if no parameters given + if (empty($params)) + $params = $this->config; + + // treat the paramater as a Config object? + if (is_object($params)) + $params = (array) $params; + + // Capitalize cipher & digest + if (isset($params['cipher'])) + $params['cipher'] = strtoupper($params['cipher']); + if (isset($params['digest'])) + $params['digest'] = strtoupper($params['digest']); + + // override base config with passed parameters + $params = array_merge($this->config, $params); + // make sure we only have expected parameters + $params = array_intersect_key($params, $this->default); + + // and remember what we are up to + $this->config = $params; + + // make the parameters conveniently accessible + foreach ($params as $pkey => $value) + $this->$pkey = $value; + + return $params; + } + +// -------------------------------------------------------------------- + + /** + * Create a random key + * + * @param int $length Output length + * @return string + */ + public static function createKey($length = 32) + { + return \openssl_random_pseudo_bytes($length); + } + + // -------------------------------------------------------------------- + + /** + * __get() magic, providing readonly access to some of our protected properties + * + * @param string $key Property name + * @return mixed + */ + public function __get($key) + { + if (in_array($key, ['config', 'cipher', 'key', 'driver', 'drivers', 'digest', 'digests', 'default', 'encoding'], true)) + { + return $this->{$key}; + } + + return null; + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return mb_strlen($str, '8bit'); + } + + // -------------------------------------------------------------------- + + /** + * HKDF legacy implementation, from CodeIgniter3. + * + * Fallback if PHP version < 7.1.2 + * + * @link https://tools.ietf.org/rfc/rfc5869.txt + * + * @param string $key Input key + * @param string $digest A SHA-2 hashing algorithm + * @param string $salt Optional salt + * @param int $length Output length (defaults to the selected digest size) + * @param string $info Optional context/application-specific info + * + * @return string A pseudo-random key + */ + public function hkdf($key, $digest = 'sha512', $salt = null, $length = null, $info = '') + { + if ( ! isset($this->digests[$digest])) + { + return false; + } + + if (empty($length) OR ! is_int($length)) + { + $length = $this->digests[$digest]; + } + elseif ($length > (255 * $this->digests[$digest])) + { + return false; + } + + self::strlen($salt) OR $salt = str_repeat("\0", $this->digests[$digest]); + + $prk = hash_hmac($digest, $key, $salt, true); + $key = ''; + for ($key_block = '', $block_index = 1; self::strlen($key) < $length; $block_index ++) + { + $key_block = hash_hmac($digest, $key_block . $info . chr($block_index), $prk, true); + $key .= $key_block; + } + + return self::substr($key, 0, $length); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe substr() + * + * @param string $str + * @param int $start + * @param int $length + * @return string + */ + protected static function substr($str, $start, $length = null) + { + return mb_substr($str, $start, $length, '8bit'); + } + +} diff --git a/system/Encryption/Handlers/BaseHandler.php b/system/Encryption/Handlers/BaseHandler.php new file mode 100644 index 0000000..1731d67 --- /dev/null +++ b/system/Encryption/Handlers/BaseHandler.php @@ -0,0 +1,140 @@ +logger = \Config\Services::logger(true); + + if (empty($config)) + throw new EncryptionException("Encryption handler needs configuration parameters."); + $this->config = $config; + + // make the parameters conveniently accessible + foreach ($this->config as $pkey => $value) + $this->$pkey = $value; + + } + +// -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return mb_strlen($str, '8bit'); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe substr() + * + * @param string $str + * @param int $start + * @param int $length + * @return string + */ + protected static function substr($str, $start, $length = null) + { + return mb_substr($str, $start, $length, '8bit'); + } + + /** + * __get() magic, providing readonly access to some of our properties + * + * @param string $key Property name + * @return mixed + */ + public function __get($key) + { + //FIXME + if (in_array($key, ['cipher', 'key', 'hmac', 'digest', 'base64'], true)) + { + return $this->{$key}; + } + + return null; + } + +} diff --git a/system/Encryption/Handlers/OpenSSLHandler.php b/system/Encryption/Handlers/OpenSSLHandler.php new file mode 100644 index 0000000..41aa21e --- /dev/null +++ b/system/Encryption/Handlers/OpenSSLHandler.php @@ -0,0 +1,140 @@ +cipher)) + throw new \CodeIgniter\Encryption\EncryptionException("OpenSSL handler configuration missing cipher."); + if ( ! in_array($this->cipher, openssl_get_cipher_methods(), true)) + throw new \CodeIgniter\Encryption\EncryptionException("OpenSSL handler does not support the " . $this->cipher . " cipher."); + + if (empty($this->key)) + throw new \CodeIgniter\Encryption\EncryptionException("OpenSSL handler configuration missing key."); + + $this->logger->info('OpenSSL handler initialized with cipher ' . $this->cipher . '.'); + } + + /** + * Encrypt plaintext, with optional HMAC and base64 encoding + * + * @param string $data Input data + * @return string + */ + public function encrypt($data) + { + // basic encryption + $iv = ($iv_size = \openssl_cipher_iv_length($this->cipher)) ? \openssl_random_pseudo_bytes($iv_size) : null; + + $data = \openssl_encrypt($data, $this->cipher, $this->secret, OPENSSL_RAW_DATA, $iv); + + if ($data === false) + return false; + + $result = $iv . $data; + + // HMAC? + if ( ! empty($this->digest)) + { + $hmacKey = \hash_hmac($this->digest, $result, $this->secret, true); + $result = $hmacKey . $result; + } + + if ( ! empty($this->encoding)) + if ($this->encoding == 'base64') + $result = \base64_encode($result); + elseif ($this->encoding == 'hex') + $result = \bin2hex($result); + + return $result; + } + + // -------------------------------------------------------------------- + + /** + * Decrypt ciphertext, with optional HMAC and base64 encoding + * + * @param string $data Encrypted data + * + * @return string + * @throws \CodeIgniter\Encryption\EncryptionException + */ + public function decrypt($data) + { + if ( ! empty($this->encoding)) + if ($this->encoding == 'base64') + $data = \base64_decode($data); + elseif ($this->encoding == 'hex') + $data = \hex2bin($data); + + // HMAC? + if ( ! empty($this->digest)) + { + $hmacLength = self::substr($this->digest, 3) / 8; + $hmacKey = self::substr($data, 0, $hmacLength); + $data = self::substr($data, $hmacLength); + $hmacCalc = \hash_hmac($this->digest, $data, $this->secret, true); + if ($hmacKey != $hmacCalc) + throw new \CodeIgniter\Encryption\EncryptionException("Message authentication failed."); + } + + if ($iv_size = \openssl_cipher_iv_length($this->cipher)) + { + $iv = self::substr($data, 0, $iv_size); + $data = self::substr($data, $iv_size); + } + else + { + $iv = null; + } + + return \openssl_decrypt($data, $this->cipher, $this->secret, OPENSSL_RAW_DATA, $iv); + } + +} diff --git a/system/Entity.php b/system/Entity.php new file mode 100644 index 0000000..bd7e54f --- /dev/null +++ b/system/Entity.php @@ -0,0 +1,365 @@ + 'class_name' + * ]; + */ + 'datamap' => [], + + /* + * Define properties that are automatically converted to Time instances. + */ + 'dates' => ['created_at', 'updated_at', 'deleted_at'], + + /* + * Array of field names and the type of value to cast them as + * when they are accessed. + */ + 'casts' => [] + ]; + + /** + * Allows filling in Entity parameters during construction. + * + * @param array|null $data + */ + public function __construct(array $data = null) + { + if (is_array($data)) + { + $this->fill($data); + } + } + + /** + * Takes an array of key/value pairs and sets them as + * class properties, using any `setCamelCasedProperty()` methods + * that may or may not exist. + * + * @param array $data + */ + public function fill(array $data) + { + foreach ($data as $key => $value) + { + $method = 'set' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key))); + + if (method_exists($this, $method)) + { + $this->$method($value); + } + elseif (property_exists($this, $key)) + { + $this->$key = $value; + } + } + } + + //-------------------------------------------------------------------- + + /** + * Magic method to allow retrieval of protected and private + * class properties either by their name, or through a `getCamelCasedProperty()` + * method. + * + * Examples: + * + * $p = $this->my_property + * $p = $this->getMyProperty() + * + * @param string $key + * + * @return mixed + */ + public function __get(string $key) + { + $key = $this->mapProperty($key); + + // Convert to CamelCase for the method + $method = 'get' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key))); + + // if a set* method exists for this key,
 + // use that method to insert this value.
 + if (method_exists($this, $method)) + { + $result = $this->$method(); + } + + // Otherwise return the protected property + // if it exists. + else if (property_exists($this, $key)) + { + $result = $this->$key; + } + + // Do we need to mutate this into a date? + if (in_array($key, $this->_options['dates'])) + { + $result = $this->mutateDate($result); + } + // Or cast it as something? + else if (array_key_exists($key, $this->_options['casts'])) + { + $result = $this->castAs($result, $this->_options['casts'][$key]); + } + + return $result; + } + + //-------------------------------------------------------------------- + + /** + * Magic method to all protected/private class properties to be easily set, + * either through a direct access or a `setCamelCasedProperty()` method. + * + * Examples: + * + * $this->my_property = $p; + * $this->setMyProperty() = $p; + * + * @param string $key + * @param null $value + * + * @return $this + */ + public function __set(string $key, $value = null) + { + $key = $this->mapProperty($key); + + // Check if the field should be mutated into a date + if (in_array($key, $this->_options['dates'])) + { + $value = $this->mutateDate($value); + } + + // Array casting requires that we serialize the value + // when setting it so that it can easily be stored + // back to the database. + if (array_key_exists($key, $this->_options['casts']) && $this->_options['casts'][$key] === 'array') + { + $value = serialize($value); + } + + // if a set* method exists for this key,
 + // use that method to insert this value.
 + $method = 'set' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key))); + if (method_exists($this, $method)) + { + $this->$method($value); + + return $this; + } + + // Otherwise, just the value. + // This allows for creation of new class + // properties that are undefined, though + // they cannot be saved. Useful for + // grabbing values through joins, + // assigning relationships, etc. + $this->$key = $value; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Unsets a protected/private class property. Sets the value to null. + * However, if there was a default value for the parent class, this + * attribute will be reset to that default value. + * + * @param string $key + */ + public function __unset(string $key) + { + // If not actual property exists, get out + // before we confuse our data mapping. + if ( ! property_exists($this, $key)) + return; + + $this->$key = null; + + // Get the class' original default value for this property + // so we can reset it to the original value. + $reflectionClass = new \ReflectionClass($this); + $defaultProperties = $reflectionClass->getDefaultProperties(); + + if (isset($defaultProperties[$key])) + { + $this->$key = $defaultProperties[$key]; + } + } + + //-------------------------------------------------------------------- + + /** + * Returns true if a property exists names $key, or a getter method + * exists named like for __get(). + * + * @param string $key + * + * @return bool + */ + public function __isset(string $key): bool + { + // Ensure an actual property exists, otherwise + // we confuse the data mapping. + $value = property_exists($this, $key) ? $this->$key : null; + + return ! is_null($value); + } + + //-------------------------------------------------------------------- + + /** + * Checks the datamap to see if this column name is being mapped, + * and returns the mapped name, if any, or the original name. + * + * @param string $key + * + * @return mixed|string + */ + protected function mapProperty(string $key) + { + if (array_key_exists($key, $this->_options['datamap'])) + { + return $this->_options['datamap'][$key]; + } + + return $key; + } + + //-------------------------------------------------------------------- + + /** + * Converts the given string|timestamp|DateTime|Time instance + * into a \CodeIgniter\I18n\Time object. + * + * @param $value + * + * @return \CodeIgniter\I18n\Time + */ + protected function mutateDate($value) + { + if ($value instanceof Time) + { + return $value; + } + + if ($value instanceof \DateTime) + { + return Time::instance($value); + } + + if (is_numeric($value)) + { + return Time::createFromTimestamp($value); + } + + if (is_string($value)) + { + return Time::parse($value); + } + + return $value; + } + + //-------------------------------------------------------------------- + + /** + * Provides the ability to cast an item as a specific data type. + * + * @param $value + * @param string $type + * + * @return mixed + */ + protected function castAs($value, string $type) + { + switch($type) + { + case 'integer': + $value = (int)$value; + break; + case 'float': + $value = (float)$value; + break; + case 'double': + $value = (double)$value; + break; + case 'string': + $value = (string)$value; + break; + case 'boolean': + $value = (bool)$value; + break; + case 'object': + $value = (object)$value; + break; + case 'array': + if (is_string($value) && (substr($value, 0, 2) === 'a:' || substr($value, 0, 2) === 's:')) + { + $value = unserialize($value); + } + else + { + $value = (object)$value; + } + break; + case 'datetime': + return new \DateTime($value); + break; + case 'timestamp': + return strtotime($value); + break; + } + + return $value; + } +} diff --git a/system/Events/Events.php b/system/Events/Events.php new file mode 100644 index 0000000..0e3fe11 --- /dev/null +++ b/system/Events/Events.php @@ -0,0 +1,326 @@ + $start, + 'end' => microtime(true), + 'event' => strtolower($eventName) + ]; + } + + if ($result === false) + { + return false; + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Returns an array of listeners for a single event. They are + * sorted by priority. + * + * If the listener could not be found, returns FALSE, or TRUE if + * it was removed. + * + * @param $event_name + * + * @return array + */ + public static function listeners($event_name): array + { + if ( ! isset(self::$listeners[$event_name])) + { + return []; + } + + // The list is not sorted + if ( ! self::$listeners[$event_name][0]) + { + // Sort it! + array_multisort(self::$listeners[$event_name][1], SORT_NUMERIC, self::$listeners[$event_name][2]); + + // Mark it as sorted already! + self::$listeners[$event_name][0] = true; + } + + return self::$listeners[$event_name][2]; + } + + //-------------------------------------------------------------------- + + /** + * Removes a single listener from an event. + * + * If the listener couldn't be found, returns FALSE, else TRUE if + * it was removed. + * + * @param $event_name + * @param callable $listener + * + * @return bool + */ + public static function removeListener($event_name, callable $listener): bool + { + if ( ! isset(self::$listeners[$event_name])) + { + return false; + } + + foreach (self::$listeners[$event_name][2] as $index => $check) + { + if ($check === $listener) + { + unset(self::$listeners[$event_name][1][$index]); + unset(self::$listeners[$event_name][2][$index]); + + return true; + } + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Removes all listeners. + * + * If the event_name is specified, only listeners for that event will be + * removed, otherwise all listeners for all events are removed. + * + * @param null $event_name + */ + public static function removeAllListeners($event_name = null) + { + if ( ! is_null($event_name)) + { + unset(self::$listeners[$event_name]); + } + else + { + self::$listeners = []; + } + } + + //-------------------------------------------------------------------- + + /** + * Sets the path to the file that routes are read from. + * + * @param string $path + */ + public function setFile(string $path) + { + self::$eventsFile = $path; + } + + //-------------------------------------------------------------------- + + /** + * Turns simulation on or off. When on, events will not be triggered, + * simply logged. Useful during testing when you don't actually want + * the tests to run. + * + * @param bool $choice + */ + public static function simulate(bool $choice = true) + { + static::$simulate = $choice; + } + + //-------------------------------------------------------------------- + + /** + * Getter for the performance log records. + * + * @return array + */ + public static function getPerformanceLogs() + { + return static::$performanceLog; + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Files/Exceptions.php b/system/Files/Exceptions.php new file mode 100644 index 0000000..eece242 --- /dev/null +++ b/system/Files/Exceptions.php @@ -0,0 +1,46 @@ +size)) + { + $this->size = filesize($this->getPathname()); + } + + switch (strtolower($unit)) + { + case 'kb': + return number_format($this->size / 1024, 3); + break; + case 'mb': + return number_format(($this->size / 1024) / 1024, 3); + break; + } + + return $this->size; + } + + //-------------------------------------------------------------------- + + /** + * Attempts to determine the file extension based on the trusted + * getType() method. If the mime type is unknown, will return null. + * + * @return string + */ + public function guessExtension(): string + { + return \Config\Mimes::guessExtensionFromType($this->getMimeType()); + } + + //-------------------------------------------------------------------- + + /** + * Retrieve the media type of the file. SHOULD not use information from + * the $_FILES array, but should use other methods to more accurately + * determine the type of file, like finfo, or mime_content_type(). + * + * @return string|null The media type we determined it to be. + */ + public function getMimeType(): string + { + if (function_exists('finfo_file')) + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mimeType = finfo_file($finfo, $this->getRealPath()); + finfo_close($finfo); + } + else + { + $mimeType = mime_content_type($this->getRealPath()); + } + + return $mimeType; + } + + //-------------------------------------------------------------------- + + /** + * Generates a random names based on a simple hash and the time, with + * the correct file extension attached. + * + * @return string + */ + public function getRandomName(): string + { + return time() . '_' . bin2hex(random_bytes(10)) . '.' . $this->getExtension(); + } + + //-------------------------------------------------------------------- + + /** + * Moves a file to a new location. + * + * @param string $targetPath + * @param string|null $name + * @param bool $overwrite + * + * @return bool + */ + public function move(string $targetPath, string $name = null, bool $overwrite = false) + { + $targetPath = rtrim($targetPath, '/') . '/'; + $name = $name ?? $this->getBaseName(); + $destination = $overwrite ? $this->getDestination($targetPath . $name) : $targetPath . $name; + + if ( ! @rename($this->getPath(), $destination)) + { + $error = error_get_last(); + throw new \RuntimeException(sprintf('Could not move file %s to %s (%s)', $this->getBasename(), $targetPath, strip_tags($error['message']))); + } + + @chmod($targetPath, 0777 & ~umask()); + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Returns the destination path for the move operation where overwriting is not expected. + * + * First, it checks whether the delimiter is present in the filename, if it is, then it checks whether the + * last element is an integer as there may be cases that the delimiter may be present in the filename. + * For the all other cases, it appends an integer starting from zero before the file's extension. + * + * @param string $destination + * @param string $delimiter + * @param int $i + * + * @return string + */ + public function getDestination(string $destination, string $delimiter = '_', int $i = 0): string + { + while (file_exists($destination)) + { + $info = pathinfo($destination); + if (strpos($info['filename'], $delimiter) !== false) + { + $parts = explode($delimiter, $info['filename']); + if (is_numeric(end($parts))) + { + $i = end($parts); + array_pop($parts); + array_push($parts, ++ $i); + $destination = $info['dirname'] . '/' . implode($delimiter, $parts) . '.' . $info['extension']; + } + else + { + $destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++ $i . '.' . $info['extension']; + } + } + else + { + $destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++ $i . '.' . $info['extension']; + } + } + return $destination; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Filters/FilterInterface.php b/system/Filters/FilterInterface.php new file mode 100644 index 0000000..0be058c --- /dev/null +++ b/system/Filters/FilterInterface.php @@ -0,0 +1,76 @@ + [], + 'after' => [] + ]; + + /** + * The original config file + * @var BaseConfig + */ + protected $config; + + /** + * The active IncomingRequest or CLIRequest + * @var RequestInterface + */ + protected $request; + + /** + * The active Response instance + * @var ResponseInterface + */ + protected $response; + + /** + * Whether we've done initial processing + * on the filter lists. + * @var bool + */ + protected $initialized = false; + + //-------------------------------------------------------------------- + + public function __construct($config, RequestInterface $request, ResponseInterface $response) + { + $this->config = $config; + $this->request = & $request; + $this->response = & $response; + } + + //-------------------------------------------------------------------- + + /** + * Runs through all of the filters for the specified + * uri and position. + * + * @param string $uri + * @param string $position + * + * @return \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\ResponseInterface|mixed + */ + public function run(string $uri, $position = 'before') + { + $this->initialize($uri); + + foreach ($this->filters[$position] as $alias => $rules) + { + if (is_numeric($alias) && is_string($rules)) + { + $alias = $rules; + } + + if ( ! array_key_exists($alias, $this->config->aliases)) + { + throw new \InvalidArgumentException("'{$alias}' filter must have a matching alias defined."); + } + + $class = new $this->config->aliases[$alias](); + + if ( ! $class instanceof FilterInterface) + { + throw new \RuntimeException(get_class($class) . ' must implement CodeIgniter\Filters\FilterInterface.'); + } + + if ($position == 'before') + { + $result = $class->before($this->request); + + if ($result instanceof RequestInterface) + { + $this->request = $result; + continue; + } + + // If the response object was sent back, + // then send it and quit. + if ($result instanceof ResponseInterface) + { + $result->send(); + exit(EXIT_ERROR); + } + + if (empty($result)) + { + continue; + } + + return $result; + } + elseif ($position == 'after') + { + $result = $class->after($this->request, $this->response); + + if ($result instanceof ResponseInterface) + { + $this->response = $result; + continue; + } + } + } + + return $position == 'before' ? $this->request : $this->response; + } + + //-------------------------------------------------------------------- + + /** + * Runs through our list of filters provided by the configuration + * object to get them ready for use, including getting uri masks + * to proper regex, removing those we can from the possibilities + * based on HTTP method, etc. + * + * The resulting $this->filters is an array of only filters + * that should be applied to this request. + * + * We go ahead an process the entire tree because we'll need to + * run through both a before and after and don't want to double + * process the rows. + * + * @param string $uri + * + * @return Filters + */ + public function initialize(string $uri = null) + { + if ($this->initialized === true) + { + return; + } + + $this->processGlobals($uri); + $this->processMethods(); + $this->processFilters($uri); + + $this->initialized = true; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the processed filters array. + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + // Processors + //-------------------------------------------------------------------- + + protected function processGlobals(string $uri = null) + { + if ( ! isset($this->config->globals) || ! is_array($this->config->globals)) + { + return; + } + + // Before + if (isset($this->config->globals['before'])) + { + // Take any 'except' routes into consideration + foreach ($this->config->globals['before'] as $alias => $rules) + { + if ( ! is_array($rules) || ! array_key_exists('except', $rules)) + { + continue; + } + + $rules = $rules['except']; + + if (is_string($rules)) + { + $rules = [$rules]; + } + + foreach ($rules as $path) + { + // Prep it for regex + $path = str_replace('/*', '*', $path); + $path = trim(str_replace('*', '.+', $path), '/ '); + + // Path doesn't match the URI? continue on... + if (preg_match('/' . $path . '/', $uri, $match) !== 1) + { + continue; + } + + unset($this->config->globals['before'][$alias]); + break; + } + } + + $this->filters['before'] = array_merge($this->filters['before'], $this->config->globals['before']); + } + + // After + if (isset($this->config->globals['after'])) + { + // Take any 'except' routes into consideration + foreach ($this->config->globals['after'] as $alias => $rules) + { + if ( ! is_array($rules) || ! array_key_exists('except', $rules)) + { + continue; + } + + $rules = $rules['except']; + + if (is_string($rules)) + { + $rules = [$rules]; + } + + foreach ($rules as $path) + { + // Prep it for regex + $path = str_replace('/*', '*', $path); + $path = trim(str_replace('*', '.+', $path), '/ '); + + // Path doesn't match the URI? continue on... + if (preg_match('/' . $path . '/', $uri, $match) !== 1) + { + continue; + } + + unset($this->config->globals['after'][$alias]); + break; + } + } + + $this->filters['after'] = array_merge($this->filters['after'], $this->config->globals['after']); + } + } + + //-------------------------------------------------------------------- + + protected function processMethods() + { + if ( ! isset($this->config->methods) || ! is_array($this->config->methods)) + { + return; + } + + // Request method won't be set for CLI-based requests + $method = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli'; + + if (array_key_exists($method, $this->config->methods)) + { + $this->filters['before'] = array_merge($this->filters['before'], $this->config->methods[$method]); + return; + } + } + + //-------------------------------------------------------------------- + + protected function processFilters(string $uri = null) + { + if ( ! isset($this->config->filters) || ! count($this->config->filters)) + { + return; + } + + $uri = trim($uri, '/ '); + + $matches = []; + + foreach ($this->config->filters as $alias => $settings) + { + // Before + if (isset($settings['before'])) + { + foreach ($settings['before'] as $path) + { + // Prep it for regex + $path = str_replace('/*', '*', $path); + $path = trim(str_replace('*', '.+', $path), '/ '); + + if (preg_match('/' . $path . '/', $uri) !== 1) + { + continue; + } + + $matches[] = $alias; + } + + $this->filters['before'] = array_merge($this->filters['before'], $matches); + $matches = []; + } + + // After + if (isset($settings['after'])) + { + foreach ($settings['after'] as $path) + { + // Prep it for regex + $path = str_replace('/*', '*', $path); + $path = trim(str_replace('*', '.+', $path), '/ '); + + if (preg_match('/' . $path . '/', $uri) !== 1) + { + continue; + } + + $matches[] = $alias; + } + + $this->filters['after'] = array_merge($this->filters['after'], $matches); + } + } + } + + //-------------------------------------------------------------------- +} diff --git a/system/Format/FormatterInterface.php b/system/Format/FormatterInterface.php new file mode 100644 index 0000000..679885c --- /dev/null +++ b/system/Format/FormatterInterface.php @@ -0,0 +1,49 @@ +"); + + $this->arrayToXML($data, $output); + + return $output->asXML(); + } + + //-------------------------------------------------------------------- + + /** + * A recursive method to convert an array into a valid XML string. + * + * Written by CodexWorld. Received permission by email on Nov 24, 2016 to use this code. + * + * @see http://www.codexworld.com/convert-array-to-xml-in-php/ + * + * @param array $data + * @param \SimpleXMLElement $output + */ + protected function arrayToXML(array $data, &$output) + { + foreach ($data as $key => $value) + { + if (is_array($value)) + { + if ( ! is_numeric($key)) + { + $subnode = $output->addChild("$key"); + $this->arrayToXML($value, $subnode); + } + else + { + $subnode = $output->addChild("item{$key}"); + $this->arrayToXML($value, $subnode); + } + } + else + { + $output->addChild("$key", htmlspecialchars("$value")); + } + } + } + + //-------------------------------------------------------------------- +} diff --git a/system/HTTP/CLIRequest.php b/system/HTTP/CLIRequest.php new file mode 100644 index 0000000..292e626 --- /dev/null +++ b/system/HTTP/CLIRequest.php @@ -0,0 +1,236 @@ +parseCommand(); + } + + //-------------------------------------------------------------------- + + /** + * Returns the "path" of the request script so that it can be used + * in routing to the appropriate controller/method. + * + * The path is determined by treating the command line arguments + * as if it were a URL - up until we hit our first option. + * + * Example: + * php index.php users 21 profile -foo bar + * + * // Routes to /users/21/profile (index is removed for routing sake) + * // with the option foo = bar. + * + * @return string + */ + public function getPath(): string + { + $path = implode('/', $this->segments); + + return empty($path) ? '' : $path; + } + + //-------------------------------------------------------------------- + + /** + * Returns an associative array of all CLI options found, with + * their values. + * + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + //-------------------------------------------------------------------- + + /** + * Returns the value for a single CLI option that was passed in. + * + * @param string $key + * + * @return string|null + */ + public function getOption(string $key) + { + if (array_key_exists($key, $this->options)) + { + return $this->options[$key]; + } + + return null; + } + + //-------------------------------------------------------------------- + + /** + * Returns the options as a string, suitable for passing along on + * the CLI to other commands. + * + * Example: + * $options = [ + * 'foo' => 'bar', + * 'baz' => 'queue some stuff' + * ]; + * + * getOptionString() = '-foo bar -baz "queue some stuff"' + * + * @return string + */ + public function getOptionString(): string + { + if (empty($this->options)) + { + return ''; + } + + $out = ''; + + foreach ($this->options as $name => $value) + { + // If there's a space, we need to group + // so it will pass correctly. + if (strpos($value, ' ') !== false) + { + $value = '"' . $value . '"'; + } + + $out .= "-{$name} $value "; + } + + return $out; + } + + //-------------------------------------------------------------------- + + /** + * Parses the command line it was called from and collects all options + * and valid segments. + * + * NOTE: I tried to use getopt but had it fail occasionally to find + * any options, where argv has always had our back. + */ + protected function parseCommand() + { + // Since we're building the options ourselves, + // we stop adding it to the segments array once + // we have found the first dash. + $options_found = false; + + $argc = $this->getServer('argc', FILTER_SANITIZE_NUMBER_INT); + $argv = $this->getServer('argv'); + + // We start at 1 since we never want to include index.php + for ($i = 1; $i < $argc; $i ++ ) + { + // If there's no '-' at the beginning of the argument + // then add it to our segments. + if ( ! $options_found && strpos($argv[$i], '-') === false) + { + $this->segments[] = filter_var($argv[$i], FILTER_SANITIZE_STRING); + continue; + } + + $options_found = true; + + if (substr($argv[$i], 0, 1) != '-') + { + continue; + } + + $arg = filter_var(str_replace('-', '', $argv[$i]), FILTER_SANITIZE_STRING); + $value = null; + + // If the next item starts with a dash it's a value + if (isset($argv[$i + 1]) && substr($argv[$i + 1], 0, 1) != '-') + { + $value = filter_var($argv[$i + 1], FILTER_SANITIZE_STRING); + $i ++; + } + + $this->options[$arg] = $value; + } + } + + //-------------------------------------------------------------------- +} diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php new file mode 100644 index 0000000..3163424 --- /dev/null +++ b/system/HTTP/CURLRequest.php @@ -0,0 +1,749 @@ + 0.0, + 'connect_timeout' => 150, + 'debug' => false, + 'verify' => true + ]; + + /** + * Default values for when 'allow_redirects' + * option is true. + * + * @var array + */ + protected $redirectDefaults = [ + 'max' => 5, + 'strict' => true, + 'protocols' => ['http', 'https'], + ]; + + /** + * The number of milliseconds to delay before + * sending the request. + * @var float + */ + protected $delay = 0.0; + + //-------------------------------------------------------------------- + + /** + * Takes an array of options to set the following possible class properties: + * + * - baseURI + * - timeout + * - any other request options to use as defaults. + * + * @param App $config + * @param URI $uri + * @param ResponseInterface $response + * @param array $options + */ + public function __construct(App $config, URI $uri, ResponseInterface $response = null, array $options = []) + { + if ( ! function_exists('curl_version')) + { + throw new \RuntimeException('CURL must be enabled to use the CURLRequest class.'); + } + + parent::__construct($config); + + $this->response = $response; + $this->baseURI = $uri; + + $this->parseOptions($options); + } + + //-------------------------------------------------------------------- + + /** + * Sends an HTTP request to the specified $url. If this is a relative + * URL, it will be merged with $this->baseURI to form a complete URL. + * + * @param $method + * @param string $url + * @param array $options + * + * @return \CodeIgniter\HTTP\ResponseInterface + */ + public function request($method, string $url, array $options = []): ResponseInterface + { + $this->parseOptions($options); + + $url = $this->prepareURL($url); + + $method = filter_var($method, FILTER_SANITIZE_STRING); + + $this->send($method, $url); + + return $this->response; + } + + //-------------------------------------------------------------------- + + /** + * Convenience method for sending a GET request. + * + * @param string $url + * @param array $options + * + * @return \CodeIgniter\HTTP\ResponseInterface + */ + public function get(string $url, array $options = []): ResponseInterface + { + return $this->request('get', $url, $options); + } + + //-------------------------------------------------------------------- + + /** + * Convenience method for sending a DELETE request. + * + * @param string $url + * @param array $options + * + * @return \CodeIgniter\HTTP\ResponseInterface + */ + public function delete(string $url, array $options = []): ResponseInterface + { + return $this->request('delete', $url, $options); + } + + //-------------------------------------------------------------------- + + /** + * Convenience method for sending a HEAD request. + * + * @param string $url + * @param array $options + * + * @return ResponseInterface + */ + public function head(string $url, array $options = []): ResponseInterface + { + return $this->request('head', $url, $options); + } + + //-------------------------------------------------------------------- + + /** + * Convenience method for sending an OPTIONS request. + * + * @param string $url + * @param array $options + * + * @return Response + */ + public function options(string $url, array $options = []): ResponseInterface + { + return $this->request('options', $url, $options); + } + + //-------------------------------------------------------------------- + + /** + * Convenience method for sending a PATCH request. + * + * @param string $url + * @param array $options + * + * @return \CodeIgniter\HTTP\ResponseInterface + */ + public function patch(string $url, array $options = []): ResponseInterface + { + return $this->request('patch', $url, $options); + } + + //-------------------------------------------------------------------- + + /** + * Convenience method for sending a POST request. + * + * @param string $url + * @param array $options + * + * @return \CodeIgniter\HTTP\ResponseInterface + */ + public function post(string $url, array $options = []): ResponseInterface + { + return $this->request('post', $url, $options); + } + + //-------------------------------------------------------------------- + + /** + * Convenience method for sending a PUT request. + * + * @param string $url + * @param array $options + * + * @return \CodeIgniter\HTTP\ResponseInterface + */ + public function put(string $url, array $options = []): ResponseInterface + { + return $this->request('put', $url, $options); + } + + //-------------------------------------------------------------------- + + /** + * Sets the correct settings based on the options array + * passed in. + * + * @param array $options + */ + protected function parseOptions(array $options) + { + if (array_key_exists('baseURI', $options)) + { + $this->baseURI = $this->baseURI->setURI($options['baseURI']); + unset($options['baseURI']); + } + + if (array_key_exists('headers', $options) && is_array($options['headers'])) + { + foreach ($options['headers'] as $name => $value) + { + $this->setHeader($name, $value); + } + + unset($options['headers']); + } + + if (array_key_exists('delay', $options)) + { + // Convert from the milliseconds passed in + // to the seconds that sleep requires. + $this->delay = (float) $options['delay'] / 1000; + unset($options['delay']); + } + + foreach ($options as $key => $value) + { + $this->config[$key] = $value; + } + } + + //-------------------------------------------------------------------- + + /** + * If the $url is a relative URL, will attempt to create + * a full URL by prepending $this->baseURI to it. + * + * @param string $url + * + * @return string + */ + protected function prepareURL(string $url): string + { + // If it's a full URI, then we have nothing to do here... + if (strpos($url, '://') !== false) + { + return $url; + } + + $uri = $this->baseURI->resolveRelativeURI($url); + + return (string) $uri; + } + + //-------------------------------------------------------------------- + + /** + * Get the request method. Overrides the Request class' method + * since users expect a different answer here. + * + * @param bool|false $upper Whether to return in upper or lower case. + * + * @return string + */ + public function getMethod($upper = false): string + { + return ($upper) ? strtoupper($this->method) : strtolower($this->method); + } + + //-------------------------------------------------------------------- + + /** + * Fires the actual cURL request. + * + * @param string $method + * @param string $url + * + * @return \CodeIgniter\HTTP\ResponseInterface + */ + public function send(string $method, string $url) + { + // Reset our curl options so we're on a fresh slate. + $curl_options = []; + + if ( ! empty($this->config['query']) && is_array($this->config['query'])) + { + // This is likely too naive a solution. + // Should look into handling when $url already + // has query vars on it. + $url .= '?' . http_build_query($this->config['query']); + unset($this->config['query']); + } + + $curl_options[CURLOPT_URL] = $url; + $curl_options[CURLOPT_RETURNTRANSFER] = true; + $curl_options[CURLOPT_HEADER] = true; + $curl_options[CURLOPT_FRESH_CONNECT] = true; + // Disable @file uploads in post data. + $curl_options[CURLOPT_SAFE_UPLOAD] = true; + + $curl_options = $this->setCURLOptions($curl_options, $this->config); + $curl_options = $this->applyMethod($method, $curl_options); + $curl_options = $this->applyRequestHeaders($curl_options); + + // Do we need to delay this request? + if ($this->delay > 0) + { + sleep($this->delay); + } + + $output = $this->sendRequest($curl_options); + + $continueStr = "HTTP/1.1 100 Continue\x0d\x0a\x0d\x0a"; + if (strpos($output, $continueStr) === 0) + { + $output = substr($output, strlen($continueStr)); + } + + // Split out our headers and body + $break = strpos($output, "\r\n\r\n"); + + if ($break !== false) + { + // Our headers + $headers = explode("\n", substr($output, 0, $break)); + + $this->setResponseHeaders($headers); + + // Our body + $body = substr($output, $break + 4); + $this->response->setBody($body); + } + else + { + $this->response->setBody($output); + } + + return $this->response; + } + + //-------------------------------------------------------------------- + + /** + * Takes all headers current part of this request and adds them + * to the cURL request. + * + * @param array $curl_options + * + * @return array + */ + protected function applyRequestHeaders(array $curl_options = []): array + { + $headers = $this->getHeaders(); + + if (empty($headers)) + { + return $curl_options; + } + + $set = []; + + foreach ($headers as $name => $value) + { + $set[] = $name . ': ' . $this->getHeaderLine($name); + } + + $curl_options[CURLOPT_HTTPHEADER] = $set; + + return $curl_options; + } + + //-------------------------------------------------------------------- + + /** + * Apply method + * + * @param string $method + * @param array $curl_options + * + * @return array|int + */ + protected function applyMethod($method, array $curl_options): array + { + $method = strtoupper($method); + + $this->method = $method; + $curl_options[CURLOPT_CUSTOMREQUEST] = $method; + + $size = strlen($this->body); + + // Have content? + if ($size === null || $size > 0) + { + $curl_options = $this->applyBody($curl_options); + + return $curl_options; + } + + if ($method == 'PUT' || $method == 'POST') + { + // See http://tools.ietf.org/html/rfc7230#section-3.3.2 + if (is_null($this->getHeader('content-length'))) + { + $this->setHeader('Content-Length', 0); + } + } + else if ($method == 'HEAD') + { + $curl_options[CURLOPT_NOBODY] = 1; + } + + return $curl_options; + } + + //-------------------------------------------------------------------- + + /** + * Apply body + * + * @param array $curl_options + * + * @return array + */ + protected function applyBody(array $curl_options = []): array + { + if ( ! empty($this->body)) + { + $curl_options[CURLOPT_POSTFIELDS] = (string) $this->getBody(); + } + + return $curl_options; + } + + //-------------------------------------------------------------------- + + /** + * Parses the header retrieved from the cURL response into + * our Response object. + * + * @param array $headers + */ + protected function setResponseHeaders(array $headers = []) + { + foreach ($headers as $header) + { + if (($pos = strpos($header, ':')) !== false) + { + $title = substr($header, 0, $pos); + $value = substr($header, $pos + 1); + + $this->response->setHeader($title, $value); + } + else if (substr($header, 0, 4) == 'HTTP') + { + preg_match('#^HTTP\/([12]\.[01]) ([0-9]+) (.+)#', $header, $matches); + + if (isset($matches[1])) + { + $this->response->setProtocolVersion($matches[1]); + } + + if (isset($matches[2])) + { + $this->response->setStatusCode($matches[2], isset($matches[3]) ? $matches[3] : null); + } + } + } + } + + //-------------------------------------------------------------------- + + /** + * Set CURL options + * + * @param array $curl_options + * @param array $config + * @return array + * @throws \InvalidArgumentException + */ + protected function setCURLOptions(array $curl_options = [], array $config = []) + { + // Auth Headers + if ( ! empty($config['auth'])) + { + $curl_options[CURLOPT_USERPWD] = $config['auth'][0] . ':' . $config['auth'][1]; + + if ( ! empty($config['auth'][2]) && strtolower($config['auth'][2]) == 'digest') + { + $curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; + } + else + { + $curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC; + } + } + + // Certificate + if ( ! empty($config['cert'])) + { + $cert = $config['cert']; + + if (is_array($cert)) + { + $curl_options[CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + + if ( ! file_exists($cert)) + { + throw new \InvalidArgumentException('SSL certificate not found at: ' . $cert); + } + + $curl_options[CURLOPT_SSLCERT] = $cert; + } + + // SSL Verification + if (isset($config['verify'])) + { + if (is_string($config['verify'])) + { + $file = realpath($config['ssl_key']); + + if ( ! $file) + { + throw new \InvalidArgumentException('Cannot set SSL Key. ' . $config['ssl_key'] . + ' is not a valid file.'); + } + $curl_options[CURLOPT_CAINFO] = $file; + $curl_options[CURLOPT_SSL_VERIFYPEER] = 1; + } + else if (is_bool($config['verify'])) + { + $curl_options[CURLOPT_SSL_VERIFYPEER] = $config['verify']; + } + } + + // Debug + if (isset($config['debug'])) + { + $curl_options[CURLOPT_VERBOSE] = $config['debug'] === true ? 1 : 0; + $curl_options[CURLOPT_STDERR] = is_bool($config['debug']) ? fopen('php://output', 'w+') : $config['debug']; + } + + // Decode Content + if ( ! empty($config['decode_content'])) + { + $accept = $this->getHeaderLine('Accept-Encoding'); + + if ($accept) + { + $curl_options[CURLOPT_ENCODING] = $accept; + } + else + { + $curl_options[CURLOPT_ENCODING] = ''; + $curl_options[CURLOPT_HTTPHEADER] = 'Accept-Encoding'; + } + } + + // Allow Redirects + if (array_key_exists('allow_redirects', $config)) + { + $settings = $this->redirectDefaults; + + if (is_array($config['allow_redirects'])) + { + $settings = array_merge($settings, $config['allow_redirects']); + } + + if ($config['allow_redirects'] === false) + { + $curl_options[CURLOPT_FOLLOWLOCATION] = 0; + } + else + { + $curl_options[CURLOPT_FOLLOWLOCATION] = 1; + $curl_options[CURLOPT_MAXREDIRS] = $settings['max']; + + if ($settings['strict'] === true) + { + $curl_options[CURLOPT_POSTREDIR] = 1 | 2 | 4; + } + + $protocols = 0; + foreach ($settings['protocols'] as $proto) + { + $protocols += constant('CURLPROTO_' . strtoupper($proto)); + } + + $curl_options[CURLOPT_REDIR_PROTOCOLS] = $protocols; + } + } + + // Timeout + $curl_options[CURLOPT_TIMEOUT_MS] = (float) $config['timeout'] * 1000; + + // Connection Timeout + $curl_options[CURLOPT_CONNECTTIMEOUT_MS] = (float) $config['connect_timeout'] * 1000; + + // Post Data - application/x-www-form-urlencoded + if ( ! empty($config['form_params']) && is_array($config['form_params'])) + { + $postFields = http_build_query($config['form_params']); + $curl_options[CURLOPT_POSTFIELDS] = $postFields; + + // Ensure content-length is set, since CURL doesn't seem to + // calculate it when HTTPHEADER is set. + $this->setHeader('Content-Length', (string) strlen($postFields)); + $this->setHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + + // Post Data - multipart/form-data + if ( ! empty($config['multipart']) && is_array($config['multipart'])) + { + // setting the POSTFIELDS option automatically sets multipart + $curl_options[CURLOPT_POSTFIELDS] = $config['multipart']; + } + + // HTTP Errors + $curl_options[CURLOPT_FAILONERROR] = array_key_exists('http_errors', $config) ? (bool) $config['http_errors'] : true; + + // JSON + if (isset($config['json'])) + { + // Will be set as the body in `applyBody()` + $json = json_encode($config['json']); + $this->setBody($json); + $this->setHeader('Content-Type', 'application/json'); + } + + // version + if ( ! empty($config['version'])) + { + if ($config['version'] == 1.0) + { + $curl_options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; + } + else if ($config['version'] == 1.1) + { + $curl_options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; + } + } + + // Cookie + if (isset($config['cookie'])) + { + $curl_options[CURLOPT_COOKIEJAR] = $config['cookie']; + $curl_options[CURLOPT_COOKIEFILE] = $config['cookie']; + } + + return $curl_options; + } + + //-------------------------------------------------------------------- + + /** + * Does the actual work of initializing cURL, setting the options, + * and grabbing the output. + * + * @param array $curl_options + * + * @return string + */ + protected function sendRequest(array $curl_options = []): string + { + $ch = curl_init(); + + curl_setopt_array($ch, $curl_options); + + // Send the request and wait for a response. + $output = curl_exec($ch); + + if ($output === false) + { + throw new \RuntimeException(curl_errno($ch) . ': ' . curl_error($ch)); + } + + curl_close($ch); + + return $output; + } + + //-------------------------------------------------------------------- +} diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php new file mode 100644 index 0000000..087516a --- /dev/null +++ b/system/HTTP/ContentSecurityPolicy.php @@ -0,0 +1,797 @@ + $value) + { + if (isset($this->{$setting})) + { + $this->{$setting} = $value; + } + } + } + + //-------------------------------------------------------------------- + + /** + * Compiles and sets the appropriate headers in the request. + * + * Should be called just prior to sending the response to the user agent. + * + * @param ResponseInterface $response + */ + public function finalize(ResponseInterface &$response) + { + $this->generateNonces($response); + + $this->buildHeaders($response); + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + // Setters + //-------------------------------------------------------------------- + + /** + * If TRUE, nothing will be restricted. Instead all violations will + * be reported to the reportURI for monitoring. This is useful when + * you are just starting to implement the policy, and will help + * determine what errors need to be addressed before you turn on + * all filtering. + * + * @param bool|true $value + * + * @return $this + */ + public function reportOnly(bool $value = true) + { + $this->reportOnly = $value; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Sets the base_uri value. Can be either a URI class or a simple string. + * + * base_uri restricts the URLs that can appear in a page’s element. + * + * @see http://www.w3.org/TR/CSP/#directive-base-uri + * + * @param string $uri + * @param bool $reportOnly + * + * @return $this + */ + public function setBaseURI($uri, bool $reportOnly) + { + $this->baseURI = [(string) $uri => $reportOnly]; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Adds a new valid endpoint for a form's action. Can be either + * a URI class or a simple string. + * + * child-src lists the URLs for workers and embedded frame contents. + * For example: child-src https://youtube.com would enable embedding + * videos from YouTube but not from other origins. + * + * @see http://www.w3.org/TR/CSP/#directive-child-src + * + * @param $uri + * @param bool $reportOnly + * + * @return $this + */ + public function addChildSrc($uri, bool $reportOnly = false) + { + $this->addOption($uri, 'childSrc', $reportOnly); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Adds a new valid endpoint for a form's action. Can be either + * a URI class or a simple string. + * + * connect-src limits the origins to which you can connect + * (via XHR, WebSockets, and EventSource). + * + * @see http://www.w3.org/TR/CSP/#directive-connect-src + * + * @param $uri + * @param bool $reportOnly + * + * @return $this + */ + public function addConnectSrc($uri, bool $reportOnly = false) + { + $this->addOption($uri, 'connectSrc', $reportOnly); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Adds a new valid endpoint for a form's action. Can be either + * a URI class or a simple string. + * + * default_src is the URI that is used for many of the settings when + * no other source has been set. + * + * @see http://www.w3.org/TR/CSP/#directive-default-src + * + * @param $uri + * @param bool $reportOnly + * + * @return $this + */ + public function setDefaultSrc($uri, bool $reportOnly = false) + { + $this->defaultSrc = [(string) $uri => $reportOnly]; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Adds a new valid endpoint for a form's action. Can be either + * a URI class or a simple string. + * + * font-src specifies the origins that can serve web fonts. + * + * @see http://www.w3.org/TR/CSP/#directive-font-src + * + * @param $uri + * @param bool $reportOnly + * + * @return $this + */ + public function addFontSrc($uri, bool $reportOnly = false) + { + $this->addOption($uri, 'fontSrc', $reportOnly); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Adds a new valid endpoint for a form's action. Can be either + * a URI class or a simple string. + * + * @see http://www.w3.org/TR/CSP/#directive-form-action + * + * @param $uri + * @param bool $reportOnly + * + * @return $this + */ + public function addFormAction($uri, bool $reportOnly = false) + { + $this->addOption($uri, 'formAction', $reportOnly); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Adds a new resource that should allow embedding the resource using + * ,