From c9044b9ef5c05977558b3f796f0090f1646fb748 Mon Sep 17 00:00:00 2001 From: Alexey Marunin Date: Mon, 9 Oct 2017 17:01:31 +0300 Subject: [PATCH] Init commit --- .env.example | 18 + .gitignore | 106 ++ README.md | 36 + assets/DashboardAsset.php | 31 + assets/css/custom.css | 57 + assets/js/app.js | 21 + autocompletion.php | 45 + composer.json | 46 + composer.lock | 1442 ++++++++++++++++++++ config/_aliases.php | 7 + config/base.php | 42 + config/console.php | 37 + config/env.php | 41 + config/web.php | 103 ++ controllers/AbstractController.php | 90 ++ controllers/AuthController.php | 101 ++ controllers/StatusController.php | 196 +++ controllers/TaskController.php | 252 ++++ controllers/UserController.php | 196 +++ helpers/DateTimeHelper.php | 154 +++ helpers/Html.php | 127 ++ migrations/m170525_125816_init.php | 102 ++ models/Status.php | 90 ++ models/Task.php | 158 +++ models/User.php | 245 ++++ models/form/LoginForm.php | 112 ++ models/query/StatusQuery.php | 30 + models/query/TaskQuery.php | 19 + models/query/UserQuery.php | 56 + modules/api/Module.php | 17 + modules/api/controllers/TaskController.php | 142 ++ modules/api/models/Status.php | 30 + modules/api/models/Task.php | 46 + requirements.php | 147 ++ runtime/.gitignore | 2 + views/auth/login.php | 38 + views/layouts/_content.php | 43 + views/layouts/_header.php | 58 + views/layouts/_left.php | 32 + views/layouts/_right.php | 201 +++ views/layouts/main-login.php | 41 + views/layouts/main.php | 63 + views/status/_form.php | 23 + views/status/create.php | 23 + views/status/index.php | 75 + views/status/update.php | 23 + views/status/view.php | 34 + views/task/_form.php | 42 + views/task/create.php | 25 + views/task/index.php | 110 ++ views/task/update.php | 23 + views/task/view.php | 48 + views/user/_form.php | 32 + views/user/create.php | 23 + views/user/index.php | 70 + views/user/update.php | 23 + views/user/view.php | 42 + web/.htaccess.example | 6 + web/assets/.gitignore | 2 + web/favicon.png | Bin 0 -> 938 bytes web/index.php | 22 + widgets/DatePicker.php | 65 + widgets/DetailView.php | 134 ++ widgets/GridView.php | 49 + yii | 26 + 65 files changed, 5740 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 assets/DashboardAsset.php create mode 100644 assets/css/custom.css create mode 100644 assets/js/app.js create mode 100644 autocompletion.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 config/_aliases.php create mode 100644 config/base.php create mode 100644 config/console.php create mode 100644 config/env.php create mode 100644 config/web.php create mode 100644 controllers/AbstractController.php create mode 100644 controllers/AuthController.php create mode 100644 controllers/StatusController.php create mode 100644 controllers/TaskController.php create mode 100644 controllers/UserController.php create mode 100644 helpers/DateTimeHelper.php create mode 100644 helpers/Html.php create mode 100644 migrations/m170525_125816_init.php create mode 100644 models/Status.php create mode 100644 models/Task.php create mode 100644 models/User.php create mode 100644 models/form/LoginForm.php create mode 100644 models/query/StatusQuery.php create mode 100644 models/query/TaskQuery.php create mode 100644 models/query/UserQuery.php create mode 100644 modules/api/Module.php create mode 100644 modules/api/controllers/TaskController.php create mode 100644 modules/api/models/Status.php create mode 100644 modules/api/models/Task.php create mode 100644 requirements.php create mode 100644 runtime/.gitignore create mode 100644 views/auth/login.php create mode 100644 views/layouts/_content.php create mode 100644 views/layouts/_header.php create mode 100644 views/layouts/_left.php create mode 100644 views/layouts/_right.php create mode 100644 views/layouts/main-login.php create mode 100644 views/layouts/main.php create mode 100644 views/status/_form.php create mode 100644 views/status/create.php create mode 100644 views/status/index.php create mode 100644 views/status/update.php create mode 100644 views/status/view.php create mode 100644 views/task/_form.php create mode 100644 views/task/create.php create mode 100644 views/task/index.php create mode 100644 views/task/update.php create mode 100644 views/task/view.php create mode 100644 views/user/_form.php create mode 100644 views/user/create.php create mode 100644 views/user/index.php create mode 100644 views/user/update.php create mode 100644 views/user/view.php create mode 100644 web/.htaccess.example create mode 100644 web/assets/.gitignore create mode 100644 web/favicon.png create mode 100644 web/index.php create mode 100644 widgets/DatePicker.php create mode 100644 widgets/DetailView.php create mode 100644 widgets/GridView.php create mode 100644 yii diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..88bebd5 --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +#-------------------------------------------------------------------------------------- +# Application settings +#-------------------------------------------------------------------------------------- +APP_NAME = 'Task Tracker' +APP_DOMAIN = ylab-test.dev +COOKIE_VALIDATION_KEY = 'H04V16iCzDD9980ebfTf99i4kSDNlvLpE09r0Wkp' + +YII_DEBUG = true +YII_ENV = dev + +#-------------------------------------------------------------------------------------- +# E-mails +#-------------------------------------------------------------------------------------- +ADMIN_EMAIL = 'pigu@p33.org' +ROBOT_EMAIL = ${ADMIN_EMAIL} +SYSTEM_EMAIL = ${ADMIN_EMAIL} + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d837b79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,106 @@ +.htaccess +.env +node_modules +robots.txt +.DS_Store +.idea +Thumbs.db +.buildpath +.settings +composer.phar + +/vendor + + +### macOS template +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +### Windows template +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml + +# Sensitive or high-churn files: +.idea/dataSources/ +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Vagrant template +.vagrant/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..5598db5 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Установка +Убедитесь, что установлен Apache, PHP, composer, NPM +Не требуется MySQL! Работаем с SQLite (дабы не усложнять окружение) + +## Запуск composer +``` +composer global require "fxp/composer-asset-plugin" +composer install +``` + +Проверяем наличие необходимых системных компонентов +``` +php requirements.php +``` +Обращаем внимание на warnings, не должно быть ошибок + +## Настройка рабочего окружения +Копируем файл `.env.dist` в `.env`, меняем в нем нужные настройки. + +Копируем файл `web/.htaccess.example` в `web/.htaccess` (если в качестве веб-сервера установлен Apache). + +В каталоге runtime созадаем пустой файл `data.db` + +## Миграции +Запускаем миграции +``` +php yii migrate +``` + +## Frontend API + +`/api/tasks` - просмотр всех задач + +`/api/task/10` - просмотр задачи с id=10 + +`/api/task/10/change-status` - сменить статус для задачи с id=10 \ No newline at end of file diff --git a/assets/DashboardAsset.php b/assets/DashboardAsset.php new file mode 100644 index 0000000..ed15493 --- /dev/null +++ b/assets/DashboardAsset.php @@ -0,0 +1,31 @@ +.info { + position: relative; +} + +.main-header .logo img { + margin-bottom: 10px; + height: 50px; + width: auto; +} + +.auth-login .panel { + border-bottom: none; + box-shadow: none; + -webkit-box-shadow: none; +} + +.auth-login h1 { + color: #fff; +} + +.auth-login .panel-heading.logo-heading { + text-align: center; + background-color: #00a65a; +} + diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..1c399dd --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,21 @@ +function onChangeTaskStatus( task_id ) { + var el = $( '#task-' + task_id + '-status' ); + var status_id = el.val(); + + el.attr( 'disabled', 'disabled' ); + $.ajax( { + url: '/task/change-status?id=' + task_id, + type: 'post', + data: { + 'status_id': status_id + } + } ) + .done( function ( response ) { + el.val( response.status_id ); + } ) + .fail( function ( response ) { + } ) + .always( function() { + el.removeAttr( 'disabled' ); + }) +} \ No newline at end of file diff --git a/autocompletion.php b/autocompletion.php new file mode 100644 index 0000000..552ee30 --- /dev/null +++ b/autocompletion.php @@ -0,0 +1,45 @@ +=5.6.0", + "yiisoft/yii2": "2.0.11.2", + "vlucas/phpdotenv": "2.4.0", + "rmrevin/yii2-fontawesome": "^2.17", + "dmstr/yii2-adminlte-asset": "2.4.2", + "kartik-v/yii2-detail-view": "^1.7", + "kartik-v/yii2-widget-select2": "^2.0", + "kartik-v/yii2-widget-datepicker": "^1.4", + "kartik-v/yii2-builder": "^1.6", + "yiisoft/yii2-httpclient": "*" + }, + + "minimum-stability": "stable", + "config": { + "process-timeout": 1800 + }, + + "extra": { + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + }, + + "require-dev": { + "yiisoft/yii2-debug": "^2.0" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..99cbcff --- /dev/null +++ b/composer.lock @@ -0,0 +1,1442 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "4c94b0fdaca86f05219725b75c5ed07b", + "packages": [ + { + "name": "almasaeed2010/adminlte", + "version": "v2.3.11", + "source": { + "type": "git", + "url": "https://github.com/almasaeed2010/AdminLTE.git", + "reference": "2be703222af2edcb87e562d3da2299e4352bff8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/almasaeed2010/AdminLTE/zipball/2be703222af2edcb87e562d3da2299e4352bff8a", + "reference": "2be703222af2edcb87e562d3da2299e4352bff8a", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Abdullah Almsaeed", + "email": "abdullah@almsaeedstudio.com" + } + ], + "description": "AdminLTE - admin control panel and dashboard that's based on Bootstrap 3", + "homepage": "http://almsaeedstudio.com/", + "keywords": [ + "JS", + "admin", + "back-end", + "css", + "less", + "responsive", + "template", + "theme", + "web" + ], + "time": "2017-01-08T21:03:57+00:00" + }, + { + "name": "bower-asset/bootstrap", + "version": "v3.3.7", + "source": { + "type": "git", + "url": "https://github.com/twbs/bootstrap.git", + "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/0b9c4a4007c44201dce9a6cc1a38407005c26c86", + "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=1.9.1,<4.0" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": [ + "less/bootstrap.less", + "dist/js/bootstrap.js" + ], + "bower-asset-ignore": [ + "/.*", + "_config.yml", + "CNAME", + "composer.json", + "CONTRIBUTING.md", + "docs", + "js/tests", + "test-infra" + ] + }, + "license": [ + "MIT" + ], + "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", + "keywords": [ + "css", + "framework", + "front-end", + "js", + "less", + "mobile-first", + "responsive", + "web" + ] + }, + { + "name": "bower-asset/bootstrap3-dialog", + "version": "v1.35.4", + "source": { + "type": "git", + "url": "https://github.com/nakupanda/bootstrap3-dialog.git", + "reference": "3dd11d586f78de75356af418907ec6e3b347377c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nakupanda/bootstrap3-dialog/zipball/3dd11d586f78de75356af418907ec6e3b347377c", + "reference": "3dd11d586f78de75356af418907ec6e3b347377c", + "shasum": "" + }, + "require": { + "bower-asset/bootstrap": ">=3.1.0", + "bower-asset/jquery": ">=1.9.0" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": [ + "dist/less/bootstrap-dialog.less", + "dist/css/bootstrap-dialog.min.css", + "dist/js/bootstrap-dialog.min.js" + ], + "bower-asset-ignore": [ + "source", + "spec", + ".bowerrc", + ".gitignore", + ".jshintignore", + ".jshintrc", + "bower.json", + "gruntfile.js", + "package.json", + "README.md" + ] + }, + "license": [ + "MIT" + ], + "description": "Make use of Bootstrap Modal more monkey-friendly. http://nakupanda.github.io/bootstrap3-dialog/", + "keywords": [ + "css", + "framework", + "front-end", + "js", + "less", + "mobile-first", + "responsive", + "web" + ] + }, + { + "name": "bower-asset/jquery", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/jquery/jquery-dist.git", + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/c0185ab7c75aab88762c5aae780b9d83b80eda72", + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72", + "shasum": "" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "dist/jquery.js", + "bower-asset-ignore": [ + "package.json" + ] + }, + "license": [ + "MIT" + ], + "keywords": [ + "browser", + "javascript", + "jquery", + "library" + ] + }, + { + "name": "bower-asset/jquery.inputmask", + "version": "3.3.7", + "source": { + "type": "git", + "url": "https://github.com/RobinHerbots/Inputmask.git", + "reference": "9835731cb78cac749734d94a1cb5bd70da4d3b10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/9835731cb78cac749734d94a1cb5bd70da4d3b10", + "reference": "9835731cb78cac749734d94a1cb5bd70da4d3b10", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=1.7" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": [ + "./dist/inputmask/inputmask.js", + "./dist/inputmask/inputmask.extensions.js", + "./dist/inputmask/inputmask.date.extensions.js", + "./dist/inputmask/inputmask.numeric.extensions.js", + "./dist/inputmask/inputmask.phone.extensions.js", + "./dist/inputmask/jquery.inputmask.js", + "./dist/inputmask/global/document.js", + "./dist/inputmask/global/window.js", + "./dist/inputmask/phone-codes/phone.js", + "./dist/inputmask/phone-codes/phone-be.js", + "./dist/inputmask/phone-codes/phone-nl.js", + "./dist/inputmask/phone-codes/phone-ru.js", + "./dist/inputmask/phone-codes/phone-uk.js", + "./dist/inputmask/dependencyLibs/inputmask.dependencyLib.jqlite.js", + "./dist/inputmask/dependencyLibs/inputmask.dependencyLib.jquery.js", + "./dist/inputmask/dependencyLibs/inputmask.dependencyLib.js", + "./dist/inputmask/bindings/inputmask.binding.js" + ], + "bower-asset-ignore": [ + "**/*", + "!dist/*", + "!dist/inputmask/*", + "!dist/min/*", + "!dist/min/inputmask/*" + ] + }, + "license": [ + "http://opensource.org/licenses/mit-license.php" + ], + "description": "Inputmask is a javascript library which creates an input mask. Inputmask can run against vanilla javascript, jQuery and jqlite.", + "keywords": [ + "form", + "input", + "inputmask", + "jquery", + "mask", + "plugins" + ] + }, + { + "name": "bower-asset/punycode", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/bestiejs/punycode.js.git", + "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", + "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", + "shasum": "" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "punycode.js", + "bower-asset-ignore": [ + "coverage", + "tests", + ".*", + "component.json", + "Gruntfile.js", + "node_modules", + "package.json" + ] + } + }, + { + "name": "bower-asset/yii2-pjax", + "version": "v2.0.6", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/jquery-pjax.git", + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978", + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=1.8" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "./jquery.pjax.js", + "bower-asset-ignore": [ + ".travis.yml", + "Gemfile", + "Gemfile.lock", + "CONTRIBUTING.md", + "vendor/", + "script/", + "test/" + ] + }, + "license": [ + "MIT" + ] + }, + { + "name": "cebe/markdown", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/cebe/markdown.git", + "reference": "25b28bae8a6f185b5030673af77b32e1163d5c6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/markdown/zipball/25b28bae8a6f185b5030673af77b32e1163d5c6e", + "reference": "25b28bae8a6f185b5030673af77b32e1163d5c6e", + "shasum": "" + }, + "require": { + "lib-pcre": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "cebe/indent": "*", + "facebook/xhprof": "*@dev", + "phpunit/phpunit": "4.1.*" + }, + "bin": [ + "bin/markdown" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "cebe\\markdown\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Creator" + } + ], + "description": "A super fast, highly extensible markdown parser for PHP", + "homepage": "https://github.com/cebe/markdown#readme", + "keywords": [ + "extensible", + "fast", + "gfm", + "markdown", + "markdown-extra" + ], + "time": "2017-07-16T21:13:23+00:00" + }, + { + "name": "cebe/yii2-gravatar", + "version": "1.1", + "target-dir": "cebe/gravatar", + "source": { + "type": "git", + "url": "https://github.com/cebe/yii2-gravatar.git", + "reference": "c9c01bd14c9bdee9e5ae1ef1aad23f80c182c057" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/yii2-gravatar/zipball/c9c01bd14c9bdee9e5ae1ef1aad23f80c182c057", + "reference": "c9c01bd14c9bdee9e5ae1ef1aad23f80c182c057", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-0": { + "cebe\\gravatar\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" + } + ], + "description": "Gravatar Widget for Yii 2", + "keywords": [ + "gravatar", + "yii" + ], + "time": "2013-12-10T17:49:58+00:00" + }, + { + "name": "dmstr/yii2-adminlte-asset", + "version": "2.4.2", + "source": { + "type": "git", + "url": "https://github.com/dmstr/yii2-adminlte-asset.git", + "reference": "b339611f9f34621d731d3feca3a83b42720c7d5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dmstr/yii2-adminlte-asset/zipball/b339611f9f34621d731d3feca3a83b42720c7d5c", + "reference": "b339611f9f34621d731d3feca3a83b42720c7d5c", + "shasum": "" + }, + "require": { + "almasaeed2010/adminlte": "~2.0", + "cebe/yii2-gravatar": "1.*", + "rmrevin/yii2-fontawesome": "~2.9", + "yiisoft/yii2": "2.*", + "yiisoft/yii2-bootstrap": "2.*" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "dmstr\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tobias Munk", + "email": "tobias@diemeisterei.de" + }, + { + "name": "Evgeniy Tkachenko", + "email": "et.coder@gmail.com" + } + ], + "description": "AdminLTE backend theme asset bundle for Yii 2.0 Framework", + "keywords": [ + "AdminLTE", + "admin", + "asset", + "backend", + "css", + "extension", + "less", + "theme", + "yii2" + ], + "time": "2017-03-30T09:04:16+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.9.3", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "95e1bae3182efc0f3422896a3236e991049dac69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/95e1bae3182efc0f3422896a3236e991049dac69", + "reference": "95e1bae3182efc0f3422896a3236e991049dac69", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "simpletest/simpletest": "^1.1" + }, + "type": "library", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "time": "2017-06-03T02:28:16+00:00" + }, + { + "name": "fortawesome/font-awesome", + "version": "v4.7.0", + "source": { + "type": "git", + "url": "https://github.com/FortAwesome/Font-Awesome.git", + "reference": "a8386aae19e200ddb0f6845b5feeee5eb7013687" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FortAwesome/Font-Awesome/zipball/a8386aae19e200ddb0f6845b5feeee5eb7013687", + "reference": "a8386aae19e200ddb0f6845b5feeee5eb7013687", + "shasum": "" + }, + "require-dev": { + "jekyll": "1.0.2", + "lessc": "1.4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.6.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OFL-1.1", + "MIT" + ], + "authors": [ + { + "name": "Dave Gandy", + "email": "dave@fontawesome.io", + "homepage": "http://twitter.com/davegandy", + "role": "Developer" + } + ], + "description": "The iconic font and CSS framework", + "homepage": "http://fontawesome.io/", + "keywords": [ + "FontAwesome", + "awesome", + "bootstrap", + "font", + "icon" + ], + "time": "2016-10-24T15:52:54+00:00" + }, + { + "name": "kartik-v/yii2-builder", + "version": "v1.6.2", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-builder.git", + "reference": "f6236484c753059935dcd1487bb725fec8f9823c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-builder/zipball/f6236484c753059935dcd1487bb725fec8f9823c", + "reference": "f6236484c753059935dcd1487bb725fec8f9823c", + "shasum": "" + }, + "require": { + "kartik-v/yii2-grid": "~3.0", + "kartik-v/yii2-helpers": "~1.3", + "kartik-v/yii2-krajee-base": "~1.7", + "kartik-v/yii2-widget-activeform": "~1.4" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\builder\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Build forms (single-row or multi-row/tabular) easily for Yii Framework 2.0", + "homepage": "https://github.com/kartik-v/yii2-builder", + "keywords": [ + "builder", + "extension", + "form", + "widget", + "yii2" + ], + "time": "2016-11-19T11:38:12+00:00" + }, + { + "name": "kartik-v/yii2-detail-view", + "version": "v1.7.5", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-detail-view.git", + "reference": "65e626c0b55e7cca0db0886410771cd304c1fbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-detail-view/zipball/65e626c0b55e7cca0db0886410771cd304c1fbd2", + "reference": "65e626c0b55e7cca0db0886410771cd304c1fbd2", + "shasum": "" + }, + "require": { + "kartik-v/yii2-dialog": "~1.0", + "kartik-v/yii2-helpers": ">=1.3.5", + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\detail\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Various enhancements to the Yii 2 Detail View with multi models, ability to edit data, manage Bootstrap 3 styles and more.", + "homepage": "https://github.com/kartik-v/yii2-detail-view", + "keywords": [ + "detail", + "detail view", + "extension", + "form", + "grid", + "widget", + "yii2" + ], + "time": "2016-06-22T01:29:56+00:00" + }, + { + "name": "kartik-v/yii2-dialog", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-dialog.git", + "reference": "4aaf8918c6dbd90218b6ad9036b1aae211480716" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-dialog/zipball/4aaf8918c6dbd90218b6ad9036b1aae211480716", + "reference": "4aaf8918c6dbd90218b6ad9036b1aae211480716", + "shasum": "" + }, + "require": { + "bower-asset/bootstrap3-dialog": "~1.34", + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\dialog\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "An asset bundle for bootstrap3-dialog for Yii 2.0 framework.", + "homepage": "https://github.com/kartik-v/yii2-dialog", + "keywords": [ + "alert", + "bootstrap", + "dialog", + "extension", + "modal", + "yii2" + ], + "time": "2016-09-13T18:15:26+00:00" + }, + { + "name": "kartik-v/yii2-grid", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-grid.git", + "reference": "ffccccb8ab23838461483a6139814f66187bcb3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-grid/zipball/ffccccb8ab23838461483a6139814f66187bcb3b", + "reference": "ffccccb8ab23838461483a6139814f66187bcb3b", + "shasum": "" + }, + "require": { + "kartik-v/yii2-dialog": "~1.0", + "kartik-v/yii2-krajee-base": "~1.7" + }, + "suggest": { + "kartik-v/yii2-mpdf": "For exporting grids to PDF" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\grid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Yii 2 GridView on steroids. Various enhancements and utilities for the Yii 2.0 GridView widget.", + "homepage": "https://github.com/kartik-v/yii2-grid", + "keywords": [ + "extension", + "grid", + "widget", + "yii2" + ], + "time": "2017-06-09T10:08:02+00:00" + }, + { + "name": "kartik-v/yii2-helpers", + "version": "v1.3.6", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-helpers.git", + "reference": "0b9d5f17464df9f68e249ed4411f9f1381927b83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-helpers/zipball/0b9d5f17464df9f68e249ed4411f9f1381927b83", + "reference": "0b9d5f17464df9f68e249ed4411f9f1381927b83", + "shasum": "" + }, + "require": { + "yiisoft/yii2-bootstrap": "*" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\helpers\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A collection of useful helper functions for Yii Framework 2.0", + "homepage": "https://github.com/kartik-v/yii2-helpers", + "keywords": [ + "bootstrap", + "extension", + "helper", + "utilities", + "yii", + "yii2" + ], + "time": "2016-11-26T06:03:52+00:00" + }, + { + "name": "kartik-v/yii2-krajee-base", + "version": "v1.8.8", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-krajee-base.git", + "reference": "2479241c03c87995cfc528ae7b297f5ae9e733cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-krajee-base/zipball/2479241c03c87995cfc528ae7b297f5ae9e733cb", + "reference": "2479241c03c87995cfc528ae7b297f5ae9e733cb", + "shasum": "" + }, + "require": { + "yiisoft/yii2-bootstrap": "@dev" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\base\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Base library and foundation components for all Yii2 Krajee extensions.", + "homepage": "https://github.com/kartik-v/yii2-krajee-base", + "keywords": [ + "base", + "extension", + "foundation", + "krajee", + "widget", + "yii2" + ], + "time": "2017-02-22T05:58:53+00:00" + }, + { + "name": "kartik-v/yii2-widget-activeform", + "version": "v1.4.8", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-activeform.git", + "reference": "53c2f877f12ba0b79e8346b6cae50cbba2bcfc69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-activeform/zipball/53c2f877f12ba0b79e8346b6cae50cbba2bcfc69", + "reference": "53c2f877f12ba0b79e8346b6cae50cbba2bcfc69", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\form\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Enhanced Yii2 active-form and active-field with full bootstrap styling support (sub repo split from yii2-widgets).", + "homepage": "https://github.com/kartik-v/yii2-widget-activeform", + "keywords": [ + "activefield", + "activeform", + "extension", + "field", + "form", + "widget", + "yii2" + ], + "time": "2016-04-27T18:38:05+00:00" + }, + { + "name": "kartik-v/yii2-widget-datepicker", + "version": "v1.4.2", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-datepicker.git", + "reference": "8738f6dc3211d949b05189ba103aab786143fc6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-datepicker/zipball/8738f6dc3211d949b05189ba103aab786143fc6c", + "reference": "8738f6dc3211d949b05189ba103aab786143fc6c", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\date\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Enhanced Yii2 wrapper for the bootstrap datepicker plugin (sub repo split from yii2-widgets).", + "homepage": "https://github.com/kartik-v/yii2-widget-datepicker", + "keywords": [ + "date", + "extension", + "form", + "jquery", + "picker", + "plugin", + "select2", + "widget", + "yii2" + ], + "time": "2016-09-04T10:52:50+00:00" + }, + { + "name": "kartik-v/yii2-widget-select2", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-select2.git", + "reference": "a129c6663078fe0ad14cde2a5d0afd071c7608e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-select2/zipball/a129c6663078fe0ad14cde2a5d0afd071c7608e9", + "reference": "a129c6663078fe0ad14cde2a5d0afd071c7608e9", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\select2\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Enhanced Yii2 wrapper for the Select2 jQuery plugin (sub repo split from yii2-widgets).", + "homepage": "https://github.com/kartik-v/yii2-widget-select2", + "keywords": [ + "dropdown", + "extension", + "form", + "jquery", + "plugin", + "select2", + "widget", + "yii2" + ], + "time": "2017-08-07T18:12:36+00:00" + }, + { + "name": "rmrevin/yii2-fontawesome", + "version": "2.17.1", + "source": { + "type": "git", + "url": "https://github.com/rmrevin/yii2-fontawesome.git", + "reference": "65ce306da864f4d558348aeba040ed7876878090" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rmrevin/yii2-fontawesome/zipball/65ce306da864f4d558348aeba040ed7876878090", + "reference": "65ce306da864f4d558348aeba040ed7876878090", + "shasum": "" + }, + "require": { + "fortawesome/font-awesome": "~4.7", + "php": ">=5.4.0", + "yiisoft/yii2": "2.0.*" + }, + "type": "yii2-extension", + "extra": { + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + }, + "autoload": { + "psr-4": { + "rmrevin\\yii\\fontawesome\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Revin Roman", + "email": "roman@rmrevin.com", + "homepage": "https://rmrevin.com/" + } + ], + "description": "Asset Bundle for Yii2 with Font Awesome", + "keywords": [ + "asset", + "awesome", + "bundle", + "font", + "yii" + ], + "time": "2017-01-11T14:05:47+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause-Attribution" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2016-09-01T10:05:43+00:00" + }, + { + "name": "yiisoft/yii2", + "version": "2.0.11.2", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-framework.git", + "reference": "ee996adec1dfd7babb67bd0c604f5bd6425fe5ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/ee996adec1dfd7babb67bd0c604f5bd6425fe5ab", + "reference": "ee996adec1dfd7babb67bd0c604f5bd6425fe5ab", + "shasum": "" + }, + "require": { + "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", + "bower-asset/jquery.inputmask": "~3.2.2 | ~3.3.3", + "bower-asset/punycode": "1.3.*", + "bower-asset/yii2-pjax": "~2.0.1", + "cebe/markdown": "~1.0.0 | ~1.1.0", + "ext-ctype": "*", + "ext-mbstring": "*", + "ezyang/htmlpurifier": "~4.6", + "lib-pcre": "*", + "php": ">=5.4.0", + "yiisoft/yii2-composer": "~2.0.4" + }, + "bin": [ + "yii" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "http://www.yiiframework.com/", + "role": "Founder and project lead" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "http://rmcreative.ru/", + "role": "Core framework development" + }, + { + "name": "Maurizio Domba", + "homepage": "http://mdomba.info/", + "role": "Core framework development" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" + }, + { + "name": "Timur Ruziev", + "email": "resurtm@gmail.com", + "homepage": "http://resurtm.com/", + "role": "Core framework development" + }, + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com", + "role": "Core framework development" + }, + { + "name": "Dmitry Naumenko", + "email": "d.naumenko.a@gmail.com", + "role": "Core framework development" + }, + { + "name": "Boudewijn Vahrmeijer", + "email": "info@dynasource.eu", + "homepage": "http://dynasource.eu", + "role": "Core framework development" + } + ], + "description": "Yii PHP Framework Version 2", + "homepage": "http://www.yiiframework.com/", + "keywords": [ + "framework", + "yii2" + ], + "time": "2017-02-08T09:04:32+00:00" + }, + { + "name": "yiisoft/yii2-bootstrap", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-bootstrap.git", + "reference": "3fd2b8c950cce79d60e9702d6bcb24eb3c80f6c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-bootstrap/zipball/3fd2b8c950cce79d60e9702d6bcb24eb3c80f6c5", + "reference": "3fd2b8c950cce79d60e9702d6bcb24eb3c80f6c5", + "shasum": "" + }, + "require": { + "bower-asset/bootstrap": "3.3.* | 3.2.* | 3.1.*", + "yiisoft/yii2": ">=2.0.6" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + }, + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + }, + "autoload": { + "psr-4": { + "yii\\bootstrap\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The Twitter Bootstrap extension for the Yii framework", + "keywords": [ + "bootstrap", + "yii2" + ], + "time": "2016-03-17T03:29:28+00:00" + }, + { + "name": "yiisoft/yii2-composer", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-composer.git", + "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", + "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "require-dev": { + "composer/composer": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "yii\\composer\\Plugin", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\composer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The composer plugin for Yii extension installer", + "keywords": [ + "composer", + "extension installer", + "yii2" + ], + "time": "2016-12-20T13:26:02+00:00" + }, + { + "name": "yiisoft/yii2-httpclient", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-httpclient.git", + "reference": "720e3c9bdda260abffe61babfe39b91c4308ac4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-httpclient/zipball/720e3c9bdda260abffe61babfe39b91c4308ac4c", + "reference": "720e3c9bdda260abffe61babfe39b91c4308ac4c", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "~2.0.0" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\httpclient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + } + ], + "description": "HTTP client extension for the Yii framework", + "keywords": [ + "curl", + "http", + "httpclient", + "yii2" + ], + "time": "2017-06-23T09:36:13+00:00" + } + ], + "packages-dev": [ + { + "name": "yiisoft/yii2-debug", + "version": "2.0.9", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-debug.git", + "reference": "647be6c9d48dc2f3c2e2f33b9eba0a4ca78abde9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/647be6c9d48dc2f3c2e2f33b9eba0a4ca78abde9", + "reference": "647be6c9d48dc2f3c2e2f33b9eba0a4ca78abde9", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "~2.0.11", + "yiisoft/yii2-bootstrap": "~2.0.0" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\debug\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The debugger extension for the Yii framework", + "keywords": [ + "debug", + "debugger", + "yii2" + ], + "time": "2017-02-21T10:30:50+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6.0" + }, + "platform-dev": [] +} diff --git a/config/_aliases.php b/config/_aliases.php new file mode 100644 index 0000000..5c4d621 --- /dev/null +++ b/config/_aliases.php @@ -0,0 +1,7 @@ + env( 'APP_NAME' ), + 'version' => '1.0', + 'basePath' => dirname( __DIR__ ), + 'vendorPath' => __DIR__ . '/../vendor', + 'extensions' => require( __DIR__ . '/../vendor/yiisoft/extensions.php' ), + 'language' => 'ru-RU', + 'sourceLanguage' => 'ru', + 'timeZone' => 'Europe/Moscow', + + // Компоненты + 'components' => [ + + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'sqlite:' . realpath( __DIR__ . '/../runtime/data.db' ), +// 'dsn' => 'sqlsrv:Server=GALOSH\SQLEXPRESS;Database=test', +// 'username' => 'test', +// 'password' => '1234', + 'charset' => 'utf8', + ], + + 'formatter' => [ + 'class' => 'yii\i18n\Formatter', + 'nullDisplay' => '', + 'dateFormat' => 'php:d.m.Y', + 'thousandSeparator' => '', + 'decimalSeparator' => '.', + ], + + ], + + // Дополнительные параметры + 'params' => [ + 'adminEmail' => env( 'ADMIN_EMAIL' ), + ], + +]; \ No newline at end of file diff --git a/config/console.php b/config/console.php new file mode 100644 index 0000000..c743b0b --- /dev/null +++ b/config/console.php @@ -0,0 +1,37 @@ + 'console-app', + + 'components' => [ + + 'log' => [ + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + 'levels' => [ 'error', 'warning' ], + 'logVars' => [ ], + ], + ], + ], + + + // Кэш + 'cache' => [ + 'class' => 'yii\caching\FileCache', + 'cachePath' => '@runtime/cache', + ], + + ], + + 'bootstrap' => [ + 'log', + ], + + 'controllerNamespace' => 'app\\commands', +]; diff --git a/config/env.php b/config/env.php new file mode 100644 index 0000000..a6d7206 --- /dev/null +++ b/config/env.php @@ -0,0 +1,41 @@ +load(); + +// Проверяем наличие необходимых переменных +$env->required( 'APP_NAME' )->notEmpty(); +$env->required( 'APP_DOMAIN' )->notEmpty(); +$env->required( 'COOKIE_VALIDATION_KEY' )->notEmpty(); + +if ( !function_exists( 'env' ) ) { + // Вспомогательная функция + function env( $name, $default = null ) + { + $value = getenv( $name ); + + if ( $value === false ) { + return $default; + } + + switch ( strtolower( $value ) ) { + case 'true': + case '(true)': + return true; + + case 'false': + case '(false)': + return false; + } + + return $value; + } + +} + +defined( 'YII_DEBUG' ) or define( 'YII_DEBUG', env( 'YII_DEBUG' ), true ); +defined( 'YII_ENV' ) or define( 'YII_ENV', env( 'YII_ENV', 'dev' ) ); + diff --git a/config/web.php b/config/web.php new file mode 100644 index 0000000..d9f1f66 --- /dev/null +++ b/config/web.php @@ -0,0 +1,103 @@ + 'admin-app', + 'defaultRoute' => 'task/index', + 'homeUrl' => Yii::getAlias( '@adminUrl' ), + + 'components' => [ + + 'user' => [ + 'class' => 'yii\web\User', + 'identityClass' => 'app\models\User', + 'loginUrl' => [ 'auth/login' ], + 'enableAutoLogin' => true, + ], + + 'request' => [ + 'cookieValidationKey' => env( 'COOKIE_VALIDATION_KEY' ), + ], + + 'assetManager' => [ + 'class' => 'yii\web\AssetManager', + 'linkAssets' => env( 'LINK_ASSETS', true ), + 'appendTimestamp' => false, + 'bundles' => [ + 'dmstr\web\AdminLteAsset' => [ + 'skin' => 'skin-purple', + ], + ], + ], + + 'urlManager' => [ + 'class' => 'yii\web\UrlManager', + 'enablePrettyUrl' => true, + 'showScriptName' => false, + 'rules' => [ + [ 'pattern' => '/', 'route' => 'task/index' ], + + [ 'pattern' => '/login', 'route' => 'auth/login' ], + [ 'pattern' => '/logout', 'route' => 'auth/logout' ], + + [ 'pattern' => '/users', 'route' => 'user/index' ], + [ 'pattern' => '/statuses', 'route' => 'status/index' ], + [ 'pattern' => '/tasks', 'route' => 'task/index' ], + + [ 'pattern' => '/api/tasks', 'route' => 'api/task/index' ], + [ 'pattern' => '/api/task/', 'route' => 'api/task/view' ], + [ 'pattern' => '/api/task//change-status', 'route' => 'api/task/change-status' ], + ], + ], + + 'log' => [ + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + 'levels' => [ 'error', 'warning' ], + 'logFile' => '@runtime/logs/errors.log', + 'logVars' => [ ], + ], + [ + 'class' => 'yii\log\FileTarget', + 'levels' => [ 'info' ], + 'logFile' => '@runtime/logs/debug.log', + 'logVars' => [ ], + ], + ], + ], + + // Кэш + 'cache' => [ + 'class' => 'yii\caching\FileCache', + 'cachePath' => '@runtime/cache', + ], + + ], + + 'modules' => [ + 'api' => [ + 'class' => '\app\modules\api\Module', + ], + 'gridview' => [ + 'class' => '\kartik\grid\Module', + ], + 'debug' => [ + 'class' => 'yii\debug\Module', + 'allowedIPs' => [ '127.0.0.1', '::1' ], + ], + ], + + 'bootstrap' => [ + 'log', 'debug', + ], + + 'params' => [ + // кол-во дней, в течение которых сохраняется пароль при входе + 'loginDuration' => 15, + ], +]; diff --git a/controllers/AbstractController.php b/controllers/AbstractController.php new file mode 100644 index 0000000..5b51a7d --- /dev/null +++ b/controllers/AbstractController.php @@ -0,0 +1,90 @@ +getSession()->setFlash( 'success', $text ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param $text + */ + public function dangerFlash( $text ) + { + Yii::$app->getSession()->setFlash( 'danger', $text ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param $text + */ + public function infoFlash( $text ) + { + Yii::$app->getSession()->setFlash( 'info', $text ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function behaviors() + { + $behaviors = parent::behaviors(); + + $behaviors['accessBehavior'] = ArrayHelper::merge( [ 'class' => 'yii\filters\AccessControl' ], $this->accessBehavior() ); + + return $behaviors; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function accessBehavior() + { + $rules = $this->accessRules(); + return empty( $rules ) ? [] : [ 'rules' => $rules ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function accessRules() + { + return []; + } + +} diff --git a/controllers/AuthController.php b/controllers/AuthController.php new file mode 100644 index 0000000..5542191 --- /dev/null +++ b/controllers/AuthController.php @@ -0,0 +1,101 @@ +layout = 'main-login'; + + $data = Yii::$app->request->post(); + $model = new LoginForm(); + + if ( $model->load( $data ) && $model->validate() ) { + $duration = Yii::$app->params[ 'loginDuration' ] * 24 * 3600; + Yii::$app->user->login( $model->getUser(), $duration ); + + return $this->goBack(); + } + else { + return $this->render( 'login', [ + 'model' => $model, + ] ); + } + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return Response + */ + public function actionLogout() + { + Yii::$app->user->logout(); + + return $this->goHome(); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function accessRules() + { + return [ + [ + 'allow' => true, + 'roles' => [ '?' ], + 'actions' => [ + static::ACTION_LOGIN, + ], + ], + [ + 'allow' => true, + 'roles' => [ '@' ], + 'actions' => [ + static::ACTION_LOGOUT, + ], + ], + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function verbs() + { + return [ + static::ACTION_LOGIN => [ 'POST' ], + static::ACTION_LOGOUT => [ 'POST' ], + ]; + } + +} diff --git a/controllers/StatusController.php b/controllers/StatusController.php new file mode 100644 index 0000000..74d8682 --- /dev/null +++ b/controllers/StatusController.php @@ -0,0 +1,196 @@ + ActiveDataProvider::className(), + 'query' => $query, + ] ); + + return $this->render( 'index', [ + 'dataProvider' => $dataProvider, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $id + * + * @return string + * @throws NotFoundHttpException + */ + public function actionView( $id ) + { + /** @var Status $model */ + $model = Status::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Статус не найден' ); + } + + return $this->render( 'view', [ + 'model' => $model, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return string|Response + * @throws InvalidConfigException + */ + public function actionCreate() + { + /** @var Status $model */ + $model = Yii::createObject( Status::className() ); + + if ( $model->load( Yii::$app->request->post() ) && $model->validate() ) { + $model->save( false ); + $this->successFlash( 'Создан новый статус' ); + + return $this->redirect( [ 'view', 'id' => $model->id ] ); + } + + return $this->render( 'create', [ + 'model' => $model, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * @param int $id + * + * @return string|Response + * @throws NotFoundHttpException + */ + public function actionUpdate( $id ) + { + /** @var Status $model */ + $model = Status::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Статус не найден' ); + } + + if ( $model->load( Yii::$app->request->post() ) && $model->validate() ) { + $model->save( false ); + $this->successFlash( 'Статус успешно изменен' ); + + return $this->redirect( [ 'view', 'id' => $model->id ] ); + } + + return $this->render( 'update', [ + 'model' => $model, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $id + * + * @return Response + * @throws NotFoundHttpException + * @throws \Exception + * @throws \Throwable + */ + public function actionDelete( $id ) + { + + /** @var Status $model */ + $model = Status::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Статус не найден' ); + } + + $model->delete(); + + $this->successFlash( 'Статус удален' ); + + return $this->goBack(); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function accessRules() + { + return [ + [ + 'allow' => true, + 'roles' => [ '@' ], + 'actions' => [ + static::ACTION_INDEX, + static::ACTION_VIEW, + static::ACTION_CREATE, + static::ACTION_UPDATE, + static::ACTION_DELETE, + ], + ], + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function verbs() + { + return [ + static::ACTION_INDEX => [ 'GET' ], + static::ACTION_VIEW => [ 'GET' ], + static::ACTION_CREATE => [ 'GET', 'POST' ], + static::ACTION_UPDATE => [ 'GET', 'POST' ], + static::ACTION_DELETE => [ 'POST' ], + ]; + } + + +} diff --git a/controllers/TaskController.php b/controllers/TaskController.php new file mode 100644 index 0000000..d40657c --- /dev/null +++ b/controllers/TaskController.php @@ -0,0 +1,252 @@ + ActiveDataProvider::className(), + 'query' => $query, + ] ); + + $statuses = $this->getStatuses(); + + return $this->render( 'index', [ + 'dataProvider' => $dataProvider, + 'statuses' => $statuses, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $id + * + * @return string + * @throws NotFoundHttpException + */ + public function actionView( $id ) + { + /** @var Task $model */ + $model = Task::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Задача не найдена' ); + } + + return $this->render( 'view', [ + 'model' => $model, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return string|Response + * @throws InvalidConfigException + */ + public function actionCreate() + { + /** @var Task $model */ + $model = Yii::createObject( Task::className() ); + + if ( $model->load( Yii::$app->request->post() ) && $model->validate() ) { + $model->save( false ); + $this->successFlash( 'Создана новая задача' ); + + return $this->redirect( [ 'view', 'id' => $model->id ] ); + } + + $statuses = $this->getStatuses(); + + return $this->render( 'create', [ + 'model' => $model, + 'statuses' => $statuses, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * @param int $id + * + * @return string|Response + * @throws NotFoundHttpException + */ + public function actionUpdate( $id ) + { + /** @var Task $model */ + $model = Task::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Задача не найдена' ); + } + + if ( $model->load( Yii::$app->request->post() ) && $model->validate() ) { + $model->save( false ); + $this->successFlash( 'Задача успешно изменена' ); + + return $this->redirect( [ 'view', 'id' => $model->id ] ); + } + + $statuses = $this->getStatuses(); + + return $this->render( 'update', [ + 'model' => $model, + 'statuses' => $statuses, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $id + * + * @return array + * @throws NotFoundHttpException + */ + public function actionChangeStatus( $id ) + { + Yii::$app->response->format = Response::FORMAT_JSON; + + /** @var Task $model */ + $model = Task::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Задача не найдена' ); + } + + if ( $model->load( Yii::$app->request->post(), '' ) && $model->validate() ) { + $model->save( false ); + } + + return [ 'id' => $model->id, 'status_id' => $model->status_id ]; + } + + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $id + * + * @return Response + * @throws NotFoundHttpException + * @throws \Exception + * @throws \Throwable + */ + public function actionDelete( $id ) + { + + /** @var Task $model */ + $model = Task::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Задача не найдена' ); + } + + $model->delete(); + + $this->successFlash( 'Задача удалена' ); + + return $this->goBack(); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + protected function getStatuses() + { + $statuses = Status::find()->orderByTitle()->asArray()->all(); + + return ArrayHelper::map( $statuses, 'id', 'title' ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function accessRules() + { + return [ + [ + 'allow' => true, + 'roles' => [ '@' ], + 'actions' => [ + static::ACTION_INDEX, + static::ACTION_VIEW, + static::ACTION_CREATE, + static::ACTION_UPDATE, + static::ACTION_CHANGE_STATUS, + static::ACTION_DELETE, + ], + ], + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function verbs() + { + return [ + static::ACTION_INDEX => [ 'GET' ], + static::ACTION_VIEW => [ 'GET' ], + static::ACTION_CREATE => [ 'GET', 'POST' ], + static::ACTION_UPDATE => [ 'GET', 'POST' ], + static::ACTION_CHANGE_STATUS => [ 'POST' ], + static::ACTION_DELETE => [ 'POST' ], + ]; + } + + +} diff --git a/controllers/UserController.php b/controllers/UserController.php new file mode 100644 index 0000000..e6c8c4d --- /dev/null +++ b/controllers/UserController.php @@ -0,0 +1,196 @@ + ActiveDataProvider::className(), + 'query' => $query, + ] ); + + return $this->render( 'index', [ + 'dataProvider' => $dataProvider, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $id + * + * @return string + * @throws NotFoundHttpException + */ + public function actionView( $id ) + { + /** @var User $model */ + $model = User::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Пользователь не найден' ); + } + + return $this->render( 'view', [ + 'model' => $model, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return string|Response + * @throws InvalidConfigException + */ + public function actionCreate() + { + /** @var User $model */ + $model = Yii::createObject( User::className() ); + + if ( $model->load( Yii::$app->request->post() ) && $model->validate() ) { + $model->save( false ); + $this->successFlash( 'Создан новый пользователь' ); + + return $this->redirect( [ 'view', 'id' => $model->id ] ); + } + + return $this->render( 'create', [ + 'model' => $model, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * @param int $id + * + * @return string|Response + * @throws NotFoundHttpException + */ + public function actionUpdate( $id ) + { + /** @var User $model */ + $model = User::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Пользователь не найден' ); + } + + if ( $model->load( Yii::$app->request->post() ) && $model->validate() ) { + $model->save( false ); + $this->successFlash( 'Пользователь успешно изменен' ); + + return $this->redirect( [ 'view', 'id' => $model->id ] ); + } + + return $this->render( 'update', [ + 'model' => $model, + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $id + * + * @return Response + * @throws NotFoundHttpException + * @throws \Exception + * @throws \Throwable + */ + public function actionDelete( $id ) + { + + /** @var User $model */ + $model = User::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Пользователь не найден' ); + } + + $model->delete(); + + $this->successFlash( 'Пользователь удален' ); + + return $this->goBack(); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function accessRules() + { + return [ + [ + 'allow' => true, + 'roles' => [ '@' ], + 'actions' => [ + static::ACTION_INDEX, + static::ACTION_VIEW, + static::ACTION_CREATE, + static::ACTION_UPDATE, + static::ACTION_DELETE, + ], + ], + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function verbs() + { + return [ + static::ACTION_INDEX => [ 'GET' ], + static::ACTION_VIEW => [ 'GET' ], + static::ACTION_CREATE => [ 'GET', 'POST' ], + static::ACTION_UPDATE => [ 'GET', 'POST' ], + static::ACTION_DELETE => [ 'POST' ], + ]; + } + + +} diff --git a/helpers/DateTimeHelper.php b/helpers/DateTimeHelper.php new file mode 100644 index 0000000..1b2db1c --- /dev/null +++ b/helpers/DateTimeHelper.php @@ -0,0 +1,154 @@ +timeZone; + } + $datetime = new DateTime( $value, new DateTimeZone ( $timeZone ) ); + + return $datetime->getTimestamp(); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $value + * @param string $format + * @param string $timeZone + * + * @return string + */ + public static function timestampToDateTime( $value, $format = null, $timeZone = null ) + { + if ( $format === null ) { + $format = 'Y-m-d H:i:s'; + } + + if ( $timeZone === null ) { + $timeZone = Yii::$app->timeZone; + } + + $datetime = new DateTime( 'now', new DateTimeZone ( $timeZone ) ); + + return $datetime->setTimestamp( $value )->format( $format ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $value + * @param string $format + * @param string $timeZone + * + * @return string|null + */ + public static function normalizeDateTime( $value, $format = null, $timeZone = null ) + { + if ( $value === null ) return $value; + + $timestamp = static::dateTimeToTimestamp( $value, $timeZone ); + return static::timestampToDateTime( $timestamp, $format, $timeZone ); + } + + /** + * @author Марунин Алексей + * @since 1.3 + * + * @param string $date + * @param string $format + * @param string $timeZone + * + * @return string + */ + public static function dayBefore( $date, $format = null, $timeZone = null ) + { + if ( $date === null ) return $date; + + $timestamp = static::dateTimeToTimestamp( $date, $timeZone ); + $timestamp -= 24*3600; + + return static::timestampToDateTime( $timestamp, $format, $timeZone ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $date + * @param string $format + * @param string $timeZone + * + * @return string + */ + public static function dayAfter( $date, $format = null, $timeZone = null ) + { + if ( $date === null ) return $date; + + $timestamp = static::dateTimeToTimestamp( $date, $timeZone ); + $timestamp += 24*3600; + + return static::timestampToDateTime( $timestamp, $format, $timeZone ); + } + + /** + * Возвращает разницу (в часах) между двумя датами + * @author Марунин Алексей + * @since 1.0 + * + * @param string $startDate + * @param string $endDate + * + * @return int + */ + public static function hoursDiff( $startDate, $endDate ) + { + $startTimestamp = static::dateTimeToTimestamp( $startDate ); + $endTimestamp = static::dateTimeToTimestamp( $endDate ); + + return intval( ( $endTimestamp - $startTimestamp ) / 3600 ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $date1 + * @param string $date2 + * + * @return int + */ + public static function compareDates( $date1, $date2 ) + { + $timestamp1 = static::dateTimeToTimestamp( $date1 ); + $timestamp2 = static::dateTimeToTimestamp( $date2 ); + + return ( $timestamp1 - $timestamp2 ); + } +} diff --git a/helpers/Html.php b/helpers/Html.php new file mode 100644 index 0000000..2c3830f --- /dev/null +++ b/helpers/Html.php @@ -0,0 +1,127 @@ + 'btn btn-success' ] ); + + return ( $visible ? static::tag( 'span', $button ) : '' ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $title + * @param ActiveRecord $model + * @param string $url + * + * @return string + */ + public static function viewButton( $title, $model, $url = 'view' ) + { + return static::a( FA::i( FA::_FOLDER_OPEN ), [ $url, 'id' => $model->id ], [ + 'class' => 'btn btn-xs btn-default', + 'title' => $title, + 'aria-label' => $title, + 'data-pjax' => '0', + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $title + * @param ActiveRecord $model + * @param string $url + * + * @return string + */ + public static function updateButton( $title, $model, $url = 'update' ) + { + return static::a( FA::i( FA::_PENCIL ), [ $url, 'id' => $model->id ], [ + 'class' => 'btn btn-xs btn-primary', + 'title' => $title, + 'aria-label' => $title, + 'data-pjax' => '0', + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $confirm + * @param ActiveRecord $model + * @param string $url + * + * @return string + */ + public static function deleteButton( $confirm, $model, $url = 'delete' ) + { + return static::a( FA::i( FA::_TIMES ), [ $url, 'id' => $model->id ], [ + 'class' => 'btn btn-xs btn-danger', + 'data-confirm' => $confirm, + 'data-method' => 'post', + ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $icon + * @param array $options + * + * @return string + */ + public static function icon( $icon, $options = [ ] ) + { + return FA::i( $icon, $options ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $text + * @param array|string|null $url + * @param array $options + * + * @return string + */ + public static function external( $text, $url = null, $options = [ ] ) + { + $icon = ' ' . static::icon( 'external-link' ); + + return $text ? static::a( $text . $icon, $url, ArrayHelper::merge( [ 'class' => 'external-link', 'target' => '_blank' ], $options ) ) : ''; + } +} diff --git a/migrations/m170525_125816_init.php b/migrations/m170525_125816_init.php new file mode 100644 index 0000000..cd25d70 --- /dev/null +++ b/migrations/m170525_125816_init.php @@ -0,0 +1,102 @@ +createTable( '{{%users}}', [ + 'id' => $this->primaryKey( 11 ), + 'login' => $this->string( 32 )->notNull(), + 'first_name' => $this->string( 255 ), + 'middle_name' => $this->string( 255 ), + 'last_name' => $this->string( 255 ), + 'hashed_password' => $this->string( 60 )->notNull(), + 'auth_key' => $this->string( 40 )->notNull(), + 'created_at' => $this->integer( 11 ), + 'updated_at' => $this->integer( 11 ), + ] ); + + $this->insert( '{{%users}}', [ + 'login' => 'admin', + 'first_name' => 'Администратор', + 'hashed_password' => Yii::$app->getSecurity()->generatePasswordHash( 'admin' ), + 'auth_key' => Yii::$app->getSecurity()->generateRandomString( 40 ), + 'created_at' => $now, + 'updated_at' => $now, + ] ); + + + // Таблица статусов задач + $this->createTable( '{{%statuses}}', [ + 'id' => $this->primaryKey( 11 ), + 'title' => $this->string( 255 ), + 'order' => $this->integer( 4 ), + ] ); + + $this->insert( '{{%statuses}}', [ 'title' => 'Новая задача', 'order' => 0 ] ); + $this->insert( '{{%statuses}}', [ 'title' => 'Анализ задачи', 'order' => 1 ] ); + $this->insert( '{{%statuses}}', [ 'title' => 'Задача в разработке', 'order' => 2 ] ); + $this->insert( '{{%statuses}}', [ 'title' => 'Задача отложена', 'order' => 3 ] ); + $this->insert( '{{%statuses}}', [ 'title' => 'Задача тестируется', 'order' => 4 ] ); + $this->insert( '{{%statuses}}', [ 'title' => 'Задача принята заказчиком', 'order' => 5 ] ); + $this->insert( '{{%statuses}}', [ 'title' => 'Задача закрыта', 'order' => 6 ] ); + + + // Таблица задач + $this->createTable( '{{%tasks}}', [ + 'id' => $this->primaryKey( 11 ), + 'title' => $this->string( 255 )->notNull(), + 'desc' => $this->string( 4000 ), + 'due_date' => $this->date(), + 'status_id' => $this->date()->defaultValue( 1 ), + 'created_at' => $this->integer( 11 ), + 'updated_at' => $this->integer( 11 ), + 'created_by' => $this->integer( 11 ), + 'updated_by' => $this->integer( 11 ), + ] ); + + $this->insert( '{{%tasks}}', [ + 'title' => 'Тестовое задание', + 'desc' => 'Сдать тестовое задание', + 'due_date' => '2017-08-09', + 'created_at' => $now, + 'updated_at' => $now, + 'created_by' => 1, + 'updated_by' => 1, + ] ); + $this->insert( '{{%tasks}}', [ + 'title' => 'День программиста', + 'desc' => 'Поздравить всех коллег с днем программиста', + 'due_date' => '2017-09-13', + 'created_at' => $now, + 'updated_at' => $now, + 'created_by' => 1, + 'updated_by' => 1, + ] ); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable( '{{%tasks}}' ); + $this->dropTable( '{{%statuses}}' ); + $this->dropTable( '{{%users}}' ); + } + +} diff --git a/models/Status.php b/models/Status.php new file mode 100644 index 0000000..0225eca --- /dev/null +++ b/models/Status.php @@ -0,0 +1,90 @@ + 255 ], + [ 'order', 'integer' ], + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return TaskQuery + */ + public function getTasks() + { + return $this->hasMany( Task::className(), [ 'status_id' => 'id' ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function attributeLabels() + { + return [ + 'id' => 'ID', + 'title' => 'Название', + 'order' => 'Сортировка', + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return string + */ + public static function tableName() + { + return '{{%statuses}}'; + } +} diff --git a/models/Task.php b/models/Task.php new file mode 100644 index 0000000..7bf6425 --- /dev/null +++ b/models/Task.php @@ -0,0 +1,158 @@ + 'yii\behaviors\TimestampBehavior', + 'blameableBehavior' => 'yii\behaviors\BlameableBehavior', + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function rules() + { + return [ + [ 'title', 'required' ], + [ 'title', 'string', 'max' => 255 ], + [ 'desc', 'required' ], + [ 'desc', 'string', 'max' => 4000 ], + [ 'due_date', 'date', 'format' => 'php:Y-m-d' ], + [ 'status_id', 'integer' ], + [ 'status_id', 'exist', 'targetClass' => Status::className(), 'targetAttribute' => 'id' ], + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return StatusQuery + */ + public function getStatus() + { + return $this->hasOne( Status::className(), [ 'id' => 'status_id' ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return string + */ + public function getStatusTitle() + { + return $this->getStatus()->exists() ? $this->status->title : '<неизвестно>'; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return UserQuery + */ + public function getAuthor() + { + return $this->hasOne( User::className(), [ 'id' => 'created_by' ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return string + */ + public function getAuthorName() + { + return $this->getAuthor()->exists() ? $this->author->fullName : '<неизвестно>'; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function attributeLabels() + { + return [ + 'id' => 'ID', + 'title' => 'Название', + 'desc' => 'Описание', + 'due_date' => 'Срок выполнения', + 'created_at' => 'Создана', + 'updated_at' => 'Изменена', + 'created_by' => 'Автор', + 'authorName' => 'Автор', + 'status_id' => 'Статус', + 'statusTitle' => 'Статус', + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return string + */ + public static function tableName() + { + return '{{%tasks}}'; + } +} diff --git a/models/User.php b/models/User.php new file mode 100644 index 0000000..efb81c1 --- /dev/null +++ b/models/User.php @@ -0,0 +1,245 @@ + 'yii\behaviors\TimestampBehavior', + 'authKeyBehavior' => [ + 'class' => 'yii\behaviors\AttributeBehavior', + 'attributes' => [ ActiveRecord::EVENT_BEFORE_INSERT => 'auth_key' ], + 'value' => Yii::$app->getSecurity()->generateRandomString(), + ], + 'passwordHashBehavior' => [ + 'class' => 'yii\behaviors\AttributeBehavior', + 'attributes' => [ + ActiveRecord::EVENT_BEFORE_INSERT => 'hashed_password', + ActiveRecord::EVENT_BEFORE_UPDATE => 'hashed_password', + ], + 'value' => function( $event ) { + /** @var User $model */ + $model = $event->sender; + if ( $model->password ) { + return Yii::$app->getSecurity()->generatePasswordHash( $model->password ); + } + return $model->hashed_password; + }, + ], + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function rules() + { + return [ + [ 'login', 'required' ], + [ 'login', 'string', 'max' => 32 ], + [ 'login', 'unique' ], + [ 'first_name', 'string', 'max' => 255 ], + [ 'middle_name', 'string', 'max' => 255 ], + [ 'last_name', 'string', 'max' => 255 ], + [ 'password', 'string', 'min' => 4 ], + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return string + */ + public function getFullName() + { + $parts = [ ]; + if ( $this->first_name ) { + $parts[] = $this->first_name; + } + if ( $this->middle_name ) { + $parts[] = $this->middle_name; + } + if ( $this->last_name ) { + $parts[] = $this->last_name; + } + + return implode( ' ', $parts ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return TaskQuery + */ + public function getTasks() + { + return $this->hasMany( Task::className(), [ 'created_by' => 'id' ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'id' => 'ID', + 'login' => 'Логин', + 'password' => 'Пароль', + 'first_name' => 'Имя', + 'middle_name' => 'Отчество', + 'last_name' => 'Фамилия', + 'fullName' => 'ФИО', + 'created_at' => 'Создан', + 'updated_at' => 'Обновлен', + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @inheritdoc + */ + public static function findIdentity( $id ) + { + return static::find()->andWhereId( $id )->one(); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @inheritdoc + */ + public static function findIdentityByAccessToken( $token, $type = null ) + { + return static::find()->andWhereHash( $token )->one(); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @inheritdoc + */ + public function getId() + { + return $this->getPrimaryKey(); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @inheritdoc + */ + public function getAuthKey() + { + return $this->auth_key; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @inheritdoc + */ + public function validateAuthKey( $authKey ) + { + return ( $this->getAuthKey() === $authKey ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return string + */ + public static function generatePassword() + { + return Yii::$app->getSecurity()->generateRandomString( 8 ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $password + * + * @return bool + */ + public function validatePassword( $password ) + { + return Yii::$app->getSecurity()->validatePassword( $password, $this->hashed_password ); + } + + /** + * @author Марунин Алексей + * @since 1.1 + * + * @inheritdoc + */ + public static function tableName() + { + return '{{%users}}'; + } + +} diff --git a/models/form/LoginForm.php b/models/form/LoginForm.php new file mode 100644 index 0000000..eae09ea --- /dev/null +++ b/models/form/LoginForm.php @@ -0,0 +1,112 @@ +hasErrors() ) return; + + $user = $this->getUser(); + if ( !$user ) { + $this->addError( $attribute, 'Пользователь не найден' ); + } + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $attribute + */ + public function validatePassword( $attribute ) + { + if ( $this->hasErrors() ) return; + + $password = $this->$attribute; + if ( !$this->getUser()->validatePassword( $password ) ) { + $this->addError( $attribute, 'Неверный пароль' ); + } + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return User|null + */ + public function getUser() + { + if ( !$this->user ) { + $this->user = User::find()->andWhereLogin( $this->username )->one(); + } + + return $this->user; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'username' => 'Логин', + 'password' => 'Пароль', + 'rememberMe' => 'Запомнить', + ]; + } + +} diff --git a/models/query/StatusQuery.php b/models/query/StatusQuery.php new file mode 100644 index 0000000..c562d80 --- /dev/null +++ b/models/query/StatusQuery.php @@ -0,0 +1,30 @@ +orderBy( [ 'title' => $sort ] ); + } +} diff --git a/models/query/TaskQuery.php b/models/query/TaskQuery.php new file mode 100644 index 0000000..8a117f5 --- /dev/null +++ b/models/query/TaskQuery.php @@ -0,0 +1,19 @@ +andWhere( [ 'id' => $id ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $hash + * + * @return UserQuery + */ + public function andWhereHash( $hash ) + { + return $this->andWhere( [ 'hashed_password' => $hash ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param string $login + * + * @return UserQuery + */ + public function andWhereLogin( $login ) + { + return $this->andWhere( [ 'login' => $login ] ); + } +} diff --git a/modules/api/Module.php b/modules/api/Module.php new file mode 100644 index 0000000..6635190 --- /dev/null +++ b/modules/api/Module.php @@ -0,0 +1,17 @@ +response->format = Response::FORMAT_JSON; + + $query = Task::find(); + + /** @var ActiveDataProvider $dataProvider */ + $dataProvider = Yii::createObject( [ + 'class' => ActiveDataProvider::className(), + 'query' => $query, + ] ); + + return $dataProvider->getModels(); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $id + * + * @return string + * @throws NotFoundHttpException + */ + public function actionView( $id ) + { + Yii::$app->response->format = Response::FORMAT_JSON; + + /** @var Task $model */ + $model = Task::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Задача не найдена' ); + } + + return $model; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @param int $id + * + * @return array + * @throws NotFoundHttpException + */ + public function actionChangeStatus( $id ) + { + Yii::$app->response->format = Response::FORMAT_JSON; + + /** @var Task $model */ + $model = Task::findOne( $id ); + + if ( !$model ) { + throw new NotFoundHttpException( 'Задача не найдена' ); + } + + if ( $model->load( Yii::$app->request->post(), '' ) && $model->validate() ) { + $model->save( false ); + } + + return $model; + } + + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function accessRules() + { + return [ + [ + 'allow' => true, + 'roles' => [ '?' ], + 'actions' => [ + static::ACTION_INDEX, + static::ACTION_VIEW, + ], + ], + [ + 'allow' => true, + 'roles' => [ '@' ], + 'actions' => [ + static::ACTION_CHANGE_STATUS, + ], + ], + ]; + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function verbs() + { + return [ + static::ACTION_INDEX => [ 'GET' ], + static::ACTION_VIEW => [ 'GET' ], + static::ACTION_CHANGE_STATUS => [ 'POST' ], + ]; + } + +} diff --git a/modules/api/models/Status.php b/modules/api/models/Status.php new file mode 100644 index 0000000..4906b36 --- /dev/null +++ b/modules/api/models/Status.php @@ -0,0 +1,30 @@ +hasOne( Status::className(), [ 'id' => 'status_id' ] ); + } + + /** + * @author Марунин Алексей + * @since 1.0 + * + * @return array + */ + public function fields() + { + return [ + 'id', + 'desc', + 'due_date', + 'created_at', + 'updated_at', + 'status', + 'author' => 'authorName', + ]; + } +} diff --git a/requirements.php b/requirements.php new file mode 100644 index 0000000..03ab06d --- /dev/null +++ b/requirements.php @@ -0,0 +1,147 @@ +Error'; + echo '

The path to yii framework seems to be incorrect.

'; + echo '

You need to install Yii framework via composer or adjust the framework path in file ' . basename( __FILE__ ) . '.

'; + echo '

Please refer to the README on how to install Yii.

'; +} + +require_once( $frameworkPath . '/requirements/YiiRequirementChecker.php' ); +$requirementsChecker = new YiiRequirementChecker(); + +$gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.'; +$gdOK = $imagickOK = false; + +if ( extension_loaded( 'imagick' ) ) { + $imagick = new Imagick(); + $imagickFormats = $imagick->queryFormats( 'PNG' ); + if ( in_array( 'PNG', $imagickFormats ) ) { + $imagickOK = true; + } + else { + $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.'; + } +} + +if ( extension_loaded( 'gd' ) ) { + $gdInfo = gd_info(); + if ( !empty( $gdInfo['FreeType Support'] ) ) { + $gdOK = true; + } + else { + $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.'; + } +} + +/** + * Adjust requirements according to your application specifics. + */ +$requirements = [ + // Database : + [ + 'name' => 'PDO extension', + 'mandatory' => true, + 'condition' => extension_loaded( 'pdo' ), + 'by' => 'All DB-related classes', + ], + [ + 'name' => 'PDO SQLite extension', + 'mandatory' => true, + 'condition' => extension_loaded( 'pdo_sqlite' ), + 'by' => 'All DB-related classes', + 'memo' => 'Required for SQLite database.', + ], + [ + 'name' => 'PDO MySQL extension', + 'mandatory' => false, + 'condition' => extension_loaded( 'pdo_mysql' ), + 'by' => 'All DB-related classes', + 'memo' => 'Required for MySQL database.', + ], + [ + 'name' => 'PDO PostgreSQL extension', + 'mandatory' => false, + 'condition' => extension_loaded( 'pdo_pgsql' ), + 'by' => 'All DB-related classes', + 'memo' => 'Required for PostgreSQL database.', + ], + // Cache : + [ + 'name' => 'Memcache extension', + 'mandatory' => false, + 'condition' => extension_loaded( 'memcache' ) || extension_loaded( 'memcached' ), + 'by' => 'MemCache', + 'memo' => extension_loaded( 'memcached' ) ? 'To use memcached set MemCache::useMemcached to true.' : '', + ], + // CAPTCHA: + [ + 'name' => 'GD PHP extension with FreeType support', + 'mandatory' => false, + 'condition' => $gdOK, + 'by' => 'Captcha', + 'memo' => $gdMemo, + ], + [ + 'name' => 'ImageMagick PHP extension with PNG support', + 'mandatory' => false, + 'condition' => $imagickOK, + 'by' => 'Captcha', + 'memo' => $imagickMemo, + ], + // Internationalization: + [ + 'name' => 'Internationalization support', + 'mandatory' => true, + 'condition' => extension_loaded( 'intl' ), + 'by' => 'intl', + 'memo' => 'PHP-extension "intl" required', + ], + // PHP ini : + 'phpExposePhp' => [ + 'name' => 'Expose PHP', + 'mandatory' => false, + 'condition' => $requirementsChecker->checkPhpIniOff( "expose_php" ), + 'by' => 'Security reasons', + 'memo' => '"expose_php" should be disabled at php.ini', + ], + 'phpAllowUrlInclude' => [ + 'name' => 'PHP allow url include', + 'mandatory' => false, + 'condition' => $requirementsChecker->checkPhpIniOff( "allow_url_include" ), + 'by' => 'Security reasons', + 'memo' => '"allow_url_include" should be disabled at php.ini', + ], + 'phpSmtp' => [ + 'name' => 'PHP mail SMTP', + 'mandatory' => false, + 'condition' => strlen( ini_get( 'SMTP' ) ) > 0, + 'by' => 'Email sending', + 'memo' => 'PHP mail SMTP server required', + ], +]; + +// OPcache check +if ( !version_compare( phpversion(), '5.5', '>=' ) ) { + $requirements[] = [ + 'name' => 'APC extension', + 'mandatory' => false, + 'condition' => extension_loaded( 'apc' ), + 'by' => 'ApcCache', + ]; +} + +$requirementsChecker->checkYii()->check( $requirements )->render(); diff --git a/runtime/.gitignore b/runtime/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/views/auth/login.php b/views/auth/login.php new file mode 100644 index 0000000..2aec4b3 --- /dev/null +++ b/views/auth/login.php @@ -0,0 +1,38 @@ +title = 'Вход'; + +?> + diff --git a/views/layouts/_content.php b/views/layouts/_content.php new file mode 100644 index 0000000..0cbae5c --- /dev/null +++ b/views/layouts/_content.php @@ -0,0 +1,43 @@ +blocks, 'content-header', Html::encode( $this->title ) ); +$breadcrumbs = ArrayHelper::getValue( $this->params, 'breadcrumbs', [] ); +$isError = ( Yii::$app->controller->action->id == 'error' ); +?> +
+
+
+ +
+ + $breadcrumbs, ] ) ?> +
+
+

+
+
+ +
+ +
+
+
+ +
+ Тестовое задание для YLab, версия version ?> +
+ + diff --git a/views/layouts/_header.php b/views/layouts/_header.php new file mode 100644 index 0000000..e5f28ac --- /dev/null +++ b/views/layouts/_header.php @@ -0,0 +1,58 @@ + + +
+ + +
diff --git a/views/layouts/_left.php b/views/layouts/_left.php new file mode 100644 index 0000000..ef6cb1c --- /dev/null +++ b/views/layouts/_left.php @@ -0,0 +1,32 @@ + + + diff --git a/views/layouts/_right.php b/views/layouts/_right.php new file mode 100644 index 0000000..003116c --- /dev/null +++ b/views/layouts/_right.php @@ -0,0 +1,201 @@ + + + + +
\ No newline at end of file diff --git a/views/layouts/main-login.php b/views/layouts/main-login.php new file mode 100644 index 0000000..a1748e4 --- /dev/null +++ b/views/layouts/main-login.php @@ -0,0 +1,41 @@ +beginPage(); + +?> + + + + + + + <?= Html::encode( $this->title ) ?> + head() ?> + + + +beginBody() ?> + +
+
+
+ +
+
+
+ +endBody() ?> + + +endPage() ?> diff --git a/views/layouts/main.php b/views/layouts/main.php new file mode 100644 index 0000000..2bd6e10 --- /dev/null +++ b/views/layouts/main.php @@ -0,0 +1,63 @@ +assetManager->getPublishedUrl( '@vendor/almasaeed2010/adminlte/dist' ); + +/** @var User $user */ +$user = Yii::$app->user->identity; + +DashboardAsset::register( $this ); + +$this->beginPage(); +?> + + + + + + + + + <?= Html::encode( $this->title ) ?> + + head() ?> + + + +beginBody() ?> +
+ + render( + '_header.php', + [ 'directoryAsset' => $directoryAsset, 'user' => $user ] + ) ?> + + render( + '_left.php', + [ 'directoryAsset' => $directoryAsset, 'user' => $user ] + ) + ?> + + render( + '_content.php', + [ 'content' => $content, 'directoryAsset' => $directoryAsset, 'user' => $user ] + ) ?> + +
+ +endBody() ?> + + + + +endPage() ?> \ No newline at end of file diff --git a/views/status/_form.php b/views/status/_form.php new file mode 100644 index 0000000..2faf002 --- /dev/null +++ b/views/status/_form.php @@ -0,0 +1,23 @@ + + + $model, + 'mode' => DetailView::MODE_EDIT, + + 'attributes' => [ + 'title', + 'order', + ], +] ) ?> + diff --git a/views/status/create.php b/views/status/create.php new file mode 100644 index 0000000..95f0b02 --- /dev/null +++ b/views/status/create.php @@ -0,0 +1,23 @@ +title = 'Добавить статус'; +$this->params[ 'breadcrumbs' ][] = [ 'label' => 'Статусы', 'url' => [ '/statuses' ] ]; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + render( '_form', [ + 'model' => $model, + ] ) ?> + +
\ No newline at end of file diff --git a/views/status/index.php b/views/status/index.php new file mode 100644 index 0000000..7e9630f --- /dev/null +++ b/views/status/index.php @@ -0,0 +1,75 @@ +title = 'Статусы'; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + + + $dataProvider, + 'layout' => '{items}{summary}{pager}', + 'tableOptions' => [ + 'class' => 'table table-striped table-responsive table-hover table-bordered', + ], + 'columns' => [ + [ + 'class' => 'yii\grid\SerialColumn', + 'options' => [ + 'width' => '30px', + ], + ], + + [ + 'attribute' => 'title', + ], + + [ + 'attribute' => 'order', + 'hAlign' => GridView::ALIGN_CENTER, + 'options' => [ + 'width' => '30px', + ], + ], + + [ + 'class' => 'yii\grid\ActionColumn', + 'options' => [ + 'width' => '100px', + ], + 'buttons' => [ + 'view' => function ( $url, $model ) { + return Html::viewButton( 'Просмотр', $model ); + }, + 'update' => function ( $url, $model ) { + return Html::updateButton( 'Редактирование', $model ); + }, + 'delete' => function ( $url, $model ) { + return Html::deleteButton( 'Удалить данный статус?', $model ); + }, + ], + 'visibleButtons' => [ + 'view' => true, + 'update' => true, + 'delete' => true, + ], + ], + ], + ] ); ?> + +
+ + diff --git a/views/status/update.php b/views/status/update.php new file mode 100644 index 0000000..25a4dfd --- /dev/null +++ b/views/status/update.php @@ -0,0 +1,23 @@ +title = 'Изменить статус'; +$this->params[ 'breadcrumbs' ][] = [ 'label' => 'Статусы', 'url' => [ '/statuses' ] ]; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + render( '_form', [ + 'model' => $model, + ] ) ?> + +
\ No newline at end of file diff --git a/views/status/view.php b/views/status/view.php new file mode 100644 index 0000000..7481b62 --- /dev/null +++ b/views/status/view.php @@ -0,0 +1,34 @@ +title = 'Просмотр статуса'; +$this->params[ 'breadcrumbs' ][] = [ 'label' => 'Статусы', 'url' => [ '/statuses' ] ]; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + $model, + 'mode' => DetailView::MODE_VIEW, + + 'cancelOptions' => [ + 'url' => Url::to( [ 'index' ] ), + ], + 'attributes' => [ + 'title', + 'order', + ], + ] ) ?> + +
\ No newline at end of file diff --git a/views/task/_form.php b/views/task/_form.php new file mode 100644 index 0000000..95a6bcc --- /dev/null +++ b/views/task/_form.php @@ -0,0 +1,42 @@ + + + $model, + 'mode' => DetailView::MODE_EDIT, + + 'attributes' => [ + [ + 'attribute' => 'title', + ], + [ + 'attribute' => 'desc', + 'type' => DetailView::INPUT_TEXTAREA, + ], + [ + 'attribute' => 'due_date', + 'type' => DetailView::INPUT_WIDGET, + 'widgetOptions' => [ + 'class' => DatePicker::className(), + ], + ], + 'status_id' => [ + 'attribute' => 'status_id', + 'type' => DetailView::INPUT_DROPDOWN_LIST, + 'items' => $statuses, + ], + ], +] ) ?> + diff --git a/views/task/create.php b/views/task/create.php new file mode 100644 index 0000000..ad98a17 --- /dev/null +++ b/views/task/create.php @@ -0,0 +1,25 @@ +title = 'Добавить задачу'; +$this->params[ 'breadcrumbs' ][] = [ 'label' => 'Статусы', 'url' => [ '/statuses' ] ]; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + render( '_form', [ + 'model' => $model, + 'statuses' => $statuses, + ] ) ?> + +
\ No newline at end of file diff --git a/views/task/index.php b/views/task/index.php new file mode 100644 index 0000000..8e3925f --- /dev/null +++ b/views/task/index.php @@ -0,0 +1,110 @@ +title = 'Задачи'; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + + + $dataProvider, +// 'filterModel' => $filterModel, + 'layout' => '{items}{summary}{pager}', + 'tableOptions' => [ + 'class' => 'table table-striped table-responsive table-hover table-bordered', + ], + 'columns' => [ + [ + 'class' => 'yii\grid\SerialColumn', + 'options' => [ + 'width' => '30px', + ], + ], + + [ + 'attribute' => 'title', + ], + + [ + 'attribute' => 'due_date', + 'format' => 'date', + ], + + [ + 'attribute' => 'status_id', + 'value' => function ( $model, $key, $index, $column ) use ( $statuses ) { + return Html::activeDropDownList( $model, 'status_id', + $statuses, + [ + 'id' => 'task-' . $model->id . '-status', + 'onchange' => 'onChangeTaskStatus(' . $model->id . ')', + ] + + ); + }, + 'format' => 'raw', + 'filter' => $statuses, + ], + + [ + 'attribute' => 'created_at', + 'format' => 'datetime', + ], + [ + 'attribute' => 'updated_at', + 'format' => 'datetime', + ], + + [ + 'attribute' => 'authorName', + 'value' => function ( Task $model ) { + return Html::a( $model->authorName, [ 'user/view', 'id' => $model->created_by ] ); + }, + 'format' => 'raw', + ], + + [ + 'class' => 'yii\grid\ActionColumn', + 'options' => [ + 'width' => '150px', + ], + 'buttons' => [ + 'view' => function ( $url, $model ) { + return Html::viewButton( 'Просмотр', $model ); + }, + 'update' => function ( $url, $model ) { + return Html::updateButton( 'Редактирование', $model ); + }, + 'delete' => function ( $url, $model ) { + return Html::deleteButton( 'Удалить данную задачу?', $model ); + }, + ], + 'visibleButtons' => [ + 'view' => true, + 'update' => true, + 'delete' => true, + ], + ], + ], + ] ); ?> + +
+ diff --git a/views/task/update.php b/views/task/update.php new file mode 100644 index 0000000..dd2e851 --- /dev/null +++ b/views/task/update.php @@ -0,0 +1,23 @@ +title = 'Изменить задачу'; +$this->params[ 'breadcrumbs' ][] = [ 'label' => 'Задачи', 'url' => [ '/tasks' ] ]; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> +
+ + render( '_form', [ + 'model' => $model, + 'statuses' => $statuses, + ] ) ?> +
\ No newline at end of file diff --git a/views/task/view.php b/views/task/view.php new file mode 100644 index 0000000..1767bfe --- /dev/null +++ b/views/task/view.php @@ -0,0 +1,48 @@ +title = 'Просмотр задачи'; +$this->params[ 'breadcrumbs' ][] = [ 'label' => 'Задачи', 'url' => [ '/tasks' ] ]; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + $model, + 'mode' => DetailView::MODE_VIEW, + + 'cancelOptions' => [ + 'url' => Url::to( [ 'index' ] ), + ], + 'attributes' => [ + 'title', + 'desc', + [ + 'attribute' => 'due_date', + 'format' => 'date', + ], + 'statusTitle', + 'authorName', + [ + 'attribute' => 'created_at', + 'format' => 'datetime', + ], + [ + 'attribute' => 'updated_at', + 'format' => 'datetime', + ], + ], + ] ) ?> + +
\ No newline at end of file diff --git a/views/user/_form.php b/views/user/_form.php new file mode 100644 index 0000000..58a22c8 --- /dev/null +++ b/views/user/_form.php @@ -0,0 +1,32 @@ + + + $model, + 'mode' => DetailView::MODE_EDIT, + + 'attributes' => [ + [ + 'displayOnly' => !$model->isNewRecord, + 'attribute' => 'login', + ], + 'first_name', + 'middle_name', + 'last_name', + [ + 'type' => DetailView::INPUT_PASSWORD, + 'attribute' => 'password', + ], + ], +] ) ?> + diff --git a/views/user/create.php b/views/user/create.php new file mode 100644 index 0000000..e40cf23 --- /dev/null +++ b/views/user/create.php @@ -0,0 +1,23 @@ +title = 'Добавить пользователя'; +$this->params[ 'breadcrumbs' ][] = [ 'label' => 'Пользователи', 'url' => [ '/users' ] ]; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + render( '_form', [ + 'model' => $model, + ] ) ?> + +
\ No newline at end of file diff --git a/views/user/index.php b/views/user/index.php new file mode 100644 index 0000000..6dba112 --- /dev/null +++ b/views/user/index.php @@ -0,0 +1,70 @@ +title = 'Пользователи'; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + + + $dataProvider, + 'layout' => '{items}{summary}{pager}', + 'tableOptions' => [ + 'class' => 'table table-striped table-responsive table-hover table-bordered', + ], + 'columns' => [ + [ + 'class' => 'yii\grid\SerialColumn', + 'options' => [ + 'width' => '30px', + ], + ], + + [ + 'attribute' => 'login', + ], + [ + 'attribute' => 'fullName', + ], + + [ + 'class' => 'yii\grid\ActionColumn', + 'options' => [ + 'width' => '100px', + ], + 'buttons' => [ + 'view' => function ( $url, $model ) { + return Html::viewButton( 'Просмотр', $model ); + }, + 'update' => function ( $url, $model ) { + return Html::updateButton( 'Редактирование', $model ); + }, + 'delete' => function ( $url, $model ) { + return Html::deleteButton( 'Удалить данного пользователя?', $model ); + }, + ], + 'visibleButtons' => [ + 'view' => true, + 'update' => true, + 'delete' => true, + ], + ], + ], + ] ); ?> + +
+ + diff --git a/views/user/update.php b/views/user/update.php new file mode 100644 index 0000000..ac95bf5 --- /dev/null +++ b/views/user/update.php @@ -0,0 +1,23 @@ +title = 'Изменить пользователя'; +$this->params[ 'breadcrumbs' ][] = [ 'label' => 'Пользователи', 'url' => [ '/users' ] ]; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + render( '_form', [ + 'model' => $model, + ] ) ?> + +
\ No newline at end of file diff --git a/views/user/view.php b/views/user/view.php new file mode 100644 index 0000000..56c904f --- /dev/null +++ b/views/user/view.php @@ -0,0 +1,42 @@ +title = 'Просмотр пользователя'; +$this->params[ 'breadcrumbs' ][] = [ 'label' => 'Пользователи', 'url' => [ '/users' ] ]; +$this->params[ 'breadcrumbs' ][] = $this->title; + +?> + +
+ + $model, + 'mode' => DetailView::MODE_VIEW, + + 'cancelOptions' => [ + 'url' => Url::to( [ 'index' ] ), + ], + 'attributes' => [ + 'fullName', + 'login', + [ + 'attribute' => 'created_at', + 'format' => 'datetime', + ], + [ + 'attribute' => 'updated_at', + 'format' => 'datetime', + ], + ], + ] ) ?> + +
\ No newline at end of file diff --git a/web/.htaccess.example b/web/.htaccess.example new file mode 100644 index 0000000..182e842 --- /dev/null +++ b/web/.htaccess.example @@ -0,0 +1,6 @@ +# Copy this file to .htaccess + +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . index.php \ No newline at end of file diff --git a/web/assets/.gitignore b/web/assets/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/web/assets/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..05524718bc5e0af710cdb604b3046b3018a34627 GIT binary patch literal 938 zcmV;b16BNqP)pF8FWQhbW?9;ba!ELWdLwtX>N2bZe?^J zG%heMF*ZbLuk`=`11CvDK~z{r#g$)3Q&AkpZv{qV5cLpbkSV>au;xn`7G)2nOH7S2 z|1@&Cxh$NL>9%qjYxEFUqmZI3D1sgWv&2$@tmq+y>`BdtQY4i!Lrm1%uiv@Xd-v!3 z99@mu!)Lqa`}_XB=iGD8-2?#muaKgakfN55qLz^2eB2tF0h`?iCz9WRWot87bWbGW z+u&qYFO+-HMf8P(6vJWBxB4dz!J5*A7Rh(^2fO6^Lm@>O=Qn_YosD4B-NvD$u^pf$ zx9FO{`I*^3sOkpDU*iF5(I;^n4pL;;`!FQx3gkt)L`-{IP?l=uxE5{!#k*cZ9TuJ6 z0gkP^r1TXgJyrTBNRfpYH+Yp08O!kxSO(#gbu?rFO<>2nc2D{sKc=4RD@uJS_EC_c zDW#nYk*R1$2YMQQ&WPU(`FYRVx6%c+Ymz4vPLMGl>g0l|pHE?l_REx1j0W}OM%I8$ zJVLZTXkZ_~&wz2+eSq9mwPKL#)pSrT93{44X#r7}!7+C*84lvj2pVK3zk#$B7r|&A z#G#})z5>|leaiT6DK6*(51t8jFzuL;75@kf%g#gbRTKhz%7wijAazL%7zCF@#0AL9 z>BW-Ytd^<)fXvPJz_7Gd$<5L|z>?piTo|oG{>cNddez^(%)zJ|WM=u?L1rlCwAFQ_ zxu$R=0eXUoTc)14(tNGm&PUAswkMjkSa~hl-zd2nkCdU+%HscjhV+e=w zFLV>GFdDaJcSaXjUI0|#PjR5>UvOp!AP1kQKJKA7-*$Z?%D8|;d>%}okee4nS;q{S z&2E_0g)R81P==TYw`f=_o}NiEpHbL~hFCltRB>DMP3)&b)o^EEg3M=Rc+kMMf+{BH z@sA)RY#}@w9>=_DE}yY4|04)7_>)T&_b1%NemYbQog-rqE1VExh761SM literal 0 HcmV?d00001 diff --git a/web/index.php b/web/index.php new file mode 100644 index 0000000..c9397f6 --- /dev/null +++ b/web/index.php @@ -0,0 +1,22 @@ +run(); diff --git a/widgets/DatePicker.php b/widgets/DatePicker.php new file mode 100644 index 0000000..924053f --- /dev/null +++ b/widgets/DatePicker.php @@ -0,0 +1,65 @@ +type != static::TYPE_RANGE ) { + $this->layout = '{picker}{input}{remove}'; + } + $this->buttonOptions = ArrayHelper::merge( [ + 'label' => Html::icon( 'calendar' ), + ], $this->buttonOptions ); + + $this->pluginOptions = ArrayHelper::merge( [ + 'format' => 'php:Y-m-d', + 'autoclose' => true, + 'todayHighlight' => true, + ], $this->pluginOptions ); + + parent::init(); + } + + protected function renderAddon( &$options, $type = 'picker' ) + { + if ( $options === false ) { + return ''; + } + if ( is_string( $options ) ) { + return $options; + } + $icon = ( $type === 'picker' ) ? 'calendar' : 'remove'; + Html::addCssClass( $options, 'input-group-addon kv-date-' . $icon ); + $icon = Html::icon( ArrayHelper::remove( $options, 'icon', $icon ) ); + $title = ArrayHelper::getValue( $options, 'title', '' ); + if ( $title !== false && empty( $title ) ) { + $options[ 'title' ] = ( $type === 'picker' ) ? Yii::t( 'kvdate', 'Select date' ) : + Yii::t( 'kvdate', 'Clear field' ); + } + + return Html::tag( 'span', $icon, $options ); + } +} diff --git a/widgets/DetailView.php b/widgets/DetailView.php new file mode 100644 index 0000000..52514f8 --- /dev/null +++ b/widgets/DetailView.php @@ -0,0 +1,134 @@ +{buttons}'; + + /** @var string */ + public $buttons1 = '{update} {cancel}'; + public $buttons2 = '{save} {cancel}'; + + public $cancelOptions = [ ]; + + /** + * @author Марунин Алексей + * @since 1.0 + */ + public function init() + { + $this->panel = ArrayHelper::merge([ + 'type' => self::TYPE_ACTIVE, + 'heading' => $this->headingTitle, + 'headingOptions' => [ + 'template' => '{title}', + ], + ], $this->panel ); + + $this->buttonContainer = [ + 'class' => '', + ]; + + $isNewRecord = ( $this->model instanceof ActiveRecord ? $this->model->isNewRecord : false ); + $this->updateOptions = ArrayHelper::merge( [ + 'label' => FA::i( 'gears' ) . ' Изменить', + 'url' => Url::to( [ 'update', 'id' => $this->model->id ] ), + 'class' => 'btn btn-primary', + ], $this->updateOptions ); + $this->saveOptions = ArrayHelper::merge( [ + 'label' => FA::i( 'check' ) . ' ' . ( $isNewRecord ? 'Создать' : 'Сохранить' ), + 'class' => 'btn btn-success', + ], $this->saveOptions ); + $this->cancelOptions = ArrayHelper::merge( [ + 'label' => FA::i( 'reply' ) . ' Отменить', + 'title' => 'Отменить', + 'url' => Url::previous(), + 'class' => 'btn btn-default', + ], $this->cancelOptions ); + + parent::init(); + } + + protected function renderButtons( $mode = 1 ) + { + $buttons = "buttons{$mode}"; + + return strtr( + $this->$buttons, + [ + '{view}' => $this->renderButton( 'view' ), + '{update}' => $this->renderButton( 'update' ), + '{delete}' => $this->renderButton( 'delete' ), + '{save}' => $this->renderButton( 'save' ), + '{reset}' => $this->renderButton( 'reset' ), + '{cancel}' => $this->renderButton( 'cancel' ), + ] + ); + } + + /** + * Renders a button + * + * @param string $type the button type + * + * @return string + */ + protected function renderButton( $type ) + { + if ( !$this->enableEditMode ) { + return ''; + } + switch ( $type ) { + case 'update': + return $this->getCustomButton( 'update', 'gear', 'Изменить' ); + case 'cancel': + return $this->getCustomButton( 'cancel', 'replay', 'Отмена' ); + default: + return parent::renderButton( $type ); + } + } + + protected function getCustomButton( $type, $icon, $title ) + { + $buttonOptions = $type . 'Options'; + $options = $this->$buttonOptions; + $label = ArrayHelper::remove( $options, 'label', FA::i( $icon ) ); + if ( empty( $options[ 'class' ] ) ) { + $options[ 'class' ] = 'kv-action-btn'; + } + Html::addCssClass( $options, 'kv-btn-' . $type ); + $options = ArrayHelper::merge( [ 'title' => $title ], $options ); + if ( $this->tooltips ) { + $options[ 'data-toggle' ] = 'tooltip'; + $options[ 'data-container' ] = 'body'; + } + $url = ArrayHelper::remove( $options, 'url', '#' ); + + return Html::a( $label, $url, $options ); + } + + +} + diff --git a/widgets/GridView.php b/widgets/GridView.php new file mode 100644 index 0000000..fea8f11 --- /dev/null +++ b/widgets/GridView.php @@ -0,0 +1,49 @@ +exportConfig = ArrayHelper::merge( [ self::HTML => [], ], $this->exportConfig ); + + return parent::init(); + } +} diff --git a/yii b/yii new file mode 100644 index 0000000..2d5b62a --- /dev/null +++ b/yii @@ -0,0 +1,26 @@ +#!/usr/bin/env php +run() +); +