From a83704822d340f1b8b9a049b889046ee095f6cc8 Mon Sep 17 00:00:00 2001
From: Karnovsky A
Date: Sat, 19 Mar 2016 12:28:52 +0200
Subject: [PATCH] init project
---
.bowerrc | 3 +++
.gitignore | 40 ++++++++++++++++++++++++++++++++++++++++
.htaccess | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
LICENSE.md | 32 ++++++++++++++++++++++++++++++++
README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/config/.gitignore | 2 ++
backend/config/bootstrap.php | 1 +
backend/config/main.php | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/config/params.php | 4 ++++
backend/controllers/BlogController.php | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/controllers/SiteController.php | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/models/.gitkeep | 1 +
backend/runtime/.gitignore | 2 ++
backend/views/blog/_form.php | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/views/blog/_search.php | 43 +++++++++++++++++++++++++++++++++++++++++++
backend/views/blog/create.php | 21 +++++++++++++++++++++
backend/views/blog/index.php | 40 ++++++++++++++++++++++++++++++++++++++++
backend/views/blog/update.php | 21 +++++++++++++++++++++
backend/views/blog/view.php | 43 +++++++++++++++++++++++++++++++++++++++++++
backend/views/layouts/admin.php | 35 +++++++++++++++++++++++++++++++++++
backend/views/layouts/control-sidebar.php | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/views/layouts/footer.php | 7 +++++++
backend/views/layouts/header.php | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/views/layouts/main-sidebar.php | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/views/layouts/main-sidebar1.php | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/views/layouts/main.php | 34 ++++++++++++++++++++++++++++++++++
backend/views/layouts/none.php | 9 +++++++++
backend/views/site/error.php | 27 +++++++++++++++++++++++++++
backend/views/site/index.php | 0
backend/views/site/login.php | 35 +++++++++++++++++++++++++++++++++++
backend/web/.gitignore | 2 ++
backend/web/assets/.gitignore | 2 ++
backend/web/css/site.css | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
backend/web/favicon.ico | Bin 0 -> 318 bytes
backend/web/js/fieldWidget.js | 24 ++++++++++++++++++++++++
backend/web/robots.txt | 2 ++
common/behaviors/ShowImage.php | 43 +++++++++++++++++++++++++++++++++++++++++++
common/behaviors/Slug.php | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/components/LangRequest.php | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/components/LangUrlManager.php | 37 +++++++++++++++++++++++++++++++++++++
common/components/Request.php | 40 ++++++++++++++++++++++++++++++++++++++++
common/components/artboxtree/ArtboxTreeBehavior.php | 370 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/components/artboxtree/ArtboxTreeHelper.php | 30 ++++++++++++++++++++++++++++++
common/components/artboxtree/ArtboxTreeQueryTrait.php | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/components/artboxtree/ArtboxTreeWidget.php | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/components/artboxtree/treegrid/TreeGridColumn.php | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/components/artboxtree/treegrid/TreeGridWidget.php | 277 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/components/artboxtree/treelist/TreeListWidget.php | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/components/artboxtree/treemenu/TreeMenuWidget.php | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/config/.gitignore | 2 ++
common/config/bootstrap.php | 6 ++++++
common/config/main.php | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/config/params.php | 6 ++++++
common/mail/layouts/html.php | 22 ++++++++++++++++++++++
common/mail/layouts/text.php | 12 ++++++++++++
common/mail/passwordResetToken-html.php | 15 +++++++++++++++
common/mail/passwordResetToken-text.php | 12 ++++++++++++
common/models/Blog.php | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/models/BlogSearch.php | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/models/Fields.php | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/models/LoginForm.php | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/models/User.php | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/Module.php | 15 +++++++++++++++
common/modules/blog/behaviors/Autocomplete.php | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/config.php | 9 +++++++++
common/modules/blog/controllers/AjaxController.php | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/controllers/ArticleController.php | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/controllers/CategoryController.php | 219 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/controllers/DefaultController.php | 12 ++++++++++++
common/modules/blog/controllers/MediaController.php | 45 +++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/controllers/TestController.php | 17 +++++++++++++++++
common/modules/blog/models/Article.php | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/models/ArticleCategory.php | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/models/ArticleCategoryLang.php | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/models/ArticleCategoryMedia.php | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/models/ArticleLang.php | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/models/ArticleMedia.php | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/models/ArticleToCategory.php | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/views/ajax/_article_form.php | 38 ++++++++++++++++++++++++++++++++++++++
common/modules/blog/views/ajax/_article_form_test.php | 38 ++++++++++++++++++++++++++++++++++++++
common/modules/blog/views/ajax/_article_media_form.php | 17 +++++++++++++++++
common/modules/blog/views/ajax/_category_form.php | 33 +++++++++++++++++++++++++++++++++
common/modules/blog/views/article/_form.php | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/views/article/create.php | 19 +++++++++++++++++++
common/modules/blog/views/article/index.php | 32 ++++++++++++++++++++++++++++++++
common/modules/blog/views/article/update.php | 18 ++++++++++++++++++
common/modules/blog/views/category/_form.php | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/blog/views/category/create.php | 19 +++++++++++++++++++
common/modules/blog/views/category/index.php | 26 ++++++++++++++++++++++++++
common/modules/blog/views/category/update.php | 18 ++++++++++++++++++
common/modules/blog/views/default/index.php | 7 +++++++
common/modules/blog/views/media/index.php | 37 +++++++++++++++++++++++++++++++++++++
common/modules/blog/views/test/index.php | 11 +++++++++++
common/modules/comment/Controller.php | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/Module.php | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/Permissions.php | 12 ++++++++++++
common/modules/comment/assets/CommentAsset.php | 21 +++++++++++++++++++++
common/modules/comment/commands/RbacController.php | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/interfaces/CommentInterface.php | 11 +++++++++++
common/modules/comment/models/Comment.php | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/models/CommentProject.php | 328 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/models/Rating.php | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/rbac/ArtboxCommentCreateRule.php | 17 +++++++++++++++++
common/modules/comment/rbac/ArtboxCommentDeleteOwnRule.php | 17 +++++++++++++++++
common/modules/comment/rbac/ArtboxCommentDeleteRule.php | 17 +++++++++++++++++
common/modules/comment/rbac/ArtboxCommentUpdateOwnRule.php | 17 +++++++++++++++++
common/modules/comment/rbac/ArtboxCommentUpdateRule.php | 17 +++++++++++++++++
common/modules/comment/resources/comment.css | 19 +++++++++++++++++++
common/modules/comment/resources/comment.js | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/resources/delete-ico.png | Bin 0 -> 368 bytes
common/modules/comment/views/comment_form.php | 38 ++++++++++++++++++++++++++++++++++++++
common/modules/comment/widgets/CommentWidget.php | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/widgets/views/_project_comment_view.php | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/widgets/views/form-comment.php | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/widgets/views/form-project-comment.php | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/comment/widgets/views/list-comment.php | 12 ++++++++++++
common/modules/comment/widgets/views/list-project-comment.php | 22 ++++++++++++++++++++++
common/modules/comment/widgets/views/project_comment_view.php | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/FileUploadAsset.php | 43 +++++++++++++++++++++++++++++++++++++++++++
common/modules/file/Module.php | 15 +++++++++++++++
common/modules/file/assets/css/fileupload/style.css | 7 +++++++
common/modules/file/assets/css/jquery.fileupload-noscript.css | 22 ++++++++++++++++++++++
common/modules/file/assets/css/jquery.fileupload-ui-noscript.css | 17 +++++++++++++++++
common/modules/file/assets/css/jquery.fileupload-ui.css | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/css/jquery.fileupload.css | 37 +++++++++++++++++++++++++++++++++++++
common/modules/file/assets/img/loading.gif | Bin 0 -> 3897 bytes
common/modules/file/assets/img/progressbar.gif | Bin 0 -> 3323 bytes
common/modules/file/assets/js/cors/jquery.postmessage-transport.js | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/cors/jquery.xdr-transport.js | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.fileupload-angular.js | 425 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.fileupload-audio.js | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.fileupload-image.js | 321 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.fileupload-jquery-ui.js | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.fileupload-process.js | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.fileupload-ui.js | 710 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.fileupload-validate.js | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.fileupload-video.js | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.fileupload.js | 1477 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/jquery.iframe-transport.js | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/assets/js/vendor/jquery.ui.widget.js | 572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/config.php | 3 +++
common/modules/file/controllers/UploaderController.php | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/models/ImageSizerForm.php | 38 ++++++++++++++++++++++++++++++++++++++
common/modules/file/views/_gallery_item.php | 9 +++++++++
common/modules/file/views/_one_item.php | 17 +++++++++++++++++
common/modules/file/widgets/ImageUploader.php | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/file/widgets/views/image_sizer.php | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/product/Module.php | 24 ++++++++++++++++++++++++
common/modules/product/config.php | 8 ++++++++
common/modules/product/controllers/DefaultController.php | 20 ++++++++++++++++++++
common/modules/product/controllers/ManageController.php | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/product/helpers/ProductHelper.php | 24 ++++++++++++++++++++++++
common/modules/product/models/Product.php | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/product/models/ProductCategory.php | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/product/models/ProductQuery.php | 34 ++++++++++++++++++++++++++++++++++
common/modules/product/models/ProductSearch.php | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/product/views/default/index.php | 12 ++++++++++++
common/modules/product/views/manage/_form.php | 43 +++++++++++++++++++++++++++++++++++++++++++
common/modules/product/views/manage/_search.php | 31 +++++++++++++++++++++++++++++++
common/modules/product/views/manage/create.php | 21 +++++++++++++++++++++
common/modules/product/views/manage/index.php | 34 ++++++++++++++++++++++++++++++++++
common/modules/product/views/manage/update.php | 23 +++++++++++++++++++++++
common/modules/product/views/manage/view.php | 37 +++++++++++++++++++++++++++++++++++++
common/modules/relation/Module.php | 40 ++++++++++++++++++++++++++++++++++++++++
common/modules/relation/controllers/DefaultController.php | 20 ++++++++++++++++++++
common/modules/relation/controllers/ManageController.php | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/relation/models/Relation.php | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/relation/models/RelationQuery.php | 35 +++++++++++++++++++++++++++++++++++
common/modules/relation/relationBehavior.php | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/relation/relationHelper.php | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/relation/relationObject.php | 29 +++++++++++++++++++++++++++++
common/modules/relation/relationQueryTrait.php | 27 +++++++++++++++++++++++++++
common/modules/relation/views/default/index.php | 12 ++++++++++++
common/modules/relation/views/manage/_form.php | 36 ++++++++++++++++++++++++++++++++++++
common/modules/relation/views/manage/create.php | 25 +++++++++++++++++++++++++
common/modules/relation/views/manage/pars.php | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/relation/views/manage/relations.php | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/relation/views/manage/update.php | 25 +++++++++++++++++++++++++
common/modules/rubrication/Module.php | 28 ++++++++++++++++++++++++++++
common/modules/rubrication/controllers/DefaultController.php | 20 ++++++++++++++++++++
common/modules/rubrication/controllers/TaxGroupController.php | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/controllers/TaxOptionController.php | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/helpers/RubricationHelper.php | 43 +++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/models/TaxGroup.php | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/models/TaxGroupToGroup.php | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/models/TaxOption.php | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/models/TaxOptionQuery.php | 37 +++++++++++++++++++++++++++++++++++++
common/modules/rubrication/models/TaxOptionRelation.php | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/models/TaxOptionSearch.php | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/models/TaxValueInt.php | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/models/TaxValueLink.php | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/models/TaxValueString.php | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/views/default/index.php | 12 ++++++++++++
common/modules/rubrication/views/tax-group/_form.php | 34 ++++++++++++++++++++++++++++++++++
common/modules/rubrication/views/tax-group/create.php | 21 +++++++++++++++++++++
common/modules/rubrication/views/tax-group/index.php | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/views/tax-group/relations.php | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/views/tax-group/update.php | 23 +++++++++++++++++++++++
common/modules/rubrication/views/tax-group/view.php | 42 ++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/views/tax-option/_form.php | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/views/tax-option/_search.php | 37 +++++++++++++++++++++++++++++++++++++
common/modules/rubrication/views/tax-option/create.php | 25 +++++++++++++++++++++++++
common/modules/rubrication/views/tax-option/index.php | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/modules/rubrication/views/tax-option/update.php | 26 ++++++++++++++++++++++++++
common/modules/rubrication/views/tax-option/value/_fields_int.php | 1 +
common/modules/rubrication/views/tax-option/value/_fields_link.php | 2 ++
common/modules/rubrication/views/tax-option/value/_fields_string.php | 1 +
common/modules/rubrication/views/tax-option/view.php | 46 ++++++++++++++++++++++++++++++++++++++++++++++
common/widgets/Alert.php | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/widgets/FieldEditor.php | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
common/widgets/views/education_field.php | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
composer.json | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
composer.lock | 1969 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
console/config/.gitignore | 2 ++
console/config/bootstrap.php | 1 +
console/config/main.php | 25 +++++++++++++++++++++++++
console/config/params.php | 4 ++++
console/controllers/.gitkeep | 0
console/migrations/m130524_201442_init.php | 33 +++++++++++++++++++++++++++++++++
console/migrations/m160126_071717_rubrication.php | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
console/migrations/m160128_101543_fields.php | 29 +++++++++++++++++++++++++++++
console/migrations/m160208_111900_blog.php | 30 ++++++++++++++++++++++++++++++
console/migrations/m160304_054017_realtion.php | 44 ++++++++++++++++++++++++++++++++++++++++++++
console/migrations/m160304_065108_product.php | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
console/models/.gitkeep | 1 +
console/runtime/.gitignore | 2 ++
environments/dev/backend/config/main-local.php | 25 +++++++++++++++++++++++++
environments/dev/backend/config/params-local.php | 3 +++
environments/dev/backend/web/index-test.php | 19 +++++++++++++++++++
environments/dev/backend/web/index.php | 18 ++++++++++++++++++
environments/dev/common/config/main-local.php | 20 ++++++++++++++++++++
environments/dev/common/config/params-local.php | 3 +++
environments/dev/console/config/main-local.php | 7 +++++++
environments/dev/console/config/params-local.php | 3 +++
environments/dev/frontend/config/main-local.php | 24 ++++++++++++++++++++++++
environments/dev/frontend/config/params-local.php | 3 +++
environments/dev/frontend/web/index-test.php | 18 ++++++++++++++++++
environments/dev/frontend/web/index.php | 18 ++++++++++++++++++
environments/dev/yii | 28 ++++++++++++++++++++++++++++
environments/index.php | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
environments/prod/backend/config/main-local.php | 9 +++++++++
environments/prod/backend/config/params-local.php | 3 +++
environments/prod/backend/web/index.php | 18 ++++++++++++++++++
environments/prod/common/config/main-local.php | 16 ++++++++++++++++
environments/prod/common/config/params-local.php | 3 +++
environments/prod/console/config/main-local.php | 3 +++
environments/prod/console/config/params-local.php | 3 +++
environments/prod/frontend/config/main-local.php | 9 +++++++++
environments/prod/frontend/config/params-local.php | 3 +++
environments/prod/frontend/web/index.php | 18 ++++++++++++++++++
environments/prod/yii | 28 ++++++++++++++++++++++++++++
frontend/config/.gitignore | 2 ++
frontend/config/bootstrap.php | 1 +
frontend/config/main.php | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
frontend/config/params.php | 4 ++++
frontend/controllers/SiteController.php | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
frontend/models/ContactForm.php | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
frontend/models/PasswordResetRequestForm.php | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
frontend/models/ResetPasswordForm.php | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
frontend/models/SignupForm.php | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
frontend/runtime/.gitignore | 2 ++
frontend/views/layouts/main.php | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
frontend/views/site/about.php | 16 ++++++++++++++++
frontend/views/site/contact.php | 45 +++++++++++++++++++++++++++++++++++++++++++++
frontend/views/site/error.php | 27 +++++++++++++++++++++++++++
frontend/views/site/index.php | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
frontend/views/site/login.php | 39 +++++++++++++++++++++++++++++++++++++++
frontend/views/site/requestPasswordResetToken.php | 31 +++++++++++++++++++++++++++++++
frontend/views/site/resetPassword.php | 31 +++++++++++++++++++++++++++++++
frontend/views/site/signup.php | 35 +++++++++++++++++++++++++++++++++++
frontend/web/.gitignore | 2 ++
frontend/web/assets/.gitignore | 2 ++
frontend/web/css/site.css | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
frontend/web/favicon.ico | Bin 0 -> 318 bytes
frontend/web/robots.txt | 2 ++
init | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
init.bat | 20 ++++++++++++++++++++
requirements.php | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/README.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/codeception.yml | 11 +++++++++++
tests/codeception/_output/.gitignore | 2 ++
tests/codeception/backend/.gitignore | 4 ++++
tests/codeception/backend/_bootstrap.php | 23 +++++++++++++++++++++++
tests/codeception/backend/_output/.gitignore | 2 ++
tests/codeception/backend/acceptance.suite.yml | 28 ++++++++++++++++++++++++++++
tests/codeception/backend/acceptance/LoginCept.php | 44 ++++++++++++++++++++++++++++++++++++++++++++
tests/codeception/backend/acceptance/_bootstrap.php | 2 ++
tests/codeception/backend/codeception.yml | 17 +++++++++++++++++
tests/codeception/backend/functional.suite.yml | 17 +++++++++++++++++
tests/codeception/backend/functional/LoginCept.php | 30 ++++++++++++++++++++++++++++++
tests/codeception/backend/functional/_bootstrap.php | 2 ++
tests/codeception/backend/unit.suite.yml | 6 ++++++
tests/codeception/backend/unit/DbTestCase.php | 8 ++++++++
tests/codeception/backend/unit/TestCase.php | 8 ++++++++
tests/codeception/backend/unit/_bootstrap.php | 2 ++
tests/codeception/backend/unit/fixtures/data/.gitkeep | 0
tests/codeception/bin/_bootstrap.php | 19 +++++++++++++++++++
tests/codeception/bin/yii | 23 +++++++++++++++++++++++
tests/codeception/bin/yii.bat | 20 ++++++++++++++++++++
tests/codeception/common/.gitignore | 4 ++++
tests/codeception/common/_bootstrap.php | 15 +++++++++++++++
tests/codeception/common/_output/.gitignore | 2 ++
tests/codeception/common/_pages/LoginPage.php | 25 +++++++++++++++++++++++++
tests/codeception/common/_support/FixtureHelper.php | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/codeception/common/codeception.yml | 13 +++++++++++++
tests/codeception/common/fixtures/UserFixture.php | 13 +++++++++++++
tests/codeception/common/fixtures/data/init_login.php | 14 ++++++++++++++
tests/codeception/common/templates/fixtures/user.php | 17 +++++++++++++++++
tests/codeception/common/unit.suite.yml | 6 ++++++
tests/codeception/common/unit/DbTestCase.php | 11 +++++++++++
tests/codeception/common/unit/TestCase.php | 11 +++++++++++
tests/codeception/common/unit/_bootstrap.php | 2 ++
tests/codeception/common/unit/fixtures/data/models/user.php | 14 ++++++++++++++
tests/codeception/common/unit/models/LoginFormTest.php | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/codeception/config/acceptance.php | 7 +++++++
tests/codeception/config/backend/acceptance.php | 17 +++++++++++++++++
tests/codeception/config/backend/config.php | 5 +++++
tests/codeception/config/backend/functional.php | 18 ++++++++++++++++++
tests/codeception/config/backend/unit.php | 16 ++++++++++++++++
tests/codeception/config/common/unit.php | 14 ++++++++++++++
tests/codeception/config/config.php | 26 ++++++++++++++++++++++++++
tests/codeception/config/console/unit.php | 14 ++++++++++++++
tests/codeception/config/frontend/acceptance.php | 17 +++++++++++++++++
tests/codeception/config/frontend/config.php | 5 +++++
tests/codeception/config/frontend/functional.php | 18 ++++++++++++++++++
tests/codeception/config/frontend/unit.php | 16 ++++++++++++++++
tests/codeception/config/functional.php | 18 ++++++++++++++++++
tests/codeception/config/unit.php | 7 +++++++
tests/codeception/console/.gitignore | 2 ++
tests/codeception/console/_bootstrap.php | 16 ++++++++++++++++
tests/codeception/console/_output/.gitignore | 2 ++
tests/codeception/console/codeception.yml | 13 +++++++++++++
tests/codeception/console/unit.suite.yml | 6 ++++++
tests/codeception/console/unit/DbTestCase.php | 11 +++++++++++
tests/codeception/console/unit/TestCase.php | 11 +++++++++++
tests/codeception/console/unit/_bootstrap.php | 2 ++
tests/codeception/console/unit/fixtures/data/.gitkeep | 0
tests/codeception/frontend/.gitignore | 4 ++++
tests/codeception/frontend/_bootstrap.php | 23 +++++++++++++++++++++++
tests/codeception/frontend/_output/.gitignore | 2 ++
tests/codeception/frontend/_pages/AboutPage.php | 14 ++++++++++++++
tests/codeception/frontend/_pages/ContactPage.php | 26 ++++++++++++++++++++++++++
tests/codeception/frontend/_pages/SignupPage.php | 27 +++++++++++++++++++++++++++
tests/codeception/frontend/acceptance.suite.yml | 28 ++++++++++++++++++++++++++++
tests/codeception/frontend/acceptance/AboutCept.php | 10 ++++++++++
tests/codeception/frontend/acceptance/ContactCept.php | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/codeception/frontend/acceptance/HomeCept.php | 12 ++++++++++++
tests/codeception/frontend/acceptance/LoginCept.php | 34 ++++++++++++++++++++++++++++++++++
tests/codeception/frontend/acceptance/SignupCest.php | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/codeception/frontend/acceptance/_bootstrap.php | 2 ++
tests/codeception/frontend/codeception.yml | 17 +++++++++++++++++
tests/codeception/frontend/functional.suite.yml | 17 +++++++++++++++++
tests/codeception/frontend/functional/AboutCept.php | 10 ++++++++++
tests/codeception/frontend/functional/ContactCept.php | 47 +++++++++++++++++++++++++++++++++++++++++++++++
tests/codeception/frontend/functional/HomeCept.php | 12 ++++++++++++
tests/codeception/frontend/functional/LoginCept.php | 29 +++++++++++++++++++++++++++++
tests/codeception/frontend/functional/SignupCest.php | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/codeception/frontend/functional/_bootstrap.php | 3 +++
tests/codeception/frontend/unit.suite.yml | 6 ++++++
tests/codeception/frontend/unit/DbTestCase.php | 11 +++++++++++
tests/codeception/frontend/unit/TestCase.php | 11 +++++++++++
tests/codeception/frontend/unit/_bootstrap.php | 2 ++
tests/codeception/frontend/unit/fixtures/data/models/user.php | 23 +++++++++++++++++++++++
tests/codeception/frontend/unit/models/ContactFormTest.php | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/codeception/frontend/unit/models/ResetPasswordFormTest.php | 43 +++++++++++++++++++++++++++++++++++++++++++
tests/codeception/frontend/unit/models/SignupFormTest.php | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
yii.bat | 20 ++++++++++++++++++++
368 files changed, 23203 insertions(+), 0 deletions(-)
create mode 100644 .bowerrc
create mode 100644 .gitignore
create mode 100644 .htaccess
create mode 100644 LICENSE.md
create mode 100644 README.md
create mode 100644 backend/config/.gitignore
create mode 100644 backend/config/bootstrap.php
create mode 100644 backend/config/main.php
create mode 100644 backend/config/params.php
create mode 100644 backend/controllers/BlogController.php
create mode 100644 backend/controllers/SiteController.php
create mode 100644 backend/models/.gitkeep
create mode 100644 backend/runtime/.gitignore
create mode 100644 backend/views/blog/_form.php
create mode 100644 backend/views/blog/_search.php
create mode 100644 backend/views/blog/create.php
create mode 100644 backend/views/blog/index.php
create mode 100644 backend/views/blog/update.php
create mode 100644 backend/views/blog/view.php
create mode 100644 backend/views/layouts/admin.php
create mode 100644 backend/views/layouts/control-sidebar.php
create mode 100644 backend/views/layouts/footer.php
create mode 100644 backend/views/layouts/header.php
create mode 100644 backend/views/layouts/main-sidebar.php
create mode 100644 backend/views/layouts/main-sidebar1.php
create mode 100644 backend/views/layouts/main.php
create mode 100644 backend/views/layouts/none.php
create mode 100644 backend/views/site/error.php
create mode 100644 backend/views/site/index.php
create mode 100644 backend/views/site/login.php
create mode 100644 backend/web/.gitignore
create mode 100644 backend/web/assets/.gitignore
create mode 100644 backend/web/css/site.css
create mode 100644 backend/web/favicon.ico
create mode 100644 backend/web/js/fieldWidget.js
create mode 100644 backend/web/robots.txt
create mode 100644 common/behaviors/ShowImage.php
create mode 100644 common/behaviors/Slug.php
create mode 100644 common/components/LangRequest.php
create mode 100644 common/components/LangUrlManager.php
create mode 100644 common/components/Request.php
create mode 100644 common/components/artboxtree/ArtboxTreeBehavior.php
create mode 100644 common/components/artboxtree/ArtboxTreeHelper.php
create mode 100644 common/components/artboxtree/ArtboxTreeQueryTrait.php
create mode 100644 common/components/artboxtree/ArtboxTreeWidget.php
create mode 100644 common/components/artboxtree/treegrid/TreeGridColumn.php
create mode 100644 common/components/artboxtree/treegrid/TreeGridWidget.php
create mode 100644 common/components/artboxtree/treelist/TreeListWidget.php
create mode 100644 common/components/artboxtree/treemenu/TreeMenuWidget.php
create mode 100644 common/config/.gitignore
create mode 100644 common/config/bootstrap.php
create mode 100644 common/config/main.php
create mode 100644 common/config/params.php
create mode 100644 common/mail/layouts/html.php
create mode 100644 common/mail/layouts/text.php
create mode 100644 common/mail/passwordResetToken-html.php
create mode 100644 common/mail/passwordResetToken-text.php
create mode 100644 common/models/Blog.php
create mode 100644 common/models/BlogSearch.php
create mode 100644 common/models/Fields.php
create mode 100644 common/models/LoginForm.php
create mode 100644 common/models/User.php
create mode 100644 common/modules/blog/Module.php
create mode 100644 common/modules/blog/behaviors/Autocomplete.php
create mode 100644 common/modules/blog/config.php
create mode 100644 common/modules/blog/controllers/AjaxController.php
create mode 100644 common/modules/blog/controllers/ArticleController.php
create mode 100644 common/modules/blog/controllers/CategoryController.php
create mode 100644 common/modules/blog/controllers/DefaultController.php
create mode 100644 common/modules/blog/controllers/MediaController.php
create mode 100644 common/modules/blog/controllers/TestController.php
create mode 100644 common/modules/blog/models/Article.php
create mode 100644 common/modules/blog/models/ArticleCategory.php
create mode 100644 common/modules/blog/models/ArticleCategoryLang.php
create mode 100644 common/modules/blog/models/ArticleCategoryMedia.php
create mode 100644 common/modules/blog/models/ArticleLang.php
create mode 100644 common/modules/blog/models/ArticleMedia.php
create mode 100644 common/modules/blog/models/ArticleToCategory.php
create mode 100644 common/modules/blog/views/ajax/_article_form.php
create mode 100644 common/modules/blog/views/ajax/_article_form_test.php
create mode 100644 common/modules/blog/views/ajax/_article_media_form.php
create mode 100644 common/modules/blog/views/ajax/_category_form.php
create mode 100644 common/modules/blog/views/article/_form.php
create mode 100644 common/modules/blog/views/article/create.php
create mode 100644 common/modules/blog/views/article/index.php
create mode 100644 common/modules/blog/views/article/update.php
create mode 100644 common/modules/blog/views/category/_form.php
create mode 100644 common/modules/blog/views/category/create.php
create mode 100644 common/modules/blog/views/category/index.php
create mode 100644 common/modules/blog/views/category/update.php
create mode 100644 common/modules/blog/views/default/index.php
create mode 100644 common/modules/blog/views/media/index.php
create mode 100644 common/modules/blog/views/test/index.php
create mode 100644 common/modules/comment/Controller.php
create mode 100644 common/modules/comment/Module.php
create mode 100644 common/modules/comment/Permissions.php
create mode 100644 common/modules/comment/assets/CommentAsset.php
create mode 100644 common/modules/comment/commands/RbacController.php
create mode 100644 common/modules/comment/interfaces/CommentInterface.php
create mode 100644 common/modules/comment/models/Comment.php
create mode 100644 common/modules/comment/models/CommentProject.php
create mode 100644 common/modules/comment/models/Rating.php
create mode 100644 common/modules/comment/rbac/ArtboxCommentCreateRule.php
create mode 100644 common/modules/comment/rbac/ArtboxCommentDeleteOwnRule.php
create mode 100644 common/modules/comment/rbac/ArtboxCommentDeleteRule.php
create mode 100644 common/modules/comment/rbac/ArtboxCommentUpdateOwnRule.php
create mode 100644 common/modules/comment/rbac/ArtboxCommentUpdateRule.php
create mode 100644 common/modules/comment/resources/comment.css
create mode 100644 common/modules/comment/resources/comment.js
create mode 100644 common/modules/comment/resources/delete-ico.png
create mode 100644 common/modules/comment/views/comment_form.php
create mode 100644 common/modules/comment/widgets/CommentWidget.php
create mode 100644 common/modules/comment/widgets/views/_project_comment_view.php
create mode 100644 common/modules/comment/widgets/views/form-comment.php
create mode 100644 common/modules/comment/widgets/views/form-project-comment.php
create mode 100644 common/modules/comment/widgets/views/list-comment.php
create mode 100644 common/modules/comment/widgets/views/list-project-comment.php
create mode 100644 common/modules/comment/widgets/views/project_comment_view.php
create mode 100644 common/modules/file/FileUploadAsset.php
create mode 100644 common/modules/file/Module.php
create mode 100644 common/modules/file/assets/css/fileupload/style.css
create mode 100644 common/modules/file/assets/css/jquery.fileupload-noscript.css
create mode 100644 common/modules/file/assets/css/jquery.fileupload-ui-noscript.css
create mode 100644 common/modules/file/assets/css/jquery.fileupload-ui.css
create mode 100644 common/modules/file/assets/css/jquery.fileupload.css
create mode 100644 common/modules/file/assets/img/loading.gif
create mode 100644 common/modules/file/assets/img/progressbar.gif
create mode 100644 common/modules/file/assets/js/cors/jquery.postmessage-transport.js
create mode 100644 common/modules/file/assets/js/cors/jquery.xdr-transport.js
create mode 100644 common/modules/file/assets/js/jquery.fileupload-angular.js
create mode 100644 common/modules/file/assets/js/jquery.fileupload-audio.js
create mode 100644 common/modules/file/assets/js/jquery.fileupload-image.js
create mode 100644 common/modules/file/assets/js/jquery.fileupload-jquery-ui.js
create mode 100644 common/modules/file/assets/js/jquery.fileupload-process.js
create mode 100644 common/modules/file/assets/js/jquery.fileupload-ui.js
create mode 100644 common/modules/file/assets/js/jquery.fileupload-validate.js
create mode 100644 common/modules/file/assets/js/jquery.fileupload-video.js
create mode 100644 common/modules/file/assets/js/jquery.fileupload.js
create mode 100644 common/modules/file/assets/js/jquery.iframe-transport.js
create mode 100644 common/modules/file/assets/js/vendor/jquery.ui.widget.js
create mode 100644 common/modules/file/config.php
create mode 100644 common/modules/file/controllers/UploaderController.php
create mode 100644 common/modules/file/models/ImageSizerForm.php
create mode 100644 common/modules/file/views/_gallery_item.php
create mode 100644 common/modules/file/views/_one_item.php
create mode 100644 common/modules/file/widgets/ImageUploader.php
create mode 100644 common/modules/file/widgets/views/image_sizer.php
create mode 100644 common/modules/product/Module.php
create mode 100644 common/modules/product/config.php
create mode 100644 common/modules/product/controllers/DefaultController.php
create mode 100644 common/modules/product/controllers/ManageController.php
create mode 100644 common/modules/product/helpers/ProductHelper.php
create mode 100644 common/modules/product/models/Product.php
create mode 100644 common/modules/product/models/ProductCategory.php
create mode 100644 common/modules/product/models/ProductQuery.php
create mode 100644 common/modules/product/models/ProductSearch.php
create mode 100644 common/modules/product/views/default/index.php
create mode 100644 common/modules/product/views/manage/_form.php
create mode 100644 common/modules/product/views/manage/_search.php
create mode 100644 common/modules/product/views/manage/create.php
create mode 100644 common/modules/product/views/manage/index.php
create mode 100644 common/modules/product/views/manage/update.php
create mode 100644 common/modules/product/views/manage/view.php
create mode 100644 common/modules/relation/Module.php
create mode 100644 common/modules/relation/controllers/DefaultController.php
create mode 100644 common/modules/relation/controllers/ManageController.php
create mode 100644 common/modules/relation/models/Relation.php
create mode 100644 common/modules/relation/models/RelationQuery.php
create mode 100644 common/modules/relation/relationBehavior.php
create mode 100644 common/modules/relation/relationHelper.php
create mode 100644 common/modules/relation/relationObject.php
create mode 100644 common/modules/relation/relationQueryTrait.php
create mode 100644 common/modules/relation/views/default/index.php
create mode 100644 common/modules/relation/views/manage/_form.php
create mode 100644 common/modules/relation/views/manage/create.php
create mode 100644 common/modules/relation/views/manage/pars.php
create mode 100644 common/modules/relation/views/manage/relations.php
create mode 100644 common/modules/relation/views/manage/update.php
create mode 100644 common/modules/rubrication/Module.php
create mode 100644 common/modules/rubrication/controllers/DefaultController.php
create mode 100644 common/modules/rubrication/controllers/TaxGroupController.php
create mode 100644 common/modules/rubrication/controllers/TaxOptionController.php
create mode 100644 common/modules/rubrication/helpers/RubricationHelper.php
create mode 100644 common/modules/rubrication/models/TaxGroup.php
create mode 100644 common/modules/rubrication/models/TaxGroupToGroup.php
create mode 100644 common/modules/rubrication/models/TaxOption.php
create mode 100644 common/modules/rubrication/models/TaxOptionQuery.php
create mode 100644 common/modules/rubrication/models/TaxOptionRelation.php
create mode 100644 common/modules/rubrication/models/TaxOptionSearch.php
create mode 100644 common/modules/rubrication/models/TaxValueInt.php
create mode 100644 common/modules/rubrication/models/TaxValueLink.php
create mode 100644 common/modules/rubrication/models/TaxValueString.php
create mode 100644 common/modules/rubrication/views/default/index.php
create mode 100644 common/modules/rubrication/views/tax-group/_form.php
create mode 100644 common/modules/rubrication/views/tax-group/create.php
create mode 100644 common/modules/rubrication/views/tax-group/index.php
create mode 100644 common/modules/rubrication/views/tax-group/relations.php
create mode 100644 common/modules/rubrication/views/tax-group/update.php
create mode 100644 common/modules/rubrication/views/tax-group/view.php
create mode 100644 common/modules/rubrication/views/tax-option/_form.php
create mode 100644 common/modules/rubrication/views/tax-option/_search.php
create mode 100644 common/modules/rubrication/views/tax-option/create.php
create mode 100644 common/modules/rubrication/views/tax-option/index.php
create mode 100644 common/modules/rubrication/views/tax-option/update.php
create mode 100644 common/modules/rubrication/views/tax-option/value/_fields_int.php
create mode 100644 common/modules/rubrication/views/tax-option/value/_fields_link.php
create mode 100644 common/modules/rubrication/views/tax-option/value/_fields_string.php
create mode 100644 common/modules/rubrication/views/tax-option/view.php
create mode 100644 common/widgets/Alert.php
create mode 100644 common/widgets/FieldEditor.php
create mode 100644 common/widgets/views/education_field.php
create mode 100644 composer.json
create mode 100644 composer.lock
create mode 100644 console/config/.gitignore
create mode 100644 console/config/bootstrap.php
create mode 100644 console/config/main.php
create mode 100644 console/config/params.php
create mode 100644 console/controllers/.gitkeep
create mode 100644 console/migrations/m130524_201442_init.php
create mode 100644 console/migrations/m160126_071717_rubrication.php
create mode 100644 console/migrations/m160128_101543_fields.php
create mode 100644 console/migrations/m160208_111900_blog.php
create mode 100644 console/migrations/m160304_054017_realtion.php
create mode 100644 console/migrations/m160304_065108_product.php
create mode 100644 console/models/.gitkeep
create mode 100644 console/runtime/.gitignore
create mode 100644 environments/dev/backend/config/main-local.php
create mode 100644 environments/dev/backend/config/params-local.php
create mode 100644 environments/dev/backend/web/index-test.php
create mode 100644 environments/dev/backend/web/index.php
create mode 100644 environments/dev/common/config/main-local.php
create mode 100644 environments/dev/common/config/params-local.php
create mode 100644 environments/dev/console/config/main-local.php
create mode 100644 environments/dev/console/config/params-local.php
create mode 100644 environments/dev/frontend/config/main-local.php
create mode 100644 environments/dev/frontend/config/params-local.php
create mode 100644 environments/dev/frontend/web/index-test.php
create mode 100644 environments/dev/frontend/web/index.php
create mode 100644 environments/dev/yii
create mode 100644 environments/index.php
create mode 100644 environments/prod/backend/config/main-local.php
create mode 100644 environments/prod/backend/config/params-local.php
create mode 100644 environments/prod/backend/web/index.php
create mode 100644 environments/prod/common/config/main-local.php
create mode 100644 environments/prod/common/config/params-local.php
create mode 100644 environments/prod/console/config/main-local.php
create mode 100644 environments/prod/console/config/params-local.php
create mode 100644 environments/prod/frontend/config/main-local.php
create mode 100644 environments/prod/frontend/config/params-local.php
create mode 100644 environments/prod/frontend/web/index.php
create mode 100644 environments/prod/yii
create mode 100644 frontend/config/.gitignore
create mode 100644 frontend/config/bootstrap.php
create mode 100644 frontend/config/main.php
create mode 100644 frontend/config/params.php
create mode 100644 frontend/controllers/SiteController.php
create mode 100644 frontend/models/ContactForm.php
create mode 100644 frontend/models/PasswordResetRequestForm.php
create mode 100644 frontend/models/ResetPasswordForm.php
create mode 100644 frontend/models/SignupForm.php
create mode 100644 frontend/runtime/.gitignore
create mode 100644 frontend/views/layouts/main.php
create mode 100644 frontend/views/site/about.php
create mode 100644 frontend/views/site/contact.php
create mode 100644 frontend/views/site/error.php
create mode 100644 frontend/views/site/index.php
create mode 100644 frontend/views/site/login.php
create mode 100644 frontend/views/site/requestPasswordResetToken.php
create mode 100644 frontend/views/site/resetPassword.php
create mode 100644 frontend/views/site/signup.php
create mode 100644 frontend/web/.gitignore
create mode 100644 frontend/web/assets/.gitignore
create mode 100644 frontend/web/css/site.css
create mode 100644 frontend/web/favicon.ico
create mode 100644 frontend/web/robots.txt
create mode 100644 init
create mode 100644 init.bat
create mode 100644 requirements.php
create mode 100644 tests/README.md
create mode 100644 tests/codeception.yml
create mode 100644 tests/codeception/_output/.gitignore
create mode 100644 tests/codeception/backend/.gitignore
create mode 100644 tests/codeception/backend/_bootstrap.php
create mode 100644 tests/codeception/backend/_output/.gitignore
create mode 100644 tests/codeception/backend/acceptance.suite.yml
create mode 100644 tests/codeception/backend/acceptance/LoginCept.php
create mode 100644 tests/codeception/backend/acceptance/_bootstrap.php
create mode 100644 tests/codeception/backend/codeception.yml
create mode 100644 tests/codeception/backend/functional.suite.yml
create mode 100644 tests/codeception/backend/functional/LoginCept.php
create mode 100644 tests/codeception/backend/functional/_bootstrap.php
create mode 100644 tests/codeception/backend/unit.suite.yml
create mode 100644 tests/codeception/backend/unit/DbTestCase.php
create mode 100644 tests/codeception/backend/unit/TestCase.php
create mode 100644 tests/codeception/backend/unit/_bootstrap.php
create mode 100644 tests/codeception/backend/unit/fixtures/data/.gitkeep
create mode 100644 tests/codeception/bin/_bootstrap.php
create mode 100644 tests/codeception/bin/yii
create mode 100644 tests/codeception/bin/yii.bat
create mode 100644 tests/codeception/common/.gitignore
create mode 100644 tests/codeception/common/_bootstrap.php
create mode 100644 tests/codeception/common/_output/.gitignore
create mode 100644 tests/codeception/common/_pages/LoginPage.php
create mode 100644 tests/codeception/common/_support/FixtureHelper.php
create mode 100644 tests/codeception/common/codeception.yml
create mode 100644 tests/codeception/common/fixtures/UserFixture.php
create mode 100644 tests/codeception/common/fixtures/data/init_login.php
create mode 100644 tests/codeception/common/templates/fixtures/user.php
create mode 100644 tests/codeception/common/unit.suite.yml
create mode 100644 tests/codeception/common/unit/DbTestCase.php
create mode 100644 tests/codeception/common/unit/TestCase.php
create mode 100644 tests/codeception/common/unit/_bootstrap.php
create mode 100644 tests/codeception/common/unit/fixtures/data/models/user.php
create mode 100644 tests/codeception/common/unit/models/LoginFormTest.php
create mode 100644 tests/codeception/config/acceptance.php
create mode 100644 tests/codeception/config/backend/acceptance.php
create mode 100644 tests/codeception/config/backend/config.php
create mode 100644 tests/codeception/config/backend/functional.php
create mode 100644 tests/codeception/config/backend/unit.php
create mode 100644 tests/codeception/config/common/unit.php
create mode 100644 tests/codeception/config/config.php
create mode 100644 tests/codeception/config/console/unit.php
create mode 100644 tests/codeception/config/frontend/acceptance.php
create mode 100644 tests/codeception/config/frontend/config.php
create mode 100644 tests/codeception/config/frontend/functional.php
create mode 100644 tests/codeception/config/frontend/unit.php
create mode 100644 tests/codeception/config/functional.php
create mode 100644 tests/codeception/config/unit.php
create mode 100644 tests/codeception/console/.gitignore
create mode 100644 tests/codeception/console/_bootstrap.php
create mode 100644 tests/codeception/console/_output/.gitignore
create mode 100644 tests/codeception/console/codeception.yml
create mode 100644 tests/codeception/console/unit.suite.yml
create mode 100644 tests/codeception/console/unit/DbTestCase.php
create mode 100644 tests/codeception/console/unit/TestCase.php
create mode 100644 tests/codeception/console/unit/_bootstrap.php
create mode 100644 tests/codeception/console/unit/fixtures/data/.gitkeep
create mode 100644 tests/codeception/frontend/.gitignore
create mode 100644 tests/codeception/frontend/_bootstrap.php
create mode 100644 tests/codeception/frontend/_output/.gitignore
create mode 100644 tests/codeception/frontend/_pages/AboutPage.php
create mode 100644 tests/codeception/frontend/_pages/ContactPage.php
create mode 100644 tests/codeception/frontend/_pages/SignupPage.php
create mode 100644 tests/codeception/frontend/acceptance.suite.yml
create mode 100644 tests/codeception/frontend/acceptance/AboutCept.php
create mode 100644 tests/codeception/frontend/acceptance/ContactCept.php
create mode 100644 tests/codeception/frontend/acceptance/HomeCept.php
create mode 100644 tests/codeception/frontend/acceptance/LoginCept.php
create mode 100644 tests/codeception/frontend/acceptance/SignupCest.php
create mode 100644 tests/codeception/frontend/acceptance/_bootstrap.php
create mode 100644 tests/codeception/frontend/codeception.yml
create mode 100644 tests/codeception/frontend/functional.suite.yml
create mode 100644 tests/codeception/frontend/functional/AboutCept.php
create mode 100644 tests/codeception/frontend/functional/ContactCept.php
create mode 100644 tests/codeception/frontend/functional/HomeCept.php
create mode 100644 tests/codeception/frontend/functional/LoginCept.php
create mode 100644 tests/codeception/frontend/functional/SignupCest.php
create mode 100644 tests/codeception/frontend/functional/_bootstrap.php
create mode 100644 tests/codeception/frontend/unit.suite.yml
create mode 100644 tests/codeception/frontend/unit/DbTestCase.php
create mode 100644 tests/codeception/frontend/unit/TestCase.php
create mode 100644 tests/codeception/frontend/unit/_bootstrap.php
create mode 100644 tests/codeception/frontend/unit/fixtures/data/models/user.php
create mode 100644 tests/codeception/frontend/unit/models/ContactFormTest.php
create mode 100644 tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php
create mode 100644 tests/codeception/frontend/unit/models/ResetPasswordFormTest.php
create mode 100644 tests/codeception/frontend/unit/models/SignupFormTest.php
create mode 100644 yii.bat
diff --git a/.bowerrc b/.bowerrc
new file mode 100644
index 0000000..1669168
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory" : "vendor/bower"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7146c02
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,40 @@
+# yii console command
+/yii
+
+# phpstorm project files
+.idea
+
+# netbeans project files
+nbproject
+
+# zend studio for eclipse project files
+.buildpath
+.project
+.settings
+
+# windows thumbnail cache
+Thumbs.db
+
+# composer vendor dir
+/vendor
+
+# composer itself is not needed
+composer.phar
+
+# Mac DS_Store Files
+.DS_Store
+
+# phpunit itself is not needed
+phpunit.phar
+# local phpunit config
+/phpunit.xml
+
+/storage
+common/config/main-local.php
+common/config/params-local.php
+backend/config/main-local.php
+backend/config/params-local.php
+backend/assets/*
+frontend/config/main-local.php
+frontend/config/params-local.php
+frontend/assets/*
\ No newline at end of file
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..17d3b8c
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,75 @@
+AddDefaultCharset utf-8
+
+
+
+ Options +FollowSymlinks
+
+ RewriteEngine On
+
+
+
+
+
+ RewriteBase /
+ # deal with admin first
+
+
+
+ RewriteRule ^storage/(.*)?$ /storage/$1 [L,PT]
+
+ RewriteCond %{REQUEST_URI} ^/(admin)
+
+
+
+ RewriteRule ^admin/assets/(.*)$ backend/web/assets/$1 [L]
+
+ RewriteRule ^admin/css/(.*)$ backend/web/css/$1 [L]
+
+ RewriteRule ^admin/js/(.*)$ backend/web/js/$1 [L]
+
+ RewriteRule ^admin/images/(.*)$ backend/web/images/$1 [L]
+
+ RewriteRule ^admin/fonts/(.*)$ backend/web/fonts/$1 [L]
+
+
+
+
+
+ RewriteCond %{REQUEST_URI} !^/backend/web/(assets|css|js|images|fonts)/
+
+ RewriteCond %{REQUEST_URI} ^/(admin)
+
+ RewriteRule ^.*$ backend/web/index.php [L]
+
+
+
+ RewriteCond %{REQUEST_URI} ^/(assets|css)
+
+ RewriteRule ^assets/(.*)$ frontend/web/assets/$1 [L]
+
+ RewriteRule ^css/(.*)$ frontend/web/css/$1 [L]
+
+ RewriteRule ^js/(.*)$ frontend/web/js/$1 [L]
+
+ RewriteRule ^images/(.*)$ frontend/web/images/$1 [L]
+
+ RewriteRule ^fonts/(.*)$ frontend/web/fonts/$1 [L]
+
+
+ RewriteCond %{REQUEST_URI} !^/(frontend|backend)/web/(assets|css|js|images|fonts)/
+
+ RewriteCond %{REQUEST_URI} !index.php
+
+ RewriteCond %{REQUEST_FILENAME} !-f [OR]
+
+ RewriteCond %{REQUEST_FILENAME} !-d
+
+ RewriteRule ^.*$ frontend/web/index.php [L]
+
+
+
+#для возможности загрузки файлов парсера
+
+ php_value upload_max_filesize 20M
+ php_value post_max_size 30M
+
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..e98f03d
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,32 @@
+The Yii framework is free software. It is released under the terms of
+the following BSD License.
+
+Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Yii Software LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..45e56ad
--- /dev/null
+++ b/README.md
@@ -0,0 +1,54 @@
+Yii 2 Advanced Project Template
+===============================
+
+Yii 2 Advanced Project Template is a skeleton [Yii 2](http://www.yiiframework.com/) application best for
+developing complex Web applications with multiple tiers.
+
+The template includes three tiers: front end, back end, and console, each of which
+is a separate Yii application.
+
+The template is designed to work in a team development environment. It supports
+deploying the application in different environments.
+
+Documentation is at [docs/guide/README.md](docs/guide/README.md).
+
+[](https://packagist.org/packages/yiisoft/yii2-app-advanced)
+[](https://packagist.org/packages/yiisoft/yii2-app-advanced)
+[](https://travis-ci.org/yiisoft/yii2-app-advanced)
+
+DIRECTORY STRUCTURE
+-------------------
+
+```
+common
+ config/ contains shared configurations
+ mail/ contains view files for e-mails
+ models/ contains model classes used in both backend and frontend
+console
+ config/ contains console configurations
+ controllers/ contains console controllers (commands)
+ migrations/ contains database migrations
+ models/ contains console-specific model classes
+ runtime/ contains files generated during runtime
+backend
+ assets/ contains application assets such as JavaScript and CSS
+ config/ contains backend configurations
+ controllers/ contains Web controller classes
+ models/ contains backend-specific model classes
+ runtime/ contains files generated during runtime
+ views/ contains view files for the Web application
+ web/ contains the entry script and Web resources
+frontend
+ assets/ contains application assets such as JavaScript and CSS
+ config/ contains frontend configurations
+ controllers/ contains Web controller classes
+ models/ contains frontend-specific model classes
+ runtime/ contains files generated during runtime
+ views/ contains view files for the Web application
+ web/ contains the entry script and Web resources
+ widgets/ contains frontend widgets
+vendor/ contains dependent 3rd-party packages
+environments/ contains environment-based overrides
+tests contains various tests for the advanced application
+ codeception/ contains tests developed with Codeception PHP Testing Framework
+```
diff --git a/backend/config/.gitignore b/backend/config/.gitignore
new file mode 100644
index 0000000..20da318
--- /dev/null
+++ b/backend/config/.gitignore
@@ -0,0 +1,2 @@
+main-local.php
+params-local.php
\ No newline at end of file
diff --git a/backend/config/bootstrap.php b/backend/config/bootstrap.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/backend/config/bootstrap.php
@@ -0,0 +1 @@
+ 'app-backend',
+ 'basePath' => dirname(__DIR__),
+ 'layout' => 'admin',
+ 'controllerNamespace' => 'backend\controllers',
+ 'bootstrap' => ['log'],
+ 'modules' => [
+ 'rubrication' => [
+ 'class' => 'common\modules\rubrication\Module',
+ 'types' => [
+ 'string' => 'Strings',
+ 'float' => 'Floating number',
+ 'int' => 'Integer number',
+ 'link' => 'Web-link',
+ ]
+ ],
+ 'product' => [
+ 'class' => 'common\modules\product\Module'
+ ],
+ ],
+ 'components' => [
+ 'user' => [
+ 'identityClass' => 'common\models\User',
+ 'enableAutoLogin' => true,
+ ],
+ 'log' => [
+ 'traceLevel' => YII_DEBUG ? 3 : 0,
+ 'targets' => [
+ [
+ 'class' => 'yii\log\FileTarget',
+ 'levels' => ['error', 'warning'],
+ ],
+ ],
+ ],
+ 'errorHandler' => [
+ 'errorAction' => 'site/error',
+ ],
+ 'request'=>[
+ 'cookieValidationKey' => 'j4iuot9u5894e7tu8reyh78g9y54sy7i',
+ 'csrfParam' => '_backendCSRF',
+
+ 'class' => 'common\components\Request',
+
+ 'web'=> '/backend/web',
+
+ 'adminUrl' => '/admin'
+
+ ],
+
+ ],
+ 'params' => $params,
+];
diff --git a/backend/config/params.php b/backend/config/params.php
new file mode 100644
index 0000000..7f754b9
--- /dev/null
+++ b/backend/config/params.php
@@ -0,0 +1,4 @@
+ 'admin@example.com',
+];
diff --git a/backend/controllers/BlogController.php b/backend/controllers/BlogController.php
new file mode 100644
index 0000000..1963d1b
--- /dev/null
+++ b/backend/controllers/BlogController.php
@@ -0,0 +1,131 @@
+ [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Lists all Blog models.
+ * @return mixed
+ */
+ public function actionIndex()
+ {
+ $searchModel = new BlogSearch();
+ $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
+
+ return $this->render('index', [
+ 'searchModel' => $searchModel,
+ 'dataProvider' => $dataProvider,
+ ]);
+ }
+
+ /**
+ * Displays a single Blog model.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionView($id)
+ {
+ return $this->render('view', [
+ 'model' => $this->findModel($id),
+ ]);
+ }
+
+ /**
+ * Creates a new Blog model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return mixed
+ */
+ public function actionCreate()
+ {
+ $model = new Blog();
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+
+ Fields::saveFieldData(Yii::$app->request->post('Fields'), $model->blog_id, Blog::className(), 'ru');
+
+ return $this->redirect(['view', 'id' => $model->blog_id]);
+ } else {
+ return $this->render('create', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Updates an existing Blog model.
+ * If update is successful, the browser will be redirected to the 'view' page.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionUpdate($id)
+ {
+ $model = $this->findModel($id);
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+ Fields::saveFieldData(Yii::$app->request->post('Fields'), $model->blog_id, Blog::className(), 'ru');
+ return $this->redirect(['view', 'id' => $model->blog_id]);
+ } else {
+ return $this->render('update', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Deletes an existing Blog model.
+ * If deletion is successful, the browser will be redirected to the 'index' page.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionDelete($id)
+ {
+ $this->findModel($id)->delete();
+
+ return $this->redirect(['index']);
+ }
+
+ /**
+ * Finds the Blog model based on its primary key value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * @param integer $id
+ * @return Blog the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($id)
+ {
+ if (($model = Blog::findOne($id)) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
+}
diff --git a/backend/controllers/SiteController.php b/backend/controllers/SiteController.php
new file mode 100644
index 0000000..90d2445
--- /dev/null
+++ b/backend/controllers/SiteController.php
@@ -0,0 +1,95 @@
+ [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'actions' => ['login', 'error'],
+ 'allow' => true,
+ ],
+ [
+ 'actions' => ['logout', 'index'],
+ 'allow' => true,
+ 'roles' => ['@'],
+ ],
+ ],
+ ],
+ 'verbs' => [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'logout' => ['post'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function actions()
+ {
+ return [
+ 'error' => [
+ 'class' => 'yii\web\ErrorAction',
+ ],
+ ];
+ }
+
+ public function actionIndex()
+ {
+ $blog = new Blog();
+ $post = \Yii::$app->request->post();
+ if($blog->load($post)) {
+ $blog->save();
+ }
+ return $this->render('index',[
+ 'blog' => $blog
+ ]);
+ }
+
+ public function actionLogin()
+ {
+ $this->layout = '/none';
+
+ if (!\Yii::$app->user->isGuest) {
+ return $this->goHome();
+ }
+
+ $model = new LoginForm();
+ if ($model->load(Yii::$app->request->post()) && $model->login()) {
+ return $this->goBack();
+ } else {
+ return $this->render('login', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ public function actionLogout()
+ {
+ Yii::$app->user->logout();
+
+ return $this->goHome();
+ }
+}
diff --git a/backend/models/.gitkeep b/backend/models/.gitkeep
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/backend/models/.gitkeep
@@ -0,0 +1 @@
+*
diff --git a/backend/runtime/.gitignore b/backend/runtime/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/backend/runtime/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/backend/views/blog/_form.php b/backend/views/blog/_form.php
new file mode 100644
index 0000000..7f35383
--- /dev/null
+++ b/backend/views/blog/_form.php
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+ = $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
+
+ = $form->field($model, 'link')->textInput(['maxlength' => true]) ?>
+
+ = $form->field($model, 'description')->widget(CKEditor::className(),
+ [
+ 'editorOptions' => ElFinder::ckeditorOptions('elfinder',[
+ 'preset' => 'full', //разработанны стандартные настройки basic, standard, full данную возможность не обязательно использовать
+ 'inline' => false, //по умолчанию false]),
+ 'filebrowserUploadUrl'=>Yii::$app->getUrlManager()->createUrl('file/uploader/images-upload')
+ ]
+ )
+ ]) ?>
+
+ = ImageUploader::widget([
+ 'model'=> $model,
+ 'field'=>'cover',
+ 'size' => [
+ [
+ 'width'=>340,
+ 'height'=>260,
+ ]
+ ],
+ 'multi'=>true,
+ 'gallery' =>$model->cover,
+ 'name' => 'Загрузить миниатюру статьи'
+ ]);
+ ?>
+
+ = FieldEditor::widget([
+ 'template' => 'education',
+ 'item_id' => $model->blog_id,
+ 'model' => 'common\models\Blog',
+ 'language' => 'ru',
+ ]); ?>
+
+ = Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
+
+
+
+
+
diff --git a/backend/views/blog/_search.php b/backend/views/blog/_search.php
new file mode 100644
index 0000000..364656f
--- /dev/null
+++ b/backend/views/blog/_search.php
@@ -0,0 +1,43 @@
+
+
+
+
+ ['index'],
+ 'method' => 'get',
+ ]); ?>
+
+ = $form->field($model, 'blog_id') ?>
+
+ = $form->field($model, 'user_id') ?>
+
+ = $form->field($model, 'name') ?>
+
+ = $form->field($model, 'link') ?>
+
+ = $form->field($model, 'date_add') ?>
+
+ field($model, 'user_add_id') ?>
+
+ field($model, 'view_count') ?>
+
+ field($model, 'description') ?>
+
+ field($model, 'cover') ?>
+
+
+ = Html::submitButton('Search', ['class' => 'btn btn-primary']) ?>
+ = Html::resetButton('Reset', ['class' => 'btn btn-default']) ?>
+
+
+
+
+
diff --git a/backend/views/blog/create.php b/backend/views/blog/create.php
new file mode 100644
index 0000000..a321524
--- /dev/null
+++ b/backend/views/blog/create.php
@@ -0,0 +1,21 @@
+title = 'Create Blog';
+$this->params['breadcrumbs'][] = ['label' => 'Blogs', 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/backend/views/blog/index.php b/backend/views/blog/index.php
new file mode 100644
index 0000000..be76474
--- /dev/null
+++ b/backend/views/blog/index.php
@@ -0,0 +1,40 @@
+title = 'Blogs';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+ render('_search', ['model' => $searchModel]); ?>
+
+
+ = Html::a('Create Blog', ['create'], ['class' => 'btn btn-success']) ?>
+
+ = GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'filterModel' => $searchModel,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+
+ 'blog_id',
+ 'user_id',
+ 'name',
+ 'link',
+ 'date_add',
+ // 'user_add_id',
+ // 'view_count',
+ // 'description:ntext',
+ // 'cover',
+
+ ['class' => 'yii\grid\ActionColumn'],
+ ],
+ ]); ?>
+
diff --git a/backend/views/blog/update.php b/backend/views/blog/update.php
new file mode 100644
index 0000000..606398e
--- /dev/null
+++ b/backend/views/blog/update.php
@@ -0,0 +1,21 @@
+title = 'Update Blog: ' . ' ' . $model->name;
+$this->params['breadcrumbs'][] = ['label' => 'Blogs', 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->blog_id]];
+$this->params['breadcrumbs'][] = 'Update';
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/backend/views/blog/view.php b/backend/views/blog/view.php
new file mode 100644
index 0000000..f7ac37d
--- /dev/null
+++ b/backend/views/blog/view.php
@@ -0,0 +1,43 @@
+title = $model->name;
+$this->params['breadcrumbs'][] = ['label' => 'Blogs', 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+ = Html::a('Update', ['update', 'id' => $model->blog_id], ['class' => 'btn btn-primary']) ?>
+ = Html::a('Delete', ['delete', 'id' => $model->blog_id], [
+ 'class' => 'btn btn-danger',
+ 'data' => [
+ 'confirm' => 'Are you sure you want to delete this item?',
+ 'method' => 'post',
+ ],
+ ]) ?>
+
+
+ = DetailView::widget([
+ 'model' => $model,
+ 'attributes' => [
+ 'blog_id',
+ 'user_id',
+ 'name',
+ 'link',
+ 'date_add',
+ 'user_add_id',
+ 'view_count',
+ 'description:ntext',
+ 'cover',
+ ],
+ ]) ?>
+
+
diff --git a/backend/views/layouts/admin.php b/backend/views/layouts/admin.php
new file mode 100644
index 0000000..23b018a
--- /dev/null
+++ b/backend/views/layouts/admin.php
@@ -0,0 +1,35 @@
+beginContent('@app/views/layouts/main.php');
+?>
+
+ = $this->render('header') ?>
+
+ = $this->render('main-sidebar') ?>
+
+
+
+
+ = Breadcrumbs::widget([
+ 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
+ ]) ?>
+ = Alert::widget() ?>
+
+ = $content; ?>
+
+
+ = $this->render('footer') ?>
+
+
+
+
+ = $this->render('control-sidebar') ?>
+
+
+
+
+endContent() ?>
\ No newline at end of file
diff --git a/backend/views/layouts/control-sidebar.php b/backend/views/layouts/control-sidebar.php
new file mode 100644
index 0000000..7e34b3f
--- /dev/null
+++ b/backend/views/layouts/control-sidebar.php
@@ -0,0 +1,188 @@
+
\ No newline at end of file
diff --git a/backend/views/layouts/footer.php b/backend/views/layouts/footer.php
new file mode 100644
index 0000000..ad38e44
--- /dev/null
+++ b/backend/views/layouts/footer.php
@@ -0,0 +1,7 @@
+
+
+ Version 2.3.2
+
+ Copyright © 2014-2015 Almsaeed Studio . All rights
+ reserved.
+
\ No newline at end of file
diff --git a/backend/views/layouts/header.php b/backend/views/layouts/header.php
new file mode 100644
index 0000000..8a97ed1
--- /dev/null
+++ b/backend/views/layouts/header.php
@@ -0,0 +1,260 @@
+
\ No newline at end of file
diff --git a/backend/views/layouts/main-sidebar.php b/backend/views/layouts/main-sidebar.php
new file mode 100644
index 0000000..12efa63
--- /dev/null
+++ b/backend/views/layouts/main-sidebar.php
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/backend/views/layouts/main-sidebar1.php b/backend/views/layouts/main-sidebar1.php
new file mode 100644
index 0000000..675e21c
--- /dev/null
+++ b/backend/views/layouts/main-sidebar1.php
@@ -0,0 +1,118 @@
+
+
\ No newline at end of file
diff --git a/backend/views/layouts/main.php b/backend/views/layouts/main.php
new file mode 100644
index 0000000..cb4d7d8
--- /dev/null
+++ b/backend/views/layouts/main.php
@@ -0,0 +1,34 @@
+
+beginPage() ?>
+
+
+
+
+
+ = Html::csrfMetaTags() ?>
+ = Html::encode($this->title) ?>
+ head() ?>
+
+
+beginBody() ?>
+
+
+ = $content ?>
+
+
+
+endBody() ?>
+
+
+endPage() ?>
diff --git a/backend/views/layouts/none.php b/backend/views/layouts/none.php
new file mode 100644
index 0000000..aae13c7
--- /dev/null
+++ b/backend/views/layouts/none.php
@@ -0,0 +1,9 @@
+beginContent('@app/views/layouts/main.php');
+?>
+
+ = $content; ?>
+
+endContent() ?>
\ No newline at end of file
diff --git a/backend/views/site/error.php b/backend/views/site/error.php
new file mode 100644
index 0000000..0ba2574
--- /dev/null
+++ b/backend/views/site/error.php
@@ -0,0 +1,27 @@
+title = $name;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+ = nl2br(Html::encode($message)) ?>
+
+
+
+ The above error occurred while the Web server was processing your request.
+
+
+ Please contact us if you think this is a server error. Thank you.
+
+
+
diff --git a/backend/views/site/index.php b/backend/views/site/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/backend/views/site/index.php
diff --git a/backend/views/site/login.php b/backend/views/site/login.php
new file mode 100644
index 0000000..20f3f78
--- /dev/null
+++ b/backend/views/site/login.php
@@ -0,0 +1,35 @@
+title = 'Login';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
= Html::encode($this->title) ?>
+
+
Please fill out the following fields to login:
+
+
+
+ 'login-form']); ?>
+
+ = $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
+
+ = $form->field($model, 'password')->passwordInput() ?>
+
+ = $form->field($model, 'rememberMe')->checkbox() ?>
+
+
+ = Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
+
+
+
+
+
+
diff --git a/backend/web/.gitignore b/backend/web/.gitignore
new file mode 100644
index 0000000..25c74e6
--- /dev/null
+++ b/backend/web/.gitignore
@@ -0,0 +1,2 @@
+/index.php
+/index-test.php
diff --git a/backend/web/assets/.gitignore b/backend/web/assets/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/backend/web/assets/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/backend/web/css/site.css b/backend/web/css/site.css
new file mode 100644
index 0000000..324663d
--- /dev/null
+++ b/backend/web/css/site.css
@@ -0,0 +1,105 @@
+html,
+body {
+ height: 100%;
+}
+
+.wrap {
+ min-height: 100%;
+ height: auto;
+ margin: 0 auto -60px;
+ padding: 0 0 60px;
+}
+
+.wrap > .container {
+ padding: 70px 15px 20px;
+}
+
+.footer {
+ height: 60px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ padding-top: 20px;
+}
+
+.jumbotron {
+ text-align: center;
+ background-color: transparent;
+}
+
+.jumbotron .btn {
+ font-size: 21px;
+ padding: 14px 24px;
+}
+
+.not-set {
+ color: #c55;
+ font-style: italic;
+}
+
+/* add sorting icons to gridview sort links */
+a.asc:after, a.desc:after {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ font-family: 'Glyphicons Halflings';
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+ padding-left: 5px;
+}
+
+a.asc:after {
+ content: /*"\e113"*/ "\e151";
+}
+
+a.desc:after {
+ content: /*"\e114"*/ "\e152";
+}
+
+.sort-numerical a.asc:after {
+ content: "\e153";
+}
+
+.sort-numerical a.desc:after {
+ content: "\e154";
+}
+
+.sort-ordinal a.asc:after {
+ content: "\e155";
+}
+
+.sort-ordinal a.desc:after {
+ content: "\e156";
+}
+
+.grid-view td {
+ white-space: nowrap;
+}
+
+.grid-view .filters input,
+.grid-view .filters select {
+ min-width: 50px;
+}
+
+.hint-block {
+ display: block;
+ margin-top: 5px;
+ color: #999;
+}
+
+.error-summary {
+ color: #a94442;
+ background: #fdf7f7;
+ border-left: 3px solid #eed3d7;
+ padding: 10px 20px;
+ margin: 0 0 15px 0;
+}
+
+/* align the logout "link" (button in form) of the navbar */
+.nav > li > form {
+ padding: 8px;
+}
+
+.nav > li > form > button:hover {
+ text-decoration: none;
+}
diff --git a/backend/web/favicon.ico b/backend/web/favicon.ico
new file mode 100644
index 0000000..580ed73
Binary files /dev/null and b/backend/web/favicon.ico differ
diff --git a/backend/web/js/fieldWidget.js b/backend/web/js/fieldWidget.js
new file mode 100644
index 0000000..3d860ef
--- /dev/null
+++ b/backend/web/js/fieldWidget.js
@@ -0,0 +1,24 @@
+$(function(){
+ $.each($('.delete-field-item'), function(index, value) {
+ var container = $(value).parents('.field_list').first();
+ var count = $(container).find('.form-group').length;
+ if(count <= 1) {
+ $(container).find('.delete-field-item').addClass('hidden');
+ }
+ });
+ $(document).on('click', '.delete-field-item', function(){
+ var container = $(this).parents('.field_list').first();
+ $(this).parent('.form-group').remove();
+ var count = $(container).find('.form-group').length;
+ if(count <= 1) {
+ $(container).find('.delete-field-item').addClass('hidden');
+ }
+ });
+ $(document).on('click', '[class*=add_field_w]', function() {
+ var container = $(this).siblings('.field_list').first();
+ var count = $(container).find('.form-group').length;
+ if(count > 1) {
+ $(container).find('.delete-field-item').removeClass('hidden');
+ }
+ });
+});
\ No newline at end of file
diff --git a/backend/web/robots.txt b/backend/web/robots.txt
new file mode 100644
index 0000000..1f53798
--- /dev/null
+++ b/backend/web/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/common/behaviors/ShowImage.php b/common/behaviors/ShowImage.php
new file mode 100644
index 0000000..4960eea
--- /dev/null
+++ b/common/behaviors/ShowImage.php
@@ -0,0 +1,43 @@
+ 'getSlug'
+ ];
+ }
+
+ public function getSlug( $event )
+ {
+ if ( empty( $this->owner->{$this->out_attribute} ) ) {
+ $this->owner->{$this->out_attribute} = $this->generateSlug( $this->owner->{$this->in_attribute} );
+ } else {
+ $this->owner->{$this->out_attribute} = $this->generateSlug( $this->owner->{$this->out_attribute} );
+ }
+ }
+
+ private function generateSlug( $slug )
+ {
+ $slug = $this->slugify( $slug );
+ if ( $this->checkUniqueSlug( $slug ) ) {
+ return $slug;
+ } else {
+ for ( $suffix = 2; !$this->checkUniqueSlug( $new_slug = $slug . '-' . $suffix ); $suffix++ ) {}
+ return $new_slug;
+ }
+ }
+
+ private function slugify( $slug )
+ {
+ if ( $this->translit ) {
+ return yii\helpers\Inflector::slug( TransliteratorHelper::process( $slug ), '-', true );
+ } else {
+ return $this->slug( $slug, '-', true );
+ }
+ }
+
+ private function slug( $string, $replacement = '-', $lowercase = true )
+ {
+ $string = preg_replace( '/[^\p{L}\p{Nd}]+/u', $replacement, $string );
+ $string = trim( $string, $replacement );
+ return $lowercase ? strtolower( $string ) : $string;
+ }
+
+ private function checkUniqueSlug( $slug )
+ {
+ $pk = $this->owner->primaryKey();
+ $pk = $pk[0];
+
+ $condition = $this->out_attribute . ' = :out_attribute';
+ $params = [ ':out_attribute' => $slug ];
+ if ( !$this->owner->isNewRecord ) {
+ $condition .= ' and ' . $pk . ' != :pk';
+ $params[':pk'] = $this->owner->{$pk};
+ }
+
+ return !$this->owner->find()
+ ->where( $condition, $params )
+ ->one();
+ }
+
+}
\ No newline at end of file
diff --git a/common/components/LangRequest.php b/common/components/LangRequest.php
new file mode 100644
index 0000000..9b5ab3f
--- /dev/null
+++ b/common/components/LangRequest.php
@@ -0,0 +1,99 @@
+_lang_url === null)
+ {
+ $this->_lang_url = $this->getUrl();
+
+ $url_list = explode ('/', $this->_lang_url);
+
+ $lang_url = isset ($url_list[1]) ? $url_list[1] : null;
+
+ Language::setCurrent($lang_url);
+
+ if ($lang_url !== null && $lang_url === Language::getCurrent()->language_code
+ && strpos($this->_lang_url, Language::getCurrent()->language_code) === 1)
+ {
+ $this->_lang_url = substr ($this->_lang_url, strlen (Language::getCurrent()->language_code) + 1);
+ }
+ }
+
+ return $this->_lang_url;
+ }
+
+ protected function resolvePathInfo()
+ {
+ $pathInfo = $this->getLangUrl();
+
+ if (($pos = strpos ($pathInfo, '?')) !== false)
+ {
+ $pathInfo = substr ($pathInfo, 0, $pos);
+ }
+
+ $pathInfo = urldecode ($pathInfo);
+
+ // try to encode in UTF8 if not so
+ // http://w3.org/International/questions/qa-forms-utf-8.html
+ if (! preg_match ('%^(?:
+ [\x09\x0A\x0D\x20-\x7E] # ASCII
+ | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
+ | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
+ | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
+ | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
+ | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
+ | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
+ | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
+ )*$%xs', $pathInfo)
+ )
+ {
+ $pathInfo = utf8_encode($pathInfo);
+ }
+
+ $scriptUrl = $this->getScriptUrl();
+
+ $baseUrl = $this->getBaseUrl();
+
+ if (strpos($pathInfo, $scriptUrl) === 0)
+ {
+ $pathInfo = substr($pathInfo, strlen($scriptUrl));
+ }
+ else if ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0)
+ {
+ $pathInfo = substr($pathInfo, strlen($baseUrl));
+ }
+ elseif (isset ($_SERVER['PHP_SELF']) && strpos ($_SERVER['PHP_SELF'], $scriptUrl) === 0)
+ {
+ $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
+ }
+ else
+ {
+ throw new InvalidConfigException('Unable to determine the path info of the current request.');
+ }
+
+ if ($pathInfo[0] === '/')
+ {
+ $pathInfo = substr ($pathInfo, 1);
+ }
+
+ return (string) $pathInfo;
+ }
+}
\ No newline at end of file
diff --git a/common/components/LangUrlManager.php b/common/components/LangUrlManager.php
new file mode 100644
index 0000000..2170b02
--- /dev/null
+++ b/common/components/LangUrlManager.php
@@ -0,0 +1,37 @@
+is_default == 1 ? $url : '/'.$lang_code->lang_code.$url);
+ }
+}
\ No newline at end of file
diff --git a/common/components/Request.php b/common/components/Request.php
new file mode 100644
index 0000000..f3a4f37
--- /dev/null
+++ b/common/components/Request.php
@@ -0,0 +1,40 @@
+web, "", parent::getBaseUrl()) . $this->adminUrl;
+
+ }
+
+
+
+ public function resolvePathInfo()
+
+ {
+
+ if ($this->getUrl() === $this->adminUrl) {
+
+ return "";
+
+ } else {
+
+ return parent::resolvePathInfo();
+
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/common/components/artboxtree/ArtboxTreeBehavior.php b/common/components/artboxtree/ArtboxTreeBehavior.php
new file mode 100644
index 0000000..a5dc4b8
--- /dev/null
+++ b/common/components/artboxtree/ArtboxTreeBehavior.php
@@ -0,0 +1,370 @@
+ $keyNameDepth;
+
+ /**
+ * @var string
+ */
+ public $delimiter = '|';
+
+ /**
+ * @var ActiveRecord|self|null
+ */
+ protected $entity;
+
+ /**
+ * @param ActiveRecord $owner
+ * @throws Exception
+ */
+ public function attach($owner)
+ {
+ parent::attach($owner);
+ if ($this->keyNameId === null) {
+ $primaryKey = $owner->primaryKey();
+ if (!isset($primaryKey[0])) {
+ throw new Exception('"' . $owner->className() . '" must have a primary key.');
+ }
+ $this->keyNameId = $primaryKey[0];
+ }
+ }
+
+ public function events()
+ {
+ return [
+ // @todo Use beforeSave for automatic set MP-params
+ ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate',
+ ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
+ ];
+ }
+
+ /*
+ * Main methods
+ */
+
+ /*
+ * get one parent
+ * use AL-method
+ */
+ public function getParent() {
+ return $this->getParentAL();
+ }
+
+ /*
+ * get all parents
+ * use MP-method
+ */
+ public function getParents() {
+ return $this->getParentsMP();
+ }
+
+ /*
+ * get one-level children items
+ * use AL-method
+ */
+ public function getChildren() {
+ return $this->getChildrenAL();
+ }
+
+ /*
+ * get all-level children items
+ * use MP-method
+ */
+ public function getAllChildren($depth = null) {
+ return $this->getAllChildrenMP($depth);
+ }
+
+
+ /*
+ * ================================
+ * MP-methods
+ * ================================
+ */
+
+ /*
+ * Full-path (use MP-method)
+ */
+ public function getParentsMP($depth = null) {
+ $path = $this->getParentPath();
+ if ($path !== null) {
+ $paths = str_replace(['{', '}'], '', explode(',', $path));
+ if (!$this->primaryKeyMode) {
+ $path = null;
+ $paths = array_map(
+ function ($value) use (&$path) {
+ return $path = ($path !== null ? $path . ',' : '') . $value;
+ },
+ $paths
+ );
+ }
+ if ($depth !== null) {
+ $paths = array_slice($paths, -$depth);
+ }
+ } else {
+ $paths = [];
+ }
+
+ $tableName = $this->owner->tableName();
+ $condition = ['and'];
+ if ($this->primaryKeyMode) {
+ $condition[] = ["{$tableName}.[[{$this->keyNameId}]]" => $paths];
+ } else {
+ $condition[] = ["{$tableName}.[[{$this->keyNamePath}]]" => $paths];
+ }
+
+ $query = $this->owner->find()
+ ->andWhere($condition)
+ ->andWhere($this->treeCondition())
+ ->addOrderBy(["{$tableName}.[[{$this->keyNamePath}]]" => SORT_ASC]);
+ $query->multiple = true;
+
+ return $query;
+ }
+
+ /**
+ * @param bool $asArray = false
+ * @return null|string|array
+ */
+ public function getParentPath($asArray = false)
+ {
+ return static::getParentPathInternal($this->owner->getAttribute($this->keyNamePath), $asArray);
+ }
+
+ public function getAllChildrenMP($depth = null)
+ {
+ $tableName = $this->owner->tableName();
+ $path = $this->owner->getAttribute($this->keyNamePath);
+ $query = $this->owner->find()
+ ->andWhere(['@>', "{$tableName}.[[{$this->keyNamePath}]]", $this->getLike($path), false]);
+
+
+ if ($depth > 0) {
+ $query->andWhere(['<=', "{$tableName}.[[{$this->keyNameDepth}]]", $this->owner->getAttribute($this->keyNameDepth) + $depth]);
+ }
+
+ $orderBy = [];
+ $orderBy["{$tableName}.[[{$this->keyNameDepth}]]"] = SORT_ASC;
+ $orderBy["{$tableName}.[[{$this->keyNameId}]]"] = SORT_ASC;
+
+ $query
+ ->andWhere($this->treeCondition())
+ ->addOrderBy($orderBy);
+ $query->multiple = true;
+
+ return $query;
+ }
+
+ /*
+ * ================================
+ * AL methods
+ * ================================
+ */
+
+ /*
+ * Parent entity (use AL-method)
+ * @return \yii\db\ActiveRecord
+ */
+ public function getParentAL() {
+ $parent_id = $this->owner->getAttribute($this->keyNameParentId);
+ if (empty($parent_id))
+ return null;
+ return $this->owner->find()->where([$this->keyNameId => $parent_id, $this->keyNameGroup => $this->owner->getAttribute($this->keyNameGroup)])->one();
+ }
+
+ /*
+ * Get parents by AL-method
+ * @return array
+ */
+ public function getParentsAL() {
+ $parent_id = $this->owner->getAttribute($this->keyNameParentId);
+ if ($parent_id == 0) {
+ return [];
+ }
+
+ $parent = $this->owner;
+ $parents = [];
+ while(true) {
+ $parent = $parent->getParentAL();
+ if (is_null($parent))
+ break;
+ $parents[] = $parent;
+ }
+
+ return array_reverse($parents);
+ }
+
+ /*
+ * Children entities (one-step) (use AL-method)
+ * @return ActiveQuery
+ */
+ public function getChildrenAL() {
+ return $this->owner->find()->where([$this->keyNameParentId => $this->owner->getAttribute($this->keyNameId), $this->keyNameGroup => $this->owner->getAttribute($this->keyNameGroup)]);
+ }
+
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ /**
+ * @param array $changedAttributes
+ * @throws Exception
+ */
+ protected function _rebuildChildren($changedAttributes)
+ {
+ $path = isset($changedAttributes[$this->keyNamePath]) ? $changedAttributes[$this->keyNamePath] : $this->owner->getAttribute($this->keyNamePath);
+ $update = [];
+ $condition = [
+ 'and',
+ ['@>', "[[{$this->keyNamePath}]]", $path, false],
+ ];
+ if ($this->keyNameGroup !== null) {
+ $group = isset($changedAttributes[$this->keyNameGroup]) ? $changedAttributes[$this->keyNameGroup] : $this->owner->getAttribute($this->keyNameGroup);
+ $condition[] = [$this->keyNameGroup => $group];
+ }
+ $params = [];
+
+ if (isset($changedAttributes[$this->keyNamePath])) {
+ $substringExpr = $this->substringExpression(
+ "[[{$this->keyNamePath}]]",
+ 'array_length(:pathOld) + 1',
+ "array_length([[{$this->keyNamePath}]]) - array_length(:pathOld)"
+ );
+ $update[$this->keyNamePath] = new Expression($this->concatExpression([':pathNew', $substringExpr]));
+ $params[':pathOld'] = $path;
+ $params[':pathNew'] = $this->owner->getAttribute($this->keyNamePath);
+ }
+
+ if ($this->keyNameGroup !== null && isset($changedAttributes[$this->keyNameGroup])) {
+ $update[$this->keyNameGroup] = $this->owner->getAttribute($this->keyNameGroup);
+ }
+
+ if ($this->keyNameDepth !== null && isset($changedAttributes[$this->keyNameDepth])) {
+ $delta = $this->owner->getAttribute($this->keyNameDepth) - $changedAttributes[$this->keyNameDepth];
+ $update[$this->keyNameDepth] = new Expression("[[{$this->keyNameDepth}]]" . sprintf('%+d', $delta));
+ }
+ if (!empty($update)) {
+ $this->owner->updateAll($update, $condition, $params);
+ }
+ }
+
+ /**
+ * @param string $path
+ * @param string $delimiter
+ * @param bool $asArray = false
+ * @return null|string|array
+ */
+ protected static function getParentPathInternal($path, $asArray = false)
+ {
+ $path = str_replace(['{', '}'], '', explode(',', $path));
+ array_pop($path);
+ if ($asArray) {
+ return $path;
+ }
+ return count($path) > 0 ? implode(',', $path) : null;
+ }
+
+ protected function toLike($path) {
+ return strtr($path . ',', ['%' => '\%', '_' => '\_', '\\' => '\\\\']) . '%';
+ }
+
+ protected function concatExpression($items)
+ {
+ if ($this->owner->getDb()->driverName === 'sqlite' || $this->owner->getDb()->driverName === 'pgsql') {
+ return implode(' || ', $items);
+ }
+ return 'CONCAT(' . implode(',', $items) . ')';
+ }
+
+ protected function substringExpression($string, $from, $length)
+ {
+ if ($this->owner->getDb()->driverName === 'sqlite') {
+ return "SUBSTR({$string}, {$from}, {$length})";
+ }
+ return "SUBSTRING({$string}, {$from}, {$length})";
+ }
+
+ // =======================================================
+ public function afterInsert() {
+ $this->withSave();
+ $this->owner->updateAttributes([$this->keyNamePath => $this->owner->getAttribute($this->keyNamePath), $this->keyNameDepth => $this->owner->getAttribute($this->keyNameDepth)]);
+ }
+
+ public function beforeUpdate()
+ {
+ if ($this->owner->getIsNewRecord()) {
+ throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new entitys.');
+ }
+ $this->withSave();
+ }
+
+ protected function withSave() {
+ $id = $this->owner->getAttribute($this->keyNameId);
+ $parent_id = $this->owner->getAttribute($this->keyNameParentId);
+
+ if (is_null($parent_id)) {
+ $parent_id = 0;
+ }
+
+ // check parent_id value is changed!
+ /*if ($this->owner->getOldAttribute($this->keyNameParentId) == $parent_id) {
+ return;
+ }*/
+
+ // rebuild parents entities
+ if ($parent_id == 0) {
+ $depth = 0;
+ $path = [intval($id)];
+ } else {
+ $parents = $this->getParentsAL();
+ $path = [];
+ $depth = 0;
+ foreach ($parents as $entity) {
+ $path[] = $entity->getAttribute($this->keyNameId);
+ $depth++;
+ }
+ $path[] = intval($id);
+ }
+
+ $path = '{'. implode(',', $path) .'}';
+
+ // rebuild children entities (recurcive)
+// $this->_rebuildChildren([
+// $this->keyNamePath => $path
+// ]);
+
+ $this->owner->setAttribute('path_int', $path);
+// $this->owner->setAttribute($this->keyNamePath, $path);
+ $this->owner->setAttribute($this->keyNameDepth, $depth);
+ }
+
+ public function _recursiveRebuildChildren() {
+ $children = $this->getChildrenAL()->all();
+ $root_path = explode(',', $this->owner->getAttribute($this->keyNamePath));
+ $root_depth = $this->owner->getAttribute($this->keyNameDepth);
+
+ /** @var $child ActiveRecord */
+ foreach ($children as $child) {
+ $path = $root_path;
+ $path[] = $child->getAttribute($this->keyNameId);
+ $depth = $root_depth + 1;
+
+ $child->_recursiveRebuildChildren();
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/components/artboxtree/ArtboxTreeHelper.php b/common/components/artboxtree/ArtboxTreeHelper.php
new file mode 100644
index 0000000..d5da465
--- /dev/null
+++ b/common/components/artboxtree/ArtboxTreeHelper.php
@@ -0,0 +1,30 @@
+depth+1) . $value;
+ $result[$key] = $row;
+ if (!empty($item['children'])) {
+ self::_recursiveTreeMap($result, $item['children'], $from, $to, $symbol);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/components/artboxtree/ArtboxTreeQueryTrait.php b/common/components/artboxtree/ArtboxTreeQueryTrait.php
new file mode 100644
index 0000000..90891ca
--- /dev/null
+++ b/common/components/artboxtree/ArtboxTreeQueryTrait.php
@@ -0,0 +1,95 @@
+modelClass;
+ self::$model = new $class;
+ }
+ return self::$model;
+ }
+
+ public function getTree($group, $cached = true) {
+ if ($cached && isset(self::$cache_tree[$group]))
+ return self::$cache_tree[$group];
+
+ $model = $this->getModel();
+ $data = $this->andWhere([$model->keyNameGroup => $group])->all();
+ if (empty($data))
+ return [];
+
+ self::$cache_tree[$group] = $this->buildTree($data);
+
+ return self::$cache_tree[$group];
+ }
+
+ private function _recursiveRebuild($tree, $parentPath = null, $depth = 0) {
+ $model = $this->getModel();
+
+ foreach ($tree as $row) {
+ $path = (is_null($parentPath) ? '' : $parentPath . $model->delimiter) . $row['item']->getAttribute($model->keyNameId);
+ $row['item']->setAttribute($model->keyNamePath, $path);
+ $row['item']->setAttribute($model->keyNameDepth, $depth);
+ $row['item']->save();
+ if (!empty($row['children'])) {
+ $this->_recursiveRebuild($row['children'], $path, $depth+1);
+ }
+ }
+ }
+
+ /**
+ * @param int $group
+ */
+ public function rebuildMP($group) {
+ $tree = $this->getTree($group);
+
+ $this->_recursiveRebuild($tree);
+ }
+
+ protected function buildTree(array $data, $parentId = 0) {
+ $model = $this->getModel();
+
+ $result = [];
+ foreach ($data as $element) {
+ if ($element[$model->keyNameParentId] == $parentId) {
+ $children = $this->buildTree($data, $element[$model->keyNameId]);
+ $result[] = [
+ 'item' => $element,
+ 'children' => $children
+ ];
+ }
+ }
+ return $result;
+ }
+
+ public function normalizeTreeData(array $data, $parentId = null)
+ {
+ $model = $this->getModel();
+
+ $result = [];
+ foreach ($data as $element) {
+ if ($element[$model->keyNameParentId] == $parentId) {
+ $result[] = $element;
+ $children = $this->normalizeTreeData($data, $element[$model->keyNameId]);
+ if ($children) {
+ $result = array_merge($result, $children);
+ }
+ }
+ }
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/common/components/artboxtree/ArtboxTreeWidget.php b/common/components/artboxtree/ArtboxTreeWidget.php
new file mode 100644
index 0000000..fafa4a6
--- /dev/null
+++ b/common/components/artboxtree/ArtboxTreeWidget.php
@@ -0,0 +1,138 @@
+dataProvider === null) {
+ throw new InvalidConfigException('The "dataProvider" property must be set.');
+ }
+ if ($this->keyNameId === null) {
+ throw new InvalidConfigException('The "keyNameId" property must be set.');
+ }
+ if ($this->formatter == null) {
+ $this->formatter = Yii::$app->getFormatter();
+ } elseif (is_array($this->formatter)) {
+ $this->formatter = Yii::createObject($this->formatter);
+ }
+ if (!$this->formatter instanceof Formatter) {
+ throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
+ }
+ }
+
+ /**
+ * Runs the widget.
+ */
+ public function run()
+ {
+ if (!empty($this->assetBundle) && class_exists($this->assetBundle)) {
+ $view = $this->getView();
+ $assetBundle = $this->assetBundle;
+ $assetBundle::register($view);
+ }
+ if ($this->dataProvider->getCount() == 0) {
+ return $this->renderEmptyResult();
+ }
+
+ parent::run();
+ }
+
+ protected function renderEmptyResult() {
+ return empty($this->emptyResult) ? Yii::t('artbox', 'TreeViewEmptyResult') : Yii::t('artbox', $this->emptyResult);
+ }
+
+ /**
+ * Normalize tree data
+ * @param array $data
+ * @param string $parentId
+ * @return array
+ */
+ protected function _normalizeTreeData(array $data, $parentId = null) {
+ $result = [];
+ foreach ($data as $element) {
+ if ($element[$this->keyNameParentId] == $parentId) {
+ $result[] = $element;
+ $children = $this->_normalizeTreeData($data, $element[$this->keyNameId]);
+ if ($children) {
+ $result = array_merge($result, $children);
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Hierarchy tree data
+ * @param array $data
+ * @param string $parentId
+ * @return array
+ */
+ protected function _hierarchyTreeData(array $data, $parentId = null) {
+ $result = [];
+ foreach ($data as $element) {
+ if ($element[$this->keyNameParentId] == $parentId) {
+ $children = $this->_hierarchyTreeData($data, $element[$this->keyNameId]);
+ $result[] = [
+ 'item' => $element,
+ 'children' => $children
+ ];
+ }
+ }
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/common/components/artboxtree/treegrid/TreeGridColumn.php b/common/components/artboxtree/treegrid/TreeGridColumn.php
new file mode 100644
index 0000000..f7e2132
--- /dev/null
+++ b/common/components/artboxtree/treegrid/TreeGridColumn.php
@@ -0,0 +1,250 @@
+
+ */
+class TreeGridColumn extends Object {
+
+ /**
+ * @var TreeGrid the grid view object that owns this column.
+ */
+ public $grid;
+
+ /**
+ * @var string the header cell content. Note that it will not be HTML-encoded.
+ */
+ public $header;
+
+ /**
+ * @var string the footer cell content. Note that it will not be HTML-encoded.
+ */
+ public $footer;
+
+ /**
+ * @var callable This is a callable that will be used to generate the content of each cell.
+ * The signature of the function should be the following: `function ($model, $key, $index, $column)`.
+ * Where `$model`, `$key`, and `$index` refer to the model, key and index of the row currently being rendered
+ * and `$column` is a reference to the [[TreeColumn]] object.
+ */
+ public $content;
+
+ /**
+ * @var boolean whether this column is visible. Defaults to true.
+ */
+ public $visible = true;
+
+ /**
+ * @var array the HTML attributes for the column group tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $options = [];
+
+ /**
+ * @var array the HTML attributes for the header cell tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $headerOptions = [];
+
+ /**
+ * @var array|\Closure the HTML attributes for the data cell tag. This can either be an array of
+ * attributes or an anonymous function ([[Closure]]) that returns such an array.
+ * The signature of the function should be the following: `function ($model, $key, $index, $column)`.
+ * Where `$model`, `$key`, and `$index` refer to the model, key and index of the row currently being rendered
+ * and `$column` is a reference to the [[Column]] object.
+ * A function may be used to assign different attributes to different rows based on the data in that row.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $contentOptions = [];
+
+ /**
+ * @var array the HTML attributes for the footer cell tag.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $footerOptions = [];
+
+ /**
+ * @var string the attribute name associated with this column. When neither [[content]] nor [[value]]
+ * is specified, the value of the specified attribute will be retrieved from each data model and displayed.
+ *
+ * Also, if [[label]] is not specified, the label associated with the attribute will be displayed.
+ */
+ public $attribute;
+
+ /**
+ * @var string label to be displayed in the [[header|header cell]] and also to be used as the sorting
+ * link label when sorting is enabled for this column.
+ * If it is not set and the models provided by the GridViews data provider are instances
+ * of [[\yii\db\ActiveRecord]], the label will be determined using [[\yii\db\ActiveRecord::getAttributeLabel()]].
+ * Otherwise [[\yii\helpers\Inflector::camel2words()]] will be used to get a label.
+ */
+ public $label;
+
+ /**
+ * @var boolean whether the header label should be HTML-encoded.
+ * @see label
+ */
+ public $encodeLabel = true;
+
+ /**
+ * @var string|\Closure an anonymous function or a string that is used to determine the value to display in the current column.
+ *
+ * If this is an anonymous function, it will be called for each row and the return value will be used as the value to
+ * display for every data model. The signature of this function should be: `function ($model, $key, $index, $column)`.
+ * Where `$model`, `$key`, and `$index` refer to the model, key and index of the row currently being rendered
+ * and `$column` is a reference to the [[DataColumn]] object.
+ *
+ * You may also set this property to a string representing the attribute name to be displayed in this column.
+ * This can be used when the attribute to be displayed is different from the [[attribute]] that is used for
+ * sorting and filtering.
+ *
+ * If this is not set, `$model[$attribute]` will be used to obtain the value, where `$attribute` is the value of [[attribute]].
+ */
+ public $value;
+
+ /**
+ * @var string|array in which format should the value of each data model be displayed as (e.g. `"raw"`, `"text"`, `"html"`,
+ * `['date', 'php:Y-m-d']`). Supported formats are determined by the [[GridView::formatter|formatter]] used by
+ * the [[GridView]]. Default format is "text" which will format the value as an HTML-encoded plain text when
+ * [[\yii\i18n\Formatter]] is used as the [[GridView::$formatter|formatter]] of the GridView.
+ */
+ public $format = 'text';
+
+ /**
+ * Renders the header cell.
+ */
+ public function renderHeaderCell()
+ {
+ return Html::tag('th', $this->renderHeaderCellContent(), $this->headerOptions);
+ }
+
+ /**
+ * Renders the footer cell.
+ */
+ public function renderFooterCell()
+ {
+ return Html::tag('td', $this->renderFooterCellContent(), $this->footerOptions);
+ }
+
+ /**
+ * Renders a data cell.
+ * @param mixed $model the data model being rendered
+ * @param mixed $key the key associated with the data model
+ * @param integer $index the zero-based index of the data item among the item array returned by [[GridView::dataProvider]].
+ * @return string the rendering result
+ */
+ public function renderDataCell($model, $key, $index, $is_first = false, $symbol = '–')
+ {
+ if ($this->contentOptions instanceof Closure) {
+ $options = call_user_func($this->contentOptions, $model, $key, $index, $this);
+ } else {
+ $options = $this->contentOptions;
+ }
+ return Html::tag('td', ($is_first ? str_repeat($symbol, $model->depth) : '') . $this->renderDataCellContent($model, $key, $index), $options);
+ }
+
+ /**
+ * Renders the header cell content.
+ * The default implementation simply renders [[header]].
+ * This method may be overridden to customize the rendering of the header cell.
+ * @return string the rendering result
+ */
+ protected function renderHeaderCellContent()
+ {
+ if ($this->header !== null || $this->label === null && $this->attribute === null) {
+ return trim($this->header) !== '' ? $this->header : $this->grid->emptyCell;
+ }
+
+ $provider = $this->grid->dataProvider;
+
+ if ($this->label === null) {
+ if ($provider instanceof ActiveDataProvider && $provider->query instanceof ActiveQueryInterface) {
+ /* @var $model Model */
+ $model = new $provider->query->modelClass;
+ $label = $model->getAttributeLabel($this->attribute);
+ } else {
+ $models = $provider->getModels();
+ if (($model = reset($models)) instanceof Model) {
+ /* @var $model Model */
+ $label = $model->getAttributeLabel($this->attribute);
+ } else {
+ $label = Inflector::camel2words($this->attribute);
+ }
+ }
+ } else {
+ $label = $this->label;
+ }
+
+ return $this->encodeLabel ? Html::encode($label) : $label;
+ }
+
+ /**
+ * Renders the footer cell content.
+ * The default implementation simply renders [[footer]].
+ * This method may be overridden to customize the rendering of the footer cell.
+ * @return string the rendering result
+ */
+ protected function renderFooterCellContent()
+ {
+ return trim($this->footer) !== '' ? $this->footer : $this->grid->emptyCell;
+ }
+
+ /**
+ * Renders the data cell content.
+ * @param mixed $model the data model
+ * @param mixed $key the key associated with the data model
+ * @param integer $index the zero-based index of the data model among the models array returned by [[GridView::dataProvider]].
+ * @return string the rendering result
+ */
+ protected function renderDataCellContent($model, $key, $index)
+ {
+ if ($this->content === null) {
+ return $this->grid->formatter->format($this->getDataCellValue($model, $key, $index), $this->format);
+ } else {
+ if ($this->content !== null) {
+ return call_user_func($this->content, $model, $key, $index, $this);
+ } else {
+ return $this->grid->emptyCell;
+ }
+ }
+
+
+ }
+
+ /**
+ * Returns the data cell value.
+ * @param mixed $model the data model
+ * @param mixed $key the key associated with the data model
+ * @param integer $index the zero-based index of the data model among the models array returned by [[GridView::dataProvider]].
+ * @return string the data cell value
+ */
+ public function getDataCellValue($model, $key, $index)
+ {
+ if ($this->value !== null) {
+ if (is_string($this->value)) {
+ return ArrayHelper::getValue($model, $this->value);
+ } else {
+ return call_user_func($this->value, $model, $key, $index, $this);
+ }
+ } elseif ($this->attribute !== null) {
+ return ArrayHelper::getValue($model, $this->attribute);
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/common/components/artboxtree/treegrid/TreeGridWidget.php b/common/components/artboxtree/treegrid/TreeGridWidget.php
new file mode 100644
index 0000000..908ba69
--- /dev/null
+++ b/common/components/artboxtree/treegrid/TreeGridWidget.php
@@ -0,0 +1,277 @@
+ 'table table-striped table-bordered'];
+
+ /**
+ * @var array The plugin options
+ */
+ public $pluginOptions = [];
+
+ /**
+ * @var boolean whether to show the grid view if [[dataProvider]] returns no data.
+ */
+ public $showOnEmpty = true;
+
+ public $rowOptions = [];
+
+ /**
+ * @var Closure an anonymous function that is called once BEFORE rendering each data model.
+ * It should have the similar signature as [[rowOptions]]. The return result of the function
+ * will be rendered directly.
+ */
+ public $beforeRow;
+
+ /**
+ * @var Closure an anonymous function that is called once AFTER rendering each data model.
+ * It should have the similar signature as [[rowOptions]]. The return result of the function
+ * will be rendered directly.
+ */
+ public $afterRow;
+
+ /**
+ * @var boolean whether to show the header section of the grid table.
+ */
+ public $showHeader = true;
+
+ /**
+ * @var array the HTML attributes for the table header row.
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public $headerRowOptions = [];
+
+ /**
+ * @var boolean whether to show the footer section of the grid table.
+ */
+ public $showFooter = false;
+
+ /**
+ * @var string the HTML display when the content of a cell is empty
+ */
+ public $emptyCell = ' ';
+
+ public $levelSymbol = '–';
+
+ /**
+ * Init the widget object.
+ */
+ public function init() {
+ parent::init();
+
+ $this->initColumns();
+ }
+
+ /**
+ * Runs the widget.
+ */
+ public function run() {
+ $run = parent::run();
+ if (!is_null($run))
+ return $run;
+
+ if ($this->showOnEmpty || $this->dataProvider->getCount() > 0) {
+ $pagination = $this->dataProvider->getPagination();
+ $pagination->setPageSize($this->dataProvider->getTotalCount());
+
+ $header = $this->showHeader ? $this->renderTableHeader() : false;
+ $body = $this->renderItems();
+ $footer = $this->showFooter ? $this->renderTableFooter() : false;
+
+ $content = array_filter([
+ $header,
+ $body,
+ $footer
+ ]);
+
+ return Html::tag('table', implode("\n", $content), $this->options);
+ } else {
+ return $this->renderEmptyResult();
+ }
+ }
+
+ /**
+ * Renders the table header.
+ * @return string the rendering result.
+ */
+ public function renderTableHeader()
+ {
+ $cells = [];
+ foreach ($this->columns as $column) {
+ /* @var $column TreeGridColumn */
+ $cells[] = $column->renderHeaderCell();
+ }
+ $content = Html::tag('tr', implode('', $cells), $this->headerRowOptions);
+ return "\n" . $content . "\n ";
+ }
+
+ /**
+ * Renders the table footer.
+ * @return string the rendering result.
+ */
+ public function renderTableFooter()
+ {
+ $cells = [];
+ foreach ($this->columns as $column) {
+ /* @var $column TreeGridColumn */
+ $cells[] = $column->renderFooterCell();
+ }
+ $content = Html::tag('tr', implode('', $cells), $this->footerRowOptions);
+ return "\n" . $content . "\n ";
+ }
+
+ /**
+ * Renders the data models for the grid view.
+ */
+ public function renderItems()
+ {
+ $rows = [];
+ $models = array_values($this->dataProvider->getModels());
+ $keys = $this->dataProvider->getKeys();
+ $models = TaxOption::find()->normalizeTreeData($models, $this->rootParentId);
+ foreach ($models as $index => $model) {
+ $key = $keys[$index];
+ if ($this->beforeRow !== null) {
+ $row = call_user_func($this->beforeRow, $model, $key, $index, $this);
+ if (!empty($row)) {
+ $rows[] = $row;
+ }
+ }
+
+ $rows[] = $this->renderTableRow($model, $key, $index);
+
+ if ($this->afterRow !== null) {
+ $row = call_user_func($this->afterRow, $model, $key, $index, $this);
+ if (!empty($row)) {
+ $rows[] = $row;
+ }
+ }
+ }
+
+ if (empty($rows)) {
+ $colspan = count($this->columns);
+ return "" . $this->renderEmpty() . " ";
+ } else {
+ return implode("\n", $rows);
+ }
+ }
+
+ /**
+ * Renders a table row with the given data model and key.
+ * @param mixed $model the data model to be rendered
+ * @param mixed $key the key associated with the data model
+ * @param integer $index the zero-based index of the data model among the model array returned by [[dataProvider]].
+ * @return string the rendering result
+ */
+ public function renderTableRow($model, $key, $index)
+ {
+ $cells = [];
+ /* @var $column TreeGridColumn */
+ $i = 0;
+ foreach ($this->columns as $column) {
+ $cells[] = $column->renderDataCell($model, $key, $index, $i == 0, $this->levelSymbol);
+ $i++;
+ }
+ if ($this->rowOptions instanceof Closure) {
+ $options = call_user_func($this->rowOptions, $model, $key, $index, $this);
+ } else {
+ $options = $this->rowOptions;
+ }
+ $options['data-key'] = is_array($key) ? json_encode($key) : (string) $key;
+
+ $id = ArrayHelper::getValue($model, $this->keyNameId);
+ Html::addCssClass($options, "treegrid-$id");
+
+ $parentId = ArrayHelper::getValue($model, $this->keyNameParentId);
+ if ($parentId) {
+ Html::addCssClass($options, "treegrid-parent-$parentId");
+ }
+
+ return Html::tag('tr', implode('', $cells), $options);
+ }
+
+ /**
+ * Creates column objects and initializes them.
+ */
+ protected function initColumns()
+ {
+ if (empty($this->columns)) {
+ $this->guessColumns();
+ }
+ foreach ($this->columns as $i => $column) {
+ if (is_string($column)) {
+ $column = $this->createDataColumn($column);
+ } else {
+ $column = Yii::createObject(array_merge([
+ 'class' => $this->dataColumnClass ? : TreeGridColumn::className(),
+ 'grid' => $this,
+ ], $column));
+ }
+ if (!$column->visible) {
+ unset($this->columns[$i]);
+ continue;
+ }
+ $this->columns[$i] = $column;
+ }
+ }
+
+ /**
+ * Creates a [[DataColumn]] object based on a string in the format of "attribute:format:label".
+ * @param string $text the column specification string
+ * @return DataColumn the column instance
+ * @throws InvalidConfigException if the column specification is invalid
+ */
+ protected function createDataColumn($text)
+ {
+ if (!preg_match('/^([^:]+)(:(\w*))?(:(.*))?$/', $text, $matches)) {
+ throw new InvalidConfigException('The column must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"');
+ }
+
+ return Yii::createObject([
+ 'class' => $this->dataColumnClass ? : TreeGridColumn::className(),
+ 'grid' => $this,
+ 'attribute' => $matches[1],
+ 'format' => isset($matches[3]) ? $matches[3] : 'text',
+ 'label' => isset($matches[5]) ? $matches[5] : null,
+ ]);
+ }
+
+ /**
+ * This function tries to guess the columns to show from the given data
+ * if [[columns]] are not explicitly specified.
+ */
+ protected function guessColumns()
+ {
+ $models = $this->dataProvider->getModels();
+ $model = reset($models);
+ if (is_array($model) || is_object($model)) {
+ foreach ($model as $name => $value) {
+ $this->columns[] = $name;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/components/artboxtree/treelist/TreeListWidget.php b/common/components/artboxtree/treelist/TreeListWidget.php
new file mode 100644
index 0000000..868e82e
--- /dev/null
+++ b/common/components/artboxtree/treelist/TreeListWidget.php
@@ -0,0 +1,62 @@
+_hierarchyTreeData(array_values($this->dataProvider->getModels()), $this->rootParentId);
+ return $this->renderTreelist($models);
+ }
+
+ protected function renderTreelist($models) {
+ foreach ($models as $index => $model) {
+ $row = $this->renderTreelistItem($model['item']);
+ $children = empty($model['children']) ? '' : $this->renderTreelist($model['children']);
+ $output[] = ''. $row . $children .' ';
+ }
+
+ if (!empty($output))
+ return ''. implode("\n", $output) .' ';
+ }
+
+ protected function renderTreelistItem($model)
+ {
+ $options = [];
+ $id = ArrayHelper::getValue($model, $this->keyNameId);
+ Html::addCssClass($options, "treelistitem-$id");
+
+ $parent_id = ArrayHelper::getValue($model, $this->keyNameParentId);
+ if ($parent_id) {
+ Html::addCssClass($options, "treelistitem-parent-$parent_id");
+ }
+
+// if (is_string($this->value)) {
+// return ArrayHelper::getValue($model, $this->value);
+// } else {
+// return call_user_func($this->value, $model, $key, $index, $this);
+// }
+
+ return Html::tag('span', ArrayHelper::getValue($model, $this->displayField), $options);
+ }
+}
\ No newline at end of file
diff --git a/common/components/artboxtree/treemenu/TreeMenuWidget.php b/common/components/artboxtree/treemenu/TreeMenuWidget.php
new file mode 100644
index 0000000..381cb91
--- /dev/null
+++ b/common/components/artboxtree/treemenu/TreeMenuWidget.php
@@ -0,0 +1,62 @@
+_hierarchyTreeData(array_values($this->dataProvider->getModels()), $this->rootParentId);
+ return $this->renderTreelist($models);
+ }
+
+ protected function renderTreelist($models) {
+ foreach ($models as $index => $model) {
+ $row = $this->renderTreelistItem($model['item']);
+ $children = empty($model['children']) ? '' : $this->renderTreelist($model['children']);
+ $output[] = ''. $row . $children .' ';
+ }
+
+ if (!empty($output))
+ return ''. implode("\n", $output) .' ';
+ }
+
+ protected function renderTreelistItem($model)
+ {
+ $options = [];
+ $id = ArrayHelper::getValue($model, $this->keyNameId);
+ Html::addCssClass($options, "treelistitem-$id");
+
+ $parent_id = ArrayHelper::getValue($model, $this->keyNameParentId);
+ if ($parent_id) {
+ Html::addCssClass($options, "treelistitem-parent-$parent_id");
+ }
+
+// if (is_string($this->value)) {
+// return ArrayHelper::getValue($model, $this->value);
+// } else {
+// return call_user_func($this->value, $model, $key, $index, $this);
+// }
+
+ return Html::tag('span', ArrayHelper::getValue($model, $this->displayField), $options);
+ }
+}
\ No newline at end of file
diff --git a/common/config/.gitignore b/common/config/.gitignore
new file mode 100644
index 0000000..97c0f01
--- /dev/null
+++ b/common/config/.gitignore
@@ -0,0 +1,2 @@
+main-local.php
+params-local.php
diff --git a/common/config/bootstrap.php b/common/config/bootstrap.php
new file mode 100644
index 0000000..fb64fc7
--- /dev/null
+++ b/common/config/bootstrap.php
@@ -0,0 +1,6 @@
+ 'ru-RU',
+ 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
+ 'controllerMap' => [
+ 'elfinder' => [
+ 'class' => 'mihaildev\elfinder\Controller',
+ 'access' => ['@'], //глобальный доступ к фаил менеджеру @ - для авторизорованных , ? - для гостей , чтоб открыть всем ['@', '?']
+ 'disabledCommands' => ['netmount'], //отключение ненужных команд https://github.com/Studio-42/elFinder/wiki/Client-configuration-options#commands
+ 'roots' => [
+ [
+ 'class' => 'mihaildev\elfinder\UserPath',
+ 'path' => '../../storage/user_{id}',
+ 'name' => 'My Documents'
+ ],
+ ],
+ 'watermark' => [
+ 'source' => __DIR__.'/logo.png', // Path to Water mark image
+ 'marginRight' => 5, // Margin right pixel
+ 'marginBottom' => 5, // Margin bottom pixel
+ 'quality' => 95, // JPEG image save quality
+ 'transparency' => 70, // Water mark image transparency ( other than PNG )
+ 'targetType' => IMG_GIF|IMG_JPG|IMG_PNG|IMG_WBMP, // Target image formats ( bit-field )
+ 'targetMinPixel' => 200 // Target image minimum pixel size
+ ]
+ ],
+ 'artbox-comment' => [
+ 'class' => \common\modules\comment\Controller::className(),
+ ],
+ ],
+ 'components' => [
+ 'assetManager' => [
+ 'bundles' => [
+ 'yii\web\JqueryAsset' =>[
+ 'jsOptions' => ['position' => \yii\web\View::POS_HEAD]
+ ]
+ ],
+ ],
+ 'cache' => [
+ 'class' => 'yii\caching\FileCache',
+ ],
+ 'urlManager' => [
+ 'enablePrettyUrl' => true,
+ 'showScriptName' => false,
+ 'rules' => [
+ 'module///' => '//',
+ ]
+ ],
+ 'i18n' => [
+ 'translations' => [
+ '*' => [
+ 'class' => 'yii\i18n\PhpMessageSource',
+ 'basePath' => '@common/translation',
+ 'fileMap' => [
+ 'app' => 'app.php',
+ 'app/error' => 'error.php',
+ ],
+ ],
+ 'app' => [
+ 'class' => 'yii\i18n\PhpMessageSource',
+ 'basePath' => '@common/translation',
+ 'fileMap' => [
+ 'app' => 'app.php',
+ 'app/error' => 'error.php',
+ ],
+ ],
+ ],
+ ],
+ ],
+
+ 'modules' => [
+ 'file' => [
+ 'class' => 'common\modules\file\Module',
+ ],
+ 'relation' => [
+ 'class' => 'common\modules\relation\Module',
+ 'relations' => [
+ 'product_categories' => [
+ 'name' => Yii::t('product', 'Categories'),
+ 'field' => 'categories',
+ 'entity1' => [
+ 'model' => '\common\modules\product\models\Product',
+ 'label' => 'Product',
+ 'listField' => 'fullname',
+ 'key' => 'product_id',
+ 'linked_key' => 'product_id',
+ ],
+ 'entity2' => [
+ 'model' => '\common\modules\rubrication\models\TaxOption',
+ 'label' => 'Category',
+ 'listField' => 'ValueRenderFlash',
+ 'key' => 'tax_option_id',
+ 'linked_key' => 'category_id',
+ 'where' => [
+ 'tax_group_id' => 1
+ ],
+ 'hierarchy' => [
+ 'key' => 'tax_option_id',
+ 'parentKey' => 'parent_id',
+ ]
+ ],
+ 'via' => [
+ 'model' => '\common\modules\product\models\ProductCategory',
+ ]
+ ],
+ 'relation_categories' => [
+ 'name' => Yii::t('relation', 'Relation categories'),
+ 'field' => 'categories',
+ 'entity1' => [
+ 'model' => '\common\modules\product\models\Product',
+ 'label' => 'Product',
+ 'listField' => 'fullname',
+ 'key' => 'product_id',
+ 'linked_key' => 'product_id',
+ ],
+ 'entity2' => [
+ 'model' => '\common\modules\rubrication\models\TaxOption',
+ 'label' => 'Category',
+ 'listField' => 'ValueRenderFlash',
+ 'key' => 'tax_option_id',
+ 'linked_key' => 'category_id',
+ 'where' => [
+ 'tax_group_id' => 1
+ ]
+ ],
+ 'via' => [
+ 'model' => '\common\modules\relation\models\Relation',
+ 'alias' => 'alias',
+ ]
+ ],
+ 'tax_option_to_group' => [
+ 'name' => 'Options-Groups',
+ 'field' => 'tax_option_to_group',
+ 'linked_table' => 'tax_option_to_group',
+ 'entity1' => [
+ 'label' => 'Option',
+ 'listField' => 'ValueRenderFlash',
+ 'model' => '\common\modules\rubrication\models\TaxOption',
+ 'key' => 'tax_option_id',
+ 'linked_key' => 'tax_option_id',
+ ],
+ 'entity2' => [
+ 'label' => 'Group',
+ 'listField' => 'name',
+ 'model' => '\common\modules\rubrication\models\TaxGroup',
+ 'key' => 'tax_group_id',
+ 'linked_key' => 'tax_group_id',
+ ],
+ 'via' => [
+ 'model' => 'common\modules\rubrication\models\TaxOptionToGroup',
+ 'alias' => 'alias',
+ ]
+ ],
+ 'tax_option_to_option' => [
+ 'name' => 'Options-Options',
+ 'field' => 'tax_option_to_option',
+ 'entity1' => [
+ 'label' => 'Option',
+ 'listField' => 'ValueRenderFlash',
+ 'model' => '\common\modules\rubrication\models\TaxOption',
+ 'key' => 'tax_option_id',
+ 'linked_key' => 'tax_option1_id',
+ ],
+ 'entity2' => [
+ 'label' => 'Option',
+ 'listField' => 'ValueRenderFlash',
+ 'model' => '\common\modules\rubrication\models\TaxOption',
+ 'key' => 'tax_option_id',
+ 'linked_key' => 'tax_option2_id',
+ ],
+ 'via' => [
+ 'model' => 'common\modules\rubrication\models\TaxOptionRelation',
+ 'alias' => 'alias',
+ ]
+ ],
+ 'brand_cats' => [
+ 'name' => 'Категории производителей',
+ 'field' => 'tax_option_to_option',
+ 'entity1' => [
+ 'label' => 'Бренд',
+ 'listField' => 'ValueRenderFlash',
+ 'model' => '\common\modules\rubrication\models\TaxOption',
+ 'key' => 'tax_option_id',
+ 'linked_key' => 'tax_option1_id',
+ ],
+ 'entity2' => [
+ 'label' => 'Категория',
+ 'listField' => 'ValueRenderFlash',
+ 'model' => '\common\modules\rubrication\models\TaxOption',
+ 'key' => 'tax_option_id',
+ 'linked_key' => 'tax_option2_id',
+ ],
+ 'via' => [
+ 'model' => 'common\modules\rubrication\models\TaxOptionRelation',
+ 'alias' => 'alias',
+ ]
+ ]
+ ]
+ ],
+ 'comment' => [
+ 'class' => 'common\modules\comment\Module',
+ 'useRbac' => true,
+ 'rbac' => [
+ 'rules' => [
+ \common\modules\comment\rbac\ArtboxCommentCreateRule::className(),
+ \common\modules\comment\rbac\ArtboxCommentDeleteRule::className(),
+ \common\modules\comment\rbac\ArtboxCommentUpdateRule::className(),
+ \common\modules\comment\rbac\ArtboxCommentUpdateOwnRule::className(),
+ \common\modules\comment\rbac\ArtboxCommentDeleteOwnRule::className(),
+ ],
+ 'permissions' => [
+ [
+ 'name' => common\modules\comment\Permissions::CREATE,
+ 'description' => 'Can create comments',
+ 'ruleName' =>(new \common\modules\comment\rbac\ArtboxCommentCreateRule())->name,
+ ],
+ [
+ 'name' => common\modules\comment\Permissions::UPDATE,
+ 'description' => 'Can update comments',
+ 'ruleName' =>(new \common\modules\comment\rbac\ArtboxCommentUpdateRule())->name,
+ ],
+ [
+ 'name' => common\modules\comment\Permissions::DELETE,
+ 'description' => 'Can delete comments',
+ 'ruleName' =>(new \common\modules\comment\rbac\ArtboxCommentDeleteRule())->name,
+ ],
+ [
+ 'name' => common\modules\comment\Permissions::UPDATE_OWN,
+ 'description' => 'Can update own comments',
+ 'ruleName' =>(new \common\modules\comment\rbac\ArtboxCommentUpdateOwnRule())->name,
+ ],
+ [
+ 'name' => common\modules\comment\Permissions::DELETE_OWN,
+ 'description' => 'Can delete own comments',
+ 'ruleName' =>(new \common\modules\comment\rbac\ArtboxCommentDeleteOwnRule())->name,
+ ],
+ ],
+ ],
+
+ ],
+ ],
+ 'language' => 'ru-RU'
+];
diff --git a/common/config/params.php b/common/config/params.php
new file mode 100644
index 0000000..4ec9ba6
--- /dev/null
+++ b/common/config/params.php
@@ -0,0 +1,6 @@
+ 'admin@example.com',
+ 'supportEmail' => 'support@example.com',
+ 'user.passwordResetTokenExpire' => 3600,
+];
diff --git a/common/mail/layouts/html.php b/common/mail/layouts/html.php
new file mode 100644
index 0000000..bddbc61
--- /dev/null
+++ b/common/mail/layouts/html.php
@@ -0,0 +1,22 @@
+
+beginPage() ?>
+
+
+
+
+ = Html::encode($this->title) ?>
+ head() ?>
+
+
+ beginBody() ?>
+ = $content ?>
+ endBody() ?>
+
+
+endPage() ?>
diff --git a/common/mail/layouts/text.php b/common/mail/layouts/text.php
new file mode 100644
index 0000000..7087cea
--- /dev/null
+++ b/common/mail/layouts/text.php
@@ -0,0 +1,12 @@
+
+beginPage() ?>
+beginBody() ?>
+= $content ?>
+endBody() ?>
+endPage() ?>
diff --git a/common/mail/passwordResetToken-html.php b/common/mail/passwordResetToken-html.php
new file mode 100644
index 0000000..f3daf49
--- /dev/null
+++ b/common/mail/passwordResetToken-html.php
@@ -0,0 +1,15 @@
+urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]);
+?>
+
+
Hello = Html::encode($user->username) ?>,
+
+
Follow the link below to reset your password:
+
+
= Html::a(Html::encode($resetLink), $resetLink) ?>
+
diff --git a/common/mail/passwordResetToken-text.php b/common/mail/passwordResetToken-text.php
new file mode 100644
index 0000000..244c0cb
--- /dev/null
+++ b/common/mail/passwordResetToken-text.php
@@ -0,0 +1,12 @@
+urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]);
+?>
+Hello = $user->username ?>,
+
+Follow the link below to reset your password:
+
+= $resetLink ?>
diff --git a/common/models/Blog.php b/common/models/Blog.php
new file mode 100644
index 0000000..1547b6b
--- /dev/null
+++ b/common/models/Blog.php
@@ -0,0 +1,107 @@
+ BlameableBehavior::className(),
+ 'createdByAttribute' => 'user_id',
+ 'updatedByAttribute' => false,
+ ],
+ [
+ 'class' => TimestampBehavior::className(),
+ 'createdAtAttribute' => 'date_add',
+ 'updatedAtAttribute' => false,
+ 'value' => new Expression('NOW()'),
+ ],
+ 'slug' => [
+ 'class' => 'common\behaviors\Slug',
+ 'in_attribute' => 'name',
+ 'out_attribute' => 'link',
+ 'translit' => true
+ ]
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [
+ [ 'name', 'description' ],
+ 'required',
+ ],
+ [
+ [ 'description' ],
+ 'string',
+ ],
+ [
+ [
+ 'name',
+ 'link',
+ 'cover',
+ ],
+ 'string',
+ 'max' => 255,
+ ],
+ ];
+ }
+
+ public function getDateCreate(){
+ return date('Y-m-d',strtotime($this->date_add));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'blog_id' => Yii::t('app', 'Blog ID'),
+ 'user_id' => Yii::t('app', 'User ID'),
+ 'name' => Yii::t('app', 'Название'),
+ 'link' => Yii::t('app', 'URL'),
+ 'date_add' => Yii::t('app', 'Дата добавления'),
+ 'user_add_id' => Yii::t('app', 'User Add ID'),
+ 'view_count' => Yii::t('app', 'Количество просмотров'),
+ 'description' => Yii::t('app', 'Описание'),
+ 'cover' => Yii::t('app', 'Фото главное'),
+ ];
+ }
+ }
diff --git a/common/models/BlogSearch.php b/common/models/BlogSearch.php
new file mode 100644
index 0000000..8faadac
--- /dev/null
+++ b/common/models/BlogSearch.php
@@ -0,0 +1,135 @@
+ date('Y-m-d', 0),
+ ],
+ [
+ [
+ 'date_add_to',
+ ],
+ 'default',
+ 'value' => date('Y-m-d'),
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function scenarios()
+ {
+ // bypass scenarios() implementation in the parent class
+ return Model::scenarios();
+ }
+
+ /**
+ * Creates data provider instance with search query applied
+ *
+ * @param array $params
+ *
+ * @return ActiveDataProvider
+ */
+ public function search($params)
+ {
+ $query = Blog::find();
+
+ // add conditions that should always apply here
+
+ $dataProvider = new ActiveDataProvider([
+ 'query' => $query,
+ ]);
+
+ $this->load($params);
+
+ if(!$this->validate()) {
+ // uncomment the following line if you do not want to return any records when validation fails
+ // $query->where('0=1');
+ return $dataProvider;
+ }
+
+ $query->andWhere([ 'user_id' => \Yii::$app->user->getId() ]);
+
+ // grid filtering conditions
+ $query->andFilterWhere([
+ 'blog_id' => $this->blog_id,
+ 'date_add' => $this->date_add,
+ 'user_add_id' => $this->user_add_id,
+ 'view_count' => $this->view_count,
+ ]);
+
+ $query->andFilterWhere([
+ 'between',
+ 'date_add',
+ $this->date_add_from,
+ (new \DateTime($this->date_add_to))->modify('+1 day')->format('Y-m-d')
+ ]);
+
+ $query->andFilterWhere([
+ 'like',
+ 'name',
+ $this->name,
+ ])
+ ->andFilterWhere([
+ 'like',
+ 'description',
+ $this->description,
+ ])
+ ->andFilterWhere([
+ 'like',
+ 'cover',
+ $this->cover,
+ ]);
+
+ return $dataProvider;
+ }
+
+ }
diff --git a/common/models/Fields.php b/common/models/Fields.php
new file mode 100644
index 0000000..dcd60fc
--- /dev/null
+++ b/common/models/Fields.php
@@ -0,0 +1,155 @@
+ 255]
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'id' => 'ID',
+ 'table_name' => 'Model Name',
+ 'table_id' => 'Model ID',
+ 'value' => 'Value',
+ 'field_name' => 'Field Name',
+ 'language' => 'Language',
+ ];
+ }
+
+ public static function getData($id, $model, $type){
+ $data = ArrayHelper::toArray(self::find()->where(['table_id'=>$id, 'table_name'=>$model, 'field_type'=>$type])->all());
+ $result = [];
+ for($i=0; $i < count($data); $i ++){
+ $result[$data[$i]['parent_key']][$data[$i]['field_name']] = $data[$i]['value'];
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * @param $post - array with field data
+ * @param $table_id - row id in model table
+ * @param $table_name - madel table name
+ * @param $language - language id
+ */
+
+ public static function saveFieldData($post,$table_id,$table_name, $language){
+
+ self::deleteAll(['table_id'=>$table_id, 'table_name'=>$table_name, 'language' => $language, 'field_type' => array_keys($post)]);
+
+ if($post){
+
+
+ foreach($post as $k => $field){
+
+
+
+ foreach($field as $parent_key => $row){
+
+ foreach($row as $key => $value){
+
+ $field_model = new Fields();
+ $field_model->field_name = array_keys($value)[0];
+ $field_model->value = $value[array_keys($value)[0]];
+ $field_model->table_name = $table_name;
+ $field_model->table_id = $table_id;
+ $field_model->field_type = $k;
+ $field_model->language = 'ru';
+ $field_model->parent_key = $parent_key;
+ $field_model->key = $key;
+ $field_model->save();
+ }
+
+ }
+ }
+ }
+ }
+
+
+ /**
+ * @param $post - array with field data
+ * @param $table_id - row id in model table
+ * @param $table_name - madel table name
+ * @param $language - language id
+ */
+
+ public static function saveFieldVideoData($post,$table_id,$table_name, $language){
+
+ self::deleteAll(['table_id'=>$table_id, 'table_name'=>$table_name, 'language' => $language, 'field_type' => array_keys($post)]);
+
+ if($post){
+
+
+ foreach($post as $k => $field){
+
+
+
+ foreach($field as $parent_key => $row){
+
+ foreach($row as $key => $value){
+
+ preg_match('/src=\"(.[^"]*)\"/', $value[array_keys($value)[0]], $video_url);
+
+ if(isset($video_url[1]) && !empty($video_url[1])){
+
+ $field_model = new Fields();
+ $field_model->field_name = array_keys($value)[0];
+ $field_model->value = $video_url[1].'?showinfo=0&autoplay=0';
+ $field_model->table_name = $table_name;
+ $field_model->table_id = $table_id;
+ $field_model->field_type = $k;
+ $field_model->language = 'ru';
+ $field_model->parent_key = $parent_key;
+ $field_model->key = $key;
+ $field_model->save();
+
+ }
+
+
+ }
+
+ }
+ }
+ }
+ }
+}
diff --git a/common/models/LoginForm.php b/common/models/LoginForm.php
new file mode 100644
index 0000000..afc1c23
--- /dev/null
+++ b/common/models/LoginForm.php
@@ -0,0 +1,78 @@
+hasErrors()) {
+ $user = $this->getUser();
+ if (!$user || !$user->validatePassword($this->password)) {
+ $this->addError($attribute, 'Incorrect username or password.');
+ }
+ }
+ }
+
+ /**
+ * Logs in a user using the provided username and password.
+ *
+ * @return boolean whether the user is logged in successfully
+ */
+ public function login()
+ {
+ if ($this->validate()) {
+ return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Finds user by [[username]]
+ *
+ * @return User|null
+ */
+ protected function getUser()
+ {
+ if ($this->_user === null) {
+ $this->_user = User::findByUsername($this->username);
+ }
+
+ return $this->_user;
+ }
+}
diff --git a/common/models/User.php b/common/models/User.php
new file mode 100644
index 0000000..ce78fcd
--- /dev/null
+++ b/common/models/User.php
@@ -0,0 +1,188 @@
+ self::STATUS_ACTIVE],
+ ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function findIdentity($id)
+ {
+ return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function findIdentityByAccessToken($token, $type = null)
+ {
+ throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
+ }
+
+ /**
+ * Finds user by username
+ *
+ * @param string $username
+ * @return static|null
+ */
+ public static function findByUsername($username)
+ {
+ return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
+ }
+
+ /**
+ * Finds user by password reset token
+ *
+ * @param string $token password reset token
+ * @return static|null
+ */
+ public static function findByPasswordResetToken($token)
+ {
+ if (!static::isPasswordResetTokenValid($token)) {
+ return null;
+ }
+
+ return static::findOne([
+ 'password_reset_token' => $token,
+ 'status' => self::STATUS_ACTIVE,
+ ]);
+ }
+
+ /**
+ * Finds out if password reset token is valid
+ *
+ * @param string $token password reset token
+ * @return boolean
+ */
+ public static function isPasswordResetTokenValid($token)
+ {
+ if (empty($token)) {
+ return false;
+ }
+
+ $timestamp = (int) substr($token, strrpos($token, '_') + 1);
+ $expire = Yii::$app->params['user.passwordResetTokenExpire'];
+ return $timestamp + $expire >= time();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getId()
+ {
+ return $this->getPrimaryKey();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAuthKey()
+ {
+ return $this->auth_key;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validateAuthKey($authKey)
+ {
+ return $this->getAuthKey() === $authKey;
+ }
+
+ /**
+ * Validates password
+ *
+ * @param string $password password to validate
+ * @return boolean if password provided is valid for current user
+ */
+ public function validatePassword($password)
+ {
+ return Yii::$app->security->validatePassword($password, $this->password_hash);
+ }
+
+ /**
+ * Generates password hash from password and sets it to the model
+ *
+ * @param string $password
+ */
+ public function setPassword($password)
+ {
+ $this->password_hash = Yii::$app->security->generatePasswordHash($password);
+ }
+
+ /**
+ * Generates "remember me" authentication key
+ */
+ public function generateAuthKey()
+ {
+ $this->auth_key = Yii::$app->security->generateRandomString();
+ }
+
+ /**
+ * Generates new password reset token
+ */
+ public function generatePasswordResetToken()
+ {
+ $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
+ }
+
+ /**
+ * Removes password reset token
+ */
+ public function removePasswordResetToken()
+ {
+ $this->password_reset_token = null;
+ }
+}
diff --git a/common/modules/blog/Module.php b/common/modules/blog/Module.php
new file mode 100644
index 0000000..8752f7e
--- /dev/null
+++ b/common/modules/blog/Module.php
@@ -0,0 +1,15 @@
+ [[0 => property(свойство обьекта), ... дополнительные
+ * настройки]], ...[]]
+ *
+ */
+ public $attributes;
+
+ /**
+ * События
+ *
+ * События на которые должно срабатывать поведение. Задается ассоциативный массив, в котором ключ - событие
+ * связанного обьекта, а значение - метод, который вызывается при этом событии
+ *
+ * @return array [key(event) => val(method)]
+ *
+ */
+ public function events()
+ {
+ return [
+ ActiveRecord::EVENT_BEFORE_INSERT => 'autocomplete',
+ ActiveRecord::EVENT_BEFORE_UPDATE => 'autocomplete',
+ ];
+ }
+
+ /**
+ * События
+ *
+ * События на которые должно срабатывать поведение. Задается ассоциативный массив, в котором ключ - событие
+ * связанного обьекта, а значение - метод, который вызывается при этом событии
+ * Доступные автозаполнения:
+ * ['translit' => ['prop1', ... 'prop2']],
+ * где prop - свойство подлежащее транслитерации
+ * ['repeat' => [[string 'prop1', string 'target1', boolean 'skipFilled', int 'count', boolean 'truncate', string 'suffix'], ...[]],
+ * где prop - свойство для преобразования,
+ * target - свойство с которого взять данные,
+ * count - число для преобразования,
+ * skipFilled - пропустить непустые,
+ * truncate - true - обрезать по словам, false - по символам,
+ * suffix - суффикс, который добавить после обрезки
+ *
+ * @param mixed $event Yii обьект свойста https://github.com/yiisoft/yii2/blob/master/docs/guide-ru/concept-events.md
+ *
+ */
+ public function autocomplete($event)
+ {
+ if(!empty($this->attributes['translit'])) {
+ foreach($this->attributes['translit'] as $translit) {
+ if($this->owner->hasAttribute($translit)) {
+ $this->owner->$translit = Tools::translit($this->owner->$translit);
+ }
+ }
+ }
+ if(!empty($this->attributes['repeat'])) {
+ foreach($this->attributes['repeat'] as $repeat) {
+ if(is_array($repeat) && $this->owner->hasAttribute($repeat[0]) && $this->owner->hasAttribute($repeat[1]) && is_int($repeat[3]) && (empty($this->owner->$repeat[0]) || $repeat[2])) {
+ $suffix = $repeat[5]?:'';
+ $truncate = $repeat[4]?'truncateWords':'truncate';
+ $this->owner->$repeat[0] = StringHelper::$truncate($this->owner->$repeat[1], $repeat[3], $suffix);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/modules/blog/config.php b/common/modules/blog/config.php
new file mode 100644
index 0000000..f528ffb
--- /dev/null
+++ b/common/modules/blog/config.php
@@ -0,0 +1,9 @@
+ [
+
+ ],
+ 'params' => [
+ 'test' => 'Hello',
+ ],
+];
diff --git a/common/modules/blog/controllers/AjaxController.php b/common/modules/blog/controllers/AjaxController.php
new file mode 100644
index 0000000..8da349f
--- /dev/null
+++ b/common/modules/blog/controllers/AjaxController.php
@@ -0,0 +1,133 @@
+request->getIsAjax()) {
+ //throw new ForbiddenHttpException('Permission denied');
+ }
+
+ if(!parent::beforeAction($action)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function actionCategoryForm($language_id, $widget_id)
+ {
+ $model = Language::find()->where(['>=', 'language_id', 1])->andWhere(['status' => 1, 'language_id' => $language_id])->one();
+ if(!$model) {
+ throw new NotFoundHttpException('Language not found');
+ }
+ $category_lang = new ArticleCategoryLang();
+ return $this->renderAjax('_category_form', ['model' => $model, 'category_lang' => $category_lang, 'widget_id' => $widget_id]);
+ }
+
+ public function actionArticleForm($language_id, $widget_id)
+ {
+ $model = Language::find()->where(['>=', 'language_id', 1])->andWhere(['status' => 1, 'language_id' => $language_id])->one();
+ if(!$model) {
+ throw new NotFoundHttpException('Language not found');
+ }
+ $article_lang = new ArticleLang();
+ return $this->renderAjax('_article_form', ['model' => $model, 'article_lang' => $article_lang, 'widget_id' => $widget_id]);
+ }
+
+ public function actionArticleMediaForm($language_id, $widget_id, $type)
+ {
+ $model = Language::find()->where(['>=', 'language_id', 1])->andWhere(['status' => 1, 'language_id' => $language_id])->one();
+ if(!$model) {
+ throw new NotFoundHttpException('Language not found');
+ }
+ if(!in_array($type, ['full', 'preview'])) {
+ throw new InvalidParamException('Type must only be full/preview');
+ }
+ $article_lang = new ArticleMedia();
+ return $this->renderAjax('_article_media_form', ['model' => $model, 'article_lang' => $article_lang, 'widget_id' => $widget_id, 'type' => $type]);
+ }
+
+ public function actionArticleCategoryMediaForm($language_id, $widget_id, $type)
+ {
+ $model = Language::find()->where(['>=', 'language_id', 1])->andWhere(['status' => 1, 'language_id' => $language_id])->one();
+ if(!$model) {
+ throw new NotFoundHttpException('Language not found');
+ }
+ if(!in_array($type, ['full', 'preview'])) {
+ throw new InvalidParamException('Type must only be full/preview');
+ }
+ $article_lang = new ArticleCategoryMedia();
+ return $this->renderAjax('_article_media_form', ['model' => $model, 'article_lang' => $article_lang, 'widget_id' => $widget_id, 'type' => $type]);
+ }
+
+ public function actionRemoveImage()
+ {
+ $post = \Yii::$app->request->post();
+ if(!empty($post['article_media_id'])) {
+ $article_media = ArticleMedia::findOne($post['article_media_id']);
+ if($post['remove_media']) {
+ $media = $article_media->media->delete();
+ }
+ if(!empty($article_media)) {
+ $article_media->delete();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public function actionRemoveCategoryImage()
+ {
+ $post = \Yii::$app->request->post();
+ if(!empty($post['category_media_id'])) {
+ $category_media = ArticleCategoryMedia::findOne($post['category_media_id']);
+ if($post['remove_media']) {
+ $media = $category_media->media->delete();
+ }
+ if(!empty($category_media)) {
+ $category_media->delete();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public function actionRemoveImageCategory()
+ {
+ $post = \Yii::$app->request->post();
+ if(!empty($post['category_media_id'])) {
+ $category_media = ArticleCategoryMedia::findOne($post['category_media_id']);
+ if($post['remove_media']) {
+ $media = $category_media->media->delete();
+ }
+ if(!empty($category_media)) {
+ $category_media->delete();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public function actionMultilangForm($model, $ajaxView, $widget_id, $language_id = NULL)
+ {
+ $model = new $model(['language_id' => $language_id]);
+ return $this->renderAjax($ajaxView, ['model' => $model, 'widget_id' => $widget_id]);
+ }
+
+}
diff --git a/common/modules/blog/controllers/ArticleController.php b/common/modules/blog/controllers/ArticleController.php
new file mode 100644
index 0000000..5061555
--- /dev/null
+++ b/common/modules/blog/controllers/ArticleController.php
@@ -0,0 +1,228 @@
+ Article::find(),
+ 'pagination' => [
+ 'pageSize' => 1,
+ ],
+ ]);
+ return $this->render('index', ['dataProvider' => $dataProvider]);
+ }
+
+ public function actionCreate()
+ {
+ $article_langs = array();
+ $article = new Article();
+ $default_lang = Language::getDefaultLang();
+ $images = array();
+ $images[$default_lang->language_id]['full'] = new ArticleMedia(['scenario' => ArticleMedia::SCENARIO_FULL]);
+ $images[$default_lang->language_id]['preview'] = new ArticleMedia(['scenario' => ArticleMedia::SCENARIO_PREVIEW]);
+ $images[0]['additional'] = new ArticleMedia(['scenario' => ArticleMedia::SCENARIO_ADDITIONAL]);
+ $article->loadDefaultValues();
+ $langs = Language::getActiveLanguages();
+ $isValid = false;
+ if(!empty(\Yii::$app->request->post())) {
+ $isValid = true;
+ $article->load(\Yii::$app->request->post());
+ $article->user_id = \Yii::$app->user->getId();
+ $isValid = $article->validate();
+ foreach(\Yii::$app->request->post()['ArticleMedia'] as $lang => $value) {
+ foreach($value as $type => $fields) {
+ $images[$lang][$type] = new ArticleMedia(['scenario' => $type]);
+ $images[$lang][$type]->type = $type;
+ $images[$lang][$type]->language_id = $lang;
+ $images[$lang][$type]->imageFile = UploadedFile::getInstance($images[$lang][$type], "[{$lang}][{$type}]imageFile");
+ $isValid = $images[$lang][$type]->validate(['imageFile']) && $isValid;
+ }
+ }
+ $images[0]['additional']->language_id = 0;
+ $images[0]['additional']->type = 'additional';
+ $images[0]['additional']->imageFile = UploadedFile::getInstances($images[0]['additional'], "[0][additional]imageFile");
+ if(empty(\Yii::$app->request->post()['ArticleLang'])) {
+ $article_langs[$default_lang->language_id] = new ArticleLang();
+ $isValid = ArticleLang::validateMultiple($article_langs) && $isValid;
+ } else {
+ foreach(\Yii::$app->request->post()['ArticleLang'] as $index => $article_lang) {
+ $article_langs[$index] = new ArticleLang();
+ }
+ ArticleLang::loadMultiple($article_langs, \Yii::$app->request->post());
+ $isValid = ArticleLang::validateMultiple($article_langs) && $isValid;
+ }
+ } else {
+ $article_langs[$default_lang->language_id] = new ArticleLang();
+ }
+ if($isValid) {
+ $article->save(false);
+ $article_categories = \Yii::$app->request->post('Article')['articleCategoriesArray'];
+ if(!empty($article_categories)) {
+ foreach($article_categories as $article_category) {
+ $articletocategory[$article_category] = new ArticleToCategory();
+ $articletocategory[$article_category]->article_category_id = $article_category;
+ $articletocategory[$article_category]->link('article', $article);
+ }
+ }
+ $first = 1;
+ foreach($images as $lang => $value) {
+ foreach($value as $type => $fields) {
+ $images[$lang][$type]->upload($article->article_id);
+ if($first && $type != 'additional') {
+ $media_clone = clone $images[$lang][$type];
+ $media_clone->setIsNewRecord(true);
+ unset($media_clone->article_media_id);
+ $media_clone->language_id = 0;
+ $media_clone->upload($article->article_id);
+ unset($media_clone);
+ $first = 0;
+ }
+ }
+ }
+ $first = 1;
+ foreach($article_langs as $article_lang) {
+ if($first) {
+ $article_lang_clone = clone $article_lang;
+ $article_lang_clone->language_id = 0;
+ $article_lang_clone->link('article', $article);
+ unset($article_lang_clone);
+ }
+ $article_lang->link('article', $article);
+ $first = 0;
+ }
+ echo "ok";
+ //$this->redirect('index');
+ } else {
+ return $this->render('create', [
+ 'article_langs' => $article_langs,
+ 'article' => $article,
+ 'langs' => $langs,
+ 'images' => $images
+ ]);
+ }
+ }
+
+ public function actionUpdate($id)
+ {
+ $article = Article::findOne($id);
+ $imagestack = $article->getArticleMedia()->all();
+ $images = [];
+ $images[0]['additional'][0] = new ArticleMedia(['scenario' => ArticleMedia::SCENARIO_ADDITIONAL]);
+ $images[0]['additional'][0]->type = 'additional';
+ $images[0]['additional'][0]->language_id = 0;
+ foreach($imagestack as $image) {
+ if(in_array($image->type, ['full', 'preview'])) {
+ $images[$image->language_id][$image->type] = $image;
+ $images[$image->language_id][$image->type]->scenario = $image->type;
+ } else {
+ $images[$image->language_id][$image->type][$image->article_media_id] = $image;
+ $images[$image->language_id][$image->type][$image->article_media_id]->scenario = $image->type;
+ }
+ }
+ foreach($images as $lang => $value) {
+ $images[$lang]['additional'][0] = new ArticleMedia(['scenario' => ArticleMedia::SCENARIO_ADDITIONAL]);
+ }
+ $article_langs = $article->getArticleLangs()->where(['>=', 'language_id', '1'])->indexBy('language_id')->all();
+ $langs = Language::getActiveLanguages();
+ $default_lang = Language::getDefaultLang();
+ $isValid = false;
+ if(!empty(\Yii::$app->request->post())) {
+ $isValid = true;
+ $article->load(\Yii::$app->request->post());
+ ArticleToCategory::deleteAll(['article_id' => $article->article_id]);
+ $article_categories = \Yii::$app->request->post('Article')['articleCategoriesArray'];
+ if(!empty($article_categories)) {
+ foreach($article_categories as $article_category) {
+ $articletocategory[$article_category] = new ArticleToCategory();
+ $articletocategory[$article_category]->article_category_id = $article_category;
+ $articletocategory[$article_category]->link('article', $article);
+ }
+ }
+ $isValid = $article->validate();
+ $images[0]['additional'][0]->type = 'additional';
+ $images[0]['additional'][0]->language_id = 0;
+ $images[0]['additional'][0]->imageFile = UploadedFile::getInstances($images[0]['additional'][0], "[0][additional]imageFile");
+ $isValid = $images[0]['additional'][0]->validate(['imageFile']) && $isValid;
+ foreach(\Yii::$app->request->post()['ArticleMedia'] as $lang => $value) {
+ foreach($value as $type => $fields) {
+ if(!in_array($type, ['full', 'preview'])) continue;
+ $images[$lang][$type] = new ArticleMedia(['scenario' => $type]);
+ $images[$lang][$type]->language_id = $lang;
+ $images[$lang][$type]->type = $type;
+ $images[$lang][$type]->imageFile = UploadedFile::getInstance($images[$lang][$type], "[{$lang}][{$type}]imageFile");
+ $isValid = $images[$lang][$type]->validate(['imageFile']) && $isValid;
+ }
+ }
+ if(empty(\Yii::$app->request->post()['ArticleLang'])) {
+ $isValid = ArticleLang::validateMultiple($article_langs) && $isValid;
+ } else {
+ foreach(\Yii::$app->request->post()['ArticleLang'] as $index => $article_lang) {
+ if (!array_key_exists($index, $article_langs)) {
+ $article_langs[$index] = new ArticleLang();
+ $article_langs[$index]->article_id = $article->article_id;
+ }
+ }
+ ArticleLang::loadMultiple($article_langs, \Yii::$app->request->post());
+ $isValid = ArticleLang::validateMultiple($article_langs) && $isValid;
+ }
+ }
+ if($isValid) {
+ $article->save(false);
+ foreach($images as $lang => $value) {
+ foreach($value as $type => $fields) {
+ if($type == 'additional') {
+ $images[$lang][$type][0]->upload($article->id);
+ } else {
+ if(!empty($images[$lang][$type]->imageFile)) {
+ $images[$lang][$type]->replace($article->article_id);
+ }
+ }
+ }
+ }
+ foreach($article_langs as $article_lang) {
+ $article_lang->save(false);
+ }
+ echo "ok";
+ //$this->redirect('index');
+ } else {
+ return $this->render('update', [
+ 'article_langs' => $article_langs,
+ 'article' => $article,
+ 'langs' => $langs,
+ 'images' => $images
+ ]);
+ }
+ }
+
+ public function actionDelete($id)
+ {
+ $this->findModel($id)->delete();
+ return $this->redirect(['index']);
+ }
+
+ protected function findModel($id)
+ {
+ if (($model = Article::findOne($id)) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
+}
diff --git a/common/modules/blog/controllers/CategoryController.php b/common/modules/blog/controllers/CategoryController.php
new file mode 100644
index 0000000..7759416
--- /dev/null
+++ b/common/modules/blog/controllers/CategoryController.php
@@ -0,0 +1,219 @@
+ [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['post']
+ ]
+ ]
+ ];
+ }
+
+ public function actionIndex()
+ {
+ $dataProvider = new ActiveDataProvider([
+ 'query' => ArticleCategory::find(),
+ 'pagination' => [
+ 'pageSize' => 1,
+ ],
+ ]);
+ return $this->render('index', ['dataProvider' => $dataProvider]);
+ }
+
+ public function actionCreate()
+ {
+ $category_langs = array();
+ $category = new ArticleCategory();
+ $default_lang = Language::getDefaultLang();
+ $images = array();
+ $images[$default_lang->language_id]['full'] = new ArticleCategoryMedia(['scenario' => ArticleCategoryMedia::SCENARIO_FULL]);
+ $images[$default_lang->language_id]['preview'] = new ArticleCategoryMedia(['scenario' => ArticleCategoryMedia::SCENARIO_PREVIEW]);
+ $images[0]['additional'] = new ArticleCategoryMedia(['scenario' => ArticleCategoryMedia::SCENARIO_ADDITIONAL]);
+ $category->loadDefaultValues();
+ $langs = Language::getActiveLanguages();
+ $isValid = false;
+ if(!empty(\Yii::$app->request->post())) {
+ $isValid = true;
+ $category->load(\Yii::$app->request->post());
+ $isValid = $category->validate();
+ foreach(\Yii::$app->request->post()['ArticleCategoryMedia'] as $lang => $value) {
+ foreach($value as $type => $fields) {
+ $images[$lang][$type] = new ArticleCategoryMedia(['scenario' => $type]);
+ $images[$lang][$type]->type = $type;
+ $images[$lang][$type]->language_id = $lang;
+ $images[$lang][$type]->imageFile = UploadedFile::getInstance($images[$lang][$type], "[{$lang}][{$type}]imageFile");
+ $isValid = $images[$lang][$type]->validate(['imageFile']) && $isValid;
+ }
+ }
+ $images[0]['additional']->language_id = 0;
+ $images[0]['additional']->type = 'additional';
+ $images[0]['additional']->imageFile = UploadedFile::getInstances($images[0]['additional'], "[0][additional]imageFile");
+ if(empty(\Yii::$app->request->post()['ArticleCategoryLang'])) {
+ $category_langs[$default_lang->language_id] = new ArticleCategoryLang();
+ $isValid = ArticleCategoryLang::validateMultiple($category_langs) && $isValid;
+ } else {
+ foreach(\Yii::$app->request->post()['ArticleCategoryLang'] as $index => $category_lang) {
+ $category_langs[$index] = new ArticleCategoryLang();
+ }
+ ArticleCategoryLang::loadMultiple($category_langs, \Yii::$app->request->post());
+ $isValid = ArticleCategoryLang::validateMultiple($category_langs) && $isValid;
+ }
+ } else {
+ $category_langs[$default_lang->language_id] = new ArticleCategoryLang();
+ }
+ if($isValid) {
+ $category->save(false);
+ $first = 1;
+ foreach($images as $lang => $value) {
+ foreach($value as $type => $fields) {
+ $images[$lang][$type]->upload($category->article_category_id);
+ if($first && $type != 'additional') {
+ $media_clone = clone $images[$lang][$type];
+ $media_clone->setIsNewRecord(true);
+ unset($media_clone->article_category_media_id);
+ $media_clone->language_id = 0;
+ $media_clone->upload($category->article_category_id);
+ unset($media_clone);
+ $first = 0;
+ }
+ }
+ }
+ $first = 1;
+ foreach($category_langs as $category_lang) {
+ if($first) {
+ $category_lang_clone = clone $category_lang;
+ $category_lang_clone->language_id = 0;
+ $category_lang_clone->link('category', $category);
+ unset($category_lang_clone);
+ }
+ $category_lang->link('category', $category);
+ $first = 0;
+ }
+ echo "ok";
+ //$this->redirect('index');
+ } else {
+ return $this->render('create', [
+ 'category_langs' => $category_langs,
+ 'category' => $category,
+ 'langs' => $langs,
+ 'images' => $images
+ ]);
+ }
+ }
+
+ public function actionUpdate($id)
+ {
+ $category = ArticleCategory::findOne($id);
+ $imagestack = $category->getArticleCategoryMedia()->all();
+ $images = [];
+ $images[0]['additional'][0] = new ArticleCategoryMedia(['scenario' => ArticleCategoryMedia::SCENARIO_ADDITIONAL]);
+ $images[0]['additional'][0]->type = 'additional';
+ $images[0]['additional'][0]->language_id = 0;
+ foreach($imagestack as $image) {
+ if(in_array($image->type, ['full', 'preview'])) {
+ $images[$image->language_id][$image->type] = $image;
+ $images[$image->language_id][$image->type]->scenario = $image->type;
+ } else {
+ $images[$image->language_id][$image->type][$image->article_category_media_id] = $image;
+ $images[$image->language_id][$image->type][$image->article_category_media_id]->scenario = $image->type;
+ }
+ }
+ foreach($images as $lang => $value) {
+ $images[$lang]['additional'][0] = new ArticleCategoryMedia(['scenario' => ArticleCategoryMedia::SCENARIO_ADDITIONAL]);
+ }
+ $category_langs = $category->getArticleCategoryLangs()->where(['>=', 'language_id', '1'])->indexBy('language_id')->all();
+ $langs = Language::getActiveLanguages();
+ $default_lang = Language::getDefaultLang();
+ $isValid = false;
+ if(!empty(\Yii::$app->request->post())) {
+ $isValid = true;
+ $category->load(\Yii::$app->request->post());
+ $isValid = $category->validate();
+ $images[0]['additional'][0]->type = 'additional';
+ $images[0]['additional'][0]->language_id = 0;
+ $images[0]['additional'][0]->imageFile = UploadedFile::getInstances($images[0]['additional'][0], "[0][additional]imageFile");
+ $isValid = $images[0]['additional'][0]->validate(['imageFile']) && $isValid;
+ foreach(\Yii::$app->request->post()['ArticleCategoryMedia'] as $lang => $value) {
+ foreach($value as $type => $fields) {
+ if(!in_array($type, ['full', 'preview'])) continue;
+ $images[$lang][$type] = new ArticleCategoryMedia(['scenario' => $type]);
+ $images[$lang][$type]->language_id = $lang;
+ $images[$lang][$type]->type = $type;
+ $images[$lang][$type]->imageFile = UploadedFile::getInstance($images[$lang][$type], "[{$lang}][{$type}]imageFile");
+ $isValid = $images[$lang][$type]->validate(['imageFile']) && $isValid;
+ }
+ }
+ if(empty(\Yii::$app->request->post()['ArticleCategoryLang'])) {
+ $isValid = ArticleCategoryLang::validateMultiple($category_langs) && $isValid;
+ } else {
+ foreach(\Yii::$app->request->post()['ArticleCategoryLang'] as $index => $category_lang) {
+ if(!array_key_exists($index, $category_langs)) {
+ $category_langs[$index] = new ArticleCategoryLang();
+ $category_langs[$index]->article_category_id = $category->article_category_id;
+ }
+ }
+ ArticleCategoryLang::loadMultiple($category_langs, \Yii::$app->request->post());
+ $isValid = ArticleCategoryLang::validateMultiple($category_langs) && $isValid;
+ }
+ }
+ if($isValid) {
+ $category->save(false);
+ foreach($images as $lang => $value) {
+ foreach($value as $type => $fields) {
+ if($type == 'additional') {
+ $images[$lang][$type][0]->upload($category->article_category_id);
+ } else {
+ if(!empty($images[$lang][$type]->imageFile)) {
+ $images[$lang][$type]->replace($category->article_category_id);
+ }
+ }
+ }
+ }
+ foreach($category_langs as $category_lang) {
+ $category_lang->save(false);
+ }
+ echo "ok";
+ //$this->redirect('index');
+ } else {
+ return $this->render('update', [
+ 'category_langs' => $category_langs,
+ 'category' => $category,
+ 'langs' => $langs,
+ 'images' => $images
+ ]);
+ }
+ }
+
+ public function actionDelete($id)
+ {
+ $this->findModel($id)->delete();
+ return $this->redirect(['index']);
+ }
+
+ protected function findModel($id)
+ {
+ if (($model = ArticleCategory::findOne($id)) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
+}
diff --git a/common/modules/blog/controllers/DefaultController.php b/common/modules/blog/controllers/DefaultController.php
new file mode 100644
index 0000000..3e2294c
--- /dev/null
+++ b/common/modules/blog/controllers/DefaultController.php
@@ -0,0 +1,12 @@
+render('index');
+ }
+}
diff --git a/common/modules/blog/controllers/MediaController.php b/common/modules/blog/controllers/MediaController.php
new file mode 100644
index 0000000..e1d91f5
--- /dev/null
+++ b/common/modules/blog/controllers/MediaController.php
@@ -0,0 +1,45 @@
+request->isPost) {
+ $model->imageFile = UploadedFile::getInstance($model, 'imageFile');
+ if($model->upload()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ return $this->render('index', ['model' => $model]);
+ }
+
+ public function actionCreate()
+ {
+
+ }
+
+ public function actionUpdate($id)
+ {
+
+ }
+
+ public function actionDelete($id)
+ {
+ $model = Media::findOne($id);
+ return $model->delete();
+ }
+
+ protected function findModel($id)
+ {
+
+ }
+}
diff --git a/common/modules/blog/controllers/TestController.php b/common/modules/blog/controllers/TestController.php
new file mode 100644
index 0000000..5f77485
--- /dev/null
+++ b/common/modules/blog/controllers/TestController.php
@@ -0,0 +1,17 @@
+language_id] = new ArticleLang();
+ $model[3] = new ArticleLang();
+ return $this->render('index', ['model' => $model]);
+ }
+}
\ No newline at end of file
diff --git a/common/modules/blog/models/Article.php b/common/modules/blog/models/Article.php
new file mode 100644
index 0000000..85584b1
--- /dev/null
+++ b/common/modules/blog/models/Article.php
@@ -0,0 +1,163 @@
+ Autocomplete::className(),
+ 'attributes' => [
+ 'translit' => ['code'],
+ ]
+ ]
+ ];
+ }
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['sort', 'article_pid', 'status', 'comment', 'vote'], 'integer'],
+ [['date_add', 'date_update'], 'safe'],
+ [['code'], 'required'],
+ [['code', 'tag'], 'string']
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'article_id' => Yii::t('app', 'ID'),
+ 'sort' => Yii::t('app', 'Sort'),
+ 'date_add' => Yii::t('app', 'Create At'),
+ 'date_update' => Yii::t('app', 'Update At'),
+ 'code' => Yii::t('app', 'Code'),
+ 'user_id' => Yii::t('app', 'Author'),
+ 'tag' => Yii::t('app', 'Tags'),
+ 'article_pid' => Yii::t('app', 'Parent ID'),
+ 'status' => Yii::t('app', 'Active'),
+ 'comment' => Yii::t('app', 'Comments'),
+ 'vote' => Yii::t('app', 'Voting'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getParent()
+ {
+ return $this->hasOne(Article::className(), ['article_id' => 'article_pid']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticles()
+ {
+ return $this->hasMany(Article::className(), ['article_pid' => 'article_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getUser()
+ {
+ return $this->hasOne(User::className(), ['id' => 'user_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticleLangs()
+ {
+ return $this->hasMany(ArticleLang::className(), ['article_id' => 'article_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticleMedia()
+ {
+ return $this->hasMany(ArticleMedia::className(), ['article_id' => 'article_id']);
+ }
+
+ public function getMedia()
+ {
+ return $this->hasMany(Media::className(), ['article_id' => 'media_id'])->via('articleMedia');
+ }
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticleToCategories()
+ {
+ return $this->hasMany(ArticleToCategory::className(), ['article_id' => 'article_id']);
+ }
+
+ public function getArticleCategories()
+ {
+ return $this->hasMany(ArticleCategory::className(), ['article_category_id' => 'article_category_id'])->viaTable('article_to_category', ['article_id' => 'article_category_id']);
+ }
+
+ public static function findArticleDropdown($id)
+ {
+ $query = new Query();
+ return $query->select(['l.name', 'a.article_id'])
+ ->from(['article a'])
+ ->leftJoin(['article_lang l'], 'a.article_id = l.article_id')
+ ->where(['l.language_id' => 0, 'a.status' => 1])
+ ->andWhere(['not', ['a.article_id' => $id]])
+ ->indexBy('article_id')
+ ->column();
+ }
+
+ public function getArticleCategoriesArray()
+ {
+ return $this->getArticleToCategories()->select('article_category_id')->column();
+ }
+
+}
diff --git a/common/modules/blog/models/ArticleCategory.php b/common/modules/blog/models/ArticleCategory.php
new file mode 100644
index 0000000..f91721f
--- /dev/null
+++ b/common/modules/blog/models/ArticleCategory.php
@@ -0,0 +1,133 @@
+ Autocomplete::className(),
+ 'attributes' => [
+ 'translit' => ['code'],
+ ]
+ ]
+ ];
+ }
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['status', 'sort', 'article_category_pid'], 'integer'],
+ [['code'], 'required'],
+ [['code', 'tag'], 'string'],
+ [['date_add', 'date_update'], 'safe'],
+ [['status'], 'boolean'],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'article_category_id' => Yii::t('app', 'ID'),
+ 'status' => Yii::t('app', 'Active'),
+ 'sort' => Yii::t('app', 'Sort'),
+ 'code' => Yii::t('app', 'Code'),
+ 'date_add' => Yii::t('app', 'Created At'),
+ 'date_update' => Yii::t('app', 'Updated At'),
+ 'tag' => Yii::t('app', 'Tags'),
+ 'article_category_pid' => Yii::t('app', 'Parent ID'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticles()
+ {
+ return $this->hasMany(Article::className(), ['article_id' => 'article_id'])->viaTable('article_to_category', ['article_category_id' => 'article_category_id']) ;
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getParent()
+ {
+ return $this->hasOne(ArticleCategory::className(), ['article_category_id' => 'article_category_pid']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticleCategories()
+ {
+ return $this->hasMany(ArticleCategory::className(), ['article_category_pid' => 'article_category_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticleCategoryLangs()
+ {
+ return $this->hasMany(ArticleCategoryLang::className(), ['article_category_id' => 'article_category_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticleCategoryMedia()
+ {
+ return $this->hasMany(ArticleCategoryMedia::className(), ['article_category_id' => 'article_category_id']);
+ }
+
+ public static function findArticleCategoryDropdown($id)
+ {
+ $query = new Query();
+ return $query->select(['l.name', 'c.article_category_id'])
+ ->from(['article_category c'])
+ ->leftJoin(['article_category_lang l'], 'c.article_category_id = l.article_category_id')
+ ->where(['l.language_id' => 0, 'c.status' => 1])
+ ->andWhere(['not', ['c.article_category_id' => $id]])
+ ->indexBy('article_category_id')
+ ->column();
+ }
+
+}
diff --git a/common/modules/blog/models/ArticleCategoryLang.php b/common/modules/blog/models/ArticleCategoryLang.php
new file mode 100644
index 0000000..1851437
--- /dev/null
+++ b/common/modules/blog/models/ArticleCategoryLang.php
@@ -0,0 +1,102 @@
+ Autocomplete::className(),
+ 'attributes' => [
+ 'repeat' => [['preview', 'text', false, 5, true, '...']],
+ ]
+ ]
+ ];
+ }
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['language_id', 'article_category_id'], 'integer'],
+ [['text', 'name'], 'required'],
+ [['text', 'preview', 'seo_url', 'name', 'meta_title', 'meta_descr', 'meta_keyword', 'h1_tag', 'tag'], 'string'],
+ ['seo_url', function($attribute, $params) {
+ $pattern = "/^[a-zA-Z\d_-]+$/";
+ if(!preg_match($pattern, $this->$attribute)) {
+ $this->addError($attribute, Yii::t('app', "Pattern doesn't match."));
+ }
+ }]
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'article_category_language_id' => Yii::t('app', 'ID'),
+ 'language_id' => Yii::t('app', 'Lang ID'),
+ 'article_category_id' => Yii::t('app', 'Category ID'),
+ 'text' => Yii::t('app', 'Text'),
+ 'preview' => Yii::t('app', 'Preview'),
+ 'seo_url' => Yii::t('app', 'Seo Url'),
+ 'name' => Yii::t('app', 'Name'),
+ 'meta_title' => Yii::t('app', 'Meta Title'),
+ 'meta_descr' => Yii::t('app', 'Meta Descr'),
+ 'meta_keyword' => Yii::t('app', 'Meta Keywords'),
+ 'h1_tag' => Yii::t('app', 'H1 Tag'),
+ 'tag' => Yii::t('app', 'Tags'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getCategory()
+ {
+ return $this->hasOne(ArticleCategory::className(), ['article_category_id' => 'article_category_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getLang()
+ {
+ return $this->hasOne(Language::className(), ['language_id' => 'language_id']);
+ }
+}
diff --git a/common/modules/blog/models/ArticleCategoryMedia.php b/common/modules/blog/models/ArticleCategoryMedia.php
new file mode 100644
index 0000000..a923910
--- /dev/null
+++ b/common/modules/blog/models/ArticleCategoryMedia.php
@@ -0,0 +1,162 @@
+ 10],
+ [['imageFile'], 'file', 'extensions' => 'png, gif, jpg, jpeg', 'skipOnEmpty' => true, 'on' => self::SCENARIO_FULL],
+ [['imageFile'], 'file', 'extensions' => 'png, gif, jpg, jpeg', 'skipOnEmpty' => true, 'on' => self::SCENARIO_PREVIEW],
+ [['imageFile'], 'file', 'extensions' => 'png, gif, jpg, jpeg', 'skipOnEmpty' => true, 'maxFiles' => 10, 'on' => self::SCENARIO_ADDITIONAL]
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'article_category_media_id' => Yii::t('app', 'ID'),
+ 'article_category_id' => Yii::t('app', 'Category ID'),
+ 'media_id' => Yii::t('app', 'Media ID'),
+ 'media_alt' => Yii::t('app', 'Media Alt'),
+ 'media_title' => Yii::t('app', 'Media Title'),
+ 'media_caption' => Yii::t('app', 'Media Caption'),
+ 'type' => Yii::t('app', 'Type'),
+ 'imageFile' => Yii::t('app', 'Image File'),
+ 'language_id' => Yii::t('app', 'Language ID'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getCategory()
+ {
+ return $this->hasOne(ArticleCategory::className(), ['article_category_id' => 'article_category_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getMedia()
+ {
+ return $this->hasOne(Media::className(), ['media_id' => 'media_id']);
+ }
+
+ public function upload($category_id)
+ {
+ $this->article_category_id = $category_id;
+ if(is_array($this->imageFile)) {
+ $ok = true;
+ foreach($this->imageFile as $image) {
+ $media_category = clone $this;
+ $media = new Media();
+ $media->imageFile = $image;
+ $media->upload();
+ $media_category->media_id = $media->media_id;
+ $ok = $media_category->save() && $ok;
+ unset($media_category);
+ }
+ return $ok;
+ } elseif(!empty($this->imageFile)) {
+ $media = new Media();
+ $media->imageFile = $this->imageFile;
+ $media->upload();
+ $this->media_id = $media->media_id;
+ return $this->save();
+ }
+ }
+
+ public function replace($category_id, $removeMedia = false)
+ {
+ $this->article_category_id = $category_id;
+ if($removeMedia) {
+ $category_media = ArticleCategoryMedia::find()->select('media_id')->where(['article_category_id' => $this->article_category_id, 'type' => $this->type])->column();
+ $media = array();
+ foreach($category_media as $media_id) {
+ $media[] = Media::findOne(['media_id' => $media_id]);
+ }
+ $media = array_unique($media);
+ foreach($media as $one_media) {
+ if($one_media instanceof Media) {
+ $one_media->delete();
+ }
+ }
+ unset($media);
+ unset($category_media);
+ }
+ if(is_array($this->imageFile)) {
+ $ok = true;
+ foreach($this->imageFile as $image) {
+ $media_category = clone $this;
+ $media = new Media();
+ $media->imageFile = $image;
+ $media->upload();
+ $media_category->media_id = $media->media_id;
+ $ok = $media_category->save() && $ok;
+ unset($media_category);
+ }
+ return $ok;
+ } elseif(!empty($this->imageFile)) {
+ ArticleCategoryMedia::deleteAll(['category_id' => $this->article_category_id, 'type' => $this->type]);
+ $media = new Media();
+ $media->imageFile = $this->imageFile;
+ $media->upload();
+ $this->media_id = $media->media_id;
+ $this->setIsNewRecord(true);
+ return $this->save();
+ }
+ }
+
+}
diff --git a/common/modules/blog/models/ArticleLang.php b/common/modules/blog/models/ArticleLang.php
new file mode 100644
index 0000000..378b266
--- /dev/null
+++ b/common/modules/blog/models/ArticleLang.php
@@ -0,0 +1,85 @@
+ Yii::t('app', 'ID'),
+ 'language_id' => Yii::t('app', 'Lang ID'),
+ 'article_id' => Yii::t('app', 'Article ID'),
+ 'text' => Yii::t('app', 'Text'),
+ 'seo_url' => Yii::t('app', 'Seo Url'),
+ 'name' => Yii::t('app', 'Name'),
+ 'preview' => Yii::t('app', 'Preview'),
+ 'meta_title' => Yii::t('app', 'Meta Title'),
+ 'meta_descr' => Yii::t('app', 'Meta Descr'),
+ 'meta_keyword' => Yii::t('app', 'Meta Keywords'),
+ 'h1_tag' => Yii::t('app', 'H1 Tag'),
+ 'tag' => Yii::t('app', 'Tags'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticle()
+ {
+ return $this->hasOne(Article::className(), ['article_id' => 'article_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getLang()
+ {
+ return $this->hasOne(Language::className(), ['language_id' => 'language_id']);
+ }
+}
diff --git a/common/modules/blog/models/ArticleMedia.php b/common/modules/blog/models/ArticleMedia.php
new file mode 100644
index 0000000..42ccfa0
--- /dev/null
+++ b/common/modules/blog/models/ArticleMedia.php
@@ -0,0 +1,161 @@
+ 10],
+ [['imageFile'], 'file', 'extensions' => 'png, gif, jpg, jpeg', 'skipOnEmpty' => true, 'on' => self::SCENARIO_FULL],
+ [['imageFile'], 'file', 'extensions' => 'png, gif, jpg, jpeg', 'skipOnEmpty' => true, 'on' => self::SCENARIO_PREVIEW],
+ [['imageFile'], 'file', 'extensions' => 'png, gif, jpg, jpeg', 'skipOnEmpty' => true, 'maxFiles' => 10, 'on' => self::SCENARIO_ADDITIONAL]
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'article_media_id' => Yii::t('app', 'ID'),
+ 'article_id' => Yii::t('app', 'Article ID'),
+ 'media_id' => Yii::t('app', 'Media ID'),
+ 'type' => Yii::t('app', 'Type'),
+ 'media_alt' => Yii::t('app', 'Media Alt'),
+ 'media_title' => Yii::t('app', 'Media Title'),
+ 'media_caption' => Yii::t('app', 'Media Caption'),
+ 'imageFile' => Yii::t('app', 'Image File'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticle()
+ {
+ return $this->hasOne(Article::className(), ['article_id' => 'article_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getMedia()
+ {
+ return $this->hasOne(Media::className(), ['media_id' => 'media_id']);
+ }
+
+ public function upload($article_id)
+ {
+ $this->article_id = $article_id;
+ if(is_array($this->imageFile)) {
+ $ok = true;
+ foreach($this->imageFile as $image) {
+ $media_article = clone $this;
+ $media = new Media();
+ $media->imageFile = $image;
+ $media->upload();
+ $media_article->media_id = $media->media_id;
+ $ok = $media_article->save() && $ok;
+ unset($media_article);
+ }
+ return $ok;
+ } elseif(!empty($this->imageFile)) {
+ $media = new Media();
+ $media->imageFile = $this->imageFile;
+ $media->upload();
+ $this->media_id = $media->media_id;
+ return $this->save();
+ }
+ }
+
+ public function replace($article_id, $removeMedia = false)
+ {
+ $this->article_id = $article_id;
+ if($removeMedia && !$this->getIsNewRecord()) {
+ $article_media = ArticleMedia::find()->select('media_id')->where(['article_id' => $this->article_id, 'type' => $this->type, 'language_id' => $this->language_id])->column();
+ $media = array();
+ foreach($article_media as $media_id) {
+ $media[] = Media::findOne(['media_id' => $media_id]);
+ }
+ $media = array_unique($media);
+ foreach($media as $one_media) {
+ if($one_media instanceof Media) {
+ $one_media->delete();
+ }
+ }
+ unset($media);
+ unset($article_media);
+ }
+ if(is_array($this->imageFile)) {
+ $ok = true;
+ foreach($this->imageFile as $image) {
+ $media_article = clone $this;
+ $media = new Media();
+ $media->imageFile = $image;
+ $media->upload();
+ $media_article->media_id = $media->media_id;
+ $ok = $media_article->save() && $ok;
+ unset($media_article);
+ }
+ return $ok;
+ } elseif(!empty($this->imageFile)) {
+ ArticleMedia::deleteAll(['article_id' => $this->article_id, 'type' => $this->type, 'language_id' => $this->language_id]);
+ $media = new Media();
+ $media->imageFile = $this->imageFile;
+ $media->upload();
+ $this->media_id = $media->media_id;
+ $this->setIsNewRecord(true);
+ return $this->save();
+ }
+ }
+
+}
diff --git a/common/modules/blog/models/ArticleToCategory.php b/common/modules/blog/models/ArticleToCategory.php
new file mode 100644
index 0000000..f665d84
--- /dev/null
+++ b/common/modules/blog/models/ArticleToCategory.php
@@ -0,0 +1,62 @@
+ Yii::t('app', 'Article ID'),
+ 'article_category_id' => Yii::t('app', 'Category ID'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getArticle()
+ {
+ return $this->hasOne(Article::className(), ['article_id' => 'article_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getCategory()
+ {
+ return $this->hasOne(ArticleCategory::className(), ['article_category_id' => 'article_category_id']);
+ }
+}
diff --git a/common/modules/blog/views/ajax/_article_form.php b/common/modules/blog/views/ajax/_article_form.php
new file mode 100644
index 0000000..47e23d4
--- /dev/null
+++ b/common/modules/blog/views/ajax/_article_form.php
@@ -0,0 +1,38 @@
+
+
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]language_id"]))->label(false)->hiddenInput(['value' => $model->language_id]) ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]text", 'form' => $form]))->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ]]); ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]preview", 'form' => $form]))->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ]]); ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]seo_url"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]name"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]meta_title"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]meta_descr"]))->textarea() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]meta_keyword"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]h1_tag"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]tag"]))->textInput() ?>
+
+
+end();
+ }
+?>
diff --git a/common/modules/blog/views/ajax/_article_form_test.php b/common/modules/blog/views/ajax/_article_form_test.php
new file mode 100644
index 0000000..fbadb64
--- /dev/null
+++ b/common/modules/blog/views/ajax/_article_form_test.php
@@ -0,0 +1,38 @@
+
+
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]language_id"]))->label(false)->hiddenInput(['value' => $model->language_id]) ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]text", 'form' => $form]))->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ]]); ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]preview", 'form' => $form]))->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ]]); ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]seo_url"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]name"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]meta_title"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]meta_descr"]))->textarea() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]meta_keyword"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]h1_tag"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $model, 'attribute' => "[$model->language_id]tag"]))->textInput() ?>
+
+
+end();
+ }
+?>
diff --git a/common/modules/blog/views/ajax/_article_media_form.php b/common/modules/blog/views/ajax/_article_media_form.php
new file mode 100644
index 0000000..8ff29f3
--- /dev/null
+++ b/common/modules/blog/views/ajax/_article_media_form.php
@@ -0,0 +1,17 @@
+
+
+
+ = (new ActiveField(['model' => $article_lang, 'attribute' => "[$model->language_id][$type]language_id"]))->label(false)->hiddenInput(['value' => $model->language_id]) ?>
+
+ = (new ActiveField(['model' => $article_lang, 'attribute' => "[$model->language_id][$type]imageFile"]))->fileInput(['class' => 'image_inputs_field']) ?>
+
+
+end();
+?>
diff --git a/common/modules/blog/views/ajax/_category_form.php b/common/modules/blog/views/ajax/_category_form.php
new file mode 100644
index 0000000..521317b
--- /dev/null
+++ b/common/modules/blog/views/ajax/_category_form.php
@@ -0,0 +1,33 @@
+
+
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]language_id"]))->label(false)->hiddenInput(['value' => $model->language_id]) ?>
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]text", 'form' => $form]))->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ]]); ?>
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]preview", 'form' => $form]))->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ]]); ?>
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]seo_url"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]name"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]meta_title"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]meta_descr"]))->textarea() ?>
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]meta_keyword"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]h1_tag"]))->textInput() ?>
+
+ = (new ActiveField(['model' => $category_lang, 'attribute' => "[$model->language_id]tag"]))->textInput() ?>
+
+
+end();
+?>
diff --git a/common/modules/blog/views/article/_form.php b/common/modules/blog/views/article/_form.php
new file mode 100644
index 0000000..30eb077
--- /dev/null
+++ b/common/modules/blog/views/article/_form.php
@@ -0,0 +1,172 @@
+
+
+
+ ['enctype' => 'multipart/form-data']]); ?>
+
+ = $form->field($article, 'code')->hint(Yii::t('app', 'Insensitive latin non-space'))->textInput() ?>
+
+ = $form->field($article, 'tag')->hint(Yii::t('app', 'Comma-separated'))->textInput() ?>
+
+ = $form->field($article, 'sort')->input('number') ?>
+
+ = $form->field($article, 'article_pid')
+ ->dropDownList(Article::findArticleDropdown($article->article_id), ['prompt' => Yii::t('app', 'Select parent')]) ?>
+
+ = $form->field($article, 'articleCategoriesArray')
+ ->dropDownList(ArticleCategory::findArticleCategoryDropdown(NULL), ['multiple' => 'multiple'])->label(\Yii::t('app', 'Article Categories Array')); ?>
+
+ = $form->field($article, 'status')->checkbox() ?>
+
+
+
+
+ Url::to(['/blog/ajax/article-media-form?type=full']),
+ 'form' => $form,
+ 'data_langs' => $article->getIsNewRecord()?$images:ArticleMedia::find()->where(['article_id' => $article->article_id, 'type' => 'full'])->indexBy('language_id')->all()
+ ]);
+ $first = 1;
+ foreach($images as $lang => $value) {
+ if(!array_key_exists('full', $value)) continue;
+ ?>
+
+ field($images[$lang]['full'], "[{$lang}][full]language_id")->label(false)->hiddenInput(['value' => $lang]);
+ echo $form->field($images[$lang]['full'], "[{$lang}][full]imageFile")->fileInput(['class' => 'image_inputs_field']);
+ if(!empty($images[$lang]['full']->article_media_id)) {
+ echo "
media->hash}/original.{$images[$lang]['full']->media->extension}' width='100' class='image_inputs_prev'>";
+ }
+ ?>
+
+ end();
+ ?>
+
+
+ Url::to(['/blog/ajax/article-media-form?type=preview']),
+ 'form' => $form,
+ 'data_langs' => $article->getIsNewRecord()?$images:ArticleMedia::find()->where(['article_id' => $article->article_id, 'type' => 'preview'])->indexBy('language_id')->all()
+ ]);
+ $first = 1;
+ foreach($images as $lang => $value) {
+ if(!array_key_exists('preview', $value)) continue;
+ ?>
+
+ field($images[$lang]['preview'], "[{$lang}][preview]language_id")->label(false)->hiddenInput(['value' => $lang]);
+ echo $form->field($images[$lang]['preview'], "[{$lang}][preview]imageFile")->fileInput(['class' => 'image_inputs_field']);
+ if(!empty($images[$lang]['preview']->article_media_id)) {
+ echo "
media->hash}/original.{$images[$lang]['preview']->media->extension}' width='100' class='image_inputs_prev'>";
+ }
+ ?>
+
+ end();
+ ?>
+
+
+ field(is_array($images[0]['additional'])?$images[0]['additional'][0]:$images[0]['additional'], "[0][additional]imageFile[]")->fileInput(['multiple' => 'multiple', 'class' => 'image_inputs_field']);
+ if(is_array($images[0]['additional']) && count($images[0]['additional']) > 1) {
+ foreach($images[0]['additional'] as $onefield => $oneimage) {
+ if($onefield) {
+ ?>
+
+
+
+
+
+
+
+ $article_langs,
+ 'form' => $form,
+ 'ajaxView' => '@common/modules/blog/views/ajax/_article_form',
+ ]);
+ /*
+ $multilang = Multilang::begin(['ajaxpath' => Url::to(['/blog/ajax/article-form']), 'form' => $form, 'data_langs' => $article_langs]);
+ ?>
+ $article_lang) {
+ ?>
+
+
+ = $form->field($article_langs[$index], "[$index]language_id")->label(false)->hiddenInput(['value' => $index]) ?>
+
+ = $form->field($article_langs[$index], "[$index]text")->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ], ]); ?>
+
+ = $form->field($article_langs[$index], "[$index]preview")->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ], ]); ?>
+
+ = $form->field($article_langs[$index], "[$index]seo_url")->textInput() ?>
+
+ = $form->field($article_langs[$index], "[$index]name")->textInput() ?>
+
+ = $form->field($article_langs[$index], "[$index]meta_title")->textInput() ?>
+
+ = $form->field($article_langs[$index], "[$index]meta_descr")->textarea(); ?>
+
+ = $form->field($article_langs[$index], "[$index]meta_keywords")->textInput() ?>
+
+ = $form->field($article_langs[$index], "[$index]h1_tag")->textInput() ?>
+
+ = $form->field($article_langs[$index], "[$index]tags")->textInput() ?>
+
+
+
+ end();
+ */
+ ?>
+
+
+ = Html::submitButton($article->isNewRecord ? Yii::t('app', 'Create') : Yii::t('app', 'Update'), ['class' => $article->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/modules/blog/views/article/create.php b/common/modules/blog/views/article/create.php
new file mode 100644
index 0000000..6cd9da6
--- /dev/null
+++ b/common/modules/blog/views/article/create.php
@@ -0,0 +1,19 @@
+title = Yii::t('app', 'Article create');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Articles'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'article_langs' => $article_langs,
+ 'article' => $article,
+ 'langs' => $langs,
+ 'images' => $images
+ ]) ?>
+
+
diff --git a/common/modules/blog/views/article/index.php b/common/modules/blog/views/article/index.php
new file mode 100644
index 0000000..ce83f97
--- /dev/null
+++ b/common/modules/blog/views/article/index.php
@@ -0,0 +1,32 @@
+ $dataProvider,
+ 'columns' => [
+ 'article_id',
+ 'code',
+ 'date_add',
+ [
+ 'value' => function($data) {
+ return $data->user->firstname.' '.$data->user->lastname;
+ },
+ 'header' => Yii::t('app', 'Author')
+ ],
+ [
+ 'class' => Column::className(),
+ 'header' => Yii::t('app', 'Name'),
+ 'content' => function($model, $key, $index, $column) {
+ return $model->getArticleLangs()->where(['language_id' => Language::getDefaultLang()->language_id])->one()->name;
+ }
+ ],
+ [
+ 'class' => ActionColumn::className(),
+ 'template' => '{update} {delete}'
+ ]
+ ]
+]);
\ No newline at end of file
diff --git a/common/modules/blog/views/article/update.php b/common/modules/blog/views/article/update.php
new file mode 100644
index 0000000..4458a2b
--- /dev/null
+++ b/common/modules/blog/views/article/update.php
@@ -0,0 +1,18 @@
+title = Yii::t('app', 'Article update');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Articles'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+ = $this->render('_form', [
+ 'article_langs' => $article_langs,
+ 'article' => $article,
+ 'langs' => $langs,
+ 'images' => $images
+ ]) ?>
+
+
diff --git a/common/modules/blog/views/category/_form.php b/common/modules/blog/views/category/_form.php
new file mode 100644
index 0000000..001da73
--- /dev/null
+++ b/common/modules/blog/views/category/_form.php
@@ -0,0 +1,158 @@
+
+
+
+ ['enctype' => 'multipart/form-data']]); ?>
+
+ = $form->field($category, 'code')->hint(Yii::t('app', 'Insensitive latin non-space'))->textInput() ?>
+
+ = $form->field($category, 'tag')->hint(Yii::t('app', 'Comma-separated'))->textInput() ?>
+
+ = $form->field($category, 'sort')->input('number') ?>
+
+ = $form->field($category, 'article_category_pid')
+ ->dropDownList(ArticleCategory::findArticleCategoryDropdown($category->article_category_id), ['prompt' => Yii::t('app', 'Select parent')]) ?>
+
+ = $form->field($category, 'status')->checkbox() ?>
+
+
+
+
+ Url::to(['/blog/ajax/article-category-media-form?type=full']),
+ 'form' => $form,
+ 'data_langs' => $category->getIsNewRecord()?$images:ArticleCategoryMedia::find()->where(['article_category_id' => $category->article_category_id, 'type' => 'full'])->indexBy('language_id')->all()
+ ]);
+ $first = 1;
+ foreach($images as $lang => $value) {
+ if(!array_key_exists('full', $value)) continue;
+ ?>
+
+ field($images[$lang]['full'], "[{$lang}][full]language_id")->label(false)->hiddenInput(['value' => $lang]);
+ echo $form->field($images[$lang]['full'], "[{$lang}][full]imageFile")->fileInput(['class' => 'image_inputs_field']);
+ if(!empty($images[$lang]['full']->article_category_media_id)) {
+ echo "
media->hash}/original.{$images[$lang]['full']->media->extension}' width='100' class='image_inputs_prev'>";
+ }
+ ?>
+
+ end();
+ ?>
+
+
+ Url::to(['/blog/ajax/article-category-media-form?type=preview']),
+ 'form' => $form,
+ 'data_langs' => $category->getIsNewRecord()?$images:ArticleCategoryMedia::find()->where(['article_category_id' => $category->article_category_id, 'type' => 'preview'])->indexBy('language_id')->all()
+ ]);
+ $first = 1;
+ foreach($images as $lang => $value) {
+ if(!array_key_exists('preview', $value)) continue;
+ ?>
+
+ field($images[$lang]['preview'], "[{$lang}][preview]language_id")->label(false)->hiddenInput(['value' => $lang]);
+ echo $form->field($images[$lang]['preview'], "[{$lang}][preview]imageFile")->fileInput(['class' => 'image_inputs_field']);
+ if(!empty($images[$lang]['preview']->article_category_media_id)) {
+ echo "
media->hash}/original.{$images[$lang]['preview']->media->extension}' width='100' class='image_inputs_prev'>";
+ }
+ ?>
+
+ end();
+ ?>
+
+
+ field(is_array($images[0]['additional'])?$images[0]['additional'][0]:$images[0]['additional'], "[0][additional]imageFile[]")->fileInput(['multiple' => 'multiple', 'class' => 'image_inputs_field']);
+ if(is_array($images[0]['additional']) && count($images[0]['additional']) > 1) {
+ foreach($images[0]['additional'] as $onefield => $oneimage) {
+ if($onefield) {
+ ?>
+
+
+
+
+
+
+
+ Url::to(['/blog/ajax/category-form']), 'form' => $form, 'data_langs' => $category_langs])
+ ?>
+ $category_lang) {
+ ?>
+
+ = $form->field($category_langs[$index], "[$index]language_id")->label(false)->hiddenInput(['value' => $index]) ?>
+
+ = $form->field($category_langs[$index], "[$index]text")->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ], ]); ?>
+
+ = $form->field($category_langs[$index], "[$index]preview")->widget(CKEditor::className(),['editorOptions' => [ 'preset' => 'full', 'inline' => false, ], ]); ?>
+
+ = $form->field($category_langs[$index], "[$index]seo_url")->textInput() ?>
+
+ = $form->field($category_langs[$index], "[$index]name")->textInput() ?>
+
+ = $form->field($category_langs[$index], "[$index]meta_title")->textInput() ?>
+
+ = $form->field($category_langs[$index], "[$index]meta_descr")->textarea(); ?>
+
+ = $form->field($category_langs[$index], "[$index]meta_keyword")->textInput() ?>
+
+ = $form->field($category_langs[$index], "[$index]h1_tag")->textInput() ?>
+
+ = $form->field($category_langs[$index], "[$index]tag")->textInput() ?>
+
+
+
+ end();
+ ?>
+
+
+ = Html::submitButton($category->isNewRecord ? Yii::t('app', 'Create') : Yii::t('app', 'Update'), ['class' => $category->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/modules/blog/views/category/create.php b/common/modules/blog/views/category/create.php
new file mode 100644
index 0000000..265a30d
--- /dev/null
+++ b/common/modules/blog/views/category/create.php
@@ -0,0 +1,19 @@
+title = Yii::t('app', 'Category create');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Categories'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'category_langs' => $category_langs,
+ 'category' => $category,
+ 'langs' => $langs,
+ 'images' => $images
+ ]) ?>
+
+
diff --git a/common/modules/blog/views/category/index.php b/common/modules/blog/views/category/index.php
new file mode 100644
index 0000000..f5e1d9e
--- /dev/null
+++ b/common/modules/blog/views/category/index.php
@@ -0,0 +1,26 @@
+ $dataProvider,
+ 'columns' => [
+ 'article_category_id',
+ 'code',
+ 'date_add',
+ 'date_update',
+ [
+ 'class' => Column::className(),
+ 'header' => Yii::t('app', 'Name'),
+ 'content' => function($model, $key, $index, $column) {
+ return $model->getArticleCategoryLangs()->orderBy(['language_id' => 'ASC'])->one()->name;
+ }
+ ],
+ [
+ 'class' => ActionColumn::className(),
+ 'template' => '{update} {delete}'
+ ]
+ ]
+]);
\ No newline at end of file
diff --git a/common/modules/blog/views/category/update.php b/common/modules/blog/views/category/update.php
new file mode 100644
index 0000000..353c73e
--- /dev/null
+++ b/common/modules/blog/views/category/update.php
@@ -0,0 +1,18 @@
+title = Yii::t('app', 'Update category');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Categories'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+ = $this->render('_form', [
+ 'category_langs' => $category_langs,
+ 'category' => $category,
+ 'langs' => $langs,
+ 'images' => $images
+ ]) ?>
+
+
diff --git a/common/modules/blog/views/default/index.php b/common/modules/blog/views/default/index.php
new file mode 100644
index 0000000..17d1a81
--- /dev/null
+++ b/common/modules/blog/views/default/index.php
@@ -0,0 +1,7 @@
+value);
\ No newline at end of file
diff --git a/common/modules/blog/views/media/index.php b/common/modules/blog/views/media/index.php
new file mode 100644
index 0000000..b931b6b
--- /dev/null
+++ b/common/modules/blog/views/media/index.php
@@ -0,0 +1,37 @@
+ ['enctype' => 'multipart/form-data']]);
+
+echo $form->field($model, 'imageFile')->fileInput(['multiple' => 'multiple']);
+
+?>
+
+
+ = Html::submitButton(Yii::t('app', 'Create'), ['class' => 'btn btn-success']) ?>
+
+
+
diff --git a/common/modules/blog/views/test/index.php b/common/modules/blog/views/test/index.php
new file mode 100644
index 0000000..7244adf
--- /dev/null
+++ b/common/modules/blog/views/test/index.php
@@ -0,0 +1,11 @@
+ $model,
+ 'form' => $form,
+ 'ajaxView' => '@common/modules/blog/views/ajax/_article_form_test',
+]);
+$form->end();
\ No newline at end of file
diff --git a/common/modules/comment/Controller.php b/common/modules/comment/Controller.php
new file mode 100644
index 0000000..d0ce56f
--- /dev/null
+++ b/common/modules/comment/Controller.php
@@ -0,0 +1,96 @@
+ [
+ 'class' => \yii\filters\VerbFilter::className(),
+ 'actions' => [
+ '*' => ['post'],
+ ],
+ ],
+ ];
+ }
+
+ public function actionDelete()
+ {
+ \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+ $post = \Yii::$app->request->post('Comment');
+ if(!empty($post['comment_id'])) {
+ if($model = \common\modules\comment\models\Comment::findOne($post['comment_id'])) {
+ /**
+ * @var \common\modules\comment\models\Comment $model
+ */
+ $model->scenario = is_int(\Yii::$app->user->getId()) ? \common\modules\comment\models\Comment::SCENARIO_USER : \common\modules\comment\models\Comment::SCENARIO_GUEST;
+ if($model->deleteComment()) {
+ \Yii::$app->response->data = ['text' => 'Comment marked as deleted and will be check by administrators'];
+ } else {
+ \Yii::$app->response->data = ['error' => $model->hasErrors('comment_id')?$model->getFirstError('comment_id'):'Cannot delete message'];
+ }
+ }else {
+ \Yii::$app->response->data = ['error' => 'Comment not found'];
+ };
+ } else {
+ \Yii::$app->response->data = ['error' => 'Missing comment_id'];
+ }
+ \Yii::$app->response->send();
+ }
+
+ public function actionUpdate()
+ {
+ \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+ $post = \Yii::$app->request->post();
+ if(!empty($post['Comment']['comment_id'])) {
+ if($model = \common\modules\comment\models\Comment::findOne($post['Comment']['comment_id'])) {
+ /**
+ * @var \common\modules\comment\models\Comment $model
+ */
+ $model->scenario = is_int(\Yii::$app->user->getId()) ? \common\modules\comment\models\Comment::SCENARIO_USER : \common\modules\comment\models\Comment::SCENARIO_GUEST;
+ $model->load($post);
+ if(empty($post['Comment']['comment_pid'])) {
+ $model->comment_pid = null;
+ }
+ if($model->updateComment()) {
+ \Yii::$app->response->data = ['text' => 'Comment successfully updated'];
+ } else {
+ \Yii::$app->response->data = ['error' => $model->hasErrors()?$model->getFirstErrors():'Cannot update message'];
+ }
+ }else {
+ \Yii::$app->response->data = ['error' => 'Comment not found'];
+ };
+ } else {
+ \Yii::$app->response->data = ['error' => 'Missing comment_id'];
+ }
+ \Yii::$app->response->send();
+ }
+
+ public function actionForm()
+ {
+ $post = \Yii::$app->request->post('Comment');
+ if(!empty($post['comment_id'])) {
+ $model = \common\modules\comment\models\Comment::find()->where(['comment_id' => $post['comment_id']])->with('parent', 'author')->one();
+ if($model) {
+ /**
+ * @var \common\modules\comment\models\Comment $model
+ */
+ $model->scenario = is_int(\Yii::$app->user->getId()) ? \common\modules\comment\models\Comment::SCENARIO_USER : \common\modules\comment\models\Comment::SCENARIO_GUEST;
+ if($model->checkUpdate()) {
+ return $this->renderAjax('@common/modules/comment/views/comment_form', [
+ 'model' => $model,
+ ]);
+ } else {
+ \Yii::$app->response->data = ['error' => 'You are not able to update this comment'];
+ }
+ }else {
+ \Yii::$app->response->data = ['error' => 'Comment not found'];
+ };
+ } else {
+ \Yii::$app->response->data = ['error' => 'Missing comment_id'];
+ }
+ \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+ \Yii::$app->response->send();
+ }
+ }
\ No newline at end of file
diff --git a/common/modules/comment/Module.php b/common/modules/comment/Module.php
new file mode 100644
index 0000000..6a18e72
--- /dev/null
+++ b/common/modules/comment/Module.php
@@ -0,0 +1,74 @@
+
+ * [
+ * 'rules' => [
+ * \full\namapaced\ClassName,
+ * \another\one\ClassName,
+ * ],
+ * 'permissions' => [
+ * [
+ * 'name' => stringName,
+ * 'description' => descriptionText,
+ * 'ruleName' => (new \full\namespaced\ClassName())->name (optional)
+ * ],
+ * [
+ * 'name' => stringName2,
+ * 'description' => descriptionText2,
+ * 'ruleName' => (new \another\one\ClassName())->name (optional)
+ * ],
+ * ]
+ * ]
+ *
+ *
+ * @var array
+ * @see \common\modules\comment\commands\RbacController
+ */
+ public $rbac = [];
+
+ /**
+ * @var \yii\db\Connection Connection to the db
+ */
+ public $db = null;
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if(\Yii::$app instanceof \yii\console\Application) {
+ $this->controllerNamespace = 'common\modules\comment\commands';
+ }
+ if($this->db === null) {
+ $this->db = \Yii::$app->db;
+ } elseif(!$this->db instanceof \yii\db\Connection) {
+ throw new \yii\base\InvalidConfigException('Конфиг db обязан наследоваться от'.\yii\db\Connection::className());
+ }
+ }
+ }
\ No newline at end of file
diff --git a/common/modules/comment/Permissions.php b/common/modules/comment/Permissions.php
new file mode 100644
index 0000000..965f0f4
--- /dev/null
+++ b/common/modules/comment/Permissions.php
@@ -0,0 +1,12 @@
+controller->module;
+ if(!$module->useRbac) {
+ throw new \yii\base\InvalidConfigException('Please set useRbac config to TRUE in your module configs');
+ }
+ $auth = \Yii::$app->getAuthManager();
+ if(!$auth instanceof \yii\rbac\ManagerInterface) {
+ throw new \yii\base\InvalidConfigException('ManagerInterface is not configured');
+ }
+ if(!empty($module->rbac['rules'])) {
+ foreach($module->rbac['rules'] as $rule) {
+ $rule_model = new $rule();
+ echo "Creating rule: ".$rule_model->name."\n";
+ if($auth->add($rule_model)) {
+ echo "Successful\n";
+ } else {
+ echo "Failed\n";
+ }
+ unset($rule_model);
+ }
+ }
+ if(!empty($module->rbac['permissions'])) {
+ foreach($module->rbac['permissions'] as $permission) {
+ echo "Creating permission: ".$permission['name']."\n";
+ if($auth->add(new \yii\rbac\Permission($permission))) {
+ echo "Successful\n";
+ } else {
+ echo "Failed\n";
+ }
+ }
+ }
+ }
+
+ public function actionUninstall()
+ {
+ /**
+ * @var \common\modules\comment\Module $module
+ */
+ $module = \Yii::$app->controller->module;
+ if(!$module->useRbac) {
+ throw new \yii\base\InvalidConfigException('Please set useRbac config to TRUE in your module configs');
+ }
+ $auth = \Yii::$app->getAuthManager();
+ if(!$auth instanceof \yii\rbac\ManagerInterface) {
+ throw new \yii\base\InvalidConfigException('ManagerInterface is not configured');
+ }
+ if(!empty($module->rbac['rules'])) {
+ foreach($module->rbac['rules'] as $rule) {
+ $rule_model = new $rule();
+ echo "Removing rule: ".$rule_model->name."\n";
+ if($auth->remove($rule_model)) {
+ echo "Successful\n";
+ } else {
+ echo "Failed\n";
+ }
+ unset($rule_model);
+ }
+ }
+ if(!empty($module->rbac['permissions'])) {
+ foreach($module->rbac['permissions'] as $permission) {
+ echo "Removing permission: ".$permission['name']."\n";
+ if($auth->remove(new \yii\rbac\Permission($permission))) {
+ echo "Successful\n";
+ } else {
+ echo "Failed\n";
+ }
+ }
+ }
+ }
+
+ }
\ No newline at end of file
diff --git a/common/modules/comment/interfaces/CommentInterface.php b/common/modules/comment/interfaces/CommentInterface.php
new file mode 100644
index 0000000..471dddf
--- /dev/null
+++ b/common/modules/comment/interfaces/CommentInterface.php
@@ -0,0 +1,11 @@
+ 1,
+ ],
+ [
+ [ 'comment_pid' ],
+ 'exist',
+ 'targetAttribute' => 'comment_id',
+ 'filter' => [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ],
+ ],
+ ];
+ }
+
+ public function scenarios()
+ {
+ return [
+ self::SCENARIO_GUEST => [
+ 'user_name',
+ 'user_email',
+ 'text',
+ 'comment_pid',
+ ],
+ self::SCENARIO_USER => [
+ 'text',
+ 'comment_pid',
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function behaviors()
+ {
+ return [
+ [
+ 'class' => \yii\behaviors\TimestampBehavior::className(),
+ 'createdAtAttribute' => 'date_add',
+ 'updatedAtAttribute' => 'date_update',
+ 'value' => new \yii\db\Expression('NOW()'),
+ ],
+ ];
+ }
+
+ public static function tableName()
+ {
+ return '{{%comment}}';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'text' => \Yii::t('app', 'Комментарий'),
+ 'user_name' => \Yii::t('app', 'Имя'),
+ 'user_email' => \Yii::t('app', 'Email'),
+ ];
+ }
+
+ public function getGuestComment()
+ {
+ return $this->guestComment;
+ }
+
+ public function setGuestComment($value)
+ {
+ $this->guestComment = $value;
+ }
+
+ /**
+ * @param string $model
+ * @param integer $model_id
+ *
+ * @return ActiveQuery
+ */
+ public function getComments($model, $model_id)
+ {
+ return $this->find()
+ ->where([
+ 'comment.model' => $model,
+ 'comment.model_id' => $model_id,
+ 'comment.status' => 1,
+ ]);
+ }
+
+ public function postComment()
+ {
+ if($this->checkCreate()) {
+ if($this->insert()) {
+ $this->clearSafe();
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ $this->addError('comment_id', 'You can`t post comment here');
+ return false;
+ }
+ }
+
+ public function updateComment()
+ {
+ if($this->checkUpdate()) {
+ if(empty( $this->comment_id )) {
+ $this->addError('comment_id', 'Comment ID not found');
+ return false;
+ } else {
+ if($this->update()) {
+ $this->clearSafe();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } else {
+ $this->addError('comment_id', 'You can`t update this post');
+ return false;
+ }
+ }
+
+ public function deleteComment()
+ {
+ if($this->checkDelete()) {
+ if(empty( $this->comment_id )) {
+ $this->addError('comment_id', 'Comment ID not found');
+ return false;
+ } else {
+ if($this->status == self::STATUS_DELETED) {
+ return false;
+ }
+ $this->status = self::STATUS_DELETED;
+ if($this->update()) {
+ $this->clearSafe();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } else {
+ $this->addError('comment_id', 'You can`t delete this post');
+ return false;
+ }
+ }
+
+ public function checkCreate()
+ {
+ if($this->getGuestComment()) {
+ return true;
+ } else {
+ return \Yii::$app->user->can(\common\modules\comment\Permissions::CREATE, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]);
+ }
+ }
+
+ public function checkUpdate()
+ {
+ if($this->scenario == self::SCENARIO_GUEST) {
+ return false;
+ } else {
+ return \Yii::$app->user->can(\common\modules\comment\Permissions::UPDATE, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]) || \Yii::$app->user->can(\common\modules\comment\Permissions::UPDATE_OWN, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]);
+ }
+ }
+
+ public function checkDelete()
+ {
+ if($this->scenario == self::SCENARIO_GUEST) {
+ return false;
+ } else {
+ return \Yii::$app->user->can(\common\modules\comment\Permissions::DELETE, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]) || \Yii::$app->user->can(\common\modules\comment\Permissions::DELETE_OWN, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]);
+ }
+ }
+
+ protected function clearSafe($setNew = true)
+ {
+ $safe = $this->safeAttributes();
+ $count = count($safe);
+ $values = array_fill(0, $count, NULL);
+ $result = array_combine($safe, $values);
+ $this->setAttributes($result);
+ $this->setIsNewRecord($setNew);
+ }
+
+ public function getParent()
+ {
+ return $this->hasOne(self::className(), [ 'comment_id' => 'comment_pid' ]);
+ }
+
+ public function getAuthor()
+ {
+ // if($this->user_id != NULL) {
+ return $this->hasOne(\common\models\User::className(), [ 'id' => 'user_id' ]);
+ // } else {
+ // return ['firstname' => $this->user_name, 'email' => $this->user_email];
+ // }
+ }
+
+ public function checkRating()
+ {
+ $rating = $this->hasOne(\common\modules\comment\models\Rating::className(), [
+ 'model_id' => 'comment_id',
+ ])
+ ->andWhere([
+ 'model' => $this->className(),
+ ])
+ ->one();
+ if(!$rating instanceof \common\modules\comment\models\Rating) {
+ $rating = new \common\modules\comment\models\Rating([
+ 'model' => $this->className(),
+ 'model_id' => $this->comment_id,
+ 'user_id' => $this->user_id,
+ ]);
+ $rating->save();
+ }
+ }
+
+ public function getRating()
+ {
+ $this->checkRating();
+ return $this->hasOne(\common\modules\comment\models\Rating::className(), [
+ 'model_id' => 'comment_id',
+ ])
+ ->andWhere([ 'model' => $this->className() ]);
+ }
+
+ public function hasRating($return = true)
+ {
+ $rating = $this->hasOne(\common\modules\comment\models\Rating::className(), [
+ 'model_id' => 'comment_id',
+ ])
+ ->andWhere([ 'model' => $this->className() ])
+ ->andWhere([
+ 'not',
+ [ 'value' => NULL ],
+ ])
+ ->one();
+ if($return) {
+ return $rating;
+ } else {
+ return $rating ? true : false;
+ }
+ }
+
+ }
diff --git a/common/modules/comment/models/CommentProject.php b/common/modules/comment/models/CommentProject.php
new file mode 100644
index 0000000..7cc3793
--- /dev/null
+++ b/common/modules/comment/models/CommentProject.php
@@ -0,0 +1,328 @@
+ 0,
+ ],
+ [
+ [
+ 'budget_currency',
+ ],
+ 'default',
+ 'value' => 3,
+ ],
+ [
+ [ 'budget_currency' ],
+ 'exist',
+ 'targetClass' => Currency::className(),
+ 'targetAttribute' => 'currency_id',
+ ],
+ [
+ [
+ 'files',
+ ],
+ 'string',
+ ],
+ [
+ [
+ 'file',
+ ],
+ 'safe',
+ ],
+ [
+ [ 'status' ],
+ 'default',
+ 'value' => 1,
+ ],
+ ];
+ }
+
+ public function scenarios()
+ {
+ return [
+ self::SCENARIO_USER => [
+ 'text',
+ 'budget_from',
+ 'budget_to',
+ 'term_from',
+ 'term_to',
+ 'file',
+ ],
+ self::SCENARIO_GUEST => [
+
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function behaviors()
+ {
+ return [
+ [
+ 'class' => \yii\behaviors\TimestampBehavior::className(),
+ 'createdAtAttribute' => 'date_add',
+ 'updatedAtAttribute' => 'date_update',
+ 'value' => new \yii\db\Expression('NOW()'),
+ ],
+ ];
+ }
+
+ public static function tableName()
+ {
+ return '{{%comment_project}}';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'text' => \Yii::t('app', 'Текст ответа'),
+ 'budget_from' => \Yii::t('app', 'от'),
+ 'budget_to' => \Yii::t('app', 'до'),
+ 'term_from' => \Yii::t('app', 'от'),
+ 'term_to' => \Yii::t('app', 'до'),
+ ];
+ }
+
+ public function getGuestComment()
+ {
+ return $this->guestComment;
+ }
+
+// public function setGuestComment($value)
+// {
+// $this->guestComment = $value;
+// }
+
+ /**
+ * @param string $model
+ * @param integer $model_id
+ *
+ * @return ActiveQuery
+ */
+ public function getComments($model, $model_id)
+ {
+ return $this->find()
+ ->where([
+ 'comment_project.model' => $model,
+ 'comment_project.model_id' => $model_id,
+ 'comment_project.status' => 1,
+ ]);
+ }
+
+ public function postComment()
+ {
+ if($this->checkCreate()) {
+ if(!empty(\Yii::$app->request->post($this->formName())['anonymous'])) {
+ $this->status = self::STATUS_ANONYMOUS;
+ }
+ $this->file = UploadedFile::getInstances($this, 'file');
+ if(!empty($this->file)) {
+ $file_id = [];
+ if(is_array($this->file)){
+ foreach($this->file as $file){
+ if($file instanceof UploadedFile){
+ $file_model = new File();
+ $file_id[] = $file_model->saveFile($file);
+ }
+ }
+ } else {
+ if($this->file instanceof UploadedFile){
+ $file_model = new File();
+ $file_id[] = $file_model->saveFile($this->file);
+ }
+ }
+ $this->files = json_encode($file_id);
+ }
+ if($this->insert()) {
+ $this->clearSafe();
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ $this->addError('comment_id', 'You can`t post comment here');
+ return false;
+ }
+ }
+
+ public function updateComment()
+ {
+ if($this->checkUpdate()) {
+ if(empty( $this->comment_id )) {
+ $this->addError('comment_id', 'Comment ID not found');
+ return false;
+ } else {
+ if($this->update()) {
+ $this->clearSafe();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } else {
+ $this->addError('comment_id', 'You can`t update this post');
+ return false;
+ }
+ }
+
+ public function deleteComment()
+ {
+ if($this->checkDelete()) {
+ if(empty( $this->comment_id )) {
+ $this->addError('comment_id', 'Comment ID not found');
+ return false;
+ } else {
+ if($this->status == self::STATUS_DELETED) {
+ return false;
+ }
+ $this->status = self::STATUS_DELETED;
+ if($this->update()) {
+ $this->clearSafe();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } else {
+ $this->addError('comment_id', 'You can`t delete this post');
+ return false;
+ }
+ }
+
+ public function checkCreate()
+ {
+ if($this->getGuestComment()) {
+ return true;
+ } else {
+ return \Yii::$app->user->can(\common\modules\comment\Permissions::CREATE, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]);
+ }
+ }
+
+ public function checkUpdate()
+ {
+ if($this->scenario == self::SCENARIO_GUEST) {
+ return false;
+ } else {
+ return \Yii::$app->user->can(\common\modules\comment\Permissions::UPDATE, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]) || \Yii::$app->user->can(\common\modules\comment\Permissions::UPDATE_OWN, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]);
+ }
+ }
+
+ public function checkDelete()
+ {
+ if($this->scenario == self::SCENARIO_GUEST) {
+ return false;
+ } else {
+ return \Yii::$app->user->can(\common\modules\comment\Permissions::DELETE, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]) || \Yii::$app->user->can(\common\modules\comment\Permissions::DELETE_OWN, [
+ 'model' => $this->model,
+ 'model_id' => $this->model_id,
+ ]);
+ }
+ }
+
+ protected function clearSafe($setNew = true)
+ {
+ $safe = $this->safeAttributes();
+ $count = count($safe);
+ $values = array_fill(0, $count, NULL);
+ $result = array_combine($safe, $values);
+ $this->setAttributes($result);
+ $this->setIsNewRecord($setNew);
+ }
+
+ public function getAuthor()
+ {
+ // if($this->user_id != NULL) {
+ return $this->hasOne(\common\models\User::className(), [ 'id' => 'user_id' ]);
+ // } else {
+ // return ['firstname' => $this->user_name, 'email' => $this->user_email];
+ // }
+ }
+
+ }
diff --git a/common/modules/comment/models/Rating.php b/common/modules/comment/models/Rating.php
new file mode 100644
index 0000000..3222732
--- /dev/null
+++ b/common/modules/comment/models/Rating.php
@@ -0,0 +1,64 @@
+ Yii::t('app', 'Rating ID'),
+ 'date_add' => Yii::t('app', 'Date Add'),
+ 'date_update' => Yii::t('app', 'Date Update'),
+ 'user_id' => Yii::t('app', 'User ID'),
+ 'entity' => Yii::t('app', 'Entity'),
+ 'value' => Yii::t('app', 'Value'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getUser()
+ {
+ return $this->hasOne(\common\models\User::className(), ['id' => 'user_id']);
+ }
+}
diff --git a/common/modules/comment/rbac/ArtboxCommentCreateRule.php b/common/modules/comment/rbac/ArtboxCommentCreateRule.php
new file mode 100644
index 0000000..6f481fc
--- /dev/null
+++ b/common/modules/comment/rbac/ArtboxCommentCreateRule.php
@@ -0,0 +1,17 @@
+'+data.text+' ');
+ $(container).remove();
+ } else {
+ $(container).prepend(''+data.error+'
')
+ }
+ }
+ );
+ }
+ });
+
+ $(document).on('click', '.artbox_comment_reply', function() {
+ var container = $(this).parents('.artbox_comment_container').first();
+ var comment_id = $(container).data('comment_id');
+ var form_name = $(container).data('form_name');
+ var author = $(container).find('.artbox_comment_author').first().text();
+ var comment_form = $('.artbox_comment_form').first();
+ var offset = $(comment_form).offset();
+ var reply_block = $(comment_form).find('.artbox_comment_reply_block').first();
+ $(reply_block).empty();
+ $(reply_block).append(' ');
+ $(reply_block).append('');
+ $('html, body').animate({
+ scrollTop: offset.top - 50,
+ });
+ });
+
+ $(document).on('click', '.artbox_comment_reply_author', function() {
+ $(this).parents('.artbox_comment_reply_block').first().empty();
+ });
+
+ $(document).on('click', '.artbox_comment_update', function() {
+ $(this).removeClass('artbox_comment_update');
+ $(this).text('Сохранить');
+ $(this).addClass('artbox_comment_update_submit');
+ var container = $(this).parents('.artbox_comment_container').first();
+ var comment_id = $(container).data('comment_id');
+ var form_name = $(container).data('form_name');
+ var text = $(container).find('.artbox_comment_text');
+ var object = {};
+ object[form_name] = {comment_id: comment_id};
+ $.post(
+ '/artbox-comment/form',
+ object,
+ function(data, textStatus, jqXHR) {
+ $(text).hide();
+ $(text).after(
+ ''
+ );
+ }
+ );
+ });
+
+ $(document).on('click', '.artbox_comment_update_reply', function() {
+ $(this).remove();
+ });
+
+ $(document).on('click', '.artbox_comment_update_submit', function(e) {
+ e.preventDefault();
+ var container = $(this).parents('.artbox_comment_container').first();
+ var edit = $(container).find('.artbox_comment_text_edit').first();
+ $.post(
+ '/artbox-comment/update',
+ $(edit).find('form').serialize(),
+ function(data) {
+ if(!data.error) {
+ location.reload(true);
+ }
+ }
+ )
+ });
+});
\ No newline at end of file
diff --git a/common/modules/comment/resources/delete-ico.png b/common/modules/comment/resources/delete-ico.png
new file mode 100644
index 0000000..52c3718
Binary files /dev/null and b/common/modules/comment/resources/delete-ico.png differ
diff --git a/common/modules/comment/views/comment_form.php b/common/modules/comment/views/comment_form.php
new file mode 100644
index 0000000..717dcc1
--- /dev/null
+++ b/common/modules/comment/views/comment_form.php
@@ -0,0 +1,38 @@
+
+field($model, 'comment_id')
+ ->label(false)
+ ->hiddenInput();
+ if(!empty( $model->parent )) {
+ ?>
+
+ field($model, 'text')
+ ->label(false)
+ ->textarea();
+?>
+end();
+?>
\ No newline at end of file
diff --git a/common/modules/comment/widgets/CommentWidget.php b/common/modules/comment/widgets/CommentWidget.php
new file mode 100644
index 0000000..52c316f
--- /dev/null
+++ b/common/modules/comment/widgets/CommentWidget.php
@@ -0,0 +1,229 @@
+ 'div',
+ 'view' => 'list-comment',
+ 'class' => 'test-class',
+ ];
+
+ /**
+ * @var array Options sent to success part
+ */
+ public $success_options = [
+ 'tag' => 'div',
+ 'content' => NULL,
+ 'class' => 'test-class-success',
+ ];
+
+ /**
+ * @var array Options sent to form part
+ */
+ public $form_options = [
+ 'tag' => 'div',
+ 'view' => 'form-comment',
+ 'class' => 'test-class-form',
+ ];
+
+ /**
+ * @var bool Indicates whether any successful action happened
+ */
+ protected $isSuccess = false;
+
+ public $success_text = 'Comment successfully added';
+
+ /**
+ * @var string $model Model, to which comments attached
+ */
+ public $model;
+
+ /**
+ * @var integer $model_id Model id, to which comments attached
+ */
+ public $model_id;
+
+ /**
+ * @var string Template of the widget. You may use {success}, {form}, {list}
+ * to render particular parts. You are also able to use common HTML here.
+ */
+ public $template = "{success}\n{form}\n{list}";
+
+ /**
+ * @var array Widget options
+ */
+ public $options = [ ];
+
+ /**
+ * @var \yii\data\DataProviderInterface Data provider of comments
+ */
+ public $dataProvider;
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ \common\modules\comment\assets\CommentAsset::register($this->view);
+ if(is_string($this->comment_class)) {
+ $this->comment_class = new $this->comment_class($this->class_options);
+ } else {
+ throw new \yii\base\InvalidConfigException(__CLASS__ . '->comment_class must be defined as object full class name string.');
+ }
+ if(!empty( $this->rating_class ) && is_string($this->rating_class)) {
+ $this->rating_class = new $this->rating_class($this->rating_options);
+ } elseif(!empty( $this->rating_class )) {
+ throw new \yii\base\InvalidConfigException(__CLASS__ . '->rating_class must be defined as object full class name string.');
+ }
+ $this->comment_class->model = $this->model;
+ $this->comment_class->model_id = $this->model_id;
+ $this->createDataProvider();
+ $this->process();
+ ob_start();
+ }
+
+ /**
+ * @inheritdoc
+ * @return string
+ */
+ public function run()
+ {
+ $content = ob_get_clean();
+ $this->createParts();
+ return $this->renderWidget();
+ }
+
+ public function createParts()
+ {
+ if($this->display_comment_success && $this->isSuccess) {
+ $tag = ArrayHelper::remove($this->success_options, 'tag', 'div');
+ if(is_callable($this->success_options[ 'content' ])) {
+ $result = call_user_func(ArrayHelper::remove($this->success_options, 'content'), $this->success_text);
+ } elseif($this->success_options[ 'content' ] != NULL) {
+ $result = Html::encode(ArrayHelper::remove($this->success_options, 'content', $this->success_text));
+ } else {
+ $result = Html::encode($this->success_text);
+ }
+ $this->parts[ 'success' ] = Html::tag($tag, $result, $this->success_options);
+ unset( $tag, $result );
+ }
+
+ if($this->display_comment_list) {
+ $tag = ArrayHelper::remove($this->list_options, 'tag', 'div');
+ $view = ArrayHelper::remove($this->list_options, 'view');
+ $this->parts[ 'list' ] = Html::tag($tag, $this->renderItems($view), $this->list_options);
+ }
+
+ if($this->display_comment_form) {
+ $tag = ArrayHelper::remove($this->form_options, 'tag', 'div');
+ $view = ArrayHelper::remove($this->form_options, 'view');
+ $this->parts[ 'form' ] = Html::tag($tag, $this->renderForm($view), $this->form_options);
+ }
+ }
+
+ public function createDataProvider()
+ {
+ $this->dataProvider = new \yii\data\ActiveDataProvider([
+ 'query' => $this->comment_class->getComments($this->model, $this->model_id),
+ 'pagination' => [
+ 'pageSize' => 10,
+ ],
+ ]);
+ }
+
+ public function renderItems($view)
+ {
+ if(empty( $view )) {
+ throw new \yii\base\InvalidConfigException("list_options[view] must be set");
+ }
+ return $this->render($view, [ 'dataProvider' => $this->dataProvider ]);
+ }
+
+ public function renderForm($view)
+ {
+ if(empty( $view )) {
+ throw new \yii\base\InvalidConfigException("form_options[view] must be set");
+ }
+ return $this->render($view, [
+ 'model' => $this->comment_class,
+ 'rating' => $this->rating_class,
+ 'user' => \Yii::$app->user->identity,
+ 'dataProvider' => $this->dataProvider,
+ ]);
+ }
+
+ public function renderWidget()
+ {
+ $template = $this->template;
+ $parts = $this->parts;
+ $options = $this->options;
+ $template = preg_replace('/{success}/', ArrayHelper::remove($parts, 'success', ''), $template);
+ $template = preg_replace('/{list}/', ArrayHelper::remove($parts, 'list', ''), $template);
+ $template = preg_replace('/{form}/', ArrayHelper::remove($parts, 'form', ''), $template);
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ return Html::tag($tag, $template, $options);
+ }
+
+ public function process()
+ {
+ $data = \Yii::$app->request->post();
+ if($this->comment_class->load($data) && $this->comment_class->postComment()) {
+ if(is_object($this->rating_class) && $this->comment_class->rating->load($data) && $this->comment_class->rating->save()) {
+ $this->isSuccess = true;
+ }
+ }
+ }
+ }
\ No newline at end of file
diff --git a/common/modules/comment/widgets/views/_project_comment_view.php b/common/modules/comment/widgets/views/_project_comment_view.php
new file mode 100644
index 0000000..8e3c04f
--- /dev/null
+++ b/common/modules/comment/widgets/views/_project_comment_view.php
@@ -0,0 +1,113 @@
+user_id )) {
+ $user = User::find()
+ ->where([ 'id' => $model->user_id ])
+ ->with('userInfo')
+ ->one();
+ }
+?>
+
+
+
+
+
+
+
1.1 Строительная площадка расположена по адресу: г. Киев.
+
1.2 Существующий объект представляет собой помещение общей площадью ориентировочно – 140 м2.
+
1.3. Цель проекта состоит в проведении внутренних общестроительных и отделочных работ.
+
1.4. При разработке методов строительства и выборе материалов, используемых в настоящем проекте, необходимо учитывать климатические условия, характерные для г. Киева.
+
1.5. Требования к проектированию и производству работ определяются следующими документами:
+
- Техническим заданием.
+
- Строительными нормами и правилами.
+
Все проектные решения и все разделы рабочего проекта должны быть согласованы с Заказчиком в объеме, необходимом для последующей сдачи инженерных систем и коммуникаций.
+
+
+
+
+
+
diff --git a/common/modules/comment/widgets/views/form-comment.php b/common/modules/comment/widgets/views/form-comment.php
new file mode 100644
index 0000000..9262ebc
--- /dev/null
+++ b/common/modules/comment/widgets/views/form-comment.php
@@ -0,0 +1,70 @@
+
+Комментарии: = $dataProvider->totalCount ?>
+
+
+
+ field($rating, 'value')
+ ->label(false)
+ ->radioList([
+ 1 => 1,
+ 2 => 2,
+ 3 => 3,
+ 4 => 4,
+ 5 => 5,
+ ]);
+
+ if($model->scenario == $model::SCENARIO_GUEST) {
+ echo $form->field($model, 'user_name', [
+ 'options' => [
+ 'class' => 'input-blocks-comm',
+ ],
+ 'inputOptions' => [
+ 'class' => 'custom-input-4',
+ ],
+ ])
+ ->textInput();
+ echo $form->field($model, 'user_email', [
+ 'options' => [
+ 'class' => 'input-blocks-comm',
+ ],
+ 'inputOptions' => [
+ 'class' => 'custom-input-4',
+ ],
+ ])
+ ->textInput();
+ }
+
+ ?>
+
+ field($model, 'text', [
+ 'options' => [
+ 'class' => 'input-blocks-comm area-comm',
+ ],
+ 'inputOptions' => [
+ 'class' => 'custom-area-4',
+ ],
+ ])
+ ->textarea();
+ ?>
+
+ = Html::submitButton('Добавить комментарий') ?>
+
+ end();
+ ?>
+
\ No newline at end of file
diff --git a/common/modules/comment/widgets/views/form-project-comment.php b/common/modules/comment/widgets/views/form-project-comment.php
new file mode 100644
index 0000000..240d241
--- /dev/null
+++ b/common/modules/comment/widgets/views/form-project-comment.php
@@ -0,0 +1,101 @@
+
+
+
\ No newline at end of file
diff --git a/common/modules/comment/widgets/views/list-comment.php b/common/modules/comment/widgets/views/list-comment.php
new file mode 100644
index 0000000..4f37738
--- /dev/null
+++ b/common/modules/comment/widgets/views/list-comment.php
@@ -0,0 +1,12 @@
+ $dataProvider,
+ 'itemView' => 'project_comment_view',
+ 'itemOptions' => [
+ 'tag' => false,
+ ],
+ 'summary' => '',
+]);
\ No newline at end of file
diff --git a/common/modules/comment/widgets/views/list-project-comment.php b/common/modules/comment/widgets/views/list-project-comment.php
new file mode 100644
index 0000000..f592675
--- /dev/null
+++ b/common/modules/comment/widgets/views/list-project-comment.php
@@ -0,0 +1,22 @@
+
+
+
+
Предложения проектантов
+
+ $dataProvider,
+ 'itemView' => '_project_comment_view',
+ 'itemOptions' => [
+ 'class' => 'tender-offer-proj-blocks style',
+ ],
+ 'summary' => '',
+ ]);
+ ?>
+
+
+
\ No newline at end of file
diff --git a/common/modules/comment/widgets/views/project_comment_view.php b/common/modules/comment/widgets/views/project_comment_view.php
new file mode 100644
index 0000000..c5cf8a4
--- /dev/null
+++ b/common/modules/comment/widgets/views/project_comment_view.php
@@ -0,0 +1,84 @@
+user_id )) {
+ $user = User::find()
+ ->where([ 'id' => $model->user_id ])
+ ->with('userInfo')
+ ->one();
+ }
+?>
+
\ No newline at end of file
diff --git a/common/modules/file/FileUploadAsset.php b/common/modules/file/FileUploadAsset.php
new file mode 100644
index 0000000..7ad2791
--- /dev/null
+++ b/common/modules/file/FileUploadAsset.php
@@ -0,0 +1,43 @@
+
+ * @since 2.0
+ */
+class FileUploadAsset extends AssetBundle
+{
+
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ $this->sourcePath = __DIR__.'/assets';
+ }
+
+ public $css = [
+ 'css/jquery.fileupload.css',
+ 'css/fileupload/style.css'
+ ];
+
+ public $js = [
+ 'js/vendor/jquery.ui.widget.js',
+ 'js/jquery.iframe-transport.js',
+ 'js/jquery.fileupload.js'
+ ];
+}
diff --git a/common/modules/file/Module.php b/common/modules/file/Module.php
new file mode 100644
index 0000000..f0ac4c1
--- /dev/null
+++ b/common/modules/file/Module.php
@@ -0,0 +1,15 @@
+').prop('href', options.postMessage)[0],
+ target = loc.protocol + '//' + loc.host,
+ xhrUpload = options.xhr().upload;
+ return {
+ send: function (_, completeCallback) {
+ counter += 1;
+ var message = {
+ id: 'postmessage-transport-' + counter
+ },
+ eventName = 'message.' + message.id;
+ iframe = $(
+ ''
+ ).bind('load', function () {
+ $.each(names, function (i, name) {
+ message[name] = options[name];
+ });
+ message.dataType = message.dataType.replace('postmessage ', '');
+ $(window).bind(eventName, function (e) {
+ e = e.originalEvent;
+ var data = e.data,
+ ev;
+ if (e.origin === target && data.id === message.id) {
+ if (data.type === 'progress') {
+ ev = document.createEvent('Event');
+ ev.initEvent(data.type, false, true);
+ $.extend(ev, data);
+ xhrUpload.dispatchEvent(ev);
+ } else {
+ completeCallback(
+ data.status,
+ data.statusText,
+ {postmessage: data.result},
+ data.headers
+ );
+ iframe.remove();
+ $(window).unbind(eventName);
+ }
+ }
+ });
+ iframe[0].contentWindow.postMessage(
+ message,
+ target
+ );
+ }).appendTo(document.body);
+ },
+ abort: function () {
+ if (iframe) {
+ iframe.remove();
+ }
+ }
+ };
+ }
+ });
+
+}));
diff --git a/common/modules/file/assets/js/cors/jquery.xdr-transport.js b/common/modules/file/assets/js/cors/jquery.xdr-transport.js
new file mode 100644
index 0000000..5b9c6ca
--- /dev/null
+++ b/common/modules/file/assets/js/cors/jquery.xdr-transport.js
@@ -0,0 +1,89 @@
+/*
+ * jQuery XDomainRequest Transport Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ *
+ * Based on Julian Aubourg's ajaxHooks xdr.js:
+ * https://github.com/jaubourg/ajaxHooks/
+ */
+
+/* global define, require, window, XDomainRequest */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+ if (window.XDomainRequest && !$.support.cors) {
+ $.ajaxTransport(function (s) {
+ if (s.crossDomain && s.async) {
+ if (s.timeout) {
+ s.xdrTimeout = s.timeout;
+ delete s.timeout;
+ }
+ var xdr;
+ return {
+ send: function (headers, completeCallback) {
+ var addParamChar = /\?/.test(s.url) ? '&' : '?';
+ function callback(status, statusText, responses, responseHeaders) {
+ xdr.onload = xdr.onerror = xdr.ontimeout = $.noop;
+ xdr = null;
+ completeCallback(status, statusText, responses, responseHeaders);
+ }
+ xdr = new XDomainRequest();
+ // XDomainRequest only supports GET and POST:
+ if (s.type === 'DELETE') {
+ s.url = s.url + addParamChar + '_method=DELETE';
+ s.type = 'POST';
+ } else if (s.type === 'PUT') {
+ s.url = s.url + addParamChar + '_method=PUT';
+ s.type = 'POST';
+ } else if (s.type === 'PATCH') {
+ s.url = s.url + addParamChar + '_method=PATCH';
+ s.type = 'POST';
+ }
+ xdr.open(s.type, s.url);
+ xdr.onload = function () {
+ callback(
+ 200,
+ 'OK',
+ {text: xdr.responseText},
+ 'Content-Type: ' + xdr.contentType
+ );
+ };
+ xdr.onerror = function () {
+ callback(404, 'Not Found');
+ };
+ if (s.xdrTimeout) {
+ xdr.ontimeout = function () {
+ callback(0, 'timeout');
+ };
+ xdr.timeout = s.xdrTimeout;
+ }
+ xdr.send((s.hasContent && s.data) || null);
+ },
+ abort: function () {
+ if (xdr) {
+ xdr.onerror = $.noop();
+ xdr.abort();
+ }
+ }
+ };
+ }
+ });
+ }
+}));
diff --git a/common/modules/file/assets/js/jquery.fileupload-angular.js b/common/modules/file/assets/js/jquery.fileupload-angular.js
new file mode 100644
index 0000000..f7ba07b
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.fileupload-angular.js
@@ -0,0 +1,425 @@
+/*
+ * jQuery File Upload AngularJS Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, angular */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'angular',
+ './jquery.fileupload-image',
+ './jquery.fileupload-audio',
+ './jquery.fileupload-video',
+ './jquery.fileupload-validate'
+ ], factory);
+ } else {
+ factory();
+ }
+}(function () {
+ 'use strict';
+
+ angular.module('blueimp.fileupload', [])
+
+ // The fileUpload service provides configuration options
+ // for the fileUpload directive and default handlers for
+ // File Upload events:
+ .provider('fileUpload', function () {
+ var scopeEvalAsync = function (expression) {
+ var scope = angular.element(this)
+ .fileupload('option', 'scope');
+ // Schedule a new $digest cycle if not already inside of one
+ // and evaluate the given expression:
+ scope.$evalAsync(expression);
+ },
+ addFileMethods = function (scope, data) {
+ var files = data.files,
+ file = files[0];
+ angular.forEach(files, function (file, index) {
+ file._index = index;
+ file.$state = function () {
+ return data.state();
+ };
+ file.$processing = function () {
+ return data.processing();
+ };
+ file.$progress = function () {
+ return data.progress();
+ };
+ file.$response = function () {
+ return data.response();
+ };
+ });
+ file.$submit = function () {
+ if (!file.error) {
+ return data.submit();
+ }
+ };
+ file.$cancel = function () {
+ return data.abort();
+ };
+ },
+ $config;
+ $config = this.defaults = {
+ handleResponse: function (e, data) {
+ var files = data.result && data.result.files;
+ if (files) {
+ data.scope.replace(data.files, files);
+ } else if (data.errorThrown ||
+ data.textStatus === 'error') {
+ data.files[0].error = data.errorThrown ||
+ data.textStatus;
+ }
+ },
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var scope = data.scope,
+ filesCopy = [];
+ angular.forEach(data.files, function (file) {
+ filesCopy.push(file);
+ });
+ scope.$parent.$applyAsync(function () {
+ addFileMethods(scope, data);
+ var method = scope.option('prependFiles') ?
+ 'unshift' : 'push';
+ Array.prototype[method].apply(scope.queue, data.files);
+ });
+ data.process(function () {
+ return scope.process(data);
+ }).always(function () {
+ scope.$parent.$applyAsync(function () {
+ addFileMethods(scope, data);
+ scope.replace(filesCopy, data.files);
+ });
+ }).then(function () {
+ if ((scope.option('autoUpload') ||
+ data.autoUpload) &&
+ data.autoUpload !== false) {
+ data.submit();
+ }
+ });
+ },
+ done: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = this;
+ data.scope.$apply(function () {
+ data.handleResponse.call(that, e, data);
+ });
+ },
+ fail: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = this,
+ scope = data.scope;
+ if (data.errorThrown === 'abort') {
+ scope.clear(data.files);
+ return;
+ }
+ scope.$apply(function () {
+ data.handleResponse.call(that, e, data);
+ });
+ },
+ stop: scopeEvalAsync,
+ processstart: scopeEvalAsync,
+ processstop: scopeEvalAsync,
+ getNumberOfFiles: function () {
+ var scope = this.scope;
+ return scope.queue.length - scope.processing();
+ },
+ dataType: 'json',
+ autoUpload: false
+ };
+ this.$get = [
+ function () {
+ return {
+ defaults: $config
+ };
+ }
+ ];
+ })
+
+ // Format byte numbers to readable presentations:
+ .provider('formatFileSizeFilter', function () {
+ var $config = {
+ // Byte units following the IEC format
+ // http://en.wikipedia.org/wiki/Kilobyte
+ units: [
+ {size: 1000000000, suffix: ' GB'},
+ {size: 1000000, suffix: ' MB'},
+ {size: 1000, suffix: ' KB'}
+ ]
+ };
+ this.defaults = $config;
+ this.$get = function () {
+ return function (bytes) {
+ if (!angular.isNumber(bytes)) {
+ return '';
+ }
+ var unit = true,
+ i = 0,
+ prefix,
+ suffix;
+ while (unit) {
+ unit = $config.units[i];
+ prefix = unit.prefix || '';
+ suffix = unit.suffix || '';
+ if (i === $config.units.length - 1 || bytes >= unit.size) {
+ return prefix + (bytes / unit.size).toFixed(2) + suffix;
+ }
+ i += 1;
+ }
+ };
+ };
+ })
+
+ // The FileUploadController initializes the fileupload widget and
+ // provides scope methods to control the File Upload functionality:
+ .controller('FileUploadController', [
+ '$scope', '$element', '$attrs', '$window', 'fileUpload',
+ function ($scope, $element, $attrs, $window, fileUpload) {
+ var uploadMethods = {
+ progress: function () {
+ return $element.fileupload('progress');
+ },
+ active: function () {
+ return $element.fileupload('active');
+ },
+ option: function (option, data) {
+ if (arguments.length === 1) {
+ return $element.fileupload('option', option);
+ }
+ $element.fileupload('option', option, data);
+ },
+ add: function (data) {
+ return $element.fileupload('add', data);
+ },
+ send: function (data) {
+ return $element.fileupload('send', data);
+ },
+ process: function (data) {
+ return $element.fileupload('process', data);
+ },
+ processing: function (data) {
+ return $element.fileupload('processing', data);
+ }
+ };
+ $scope.disabled = !$window.jQuery.support.fileInput;
+ $scope.queue = $scope.queue || [];
+ $scope.clear = function (files) {
+ var queue = this.queue,
+ i = queue.length,
+ file = files,
+ length = 1;
+ if (angular.isArray(files)) {
+ file = files[0];
+ length = files.length;
+ }
+ while (i) {
+ i -= 1;
+ if (queue[i] === file) {
+ return queue.splice(i, length);
+ }
+ }
+ };
+ $scope.replace = function (oldFiles, newFiles) {
+ var queue = this.queue,
+ file = oldFiles[0],
+ i,
+ j;
+ for (i = 0; i < queue.length; i += 1) {
+ if (queue[i] === file) {
+ for (j = 0; j < newFiles.length; j += 1) {
+ queue[i + j] = newFiles[j];
+ }
+ return;
+ }
+ }
+ };
+ $scope.applyOnQueue = function (method) {
+ var list = this.queue.slice(0),
+ i,
+ file;
+ for (i = 0; i < list.length; i += 1) {
+ file = list[i];
+ if (file[method]) {
+ file[method]();
+ }
+ }
+ };
+ $scope.submit = function () {
+ this.applyOnQueue('$submit');
+ };
+ $scope.cancel = function () {
+ this.applyOnQueue('$cancel');
+ };
+ // Add upload methods to the scope:
+ angular.extend($scope, uploadMethods);
+ // The fileupload widget will initialize with
+ // the options provided via "data-"-parameters,
+ // as well as those given via options object:
+ $element.fileupload(angular.extend(
+ {scope: $scope},
+ fileUpload.defaults
+ )).on('fileuploadadd', function (e, data) {
+ data.scope = $scope;
+ }).on('fileuploadfail', function (e, data) {
+ if (data.errorThrown === 'abort') {
+ return;
+ }
+ if (data.dataType &&
+ data.dataType.indexOf('json') === data.dataType.length - 4) {
+ try {
+ data.result = angular.fromJson(data.jqXHR.responseText);
+ } catch (ignore) {}
+ }
+ }).on([
+ 'fileuploadadd',
+ 'fileuploadsubmit',
+ 'fileuploadsend',
+ 'fileuploaddone',
+ 'fileuploadfail',
+ 'fileuploadalways',
+ 'fileuploadprogress',
+ 'fileuploadprogressall',
+ 'fileuploadstart',
+ 'fileuploadstop',
+ 'fileuploadchange',
+ 'fileuploadpaste',
+ 'fileuploaddrop',
+ 'fileuploaddragover',
+ 'fileuploadchunksend',
+ 'fileuploadchunkdone',
+ 'fileuploadchunkfail',
+ 'fileuploadchunkalways',
+ 'fileuploadprocessstart',
+ 'fileuploadprocess',
+ 'fileuploadprocessdone',
+ 'fileuploadprocessfail',
+ 'fileuploadprocessalways',
+ 'fileuploadprocessstop'
+ ].join(' '), function (e, data) {
+ $scope.$parent.$applyAsync(function () {
+ if ($scope.$emit(e.type, data).defaultPrevented) {
+ e.preventDefault();
+ }
+ });
+ }).on('remove', function () {
+ // Remove upload methods from the scope,
+ // when the widget is removed:
+ var method;
+ for (method in uploadMethods) {
+ if (uploadMethods.hasOwnProperty(method)) {
+ delete $scope[method];
+ }
+ }
+ });
+ // Observe option changes:
+ $scope.$watch(
+ $attrs.fileUpload,
+ function (newOptions) {
+ if (newOptions) {
+ $element.fileupload('option', newOptions);
+ }
+ }
+ );
+ }
+ ])
+
+ // Provide File Upload progress feedback:
+ .controller('FileUploadProgressController', [
+ '$scope', '$attrs', '$parse',
+ function ($scope, $attrs, $parse) {
+ var fn = $parse($attrs.fileUploadProgress),
+ update = function () {
+ var progress = fn($scope);
+ if (!progress || !progress.total) {
+ return;
+ }
+ $scope.num = Math.floor(
+ progress.loaded / progress.total * 100
+ );
+ };
+ update();
+ $scope.$watch(
+ $attrs.fileUploadProgress + '.loaded',
+ function (newValue, oldValue) {
+ if (newValue !== oldValue) {
+ update();
+ }
+ }
+ );
+ }
+ ])
+
+ // Display File Upload previews:
+ .controller('FileUploadPreviewController', [
+ '$scope', '$element', '$attrs',
+ function ($scope, $element, $attrs) {
+ $scope.$watch(
+ $attrs.fileUploadPreview + '.preview',
+ function (preview) {
+ $element.empty();
+ if (preview) {
+ $element.append(preview);
+ }
+ }
+ );
+ }
+ ])
+
+ .directive('fileUpload', function () {
+ return {
+ controller: 'FileUploadController',
+ scope: true
+ };
+ })
+
+ .directive('fileUploadProgress', function () {
+ return {
+ controller: 'FileUploadProgressController',
+ scope: true
+ };
+ })
+
+ .directive('fileUploadPreview', function () {
+ return {
+ controller: 'FileUploadPreviewController'
+ };
+ })
+
+ // Enhance the HTML5 download attribute to
+ // allow drag&drop of files to the desktop:
+ .directive('download', function () {
+ return function (scope, elm) {
+ elm.on('dragstart', function (e) {
+ try {
+ e.originalEvent.dataTransfer.setData(
+ 'DownloadURL',
+ [
+ 'application/octet-stream',
+ elm.prop('download'),
+ elm.prop('href')
+ ].join(':')
+ );
+ } catch (ignore) {}
+ });
+ };
+ });
+
+}));
diff --git a/common/modules/file/assets/js/jquery.fileupload-audio.js b/common/modules/file/assets/js/jquery.fileupload-audio.js
new file mode 100644
index 0000000..1a746f9
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.fileupload-audio.js
@@ -0,0 +1,112 @@
+/*
+ * jQuery File Upload Audio Preview Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, document */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'load-image',
+ './jquery.fileupload-process'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('load-image')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery,
+ window.loadImage
+ );
+ }
+}(function ($, loadImage) {
+ 'use strict';
+
+ // Prepend to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
+ {
+ action: 'loadAudio',
+ // Use the action as prefix for the "@" options:
+ prefix: true,
+ fileTypes: '@',
+ maxFileSize: '@',
+ disabled: '@disableAudioPreview'
+ },
+ {
+ action: 'setAudio',
+ name: '@audioPreviewName',
+ disabled: '@disableAudioPreview'
+ }
+ );
+
+ // The File Upload Audio Preview plugin extends the fileupload widget
+ // with audio preview functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // The regular expression for the types of audio files to load,
+ // matched against the file type:
+ loadAudioFileTypes: /^audio\/.*$/
+ },
+
+ _audioElement: document.createElement('audio'),
+
+ processActions: {
+
+ // Loads the audio file given via data.files and data.index
+ // as audio element if the browser supports playing it.
+ // Accepts the options fileTypes (regular expression)
+ // and maxFileSize (integer) to limit the files to load:
+ loadAudio: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var file = data.files[data.index],
+ url,
+ audio;
+ if (this._audioElement.canPlayType &&
+ this._audioElement.canPlayType(file.type) &&
+ ($.type(options.maxFileSize) !== 'number' ||
+ file.size <= options.maxFileSize) &&
+ (!options.fileTypes ||
+ options.fileTypes.test(file.type))) {
+ url = loadImage.createObjectURL(file);
+ if (url) {
+ audio = this._audioElement.cloneNode(false);
+ audio.src = url;
+ audio.controls = true;
+ data.audio = audio;
+ return data;
+ }
+ }
+ return data;
+ },
+
+ // Sets the audio element as a property of the file object:
+ setAudio: function (data, options) {
+ if (data.audio && !options.disabled) {
+ data.files[data.index][options.name || 'preview'] = data.audio;
+ }
+ return data;
+ }
+
+ }
+
+ });
+
+}));
diff --git a/common/modules/file/assets/js/jquery.fileupload-image.js b/common/modules/file/assets/js/jquery.fileupload-image.js
new file mode 100644
index 0000000..0b91fbb
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.fileupload-image.js
@@ -0,0 +1,321 @@
+/*
+ * jQuery File Upload Image Preview & Resize Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, Blob */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'load-image',
+ 'load-image-meta',
+ 'load-image-exif',
+ 'load-image-ios',
+ 'canvas-to-blob',
+ './jquery.fileupload-process'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('load-image')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery,
+ window.loadImage
+ );
+ }
+}(function ($, loadImage) {
+ 'use strict';
+
+ // Prepend to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
+ {
+ action: 'loadImageMetaData',
+ disableImageHead: '@',
+ disableExif: '@',
+ disableExifThumbnail: '@',
+ disableExifSub: '@',
+ disableExifGps: '@',
+ disabled: '@disableImageMetaDataLoad'
+ },
+ {
+ action: 'loadImage',
+ // Use the action as prefix for the "@" options:
+ prefix: true,
+ fileTypes: '@',
+ maxFileSize: '@',
+ noRevoke: '@',
+ disabled: '@disableImageLoad'
+ },
+ {
+ action: 'resizeImage',
+ // Use "image" as prefix for the "@" options:
+ prefix: 'image',
+ maxWidth: '@',
+ maxHeight: '@',
+ minWidth: '@',
+ minHeight: '@',
+ crop: '@',
+ orientation: '@',
+ forceResize: '@',
+ disabled: '@disableImageResize'
+ },
+ {
+ action: 'saveImage',
+ quality: '@imageQuality',
+ type: '@imageType',
+ disabled: '@disableImageResize'
+ },
+ {
+ action: 'saveImageMetaData',
+ disabled: '@disableImageMetaDataSave'
+ },
+ {
+ action: 'resizeImage',
+ // Use "preview" as prefix for the "@" options:
+ prefix: 'preview',
+ maxWidth: '@',
+ maxHeight: '@',
+ minWidth: '@',
+ minHeight: '@',
+ crop: '@',
+ orientation: '@',
+ thumbnail: '@',
+ canvas: '@',
+ disabled: '@disableImagePreview'
+ },
+ {
+ action: 'setImage',
+ name: '@imagePreviewName',
+ disabled: '@disableImagePreview'
+ },
+ {
+ action: 'deleteImageReferences',
+ disabled: '@disableImageReferencesDeletion'
+ }
+ );
+
+ // The File Upload Resize plugin extends the fileupload widget
+ // with image resize functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // The regular expression for the types of images to load:
+ // matched against the file type:
+ loadImageFileTypes: /^image\/(gif|jpeg|png|svg\+xml)$/,
+ // The maximum file size of images to load:
+ loadImageMaxFileSize: 10000000, // 10MB
+ // The maximum width of resized images:
+ imageMaxWidth: 1920,
+ // The maximum height of resized images:
+ imageMaxHeight: 1080,
+ // Defines the image orientation (1-8) or takes the orientation
+ // value from Exif data if set to true:
+ imageOrientation: false,
+ // Define if resized images should be cropped or only scaled:
+ imageCrop: false,
+ // Disable the resize image functionality by default:
+ disableImageResize: true,
+ // The maximum width of the preview images:
+ previewMaxWidth: 80,
+ // The maximum height of the preview images:
+ previewMaxHeight: 80,
+ // Defines the preview orientation (1-8) or takes the orientation
+ // value from Exif data if set to true:
+ previewOrientation: true,
+ // Create the preview using the Exif data thumbnail:
+ previewThumbnail: true,
+ // Define if preview images should be cropped or only scaled:
+ previewCrop: false,
+ // Define if preview images should be resized as canvas elements:
+ previewCanvas: true
+ },
+
+ processActions: {
+
+ // Loads the image given via data.files and data.index
+ // as img element, if the browser supports the File API.
+ // Accepts the options fileTypes (regular expression)
+ // and maxFileSize (integer) to limit the files to load:
+ loadImage: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var that = this,
+ file = data.files[data.index],
+ dfd = $.Deferred();
+ if (($.type(options.maxFileSize) === 'number' &&
+ file.size > options.maxFileSize) ||
+ (options.fileTypes &&
+ !options.fileTypes.test(file.type)) ||
+ !loadImage(
+ file,
+ function (img) {
+ if (img.src) {
+ data.img = img;
+ }
+ dfd.resolveWith(that, [data]);
+ },
+ options
+ )) {
+ return data;
+ }
+ return dfd.promise();
+ },
+
+ // Resizes the image given as data.canvas or data.img
+ // and updates data.canvas or data.img with the resized image.
+ // Also stores the resized image as preview property.
+ // Accepts the options maxWidth, maxHeight, minWidth,
+ // minHeight, canvas and crop:
+ resizeImage: function (data, options) {
+ if (options.disabled || !(data.canvas || data.img)) {
+ return data;
+ }
+ options = $.extend({canvas: true}, options);
+ var that = this,
+ dfd = $.Deferred(),
+ img = (options.canvas && data.canvas) || data.img,
+ resolve = function (newImg) {
+ if (newImg && (newImg.width !== img.width ||
+ newImg.height !== img.height ||
+ options.forceResize)) {
+ data[newImg.getContext ? 'canvas' : 'img'] = newImg;
+ }
+ data.preview = newImg;
+ dfd.resolveWith(that, [data]);
+ },
+ thumbnail;
+ if (data.exif) {
+ if (options.orientation === true) {
+ options.orientation = data.exif.get('Orientation');
+ }
+ if (options.thumbnail) {
+ thumbnail = data.exif.get('Thumbnail');
+ if (thumbnail) {
+ loadImage(thumbnail, resolve, options);
+ return dfd.promise();
+ }
+ }
+ // Prevent orienting the same image twice:
+ if (data.orientation) {
+ delete options.orientation;
+ } else {
+ data.orientation = options.orientation;
+ }
+ }
+ if (img) {
+ resolve(loadImage.scale(img, options));
+ return dfd.promise();
+ }
+ return data;
+ },
+
+ // Saves the processed image given as data.canvas
+ // inplace at data.index of data.files:
+ saveImage: function (data, options) {
+ if (!data.canvas || options.disabled) {
+ return data;
+ }
+ var that = this,
+ file = data.files[data.index],
+ dfd = $.Deferred();
+ if (data.canvas.toBlob) {
+ data.canvas.toBlob(
+ function (blob) {
+ if (!blob.name) {
+ if (file.type === blob.type) {
+ blob.name = file.name;
+ } else if (file.name) {
+ blob.name = file.name.replace(
+ /\.\w+$/,
+ '.' + blob.type.substr(6)
+ );
+ }
+ }
+ // Don't restore invalid meta data:
+ if (file.type !== blob.type) {
+ delete data.imageHead;
+ }
+ // Store the created blob at the position
+ // of the original file in the files list:
+ data.files[data.index] = blob;
+ dfd.resolveWith(that, [data]);
+ },
+ options.type || file.type,
+ options.quality
+ );
+ } else {
+ return data;
+ }
+ return dfd.promise();
+ },
+
+ loadImageMetaData: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var that = this,
+ dfd = $.Deferred();
+ loadImage.parseMetaData(data.files[data.index], function (result) {
+ $.extend(data, result);
+ dfd.resolveWith(that, [data]);
+ }, options);
+ return dfd.promise();
+ },
+
+ saveImageMetaData: function (data, options) {
+ if (!(data.imageHead && data.canvas &&
+ data.canvas.toBlob && !options.disabled)) {
+ return data;
+ }
+ var file = data.files[data.index],
+ blob = new Blob([
+ data.imageHead,
+ // Resized images always have a head size of 20 bytes,
+ // including the JPEG marker and a minimal JFIF header:
+ this._blobSlice.call(file, 20)
+ ], {type: file.type});
+ blob.name = file.name;
+ data.files[data.index] = blob;
+ return data;
+ },
+
+ // Sets the resized version of the image as a property of the
+ // file object, must be called after "saveImage":
+ setImage: function (data, options) {
+ if (data.preview && !options.disabled) {
+ data.files[data.index][options.name || 'preview'] = data.preview;
+ }
+ return data;
+ },
+
+ deleteImageReferences: function (data, options) {
+ if (!options.disabled) {
+ delete data.img;
+ delete data.canvas;
+ delete data.preview;
+ delete data.imageHead;
+ }
+ return data;
+ }
+
+ }
+
+ });
+
+}));
diff --git a/common/modules/file/assets/js/jquery.fileupload-jquery-ui.js b/common/modules/file/assets/js/jquery.fileupload-jquery-ui.js
new file mode 100644
index 0000000..4f239fa
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.fileupload-jquery-ui.js
@@ -0,0 +1,155 @@
+/*
+ * jQuery File Upload jQuery UI Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery', './jquery.fileupload-ui'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ processdone: function (e, data) {
+ data.context.find('.start').button('enable');
+ },
+ progress: function (e, data) {
+ if (data.context) {
+ data.context.find('.progress').progressbar(
+ 'option',
+ 'value',
+ parseInt(data.loaded / data.total * 100, 10)
+ );
+ }
+ },
+ progressall: function (e, data) {
+ var $this = $(this);
+ $this.find('.fileupload-progress')
+ .find('.progress').progressbar(
+ 'option',
+ 'value',
+ parseInt(data.loaded / data.total * 100, 10)
+ ).end()
+ .find('.progress-extended').each(function () {
+ $(this).html(
+ ($this.data('blueimp-fileupload') ||
+ $this.data('fileupload'))
+ ._renderExtendedProgress(data)
+ );
+ });
+ }
+ },
+
+ _renderUpload: function (func, files) {
+ var node = this._super(func, files),
+ showIconText = $(window).width() > 480;
+ node.find('.progress').empty().progressbar();
+ node.find('.start').button({
+ icons: {primary: 'ui-icon-circle-arrow-e'},
+ text: showIconText
+ });
+ node.find('.cancel').button({
+ icons: {primary: 'ui-icon-cancel'},
+ text: showIconText
+ });
+ if (node.hasClass('fade')) {
+ node.hide();
+ }
+ return node;
+ },
+
+ _renderDownload: function (func, files) {
+ var node = this._super(func, files),
+ showIconText = $(window).width() > 480;
+ node.find('.delete').button({
+ icons: {primary: 'ui-icon-trash'},
+ text: showIconText
+ });
+ if (node.hasClass('fade')) {
+ node.hide();
+ }
+ return node;
+ },
+
+ _startHandler: function (e) {
+ $(e.currentTarget).button('disable');
+ this._super(e);
+ },
+
+ _transition: function (node) {
+ var deferred = $.Deferred();
+ if (node.hasClass('fade')) {
+ node.fadeToggle(
+ this.options.transitionDuration,
+ this.options.transitionEasing,
+ function () {
+ deferred.resolveWith(node);
+ }
+ );
+ } else {
+ deferred.resolveWith(node);
+ }
+ return deferred;
+ },
+
+ _create: function () {
+ this._super();
+ this.element
+ .find('.fileupload-buttonbar')
+ .find('.fileinput-button').each(function () {
+ var input = $(this).find('input:file').detach();
+ $(this)
+ .button({icons: {primary: 'ui-icon-plusthick'}})
+ .append(input);
+ })
+ .end().find('.start')
+ .button({icons: {primary: 'ui-icon-circle-arrow-e'}})
+ .end().find('.cancel')
+ .button({icons: {primary: 'ui-icon-cancel'}})
+ .end().find('.delete')
+ .button({icons: {primary: 'ui-icon-trash'}})
+ .end().find('.progress').progressbar();
+ },
+
+ _destroy: function () {
+ this.element
+ .find('.fileupload-buttonbar')
+ .find('.fileinput-button').each(function () {
+ var input = $(this).find('input:file').detach();
+ $(this)
+ .button('destroy')
+ .append(input);
+ })
+ .end().find('.start')
+ .button('destroy')
+ .end().find('.cancel')
+ .button('destroy')
+ .end().find('.delete')
+ .button('destroy')
+ .end().find('.progress').progressbar('destroy');
+ this._super();
+ }
+
+ });
+
+}));
diff --git a/common/modules/file/assets/js/jquery.fileupload-process.js b/common/modules/file/assets/js/jquery.fileupload-process.js
new file mode 100644
index 0000000..ce914df
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.fileupload-process.js
@@ -0,0 +1,175 @@
+/*
+ * jQuery File Upload Processing Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ './jquery.fileupload'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery
+ );
+ }
+}(function ($) {
+ 'use strict';
+
+ var originalAdd = $.blueimp.fileupload.prototype.options.add;
+
+ // The File Upload Processing plugin extends the fileupload widget
+ // with file processing functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // The list of processing actions:
+ processQueue: [
+ /*
+ {
+ action: 'log',
+ type: 'debug'
+ }
+ */
+ ],
+ add: function (e, data) {
+ var $this = $(this);
+ data.process(function () {
+ return $this.fileupload('process', data);
+ });
+ originalAdd.call(this, e, data);
+ }
+ },
+
+ processActions: {
+ /*
+ log: function (data, options) {
+ console[options.type](
+ 'Processing "' + data.files[data.index].name + '"'
+ );
+ }
+ */
+ },
+
+ _processFile: function (data, originalData) {
+ var that = this,
+ dfd = $.Deferred().resolveWith(that, [data]),
+ chain = dfd.promise();
+ this._trigger('process', null, data);
+ $.each(data.processQueue, function (i, settings) {
+ var func = function (data) {
+ if (originalData.errorThrown) {
+ return $.Deferred()
+ .rejectWith(that, [originalData]).promise();
+ }
+ return that.processActions[settings.action].call(
+ that,
+ data,
+ settings
+ );
+ };
+ chain = chain.pipe(func, settings.always && func);
+ });
+ chain
+ .done(function () {
+ that._trigger('processdone', null, data);
+ that._trigger('processalways', null, data);
+ })
+ .fail(function () {
+ that._trigger('processfail', null, data);
+ that._trigger('processalways', null, data);
+ });
+ return chain;
+ },
+
+ // Replaces the settings of each processQueue item that
+ // are strings starting with an "@", using the remaining
+ // substring as key for the option map,
+ // e.g. "@autoUpload" is replaced with options.autoUpload:
+ _transformProcessQueue: function (options) {
+ var processQueue = [];
+ $.each(options.processQueue, function () {
+ var settings = {},
+ action = this.action,
+ prefix = this.prefix === true ? action : this.prefix;
+ $.each(this, function (key, value) {
+ if ($.type(value) === 'string' &&
+ value.charAt(0) === '@') {
+ settings[key] = options[
+ value.slice(1) || (prefix ? prefix +
+ key.charAt(0).toUpperCase() + key.slice(1) : key)
+ ];
+ } else {
+ settings[key] = value;
+ }
+
+ });
+ processQueue.push(settings);
+ });
+ options.processQueue = processQueue;
+ },
+
+ // Returns the number of files currently in the processsing queue:
+ processing: function () {
+ return this._processing;
+ },
+
+ // Processes the files given as files property of the data parameter,
+ // returns a Promise object that allows to bind callbacks:
+ process: function (data) {
+ var that = this,
+ options = $.extend({}, this.options, data);
+ if (options.processQueue && options.processQueue.length) {
+ this._transformProcessQueue(options);
+ if (this._processing === 0) {
+ this._trigger('processstart');
+ }
+ $.each(data.files, function (index) {
+ var opts = index ? $.extend({}, options) : options,
+ func = function () {
+ if (data.errorThrown) {
+ return $.Deferred()
+ .rejectWith(that, [data]).promise();
+ }
+ return that._processFile(opts, data);
+ };
+ opts.index = index;
+ that._processing += 1;
+ that._processingQueue = that._processingQueue.pipe(func, func)
+ .always(function () {
+ that._processing -= 1;
+ if (that._processing === 0) {
+ that._trigger('processstop');
+ }
+ });
+ });
+ }
+ return this._processingQueue;
+ },
+
+ _create: function () {
+ this._super();
+ this._processing = 0;
+ this._processingQueue = $.Deferred().resolveWith(this)
+ .promise();
+ }
+
+ });
+
+}));
diff --git a/common/modules/file/assets/js/jquery.fileupload-ui.js b/common/modules/file/assets/js/jquery.fileupload-ui.js
new file mode 100644
index 0000000..8154218
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.fileupload-ui.js
@@ -0,0 +1,710 @@
+/*
+ * jQuery File Upload User Interface Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'tmpl',
+ './jquery.fileupload-image',
+ './jquery.fileupload-audio',
+ './jquery.fileupload-video',
+ './jquery.fileupload-validate'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('tmpl')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery,
+ window.tmpl
+ );
+ }
+}(function ($, tmpl) {
+ 'use strict';
+
+ $.blueimp.fileupload.prototype._specialOptions.push(
+ 'filesContainer',
+ 'uploadTemplateId',
+ 'downloadTemplateId'
+ );
+
+ // The UI version extends the file upload widget
+ // and adds complete user interface interaction:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // By default, files added to the widget are uploaded as soon
+ // as the user clicks on the start buttons. To enable automatic
+ // uploads, set the following option to true:
+ autoUpload: false,
+ // The ID of the upload template:
+ uploadTemplateId: 'template-upload',
+ // The ID of the download template:
+ downloadTemplateId: 'template-download',
+ // The container for the list of files. If undefined, it is set to
+ // an element with class "files" inside of the widget element:
+ filesContainer: undefined,
+ // By default, files are appended to the files container.
+ // Set the following option to true, to prepend files instead:
+ prependFiles: false,
+ // The expected data type of the upload response, sets the dataType
+ // option of the $.ajax upload requests:
+ dataType: 'json',
+
+ // Error and info messages:
+ messages: {
+ unknownError: 'Unknown error'
+ },
+
+ // Function returning the current number of files,
+ // used by the maxNumberOfFiles validation:
+ getNumberOfFiles: function () {
+ return this.filesContainer.children()
+ .not('.processing').length;
+ },
+
+ // Callback to retrieve the list of files from the server response:
+ getFilesFromResponse: function (data) {
+ if (data.result && $.isArray(data.result.files)) {
+ return data.result.files;
+ }
+ return [];
+ },
+
+ // The add callback is invoked as soon as files are added to the fileupload
+ // widget (via file input selection, drag & drop or add API call).
+ // See the basic file upload widget for more information:
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var $this = $(this),
+ that = $this.data('blueimp-fileupload') ||
+ $this.data('fileupload'),
+ options = that.options;
+ data.context = that._renderUpload(data.files)
+ .data('data', data)
+ .addClass('processing');
+ options.filesContainer[
+ options.prependFiles ? 'prepend' : 'append'
+ ](data.context);
+ that._forceReflow(data.context);
+ that._transition(data.context);
+ data.process(function () {
+ return $this.fileupload('process', data);
+ }).always(function () {
+ data.context.each(function (index) {
+ $(this).find('.size').text(
+ that._formatFileSize(data.files[index].size)
+ );
+ }).removeClass('processing');
+ that._renderPreviews(data);
+ }).done(function () {
+ data.context.find('.start').prop('disabled', false);
+ if ((that._trigger('added', e, data) !== false) &&
+ (options.autoUpload || data.autoUpload) &&
+ data.autoUpload !== false) {
+ data.submit();
+ }
+ }).fail(function () {
+ if (data.files.error) {
+ data.context.each(function (index) {
+ var error = data.files[index].error;
+ if (error) {
+ $(this).find('.error').text(error);
+ }
+ });
+ }
+ });
+ },
+ // Callback for the start of each file upload request:
+ send: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload');
+ if (data.context && data.dataType &&
+ data.dataType.substr(0, 6) === 'iframe') {
+ // Iframe Transport does not support progress events.
+ // In lack of an indeterminate progress bar, we set
+ // the progress to 100%, showing the full animated bar:
+ data.context
+ .find('.progress').addClass(
+ !$.support.transition && 'progress-animated'
+ )
+ .attr('aria-valuenow', 100)
+ .children().first().css(
+ 'width',
+ '100%'
+ );
+ }
+ return that._trigger('sent', e, data);
+ },
+ // Callback for successful uploads:
+ done: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload'),
+ getFilesFromResponse = data.getFilesFromResponse ||
+ that.options.getFilesFromResponse,
+ files = getFilesFromResponse(data),
+ template,
+ deferred;
+ if (data.context) {
+ data.context.each(function (index) {
+ var file = files[index] ||
+ {error: 'Empty file upload result'};
+ deferred = that._addFinishedDeferreds();
+ that._transition($(this)).done(
+ function () {
+ var node = $(this);
+ template = that._renderDownload([file])
+ .replaceAll(node);
+ that._forceReflow(template);
+ that._transition(template).done(
+ function () {
+ data.context = $(this);
+ that._trigger('completed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ }
+ );
+ });
+ } else {
+ template = that._renderDownload(files)[
+ that.options.prependFiles ? 'prependTo' : 'appendTo'
+ ](that.options.filesContainer);
+ that._forceReflow(template);
+ deferred = that._addFinishedDeferreds();
+ that._transition(template).done(
+ function () {
+ data.context = $(this);
+ that._trigger('completed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ }
+ },
+ // Callback for failed (abort or error) uploads:
+ fail: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload'),
+ template,
+ deferred;
+ if (data.context) {
+ data.context.each(function (index) {
+ if (data.errorThrown !== 'abort') {
+ var file = data.files[index];
+ file.error = file.error || data.errorThrown ||
+ data.i18n('unknownError');
+ deferred = that._addFinishedDeferreds();
+ that._transition($(this)).done(
+ function () {
+ var node = $(this);
+ template = that._renderDownload([file])
+ .replaceAll(node);
+ that._forceReflow(template);
+ that._transition(template).done(
+ function () {
+ data.context = $(this);
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ }
+ );
+ } else {
+ deferred = that._addFinishedDeferreds();
+ that._transition($(this)).done(
+ function () {
+ $(this).remove();
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ }
+ });
+ } else if (data.errorThrown !== 'abort') {
+ data.context = that._renderUpload(data.files)[
+ that.options.prependFiles ? 'prependTo' : 'appendTo'
+ ](that.options.filesContainer)
+ .data('data', data);
+ that._forceReflow(data.context);
+ deferred = that._addFinishedDeferreds();
+ that._transition(data.context).done(
+ function () {
+ data.context = $(this);
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ } else {
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ that._addFinishedDeferreds().resolve();
+ }
+ },
+ // Callback for upload progress events:
+ progress: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var progress = Math.floor(data.loaded / data.total * 100);
+ if (data.context) {
+ data.context.each(function () {
+ $(this).find('.progress')
+ .attr('aria-valuenow', progress)
+ .children().first().css(
+ 'width',
+ progress + '%'
+ );
+ });
+ }
+ },
+ // Callback for global upload progress events:
+ progressall: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var $this = $(this),
+ progress = Math.floor(data.loaded / data.total * 100),
+ globalProgressNode = $this.find('.fileupload-progress'),
+ extendedProgressNode = globalProgressNode
+ .find('.progress-extended');
+ if (extendedProgressNode.length) {
+ extendedProgressNode.html(
+ ($this.data('blueimp-fileupload') || $this.data('fileupload'))
+ ._renderExtendedProgress(data)
+ );
+ }
+ globalProgressNode
+ .find('.progress')
+ .attr('aria-valuenow', progress)
+ .children().first().css(
+ 'width',
+ progress + '%'
+ );
+ },
+ // Callback for uploads start, equivalent to the global ajaxStart event:
+ start: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload');
+ that._resetFinishedDeferreds();
+ that._transition($(this).find('.fileupload-progress')).done(
+ function () {
+ that._trigger('started', e);
+ }
+ );
+ },
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
+ stop: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload'),
+ deferred = that._addFinishedDeferreds();
+ $.when.apply($, that._getFinishedDeferreds())
+ .done(function () {
+ that._trigger('stopped', e);
+ });
+ that._transition($(this).find('.fileupload-progress')).done(
+ function () {
+ $(this).find('.progress')
+ .attr('aria-valuenow', '0')
+ .children().first().css('width', '0%');
+ $(this).find('.progress-extended').html(' ');
+ deferred.resolve();
+ }
+ );
+ },
+ processstart: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ $(this).addClass('fileupload-processing');
+ },
+ processstop: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ $(this).removeClass('fileupload-processing');
+ },
+ // Callback for file deletion:
+ destroy: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload'),
+ removeNode = function () {
+ that._transition(data.context).done(
+ function () {
+ $(this).remove();
+ that._trigger('destroyed', e, data);
+ }
+ );
+ };
+ if (data.url) {
+ data.dataType = data.dataType || that.options.dataType;
+ $.ajax(data).done(removeNode).fail(function () {
+ that._trigger('destroyfailed', e, data);
+ });
+ } else {
+ removeNode();
+ }
+ }
+ },
+
+ _resetFinishedDeferreds: function () {
+ this._finishedUploads = [];
+ },
+
+ _addFinishedDeferreds: function (deferred) {
+ if (!deferred) {
+ deferred = $.Deferred();
+ }
+ this._finishedUploads.push(deferred);
+ return deferred;
+ },
+
+ _getFinishedDeferreds: function () {
+ return this._finishedUploads;
+ },
+
+ // Link handler, that allows to download files
+ // by drag & drop of the links to the desktop:
+ _enableDragToDesktop: function () {
+ var link = $(this),
+ url = link.prop('href'),
+ name = link.prop('download'),
+ type = 'application/octet-stream';
+ link.bind('dragstart', function (e) {
+ try {
+ e.originalEvent.dataTransfer.setData(
+ 'DownloadURL',
+ [type, name, url].join(':')
+ );
+ } catch (ignore) {}
+ });
+ },
+
+ _formatFileSize: function (bytes) {
+ if (typeof bytes !== 'number') {
+ return '';
+ }
+ if (bytes >= 1000000000) {
+ return (bytes / 1000000000).toFixed(2) + ' GB';
+ }
+ if (bytes >= 1000000) {
+ return (bytes / 1000000).toFixed(2) + ' MB';
+ }
+ return (bytes / 1000).toFixed(2) + ' KB';
+ },
+
+ _formatBitrate: function (bits) {
+ if (typeof bits !== 'number') {
+ return '';
+ }
+ if (bits >= 1000000000) {
+ return (bits / 1000000000).toFixed(2) + ' Gbit/s';
+ }
+ if (bits >= 1000000) {
+ return (bits / 1000000).toFixed(2) + ' Mbit/s';
+ }
+ if (bits >= 1000) {
+ return (bits / 1000).toFixed(2) + ' kbit/s';
+ }
+ return bits.toFixed(2) + ' bit/s';
+ },
+
+ _formatTime: function (seconds) {
+ var date = new Date(seconds * 1000),
+ days = Math.floor(seconds / 86400);
+ days = days ? days + 'd ' : '';
+ return days +
+ ('0' + date.getUTCHours()).slice(-2) + ':' +
+ ('0' + date.getUTCMinutes()).slice(-2) + ':' +
+ ('0' + date.getUTCSeconds()).slice(-2);
+ },
+
+ _formatPercentage: function (floatValue) {
+ return (floatValue * 100).toFixed(2) + ' %';
+ },
+
+ _renderExtendedProgress: function (data) {
+ return this._formatBitrate(data.bitrate) + ' | ' +
+ this._formatTime(
+ (data.total - data.loaded) * 8 / data.bitrate
+ ) + ' | ' +
+ this._formatPercentage(
+ data.loaded / data.total
+ ) + ' | ' +
+ this._formatFileSize(data.loaded) + ' / ' +
+ this._formatFileSize(data.total);
+ },
+
+ _renderTemplate: function (func, files) {
+ if (!func) {
+ return $();
+ }
+ var result = func({
+ files: files,
+ formatFileSize: this._formatFileSize,
+ options: this.options
+ });
+ if (result instanceof $) {
+ return result;
+ }
+ return $(this.options.templatesContainer).html(result).children();
+ },
+
+ _renderPreviews: function (data) {
+ data.context.find('.preview').each(function (index, elm) {
+ $(elm).append(data.files[index].preview);
+ });
+ },
+
+ _renderUpload: function (files) {
+ return this._renderTemplate(
+ this.options.uploadTemplate,
+ files
+ );
+ },
+
+ _renderDownload: function (files) {
+ return this._renderTemplate(
+ this.options.downloadTemplate,
+ files
+ ).find('a[download]').each(this._enableDragToDesktop).end();
+ },
+
+ _startHandler: function (e) {
+ e.preventDefault();
+ var button = $(e.currentTarget),
+ template = button.closest('.template-upload'),
+ data = template.data('data');
+ button.prop('disabled', true);
+ if (data && data.submit) {
+ data.submit();
+ }
+ },
+
+ _cancelHandler: function (e) {
+ e.preventDefault();
+ var template = $(e.currentTarget)
+ .closest('.template-upload,.template-download'),
+ data = template.data('data') || {};
+ data.context = data.context || template;
+ if (data.abort) {
+ data.abort();
+ } else {
+ data.errorThrown = 'abort';
+ this._trigger('fail', e, data);
+ }
+ },
+
+ _deleteHandler: function (e) {
+ e.preventDefault();
+ var button = $(e.currentTarget);
+ this._trigger('destroy', e, $.extend({
+ context: button.closest('.template-download'),
+ type: 'DELETE'
+ }, button.data()));
+ },
+
+ _forceReflow: function (node) {
+ return $.support.transition && node.length &&
+ node[0].offsetWidth;
+ },
+
+ _transition: function (node) {
+ var dfd = $.Deferred();
+ if ($.support.transition && node.hasClass('fade') && node.is(':visible')) {
+ node.bind(
+ $.support.transition.end,
+ function (e) {
+ // Make sure we don't respond to other transitions events
+ // in the container element, e.g. from button elements:
+ if (e.target === node[0]) {
+ node.unbind($.support.transition.end);
+ dfd.resolveWith(node);
+ }
+ }
+ ).toggleClass('in');
+ } else {
+ node.toggleClass('in');
+ dfd.resolveWith(node);
+ }
+ return dfd;
+ },
+
+ _initButtonBarEventHandlers: function () {
+ var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
+ filesList = this.options.filesContainer;
+ this._on(fileUploadButtonBar.find('.start'), {
+ click: function (e) {
+ e.preventDefault();
+ filesList.find('.start').click();
+ }
+ });
+ this._on(fileUploadButtonBar.find('.cancel'), {
+ click: function (e) {
+ e.preventDefault();
+ filesList.find('.cancel').click();
+ }
+ });
+ this._on(fileUploadButtonBar.find('.delete'), {
+ click: function (e) {
+ e.preventDefault();
+ filesList.find('.toggle:checked')
+ .closest('.template-download')
+ .find('.delete').click();
+ fileUploadButtonBar.find('.toggle')
+ .prop('checked', false);
+ }
+ });
+ this._on(fileUploadButtonBar.find('.toggle'), {
+ change: function (e) {
+ filesList.find('.toggle').prop(
+ 'checked',
+ $(e.currentTarget).is(':checked')
+ );
+ }
+ });
+ },
+
+ _destroyButtonBarEventHandlers: function () {
+ this._off(
+ this.element.find('.fileupload-buttonbar')
+ .find('.start, .cancel, .delete'),
+ 'click'
+ );
+ this._off(
+ this.element.find('.fileupload-buttonbar .toggle'),
+ 'change.'
+ );
+ },
+
+ _initEventHandlers: function () {
+ this._super();
+ this._on(this.options.filesContainer, {
+ 'click .start': this._startHandler,
+ 'click .cancel': this._cancelHandler,
+ 'click .delete': this._deleteHandler
+ });
+ this._initButtonBarEventHandlers();
+ },
+
+ _destroyEventHandlers: function () {
+ this._destroyButtonBarEventHandlers();
+ this._off(this.options.filesContainer, 'click');
+ this._super();
+ },
+
+ _enableFileInputButton: function () {
+ this.element.find('.fileinput-button input')
+ .prop('disabled', false)
+ .parent().removeClass('disabled');
+ },
+
+ _disableFileInputButton: function () {
+ this.element.find('.fileinput-button input')
+ .prop('disabled', true)
+ .parent().addClass('disabled');
+ },
+
+ _initTemplates: function () {
+ var options = this.options;
+ options.templatesContainer = this.document[0].createElement(
+ options.filesContainer.prop('nodeName')
+ );
+ if (tmpl) {
+ if (options.uploadTemplateId) {
+ options.uploadTemplate = tmpl(options.uploadTemplateId);
+ }
+ if (options.downloadTemplateId) {
+ options.downloadTemplate = tmpl(options.downloadTemplateId);
+ }
+ }
+ },
+
+ _initFilesContainer: function () {
+ var options = this.options;
+ if (options.filesContainer === undefined) {
+ options.filesContainer = this.element.find('.files');
+ } else if (!(options.filesContainer instanceof $)) {
+ options.filesContainer = $(options.filesContainer);
+ }
+ },
+
+ _initSpecialOptions: function () {
+ this._super();
+ this._initFilesContainer();
+ this._initTemplates();
+ },
+
+ _create: function () {
+ this._super();
+ this._resetFinishedDeferreds();
+ if (!$.support.fileInput) {
+ this._disableFileInputButton();
+ }
+ },
+
+ enable: function () {
+ var wasDisabled = false;
+ if (this.options.disabled) {
+ wasDisabled = true;
+ }
+ this._super();
+ if (wasDisabled) {
+ this.element.find('input, button').prop('disabled', false);
+ this._enableFileInputButton();
+ }
+ },
+
+ disable: function () {
+ if (!this.options.disabled) {
+ this.element.find('input, button').prop('disabled', true);
+ this._disableFileInputButton();
+ }
+ this._super();
+ }
+
+ });
+
+}));
diff --git a/common/modules/file/assets/js/jquery.fileupload-validate.js b/common/modules/file/assets/js/jquery.fileupload-validate.js
new file mode 100644
index 0000000..d6f754c
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.fileupload-validate.js
@@ -0,0 +1,122 @@
+/*
+ * jQuery File Upload Validation Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* global define, require, window */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ './jquery.fileupload-process'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery
+ );
+ }
+}(function ($) {
+ 'use strict';
+
+ // Append to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.push(
+ {
+ action: 'validate',
+ // Always trigger this action,
+ // even if the previous action was rejected:
+ always: true,
+ // Options taken from the global options map:
+ acceptFileTypes: '@',
+ maxFileSize: '@',
+ minFileSize: '@',
+ maxNumberOfFiles: '@',
+ disabled: '@disableValidation'
+ }
+ );
+
+ // The File Upload Validation plugin extends the fileupload widget
+ // with file validation functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ /*
+ // The regular expression for allowed file types, matches
+ // against either file type or file name:
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
+ // The maximum allowed file size in bytes:
+ maxFileSize: 10000000, // 10 MB
+ // The minimum allowed file size in bytes:
+ minFileSize: undefined, // No minimal file size
+ // The limit of files to be uploaded:
+ maxNumberOfFiles: 10,
+ */
+
+ // Function returning the current number of files,
+ // has to be overriden for maxNumberOfFiles validation:
+ getNumberOfFiles: $.noop,
+
+ // Error and info messages:
+ messages: {
+ maxNumberOfFiles: 'Maximum number of files exceeded',
+ acceptFileTypes: 'File type not allowed',
+ maxFileSize: 'File is too large',
+ minFileSize: 'File is too small'
+ }
+ },
+
+ processActions: {
+
+ validate: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var dfd = $.Deferred(),
+ settings = this.options,
+ file = data.files[data.index],
+ fileSize;
+ if (options.minFileSize || options.maxFileSize) {
+ fileSize = file.size;
+ }
+ if ($.type(options.maxNumberOfFiles) === 'number' &&
+ (settings.getNumberOfFiles() || 0) + data.files.length >
+ options.maxNumberOfFiles) {
+ file.error = settings.i18n('maxNumberOfFiles');
+ } else if (options.acceptFileTypes &&
+ !(options.acceptFileTypes.test(file.type) ||
+ options.acceptFileTypes.test(file.name))) {
+ file.error = settings.i18n('acceptFileTypes');
+ } else if (fileSize > options.maxFileSize) {
+ file.error = settings.i18n('maxFileSize');
+ } else if ($.type(fileSize) === 'number' &&
+ fileSize < options.minFileSize) {
+ file.error = settings.i18n('minFileSize');
+ } else {
+ delete file.error;
+ }
+ if (file.error || data.files.error) {
+ data.files.error = true;
+ dfd.rejectWith(this, [data]);
+ } else {
+ dfd.resolveWith(this, [data]);
+ }
+ return dfd.promise();
+ }
+
+ }
+
+ });
+
+}));
diff --git a/common/modules/file/assets/js/jquery.fileupload-video.js b/common/modules/file/assets/js/jquery.fileupload-video.js
new file mode 100644
index 0000000..8067ca1
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.fileupload-video.js
@@ -0,0 +1,112 @@
+/*
+ * jQuery File Upload Video Preview Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, document */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'load-image',
+ './jquery.fileupload-process'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('load-image')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery,
+ window.loadImage
+ );
+ }
+}(function ($, loadImage) {
+ 'use strict';
+
+ // Prepend to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
+ {
+ action: 'loadVideo',
+ // Use the action as prefix for the "@" options:
+ prefix: true,
+ fileTypes: '@',
+ maxFileSize: '@',
+ disabled: '@disableVideoPreview'
+ },
+ {
+ action: 'setVideo',
+ name: '@videoPreviewName',
+ disabled: '@disableVideoPreview'
+ }
+ );
+
+ // The File Upload Video Preview plugin extends the fileupload widget
+ // with video preview functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // The regular expression for the types of video files to load,
+ // matched against the file type:
+ loadVideoFileTypes: /^video\/.*$/
+ },
+
+ _videoElement: document.createElement('video'),
+
+ processActions: {
+
+ // Loads the video file given via data.files and data.index
+ // as video element if the browser supports playing it.
+ // Accepts the options fileTypes (regular expression)
+ // and maxFileSize (integer) to limit the files to load:
+ loadVideo: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var file = data.files[data.index],
+ url,
+ video;
+ if (this._videoElement.canPlayType &&
+ this._videoElement.canPlayType(file.type) &&
+ ($.type(options.maxFileSize) !== 'number' ||
+ file.size <= options.maxFileSize) &&
+ (!options.fileTypes ||
+ options.fileTypes.test(file.type))) {
+ url = loadImage.createObjectURL(file);
+ if (url) {
+ video = this._videoElement.cloneNode(false);
+ video.src = url;
+ video.controls = true;
+ data.video = video;
+ return data;
+ }
+ }
+ return data;
+ },
+
+ // Sets the video element as a property of the file object:
+ setVideo: function (data, options) {
+ if (data.video && !options.disabled) {
+ data.files[data.index][options.name || 'preview'] = data.video;
+ }
+ return data;
+ }
+
+ }
+
+ });
+
+}));
diff --git a/common/modules/file/assets/js/jquery.fileupload.js b/common/modules/file/assets/js/jquery.fileupload.js
new file mode 100644
index 0000000..91b7254
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.fileupload.js
@@ -0,0 +1,1477 @@
+/*
+ * jQuery File Upload Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, document, location, Blob, FormData */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'jquery.ui.widget'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('./vendor/jquery.ui.widget')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+
+ // Detect file input support, based on
+ // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
+ $.support.fileInput = !(new RegExp(
+ // Handle devices which give false positives for the feature detection:
+ '(Android (1\\.[0156]|2\\.[01]))' +
+ '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
+ '|(w(eb)?OSBrowser)|(webOS)' +
+ '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
+ ).test(window.navigator.userAgent) ||
+ // Feature detection for all other devices:
+ $(' ').prop('disabled'));
+
+ // The FileReader API is not actually used, but works as feature detection,
+ // as some Safari versions (5?) support XHR file uploads via the FormData API,
+ // but not non-multipart XHR file uploads.
+ // window.XMLHttpRequestUpload is not available on IE10, so we check for
+ // window.ProgressEvent instead to detect XHR2 file upload capability:
+ $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
+ $.support.xhrFormDataFileUpload = !!window.FormData;
+
+ // Detect support for Blob slicing (required for chunked uploads):
+ $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
+ Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
+
+ // Helper function to create drag handlers for dragover/dragenter/dragleave:
+ function getDragHandler(type) {
+ var isDragOver = type === 'dragover';
+ return function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var dataTransfer = e.dataTransfer;
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
+ this._trigger(
+ type,
+ $.Event(type, {delegatedEvent: e})
+ ) !== false) {
+ e.preventDefault();
+ if (isDragOver) {
+ dataTransfer.dropEffect = 'copy';
+ }
+ }
+ };
+ }
+
+ // The fileupload widget listens for change events on file input fields defined
+ // via fileInput setting and paste or drop events of the given dropZone.
+ // In addition to the default jQuery Widget methods, the fileupload widget
+ // exposes the "add" and "send" methods, to add or directly send files using
+ // the fileupload API.
+ // By default, files added via file input selection, paste, drag & drop or
+ // "add" method are uploaded immediately, but it is possible to override
+ // the "add" callback option to queue file uploads.
+ $.widget('blueimp.fileupload', {
+
+ options: {
+ // The drop target element(s), by the default the complete document.
+ // Set to null to disable drag & drop support:
+ dropZone: $(document),
+ // The paste target element(s), by the default undefined.
+ // Set to a DOM node or jQuery object to enable file pasting:
+ pasteZone: undefined,
+ // The file input field(s), that are listened to for change events.
+ // If undefined, it is set to the file input fields inside
+ // of the widget element on plugin initialization.
+ // Set to null to disable the change listener.
+ fileInput: undefined,
+ // By default, the file input field is replaced with a clone after
+ // each input field change event. This is required for iframe transport
+ // queues and allows change events to be fired for the same file
+ // selection, but can be disabled by setting the following option to false:
+ replaceFileInput: true,
+ // The parameter name for the file form data (the request argument name).
+ // If undefined or empty, the name property of the file input field is
+ // used, or "files[]" if the file input name property is also empty,
+ // can be a string or an array of strings:
+ paramName: undefined,
+ // By default, each file of a selection is uploaded using an individual
+ // request for XHR type uploads. Set to false to upload file
+ // selections in one request each:
+ singleFileUploads: true,
+ // To limit the number of files uploaded with one XHR request,
+ // set the following option to an integer greater than 0:
+ limitMultiFileUploads: undefined,
+ // The following option limits the number of files uploaded with one
+ // XHR request to keep the request size under or equal to the defined
+ // limit in bytes:
+ limitMultiFileUploadSize: undefined,
+ // Multipart file uploads add a number of bytes to each uploaded file,
+ // therefore the following option adds an overhead for each file used
+ // in the limitMultiFileUploadSize configuration:
+ limitMultiFileUploadSizeOverhead: 512,
+ // Set the following option to true to issue all file upload requests
+ // in a sequential order:
+ sequentialUploads: false,
+ // To limit the number of concurrent uploads,
+ // set the following option to an integer greater than 0:
+ limitConcurrentUploads: undefined,
+ // Set the following option to true to force iframe transport uploads:
+ forceIframeTransport: false,
+ // Set the following option to the location of a redirect url on the
+ // origin server, for cross-domain iframe transport uploads:
+ redirect: undefined,
+ // The parameter name for the redirect url, sent as part of the form
+ // data and set to 'redirect' if this option is empty:
+ redirectParamName: undefined,
+ // Set the following option to the location of a postMessage window,
+ // to enable postMessage transport uploads:
+ postMessage: undefined,
+ // By default, XHR file uploads are sent as multipart/form-data.
+ // The iframe transport is always using multipart/form-data.
+ // Set to false to enable non-multipart XHR uploads:
+ multipart: true,
+ // To upload large files in smaller chunks, set the following option
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
+ // or the browser does not support the required Blob API, files will
+ // be uploaded as a whole.
+ maxChunkSize: undefined,
+ // When a non-multipart upload or a chunked multipart upload has been
+ // aborted, this option can be used to resume the upload by setting
+ // it to the size of the already uploaded bytes. This option is most
+ // useful when modifying the options object inside of the "add" or
+ // "send" callbacks, as the options are cloned for each file upload.
+ uploadedBytes: undefined,
+ // By default, failed (abort or error) file uploads are removed from the
+ // global progress calculation. Set the following option to false to
+ // prevent recalculating the global progress data:
+ recalculateProgress: true,
+ // Interval in milliseconds to calculate and trigger progress events:
+ progressInterval: 100,
+ // Interval in milliseconds to calculate progress bitrate:
+ bitrateInterval: 500,
+ // By default, uploads are started automatically when adding files:
+ autoUpload: true,
+
+ // Error and info messages:
+ messages: {
+ uploadedBytes: 'Uploaded bytes exceed file size'
+ },
+
+ // Translation function, gets the message key to be translated
+ // and an object with context specific data as arguments:
+ i18n: function (message, context) {
+ message = this.messages[message] || message.toString();
+ if (context) {
+ $.each(context, function (key, value) {
+ message = message.replace('{' + key + '}', value);
+ });
+ }
+ return message;
+ },
+
+ // Additional form data to be sent along with the file uploads can be set
+ // using this option, which accepts an array of objects with name and
+ // value properties, a function returning such an array, a FormData
+ // object (for XHR file uploads), or a simple object.
+ // The form of the first fileInput is given as parameter to the function:
+ formData: function (form) {
+ return form.serializeArray();
+ },
+
+ // The add callback is invoked as soon as files are added to the fileupload
+ // widget (via file input selection, drag & drop, paste or add API call).
+ // If the singleFileUploads option is enabled, this callback will be
+ // called once for each file in the selection for XHR file uploads, else
+ // once for each file selection.
+ //
+ // The upload starts when the submit method is invoked on the data parameter.
+ // The data object contains a files property holding the added files
+ // and allows you to override plugin options as well as define ajax settings.
+ //
+ // Listeners for this callback can also be bound the following way:
+ // .bind('fileuploadadd', func);
+ //
+ // data.submit() returns a Promise object and allows to attach additional
+ // handlers using jQuery's Deferred callbacks:
+ // data.submit().done(func).fail(func).always(func);
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ if (data.autoUpload || (data.autoUpload !== false &&
+ $(this).fileupload('option', 'autoUpload'))) {
+ data.process().done(function () {
+ data.submit();
+ });
+ }
+ },
+
+ // Other callbacks:
+
+ // Callback for the submit event of each file upload:
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
+
+ // Callback for the start of each file upload request:
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
+
+ // Callback for successful uploads:
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
+
+ // Callback for failed (abort or error) uploads:
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
+
+ // Callback for completed (success, abort or error) requests:
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
+
+ // Callback for upload progress events:
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
+
+ // Callback for global upload progress events:
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
+
+ // Callback for uploads start, equivalent to the global ajaxStart event:
+ // start: function (e) {}, // .bind('fileuploadstart', func);
+
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
+
+ // Callback for change events of the fileInput(s):
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
+
+ // Callback for paste events to the pasteZone(s):
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
+
+ // Callback for drop events of the dropZone(s):
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
+
+ // Callback for dragover events of the dropZone(s):
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
+
+ // Callback for the start of each chunk upload request:
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
+
+ // Callback for successful chunk uploads:
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
+
+ // Callback for failed (abort or error) chunk uploads:
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
+
+ // Callback for completed (success, abort or error) chunk upload requests:
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
+
+ // The plugin options are used as settings object for the ajax calls.
+ // The following are jQuery ajax settings required for the file uploads:
+ processData: false,
+ contentType: false,
+ cache: false,
+ timeout: 0
+ },
+
+ // A list of options that require reinitializing event listeners and/or
+ // special initialization code:
+ _specialOptions: [
+ 'fileInput',
+ 'dropZone',
+ 'pasteZone',
+ 'multipart',
+ 'forceIframeTransport'
+ ],
+
+ _blobSlice: $.support.blobSlice && function () {
+ var slice = this.slice || this.webkitSlice || this.mozSlice;
+ return slice.apply(this, arguments);
+ },
+
+ _BitrateTimer: function () {
+ this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
+ this.loaded = 0;
+ this.bitrate = 0;
+ this.getBitrate = function (now, loaded, interval) {
+ var timeDiff = now - this.timestamp;
+ if (!this.bitrate || !interval || timeDiff > interval) {
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
+ this.loaded = loaded;
+ this.timestamp = now;
+ }
+ return this.bitrate;
+ };
+ },
+
+ _isXHRUpload: function (options) {
+ return !options.forceIframeTransport &&
+ ((!options.multipart && $.support.xhrFileUpload) ||
+ $.support.xhrFormDataFileUpload);
+ },
+
+ _getFormData: function (options) {
+ var formData;
+ if ($.type(options.formData) === 'function') {
+ return options.formData(options.form);
+ }
+ if ($.isArray(options.formData)) {
+ return options.formData;
+ }
+ if ($.type(options.formData) === 'object') {
+ formData = [];
+ $.each(options.formData, function (name, value) {
+ formData.push({name: name, value: value});
+ });
+ return formData;
+ }
+ return [];
+ },
+
+ _getTotal: function (files) {
+ var total = 0;
+ $.each(files, function (index, file) {
+ total += file.size || 1;
+ });
+ return total;
+ },
+
+ _initProgressObject: function (obj) {
+ var progress = {
+ loaded: 0,
+ total: 0,
+ bitrate: 0
+ };
+ if (obj._progress) {
+ $.extend(obj._progress, progress);
+ } else {
+ obj._progress = progress;
+ }
+ },
+
+ _initResponseObject: function (obj) {
+ var prop;
+ if (obj._response) {
+ for (prop in obj._response) {
+ if (obj._response.hasOwnProperty(prop)) {
+ delete obj._response[prop];
+ }
+ }
+ } else {
+ obj._response = {};
+ }
+ },
+
+ _onProgress: function (e, data) {
+ if (e.lengthComputable) {
+ var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
+ loaded;
+ if (data._time && data.progressInterval &&
+ (now - data._time < data.progressInterval) &&
+ e.loaded !== e.total) {
+ return;
+ }
+ data._time = now;
+ loaded = Math.floor(
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
+ ) + (data.uploadedBytes || 0);
+ // Add the difference from the previously loaded state
+ // to the global loaded counter:
+ this._progress.loaded += (loaded - data._progress.loaded);
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
+ now,
+ this._progress.loaded,
+ data.bitrateInterval
+ );
+ data._progress.loaded = data.loaded = loaded;
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
+ now,
+ loaded,
+ data.bitrateInterval
+ );
+ // Trigger a custom progress event with a total data property set
+ // to the file size(s) of the current upload and a loaded data
+ // property calculated accordingly:
+ this._trigger(
+ 'progress',
+ $.Event('progress', {delegatedEvent: e}),
+ data
+ );
+ // Trigger a global progress event for all current file uploads,
+ // including ajax calls queued for sequential file uploads:
+ this._trigger(
+ 'progressall',
+ $.Event('progressall', {delegatedEvent: e}),
+ this._progress
+ );
+ }
+ },
+
+ _initProgressListener: function (options) {
+ var that = this,
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
+ // Accesss to the native XHR object is required to add event listeners
+ // for the upload progress event:
+ if (xhr.upload) {
+ $(xhr.upload).bind('progress', function (e) {
+ var oe = e.originalEvent;
+ // Make sure the progress event properties get copied over:
+ e.lengthComputable = oe.lengthComputable;
+ e.loaded = oe.loaded;
+ e.total = oe.total;
+ that._onProgress(e, options);
+ });
+ options.xhr = function () {
+ return xhr;
+ };
+ }
+ },
+
+ _isInstanceOf: function (type, obj) {
+ // Cross-frame instanceof check
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
+ },
+
+ _initXHRData: function (options) {
+ var that = this,
+ formData,
+ file = options.files[0],
+ // Ignore non-multipart setting if not supported:
+ multipart = options.multipart || !$.support.xhrFileUpload,
+ paramName = $.type(options.paramName) === 'array' ?
+ options.paramName[0] : options.paramName;
+ options.headers = $.extend({}, options.headers);
+ if (options.contentRange) {
+ options.headers['Content-Range'] = options.contentRange;
+ }
+ if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
+ encodeURI(file.name) + '"';
+ }
+ if (!multipart) {
+ options.contentType = file.type || 'application/octet-stream';
+ options.data = options.blob || file;
+ } else if ($.support.xhrFormDataFileUpload) {
+ if (options.postMessage) {
+ // window.postMessage does not allow sending FormData
+ // objects, so we just add the File/Blob objects to
+ // the formData array and let the postMessage window
+ // create the FormData object out of this array:
+ formData = this._getFormData(options);
+ if (options.blob) {
+ formData.push({
+ name: paramName,
+ value: options.blob
+ });
+ } else {
+ $.each(options.files, function (index, file) {
+ formData.push({
+ name: ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) || paramName,
+ value: file
+ });
+ });
+ }
+ } else {
+ if (that._isInstanceOf('FormData', options.formData)) {
+ formData = options.formData;
+ } else {
+ formData = new FormData();
+ $.each(this._getFormData(options), function (index, field) {
+ formData.append(field.name, field.value);
+ });
+ }
+ if (options.blob) {
+ formData.append(paramName, options.blob, file.name);
+ } else {
+ $.each(options.files, function (index, file) {
+ // This check allows the tests to run with
+ // dummy objects:
+ if (that._isInstanceOf('File', file) ||
+ that._isInstanceOf('Blob', file)) {
+ formData.append(
+ ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) || paramName,
+ file,
+ file.uploadName || file.name
+ );
+ }
+ });
+ }
+ }
+ options.data = formData;
+ }
+ // Blob reference is not needed anymore, free memory:
+ options.blob = null;
+ },
+
+ _initIframeSettings: function (options) {
+ var targetHost = $(' ').prop('href', options.url).prop('host');
+ // Setting the dataType to iframe enables the iframe transport:
+ options.dataType = 'iframe ' + (options.dataType || '');
+ // The iframe transport accepts a serialized array as form data:
+ options.formData = this._getFormData(options);
+ // Add redirect url to form data on cross-domain uploads:
+ if (options.redirect && targetHost && targetHost !== location.host) {
+ options.formData.push({
+ name: options.redirectParamName || 'redirect',
+ value: options.redirect
+ });
+ }
+ },
+
+ _initDataSettings: function (options) {
+ if (this._isXHRUpload(options)) {
+ if (!this._chunkedUpload(options, true)) {
+ if (!options.data) {
+ this._initXHRData(options);
+ }
+ this._initProgressListener(options);
+ }
+ if (options.postMessage) {
+ // Setting the dataType to postmessage enables the
+ // postMessage transport:
+ options.dataType = 'postmessage ' + (options.dataType || '');
+ }
+ } else {
+ this._initIframeSettings(options);
+ }
+ },
+
+ _getParamName: function (options) {
+ var fileInput = $(options.fileInput),
+ paramName = options.paramName;
+ if (!paramName) {
+ paramName = [];
+ fileInput.each(function () {
+ var input = $(this),
+ name = input.prop('name') || 'files[]',
+ i = (input.prop('files') || [1]).length;
+ while (i) {
+ paramName.push(name);
+ i -= 1;
+ }
+ });
+ if (!paramName.length) {
+ paramName = [fileInput.prop('name') || 'files[]'];
+ }
+ } else if (!$.isArray(paramName)) {
+ paramName = [paramName];
+ }
+ return paramName;
+ },
+
+ _initFormSettings: function (options) {
+ // Retrieve missing options from the input field and the
+ // associated form, if available:
+ if (!options.form || !options.form.length) {
+ options.form = $(options.fileInput.prop('form'));
+ // If the given file input doesn't have an associated form,
+ // use the default widget file input's form:
+ if (!options.form.length) {
+ options.form = $(this.options.fileInput.prop('form'));
+ }
+ }
+ options.paramName = this._getParamName(options);
+ if (!options.url) {
+ options.url = options.form.prop('action') || location.href;
+ }
+ // The HTTP request method must be "POST" or "PUT":
+ options.type = (options.type ||
+ ($.type(options.form.prop('method')) === 'string' &&
+ options.form.prop('method')) || ''
+ ).toUpperCase();
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
+ options.type !== 'PATCH') {
+ options.type = 'POST';
+ }
+ if (!options.formAcceptCharset) {
+ options.formAcceptCharset = options.form.attr('accept-charset');
+ }
+ },
+
+ _getAJAXSettings: function (data) {
+ var options = $.extend({}, this.options, data);
+ this._initFormSettings(options);
+ this._initDataSettings(options);
+ return options;
+ },
+
+ // jQuery 1.6 doesn't provide .state(),
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
+ _getDeferredState: function (deferred) {
+ if (deferred.state) {
+ return deferred.state();
+ }
+ if (deferred.isResolved()) {
+ return 'resolved';
+ }
+ if (deferred.isRejected()) {
+ return 'rejected';
+ }
+ return 'pending';
+ },
+
+ // Maps jqXHR callbacks to the equivalent
+ // methods of the given Promise object:
+ _enhancePromise: function (promise) {
+ promise.success = promise.done;
+ promise.error = promise.fail;
+ promise.complete = promise.always;
+ return promise;
+ },
+
+ // Creates and returns a Promise object enhanced with
+ // the jqXHR methods abort, success, error and complete:
+ _getXHRPromise: function (resolveOrReject, context, args) {
+ var dfd = $.Deferred(),
+ promise = dfd.promise();
+ context = context || this.options.context || promise;
+ if (resolveOrReject === true) {
+ dfd.resolveWith(context, args);
+ } else if (resolveOrReject === false) {
+ dfd.rejectWith(context, args);
+ }
+ promise.abort = dfd.promise;
+ return this._enhancePromise(promise);
+ },
+
+ // Adds convenience methods to the data callback argument:
+ _addConvenienceMethods: function (e, data) {
+ var that = this,
+ getPromise = function (args) {
+ return $.Deferred().resolveWith(that, args).promise();
+ };
+ data.process = function (resolveFunc, rejectFunc) {
+ if (resolveFunc || rejectFunc) {
+ data._processQueue = this._processQueue =
+ (this._processQueue || getPromise([this])).pipe(
+ function () {
+ if (data.errorThrown) {
+ return $.Deferred()
+ .rejectWith(that, [data]).promise();
+ }
+ return getPromise(arguments);
+ }
+ ).pipe(resolveFunc, rejectFunc);
+ }
+ return this._processQueue || getPromise([this]);
+ };
+ data.submit = function () {
+ if (this.state() !== 'pending') {
+ data.jqXHR = this.jqXHR =
+ (that._trigger(
+ 'submit',
+ $.Event('submit', {delegatedEvent: e}),
+ this
+ ) !== false) && that._onSend(e, this);
+ }
+ return this.jqXHR || that._getXHRPromise();
+ };
+ data.abort = function () {
+ if (this.jqXHR) {
+ return this.jqXHR.abort();
+ }
+ this.errorThrown = 'abort';
+ that._trigger('fail', null, this);
+ return that._getXHRPromise(false);
+ };
+ data.state = function () {
+ if (this.jqXHR) {
+ return that._getDeferredState(this.jqXHR);
+ }
+ if (this._processQueue) {
+ return that._getDeferredState(this._processQueue);
+ }
+ };
+ data.processing = function () {
+ return !this.jqXHR && this._processQueue && that
+ ._getDeferredState(this._processQueue) === 'pending';
+ };
+ data.progress = function () {
+ return this._progress;
+ };
+ data.response = function () {
+ return this._response;
+ };
+ },
+
+ // Parses the Range header from the server response
+ // and returns the uploaded bytes:
+ _getUploadedBytes: function (jqXHR) {
+ var range = jqXHR.getResponseHeader('Range'),
+ parts = range && range.split('-'),
+ upperBytesPos = parts && parts.length > 1 &&
+ parseInt(parts[1], 10);
+ return upperBytesPos && upperBytesPos + 1;
+ },
+
+ // Uploads a file in multiple, sequential requests
+ // by splitting the file up in multiple blob chunks.
+ // If the second parameter is true, only tests if the file
+ // should be uploaded in chunks, but does not invoke any
+ // upload requests:
+ _chunkedUpload: function (options, testOnly) {
+ options.uploadedBytes = options.uploadedBytes || 0;
+ var that = this,
+ file = options.files[0],
+ fs = file.size,
+ ub = options.uploadedBytes,
+ mcs = options.maxChunkSize || fs,
+ slice = this._blobSlice,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ upload;
+ if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
+ options.data) {
+ return false;
+ }
+ if (testOnly) {
+ return true;
+ }
+ if (ub >= fs) {
+ file.error = options.i18n('uploadedBytes');
+ return this._getXHRPromise(
+ false,
+ options.context,
+ [null, 'error', file.error]
+ );
+ }
+ // The chunk upload method:
+ upload = function () {
+ // Clone the options object for each chunk upload:
+ var o = $.extend({}, options),
+ currentLoaded = o._progress.loaded;
+ o.blob = slice.call(
+ file,
+ ub,
+ ub + mcs,
+ file.type
+ );
+ // Store the current chunk size, as the blob itself
+ // will be dereferenced after data processing:
+ o.chunkSize = o.blob.size;
+ // Expose the chunk bytes position range:
+ o.contentRange = 'bytes ' + ub + '-' +
+ (ub + o.chunkSize - 1) + '/' + fs;
+ // Process the upload data (the blob and potential form data):
+ that._initXHRData(o);
+ // Add progress listeners for this chunk upload:
+ that._initProgressListener(o);
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
+ that._getXHRPromise(false, o.context))
+ .done(function (result, textStatus, jqXHR) {
+ ub = that._getUploadedBytes(jqXHR) ||
+ (ub + o.chunkSize);
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered
+ // for this chunk:
+ if (currentLoaded + o.chunkSize - o._progress.loaded) {
+ that._onProgress($.Event('progress', {
+ lengthComputable: true,
+ loaded: ub - o.uploadedBytes,
+ total: ub - o.uploadedBytes
+ }), o);
+ }
+ options.uploadedBytes = o.uploadedBytes = ub;
+ o.result = result;
+ o.textStatus = textStatus;
+ o.jqXHR = jqXHR;
+ that._trigger('chunkdone', null, o);
+ that._trigger('chunkalways', null, o);
+ if (ub < fs) {
+ // File upload not yet complete,
+ // continue with the next chunk:
+ upload();
+ } else {
+ dfd.resolveWith(
+ o.context,
+ [result, textStatus, jqXHR]
+ );
+ }
+ })
+ .fail(function (jqXHR, textStatus, errorThrown) {
+ o.jqXHR = jqXHR;
+ o.textStatus = textStatus;
+ o.errorThrown = errorThrown;
+ that._trigger('chunkfail', null, o);
+ that._trigger('chunkalways', null, o);
+ dfd.rejectWith(
+ o.context,
+ [jqXHR, textStatus, errorThrown]
+ );
+ });
+ };
+ this._enhancePromise(promise);
+ promise.abort = function () {
+ return jqXHR.abort();
+ };
+ upload();
+ return promise;
+ },
+
+ _beforeSend: function (e, data) {
+ if (this._active === 0) {
+ // the start callback is triggered when an upload starts
+ // and no other uploads are currently running,
+ // equivalent to the global ajaxStart event:
+ this._trigger('start');
+ // Set timer for global bitrate progress calculation:
+ this._bitrateTimer = new this._BitrateTimer();
+ // Reset the global progress values:
+ this._progress.loaded = this._progress.total = 0;
+ this._progress.bitrate = 0;
+ }
+ // Make sure the container objects for the .response() and
+ // .progress() methods on the data object are available
+ // and reset to their initial state:
+ this._initResponseObject(data);
+ this._initProgressObject(data);
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
+ data._progress.bitrate = data.bitrate = 0;
+ this._active += 1;
+ // Initialize the global progress values:
+ this._progress.loaded += data.loaded;
+ this._progress.total += data.total;
+ },
+
+ _onDone: function (result, textStatus, jqXHR, options) {
+ var total = options._progress.total,
+ response = options._response;
+ if (options._progress.loaded < total) {
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered:
+ this._onProgress($.Event('progress', {
+ lengthComputable: true,
+ loaded: total,
+ total: total
+ }), options);
+ }
+ response.result = options.result = result;
+ response.textStatus = options.textStatus = textStatus;
+ response.jqXHR = options.jqXHR = jqXHR;
+ this._trigger('done', null, options);
+ },
+
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
+ var response = options._response;
+ if (options.recalculateProgress) {
+ // Remove the failed (error or abort) file upload from
+ // the global progress calculation:
+ this._progress.loaded -= options._progress.loaded;
+ this._progress.total -= options._progress.total;
+ }
+ response.jqXHR = options.jqXHR = jqXHR;
+ response.textStatus = options.textStatus = textStatus;
+ response.errorThrown = options.errorThrown = errorThrown;
+ this._trigger('fail', null, options);
+ },
+
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
+ // options object via done and fail callbacks
+ this._trigger('always', null, options);
+ },
+
+ _onSend: function (e, data) {
+ if (!data.submit) {
+ this._addConvenienceMethods(e, data);
+ }
+ var that = this,
+ jqXHR,
+ aborted,
+ slot,
+ pipe,
+ options = that._getAJAXSettings(data),
+ send = function () {
+ that._sending += 1;
+ // Set timer for bitrate progress calculation:
+ options._bitrateTimer = new that._BitrateTimer();
+ jqXHR = jqXHR || (
+ ((aborted || that._trigger(
+ 'send',
+ $.Event('send', {delegatedEvent: e}),
+ options
+ ) === false) &&
+ that._getXHRPromise(false, options.context, aborted)) ||
+ that._chunkedUpload(options) || $.ajax(options)
+ ).done(function (result, textStatus, jqXHR) {
+ that._onDone(result, textStatus, jqXHR, options);
+ }).fail(function (jqXHR, textStatus, errorThrown) {
+ that._onFail(jqXHR, textStatus, errorThrown, options);
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
+ that._onAlways(
+ jqXHRorResult,
+ textStatus,
+ jqXHRorError,
+ options
+ );
+ that._sending -= 1;
+ that._active -= 1;
+ if (options.limitConcurrentUploads &&
+ options.limitConcurrentUploads > that._sending) {
+ // Start the next queued upload,
+ // that has not been aborted:
+ var nextSlot = that._slots.shift();
+ while (nextSlot) {
+ if (that._getDeferredState(nextSlot) === 'pending') {
+ nextSlot.resolve();
+ break;
+ }
+ nextSlot = that._slots.shift();
+ }
+ }
+ if (that._active === 0) {
+ // The stop callback is triggered when all uploads have
+ // been completed, equivalent to the global ajaxStop event:
+ that._trigger('stop');
+ }
+ });
+ return jqXHR;
+ };
+ this._beforeSend(e, options);
+ if (this.options.sequentialUploads ||
+ (this.options.limitConcurrentUploads &&
+ this.options.limitConcurrentUploads <= this._sending)) {
+ if (this.options.limitConcurrentUploads > 1) {
+ slot = $.Deferred();
+ this._slots.push(slot);
+ pipe = slot.pipe(send);
+ } else {
+ this._sequence = this._sequence.pipe(send, send);
+ pipe = this._sequence;
+ }
+ // Return the piped Promise object, enhanced with an abort method,
+ // which is delegated to the jqXHR object of the current upload,
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
+ pipe.abort = function () {
+ aborted = [undefined, 'abort', 'abort'];
+ if (!jqXHR) {
+ if (slot) {
+ slot.rejectWith(options.context, aborted);
+ }
+ return send();
+ }
+ return jqXHR.abort();
+ };
+ return this._enhancePromise(pipe);
+ }
+ return send();
+ },
+
+ _onAdd: function (e, data) {
+ var that = this,
+ result = true,
+ options = $.extend({}, this.options, data),
+ files = data.files,
+ filesLength = files.length,
+ limit = options.limitMultiFileUploads,
+ limitSize = options.limitMultiFileUploadSize,
+ overhead = options.limitMultiFileUploadSizeOverhead,
+ batchSize = 0,
+ paramName = this._getParamName(options),
+ paramNameSet,
+ paramNameSlice,
+ fileSet,
+ i,
+ j = 0;
+ if (!filesLength) {
+ return false;
+ }
+ if (limitSize && files[0].size === undefined) {
+ limitSize = undefined;
+ }
+ if (!(options.singleFileUploads || limit || limitSize) ||
+ !this._isXHRUpload(options)) {
+ fileSet = [files];
+ paramNameSet = [paramName];
+ } else if (!(options.singleFileUploads || limitSize) && limit) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i += limit) {
+ fileSet.push(files.slice(i, i + limit));
+ paramNameSlice = paramName.slice(i, i + limit);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
+ }
+ paramNameSet.push(paramNameSlice);
+ }
+ } else if (!options.singleFileUploads && limitSize) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i = i + 1) {
+ batchSize += files[i].size + overhead;
+ if (i + 1 === filesLength ||
+ ((batchSize + files[i + 1].size + overhead) > limitSize) ||
+ (limit && i + 1 - j >= limit)) {
+ fileSet.push(files.slice(j, i + 1));
+ paramNameSlice = paramName.slice(j, i + 1);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
+ }
+ paramNameSet.push(paramNameSlice);
+ j = i + 1;
+ batchSize = 0;
+ }
+ }
+ } else {
+ paramNameSet = paramName;
+ }
+ data.originalFiles = files;
+ $.each(fileSet || files, function (index, element) {
+ var newData = $.extend({}, data);
+ newData.files = fileSet ? element : [element];
+ newData.paramName = paramNameSet[index];
+ that._initResponseObject(newData);
+ that._initProgressObject(newData);
+ that._addConvenienceMethods(e, newData);
+ result = that._trigger(
+ 'add',
+ $.Event('add', {delegatedEvent: e}),
+ newData
+ );
+ return result;
+ });
+ return result;
+ },
+
+ _replaceFileInput: function (data) {
+ var input = data.fileInput,
+ inputClone = input.clone(true),
+ restoreFocus = input.is(document.activeElement);
+ // Add a reference for the new cloned file input to the data argument:
+ data.fileInputClone = inputClone;
+ $('').append(inputClone)[0].reset();
+ // Detaching allows to insert the fileInput on another form
+ // without loosing the file input value:
+ input.after(inputClone).detach();
+ // If the fileInput had focus before it was detached,
+ // restore focus to the inputClone.
+ if (restoreFocus) {
+ inputClone.focus();
+ }
+ // Avoid memory leaks with the detached file input:
+ $.cleanData(input.unbind('remove'));
+ // Replace the original file input element in the fileInput
+ // elements set with the clone, which has been copied including
+ // event handlers:
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
+ if (el === input[0]) {
+ return inputClone[0];
+ }
+ return el;
+ });
+ // If the widget has been initialized on the file input itself,
+ // override this.element with the file input clone:
+ if (input[0] === this.element[0]) {
+ this.element = inputClone;
+ }
+ },
+
+ _handleFileTreeEntry: function (entry, path) {
+ var that = this,
+ dfd = $.Deferred(),
+ errorHandler = function (e) {
+ if (e && !e.entry) {
+ e.entry = entry;
+ }
+ // Since $.when returns immediately if one
+ // Deferred is rejected, we use resolve instead.
+ // This allows valid files and invalid items
+ // to be returned together in one set:
+ dfd.resolve([e]);
+ },
+ successHandler = function (entries) {
+ that._handleFileTreeEntries(
+ entries,
+ path + entry.name + '/'
+ ).done(function (files) {
+ dfd.resolve(files);
+ }).fail(errorHandler);
+ },
+ readEntries = function () {
+ dirReader.readEntries(function (results) {
+ if (!results.length) {
+ successHandler(entries);
+ } else {
+ entries = entries.concat(results);
+ readEntries();
+ }
+ }, errorHandler);
+ },
+ dirReader, entries = [];
+ path = path || '';
+ if (entry.isFile) {
+ if (entry._file) {
+ // Workaround for Chrome bug #149735
+ entry._file.relativePath = path;
+ dfd.resolve(entry._file);
+ } else {
+ entry.file(function (file) {
+ file.relativePath = path;
+ dfd.resolve(file);
+ }, errorHandler);
+ }
+ } else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ readEntries();
+ } else {
+ // Return an empy list for file system items
+ // other than files or directories:
+ dfd.resolve([]);
+ }
+ return dfd.promise();
+ },
+
+ _handleFileTreeEntries: function (entries, path) {
+ var that = this;
+ return $.when.apply(
+ $,
+ $.map(entries, function (entry) {
+ return that._handleFileTreeEntry(entry, path);
+ })
+ ).pipe(function () {
+ return Array.prototype.concat.apply(
+ [],
+ arguments
+ );
+ });
+ },
+
+ _getDroppedFiles: function (dataTransfer) {
+ dataTransfer = dataTransfer || {};
+ var items = dataTransfer.items;
+ if (items && items.length && (items[0].webkitGetAsEntry ||
+ items[0].getAsEntry)) {
+ return this._handleFileTreeEntries(
+ $.map(items, function (item) {
+ var entry;
+ if (item.webkitGetAsEntry) {
+ entry = item.webkitGetAsEntry();
+ if (entry) {
+ // Workaround for Chrome bug #149735:
+ entry._file = item.getAsFile();
+ }
+ return entry;
+ }
+ return item.getAsEntry();
+ })
+ );
+ }
+ return $.Deferred().resolve(
+ $.makeArray(dataTransfer.files)
+ ).promise();
+ },
+
+ _getSingleFileInputFiles: function (fileInput) {
+ fileInput = $(fileInput);
+ var entries = fileInput.prop('webkitEntries') ||
+ fileInput.prop('entries'),
+ files,
+ value;
+ if (entries && entries.length) {
+ return this._handleFileTreeEntries(entries);
+ }
+ files = $.makeArray(fileInput.prop('files'));
+ if (!files.length) {
+ value = fileInput.prop('value');
+ if (!value) {
+ return $.Deferred().resolve([]).promise();
+ }
+ // If the files property is not available, the browser does not
+ // support the File API and we add a pseudo File object with
+ // the input value as name with path information removed:
+ files = [{name: value.replace(/^.*\\/, '')}];
+ } else if (files[0].name === undefined && files[0].fileName) {
+ // File normalization for Safari 4 and Firefox 3:
+ $.each(files, function (index, file) {
+ file.name = file.fileName;
+ file.size = file.fileSize;
+ });
+ }
+ return $.Deferred().resolve(files).promise();
+ },
+
+ _getFileInputFiles: function (fileInput) {
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
+ return this._getSingleFileInputFiles(fileInput);
+ }
+ return $.when.apply(
+ $,
+ $.map(fileInput, this._getSingleFileInputFiles)
+ ).pipe(function () {
+ return Array.prototype.concat.apply(
+ [],
+ arguments
+ );
+ });
+ },
+
+ _onChange: function (e) {
+ var that = this,
+ data = {
+ fileInput: $(e.target),
+ form: $(e.target.form)
+ };
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ if (that.options.replaceFileInput) {
+ that._replaceFileInput(data);
+ }
+ if (that._trigger(
+ 'change',
+ $.Event('change', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ that._onAdd(e, data);
+ }
+ });
+ },
+
+ _onPaste: function (e) {
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
+ e.originalEvent.clipboardData.items,
+ data = {files: []};
+ if (items && items.length) {
+ $.each(items, function (index, item) {
+ var file = item.getAsFile && item.getAsFile();
+ if (file) {
+ data.files.push(file);
+ }
+ });
+ if (this._trigger(
+ 'paste',
+ $.Event('paste', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ this._onAdd(e, data);
+ }
+ }
+ },
+
+ _onDrop: function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var that = this,
+ dataTransfer = e.dataTransfer,
+ data = {};
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
+ e.preventDefault();
+ this._getDroppedFiles(dataTransfer).always(function (files) {
+ data.files = files;
+ if (that._trigger(
+ 'drop',
+ $.Event('drop', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ that._onAdd(e, data);
+ }
+ });
+ }
+ },
+
+ _onDragOver: getDragHandler('dragover'),
+
+ _onDragEnter: getDragHandler('dragenter'),
+
+ _onDragLeave: getDragHandler('dragleave'),
+
+ _initEventHandlers: function () {
+ if (this._isXHRUpload(this.options)) {
+ this._on(this.options.dropZone, {
+ dragover: this._onDragOver,
+ drop: this._onDrop,
+ // event.preventDefault() on dragenter is required for IE10+:
+ dragenter: this._onDragEnter,
+ // dragleave is not required, but added for completeness:
+ dragleave: this._onDragLeave
+ });
+ this._on(this.options.pasteZone, {
+ paste: this._onPaste
+ });
+ }
+ if ($.support.fileInput) {
+ this._on(this.options.fileInput, {
+ change: this._onChange
+ });
+ }
+ },
+
+ _destroyEventHandlers: function () {
+ this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
+ this._off(this.options.pasteZone, 'paste');
+ this._off(this.options.fileInput, 'change');
+ },
+
+ _setOption: function (key, value) {
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
+ if (reinit) {
+ this._destroyEventHandlers();
+ }
+ this._super(key, value);
+ if (reinit) {
+ this._initSpecialOptions();
+ this._initEventHandlers();
+ }
+ },
+
+ _initSpecialOptions: function () {
+ var options = this.options;
+ if (options.fileInput === undefined) {
+ options.fileInput = this.element.is('input[type="file"]') ?
+ this.element : this.element.find('input[type="file"]');
+ } else if (!(options.fileInput instanceof $)) {
+ options.fileInput = $(options.fileInput);
+ }
+ if (!(options.dropZone instanceof $)) {
+ options.dropZone = $(options.dropZone);
+ }
+ if (!(options.pasteZone instanceof $)) {
+ options.pasteZone = $(options.pasteZone);
+ }
+ },
+
+ _getRegExp: function (str) {
+ var parts = str.split('/'),
+ modifiers = parts.pop();
+ parts.shift();
+ return new RegExp(parts.join('/'), modifiers);
+ },
+
+ _isRegExpOption: function (key, value) {
+ return key !== 'url' && $.type(value) === 'string' &&
+ /^\/.*\/[igm]{0,3}$/.test(value);
+ },
+
+ _initDataAttributes: function () {
+ var that = this,
+ options = this.options,
+ data = this.element.data();
+ // Initialize options set via HTML5 data-attributes:
+ $.each(
+ this.element[0].attributes,
+ function (index, attr) {
+ var key = attr.name.toLowerCase(),
+ value;
+ if (/^data-/.test(key)) {
+ // Convert hyphen-ated key to camelCase:
+ key = key.slice(5).replace(/-[a-z]/g, function (str) {
+ return str.charAt(1).toUpperCase();
+ });
+ value = data[key];
+ if (that._isRegExpOption(key, value)) {
+ value = that._getRegExp(value);
+ }
+ options[key] = value;
+ }
+ }
+ );
+ },
+
+ _create: function () {
+ this._initDataAttributes();
+ this._initSpecialOptions();
+ this._slots = [];
+ this._sequence = this._getXHRPromise(true);
+ this._sending = this._active = 0;
+ this._initProgressObject(this);
+ this._initEventHandlers();
+ },
+
+ // This method is exposed to the widget API and allows to query
+ // the number of active uploads:
+ active: function () {
+ return this._active;
+ },
+
+ // This method is exposed to the widget API and allows to query
+ // the widget upload progress.
+ // It returns an object with loaded, total and bitrate properties
+ // for the running uploads:
+ progress: function () {
+ return this._progress;
+ },
+
+ // This method is exposed to the widget API and allows adding files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files property and can contain additional options:
+ // .fileupload('add', {files: filesList});
+ add: function (data) {
+ var that = this;
+ if (!data || this.options.disabled) {
+ return;
+ }
+ if (data.fileInput && !data.files) {
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ that._onAdd(null, data);
+ });
+ } else {
+ data.files = $.makeArray(data.files);
+ this._onAdd(null, data);
+ }
+ },
+
+ // This method is exposed to the widget API and allows sending files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files or fileInput property and can contain additional options:
+ // .fileupload('send', {files: filesList});
+ // The method returns a Promise object for the file upload call.
+ send: function (data) {
+ if (data && !this.options.disabled) {
+ if (data.fileInput && !data.files) {
+ var that = this,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ aborted;
+ promise.abort = function () {
+ aborted = true;
+ if (jqXHR) {
+ return jqXHR.abort();
+ }
+ dfd.reject(null, 'abort', 'abort');
+ return promise;
+ };
+ this._getFileInputFiles(data.fileInput).always(
+ function (files) {
+ if (aborted) {
+ return;
+ }
+ if (!files.length) {
+ dfd.reject();
+ return;
+ }
+ data.files = files;
+ jqXHR = that._onSend(null, data);
+ jqXHR.then(
+ function (result, textStatus, jqXHR) {
+ dfd.resolve(result, textStatus, jqXHR);
+ },
+ function (jqXHR, textStatus, errorThrown) {
+ dfd.reject(jqXHR, textStatus, errorThrown);
+ }
+ );
+ }
+ );
+ return this._enhancePromise(promise);
+ }
+ data.files = $.makeArray(data.files);
+ if (data.files.length) {
+ return this._onSend(null, data);
+ }
+ }
+ return this._getXHRPromise(false, data && data.context);
+ }
+
+ });
+
+}));
diff --git a/common/modules/file/assets/js/jquery.iframe-transport.js b/common/modules/file/assets/js/jquery.iframe-transport.js
new file mode 100644
index 0000000..a7d34e0
--- /dev/null
+++ b/common/modules/file/assets/js/jquery.iframe-transport.js
@@ -0,0 +1,217 @@
+/*
+ * jQuery Iframe Transport Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* global define, require, window, document */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+
+ // Helper variable to create unique names for the transport iframes:
+ var counter = 0;
+
+ // The iframe transport accepts four additional options:
+ // options.fileInput: a jQuery collection of file input fields
+ // options.paramName: the parameter name for the file form data,
+ // overrides the name property of the file input field(s),
+ // can be a string or an array of strings.
+ // options.formData: an array of objects with name and value properties,
+ // equivalent to the return data of .serializeArray(), e.g.:
+ // [{name: 'a', value: 1}, {name: 'b', value: 2}]
+ // options.initialIframeSrc: the URL of the initial iframe src,
+ // by default set to "javascript:false;"
+ $.ajaxTransport('iframe', function (options) {
+ if (options.async) {
+ // javascript:false as initial iframe src
+ // prevents warning popups on HTTPS in IE6:
+ /*jshint scripturl: true */
+ var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
+ /*jshint scripturl: false */
+ form,
+ iframe,
+ addParamChar;
+ return {
+ send: function (_, completeCallback) {
+ form = $('');
+ form.attr('accept-charset', options.formAcceptCharset);
+ addParamChar = /\?/.test(options.url) ? '&' : '?';
+ // XDomainRequest only supports GET and POST:
+ if (options.type === 'DELETE') {
+ options.url = options.url + addParamChar + '_method=DELETE';
+ options.type = 'POST';
+ } else if (options.type === 'PUT') {
+ options.url = options.url + addParamChar + '_method=PUT';
+ options.type = 'POST';
+ } else if (options.type === 'PATCH') {
+ options.url = options.url + addParamChar + '_method=PATCH';
+ options.type = 'POST';
+ }
+ // IE versions below IE8 cannot set the name property of
+ // elements that have already been added to the DOM,
+ // so we set the name along with the iframe HTML markup:
+ counter += 1;
+ iframe = $(
+ ''
+ ).bind('load', function () {
+ var fileInputClones,
+ paramNames = $.isArray(options.paramName) ?
+ options.paramName : [options.paramName];
+ iframe
+ .unbind('load')
+ .bind('load', function () {
+ var response;
+ // Wrap in a try/catch block to catch exceptions thrown
+ // when trying to access cross-domain iframe contents:
+ try {
+ response = iframe.contents();
+ // Google Chrome and Firefox do not throw an
+ // exception when calling iframe.contents() on
+ // cross-domain requests, so we unify the response:
+ if (!response.length || !response[0].firstChild) {
+ throw new Error();
+ }
+ } catch (e) {
+ response = undefined;
+ }
+ // The complete callback returns the
+ // iframe content document as response object:
+ completeCallback(
+ 200,
+ 'success',
+ {'iframe': response}
+ );
+ // Fix for IE endless progress bar activity bug
+ // (happens on form submits to iframe targets):
+ $('')
+ .appendTo(form);
+ window.setTimeout(function () {
+ // Removing the form in a setTimeout call
+ // allows Chrome's developer tools to display
+ // the response result
+ form.remove();
+ }, 0);
+ });
+ form
+ .prop('target', iframe.prop('name'))
+ .prop('action', options.url)
+ .prop('method', options.type);
+ if (options.formData) {
+ $.each(options.formData, function (index, field) {
+ $(' ')
+ .prop('name', field.name)
+ .val(field.value)
+ .appendTo(form);
+ });
+ }
+ if (options.fileInput && options.fileInput.length &&
+ options.type === 'POST') {
+ fileInputClones = options.fileInput.clone();
+ // Insert a clone for each file input field:
+ options.fileInput.after(function (index) {
+ return fileInputClones[index];
+ });
+ if (options.paramName) {
+ options.fileInput.each(function (index) {
+ $(this).prop(
+ 'name',
+ paramNames[index] || options.paramName
+ );
+ });
+ }
+ // Appending the file input fields to the hidden form
+ // removes them from their original location:
+ form
+ .append(options.fileInput)
+ .prop('enctype', 'multipart/form-data')
+ // enctype must be set as encoding for IE:
+ .prop('encoding', 'multipart/form-data');
+ // Remove the HTML5 form attribute from the input(s):
+ options.fileInput.removeAttr('form');
+ }
+ form.submit();
+ // Insert the file input fields at their original location
+ // by replacing the clones with the originals:
+ if (fileInputClones && fileInputClones.length) {
+ options.fileInput.each(function (index, input) {
+ var clone = $(fileInputClones[index]);
+ // Restore the original name and form properties:
+ $(input)
+ .prop('name', clone.prop('name'))
+ .attr('form', clone.attr('form'));
+ clone.replaceWith(input);
+ });
+ }
+ });
+ form.append(iframe).appendTo(document.body);
+ },
+ abort: function () {
+ if (iframe) {
+ // javascript:false as iframe src aborts the request
+ // and prevents warning popups on HTTPS in IE6.
+ // concat is used to avoid the "Script URL" JSLint error:
+ iframe
+ .unbind('load')
+ .prop('src', initialIframeSrc);
+ }
+ if (form) {
+ form.remove();
+ }
+ }
+ };
+ }
+ });
+
+ // The iframe transport returns the iframe content document as response.
+ // The following adds converters from iframe to text, json, html, xml
+ // and script.
+ // Please note that the Content-Type for JSON responses has to be text/plain
+ // or text/html, if the browser doesn't include application/json in the
+ // Accept header, else IE will show a download dialog.
+ // The Content-Type for XML responses on the other hand has to be always
+ // application/xml or text/xml, so IE properly parses the XML response.
+ // See also
+ // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
+ $.ajaxSetup({
+ converters: {
+ 'iframe text': function (iframe) {
+ return iframe && $(iframe[0].body).text();
+ },
+ 'iframe json': function (iframe) {
+ return iframe && $.parseJSON($(iframe[0].body).text());
+ },
+ 'iframe html': function (iframe) {
+ return iframe && $(iframe[0].body).html();
+ },
+ 'iframe xml': function (iframe) {
+ var xmlDoc = iframe && iframe[0];
+ return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
+ $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
+ $(xmlDoc.body).html());
+ },
+ 'iframe script': function (iframe) {
+ return iframe && $.globalEval($(iframe[0].body).text());
+ }
+ }
+ });
+
+}));
diff --git a/common/modules/file/assets/js/vendor/jquery.ui.widget.js b/common/modules/file/assets/js/vendor/jquery.ui.widget.js
new file mode 100644
index 0000000..e08df3f
--- /dev/null
+++ b/common/modules/file/assets/js/vendor/jquery.ui.widget.js
@@ -0,0 +1,572 @@
+/*! jQuery UI - v1.11.4+CommonJS - 2015-08-28
+* http://jqueryui.com
+* Includes: widget.js
+* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
+
+(function( factory ) {
+ if ( typeof define === "function" && define.amd ) {
+
+ // AMD. Register as an anonymous module.
+ define([ "jquery" ], factory );
+
+ } else if ( typeof exports === "object" ) {
+
+ // Node/CommonJS
+ factory( require( "jquery" ) );
+
+ } else {
+
+ // Browser globals
+ factory( jQuery );
+ }
+}(function( $ ) {
+/*!
+ * jQuery UI Widget 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/jQuery.widget/
+ */
+
+
+var widget_uuid = 0,
+ widget_slice = Array.prototype.slice;
+
+$.cleanData = (function( orig ) {
+ return function( elems ) {
+ var events, elem, i;
+ for ( i = 0; (elem = elems[i]) != null; i++ ) {
+ try {
+
+ // Only trigger remove when necessary to save time
+ events = $._data( elem, "events" );
+ if ( events && events.remove ) {
+ $( elem ).triggerHandler( "remove" );
+ }
+
+ // http://bugs.jquery.com/ticket/8235
+ } catch ( e ) {}
+ }
+ orig( elems );
+ };
+})( $.cleanData );
+
+$.widget = function( name, base, prototype ) {
+ var fullName, existingConstructor, constructor, basePrototype,
+ // proxiedPrototype allows the provided prototype to remain unmodified
+ // so that it can be used as a mixin for multiple widgets (#8876)
+ proxiedPrototype = {},
+ namespace = name.split( "." )[ 0 ];
+
+ name = name.split( "." )[ 1 ];
+ fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ // create selector for plugin
+ $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+ return !!$.data( elem, fullName );
+ };
+
+ $[ namespace ] = $[ namespace ] || {};
+ existingConstructor = $[ namespace ][ name ];
+ constructor = $[ namespace ][ name ] = function( options, element ) {
+ // allow instantiation without "new" keyword
+ if ( !this._createWidget ) {
+ return new constructor( options, element );
+ }
+
+ // allow instantiation without initializing for simple inheritance
+ // must use "new" keyword (the code above always passes args)
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+ };
+ // extend with the existing constructor to carry over any static properties
+ $.extend( constructor, existingConstructor, {
+ version: prototype.version,
+ // copy the object used to create the prototype in case we need to
+ // redefine the widget later
+ _proto: $.extend( {}, prototype ),
+ // track widgets that inherit from this widget in case this widget is
+ // redefined after a widget inherits from it
+ _childConstructors: []
+ });
+
+ basePrototype = new base();
+ // we need to make the options hash a property directly on the new instance
+ // otherwise we'll modify the options hash on the prototype that we're
+ // inheriting from
+ basePrototype.options = $.widget.extend( {}, basePrototype.options );
+ $.each( prototype, function( prop, value ) {
+ if ( !$.isFunction( value ) ) {
+ proxiedPrototype[ prop ] = value;
+ return;
+ }
+ proxiedPrototype[ prop ] = (function() {
+ var _super = function() {
+ return base.prototype[ prop ].apply( this, arguments );
+ },
+ _superApply = function( args ) {
+ return base.prototype[ prop ].apply( this, args );
+ };
+ return function() {
+ var __super = this._super,
+ __superApply = this._superApply,
+ returnValue;
+
+ this._super = _super;
+ this._superApply = _superApply;
+
+ returnValue = value.apply( this, arguments );
+
+ this._super = __super;
+ this._superApply = __superApply;
+
+ return returnValue;
+ };
+ })();
+ });
+ constructor.prototype = $.widget.extend( basePrototype, {
+ // TODO: remove support for widgetEventPrefix
+ // always use the name + a colon as the prefix, e.g., draggable:start
+ // don't prefix for widgets that aren't DOM-based
+ widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
+ }, proxiedPrototype, {
+ constructor: constructor,
+ namespace: namespace,
+ widgetName: name,
+ widgetFullName: fullName
+ });
+
+ // If this widget is being redefined then we need to find all widgets that
+ // are inheriting from it and redefine all of them so that they inherit from
+ // the new version of this widget. We're essentially trying to replace one
+ // level in the prototype chain.
+ if ( existingConstructor ) {
+ $.each( existingConstructor._childConstructors, function( i, child ) {
+ var childPrototype = child.prototype;
+
+ // redefine the child widget using the same prototype that was
+ // originally used, but inherit from the new version of the base
+ $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+ });
+ // remove the list of existing child constructors from the old constructor
+ // so the old child constructors can be garbage collected
+ delete existingConstructor._childConstructors;
+ } else {
+ base._childConstructors.push( constructor );
+ }
+
+ $.widget.bridge( name, constructor );
+
+ return constructor;
+};
+
+$.widget.extend = function( target ) {
+ var input = widget_slice.call( arguments, 1 ),
+ inputIndex = 0,
+ inputLength = input.length,
+ key,
+ value;
+ for ( ; inputIndex < inputLength; inputIndex++ ) {
+ for ( key in input[ inputIndex ] ) {
+ value = input[ inputIndex ][ key ];
+ if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+ // Clone objects
+ if ( $.isPlainObject( value ) ) {
+ target[ key ] = $.isPlainObject( target[ key ] ) ?
+ $.widget.extend( {}, target[ key ], value ) :
+ // Don't extend strings, arrays, etc. with objects
+ $.widget.extend( {}, value );
+ // Copy everything else by reference
+ } else {
+ target[ key ] = value;
+ }
+ }
+ }
+ }
+ return target;
+};
+
+$.widget.bridge = function( name, object ) {
+ var fullName = object.prototype.widgetFullName || name;
+ $.fn[ name ] = function( options ) {
+ var isMethodCall = typeof options === "string",
+ args = widget_slice.call( arguments, 1 ),
+ returnValue = this;
+
+ if ( isMethodCall ) {
+ this.each(function() {
+ var methodValue,
+ instance = $.data( this, fullName );
+ if ( options === "instance" ) {
+ returnValue = instance;
+ return false;
+ }
+ if ( !instance ) {
+ return $.error( "cannot call methods on " + name + " prior to initialization; " +
+ "attempted to call method '" + options + "'" );
+ }
+ if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+ return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+ }
+ methodValue = instance[ options ].apply( instance, args );
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue && methodValue.jquery ?
+ returnValue.pushStack( methodValue.get() ) :
+ methodValue;
+ return false;
+ }
+ });
+ } else {
+
+ // Allow multiple hashes to be passed on init
+ if ( args.length ) {
+ options = $.widget.extend.apply( null, [ options ].concat(args) );
+ }
+
+ this.each(function() {
+ var instance = $.data( this, fullName );
+ if ( instance ) {
+ instance.option( options || {} );
+ if ( instance._init ) {
+ instance._init();
+ }
+ } else {
+ $.data( this, fullName, new object( options, this ) );
+ }
+ });
+ }
+
+ return returnValue;
+ };
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ defaultElement: "",
+ options: {
+ disabled: false,
+
+ // callbacks
+ create: null
+ },
+ _createWidget: function( options, element ) {
+ element = $( element || this.defaultElement || this )[ 0 ];
+ this.element = $( element );
+ this.uuid = widget_uuid++;
+ this.eventNamespace = "." + this.widgetName + this.uuid;
+
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+
+ if ( element !== this ) {
+ $.data( element, this.widgetFullName, this );
+ this._on( true, this.element, {
+ remove: function( event ) {
+ if ( event.target === element ) {
+ this.destroy();
+ }
+ }
+ });
+ this.document = $( element.style ?
+ // element within the document
+ element.ownerDocument :
+ // element is window or document
+ element.document || element );
+ this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+ }
+
+ this.options = $.widget.extend( {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ this._create();
+ this._trigger( "create", null, this._getCreateEventData() );
+ this._init();
+ },
+ _getCreateOptions: $.noop,
+ _getCreateEventData: $.noop,
+ _create: $.noop,
+ _init: $.noop,
+
+ destroy: function() {
+ this._destroy();
+ // we can probably remove the unbind calls in 2.0
+ // all event bindings should go through this._on()
+ this.element
+ .unbind( this.eventNamespace )
+ .removeData( this.widgetFullName )
+ // support: jquery <1.6.3
+ // http://bugs.jquery.com/ticket/9413
+ .removeData( $.camelCase( this.widgetFullName ) );
+ this.widget()
+ .unbind( this.eventNamespace )
+ .removeAttr( "aria-disabled" )
+ .removeClass(
+ this.widgetFullName + "-disabled " +
+ "ui-state-disabled" );
+
+ // clean up events and states
+ this.bindings.unbind( this.eventNamespace );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ },
+ _destroy: $.noop,
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key,
+ parts,
+ curOption,
+ i;
+
+ if ( arguments.length === 0 ) {
+ // don't return a reference to the internal hash
+ return $.widget.extend( {}, this.options );
+ }
+
+ if ( typeof key === "string" ) {
+ // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+ options = {};
+ parts = key.split( "." );
+ key = parts.shift();
+ if ( parts.length ) {
+ curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+ for ( i = 0; i < parts.length - 1; i++ ) {
+ curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+ curOption = curOption[ parts[ i ] ];
+ }
+ key = parts.pop();
+ if ( arguments.length === 1 ) {
+ return curOption[ key ] === undefined ? null : curOption[ key ];
+ }
+ curOption[ key ] = value;
+ } else {
+ if ( arguments.length === 1 ) {
+ return this.options[ key ] === undefined ? null : this.options[ key ];
+ }
+ options[ key ] = value;
+ }
+ }
+
+ this._setOptions( options );
+
+ return this;
+ },
+ _setOptions: function( options ) {
+ var key;
+
+ for ( key in options ) {
+ this._setOption( key, options[ key ] );
+ }
+
+ return this;
+ },
+ _setOption: function( key, value ) {
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this.widget()
+ .toggleClass( this.widgetFullName + "-disabled", !!value );
+
+ // If the widget is becoming disabled, then nothing is interactive
+ if ( value ) {
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ }
+ }
+
+ return this;
+ },
+
+ enable: function() {
+ return this._setOptions({ disabled: false });
+ },
+ disable: function() {
+ return this._setOptions({ disabled: true });
+ },
+
+ _on: function( suppressDisabledCheck, element, handlers ) {
+ var delegateElement,
+ instance = this;
+
+ // no suppressDisabledCheck flag, shuffle arguments
+ if ( typeof suppressDisabledCheck !== "boolean" ) {
+ handlers = element;
+ element = suppressDisabledCheck;
+ suppressDisabledCheck = false;
+ }
+
+ // no element argument, shuffle and use this.element
+ if ( !handlers ) {
+ handlers = element;
+ element = this.element;
+ delegateElement = this.widget();
+ } else {
+ element = delegateElement = $( element );
+ this.bindings = this.bindings.add( element );
+ }
+
+ $.each( handlers, function( event, handler ) {
+ function handlerProxy() {
+ // allow widgets to customize the disabled handling
+ // - disabled as an array instead of boolean
+ // - disabled class as method for disabling individual parts
+ if ( !suppressDisabledCheck &&
+ ( instance.options.disabled === true ||
+ $( this ).hasClass( "ui-state-disabled" ) ) ) {
+ return;
+ }
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+
+ // copy the guid so direct unbinding works
+ if ( typeof handler !== "string" ) {
+ handlerProxy.guid = handler.guid =
+ handler.guid || handlerProxy.guid || $.guid++;
+ }
+
+ var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
+ eventName = match[1] + instance.eventNamespace,
+ selector = match[2];
+ if ( selector ) {
+ delegateElement.delegate( selector, eventName, handlerProxy );
+ } else {
+ element.bind( eventName, handlerProxy );
+ }
+ });
+ },
+
+ _off: function( element, eventName ) {
+ eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +
+ this.eventNamespace;
+ element.unbind( eventName ).undelegate( eventName );
+
+ // Clear the stack to avoid memory leaks (#10056)
+ this.bindings = $( this.bindings.not( element ).get() );
+ this.focusable = $( this.focusable.not( element ).get() );
+ this.hoverable = $( this.hoverable.not( element ).get() );
+ },
+
+ _delay: function( handler, delay ) {
+ function handlerProxy() {
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+ var instance = this;
+ return setTimeout( handlerProxy, delay || 0 );
+ },
+
+ _hoverable: function( element ) {
+ this.hoverable = this.hoverable.add( element );
+ this._on( element, {
+ mouseenter: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-hover" );
+ },
+ mouseleave: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-hover" );
+ }
+ });
+ },
+
+ _focusable: function( element ) {
+ this.focusable = this.focusable.add( element );
+ this._on( element, {
+ focusin: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-focus" );
+ },
+ focusout: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-focus" );
+ }
+ });
+ },
+
+ _trigger: function( type, event, data ) {
+ var prop, orig,
+ callback = this.options[ type ];
+
+ data = data || {};
+ event = $.Event( event );
+ event.type = ( type === this.widgetEventPrefix ?
+ type :
+ this.widgetEventPrefix + type ).toLowerCase();
+ // the original event may come from any element
+ // so we need to reset the target on the new event
+ event.target = this.element[ 0 ];
+
+ // copy original event properties over to the new event
+ orig = event.originalEvent;
+ if ( orig ) {
+ for ( prop in orig ) {
+ if ( !( prop in event ) ) {
+ event[ prop ] = orig[ prop ];
+ }
+ }
+ }
+
+ this.element.trigger( event, data );
+ return !( $.isFunction( callback ) &&
+ callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+ event.isDefaultPrevented() );
+ }
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+ $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+ if ( typeof options === "string" ) {
+ options = { effect: options };
+ }
+ var hasOptions,
+ effectName = !options ?
+ method :
+ options === true || typeof options === "number" ?
+ defaultEffect :
+ options.effect || defaultEffect;
+ options = options || {};
+ if ( typeof options === "number" ) {
+ options = { duration: options };
+ }
+ hasOptions = !$.isEmptyObject( options );
+ options.complete = callback;
+ if ( options.delay ) {
+ element.delay( options.delay );
+ }
+ if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
+ element[ method ]( options );
+ } else if ( effectName !== method && element[ effectName ] ) {
+ element[ effectName ]( options.duration, options.easing, callback );
+ } else {
+ element.queue(function( next ) {
+ $( this )[ method ]();
+ if ( callback ) {
+ callback.call( element[ 0 ] );
+ }
+ next();
+ });
+ }
+ };
+});
+
+var widget = $.widget;
+
+
+
+}));
diff --git a/common/modules/file/config.php b/common/modules/file/config.php
new file mode 100644
index 0000000..d0b9c34
--- /dev/null
+++ b/common/modules/file/config.php
@@ -0,0 +1,3 @@
+$w){
+ return true;
+ }else if($height >$h) {
+ return true;
+ }
+ return false;
+ }
+
+
+ private function getUserPath(){
+ if(isset(Yii::$app->user->id)){
+ return 'user_'.Yii::$app->user->id;
+ }else {
+ return 'guest';
+ }
+ }
+
+ private function resizeImg($w, $h, $imageAlias,$imageAliasSave){
+ $img = Image::getImagine()->open(Yii::getAlias($imageAlias));
+
+ $size = $img->getSize();
+
+ $width = $size->getWidth();
+ $height = $size->getHeight();
+
+ $e_width = $w/$h;
+ $e_height = $h/$w;
+
+ $e1_width = $width/$height;
+ $e1_height = $height/$width;
+
+
+
+ if($e_width<$e1_width){
+
+ $new_width = $width*($e_width/$e1_width);
+
+ $y = 0;
+ $x = $width/ 2-($new_width/2);
+ $width = $new_width;
+
+ }else {
+
+ $new_height = $height*($e_height/$e1_height);
+ $x = 0;
+ $y = $height/2-($new_height/2);
+ $height = $new_height;
+ }
+
+
+
+
+ Image::crop($imageAlias, $width, $height,[$x,$y])
+ ->save(Yii::getAlias($imageAliasSave), ['quality' =>
+ 100]);
+
+
+ $imagine = new Imagine();
+ $imagine->open($imageAliasSave)
+ ->resize(new Box($w, $h))
+ ->save($imageAliasSave, array('flatten' => false));
+
+
+
+ }
+
+ private function deleteImages($old_img){
+
+ if(!empty($old_img) && file_exists($_SERVER['DOCUMENT_ROOT'].$old_img)){
+
+ $rootDir = explode("/", $old_img);
+
+ $row = $_SERVER['DOCUMENT_ROOT'].'/'.$rootDir[1].'/'.$rootDir[2].'/'.$rootDir[3].'/';
+
+ $allFiles = scandir($row);
+
+ $allFiles = array_slice($allFiles, 2);
+
+ foreach($allFiles as $oldFile){
+
+ unlink($row.$oldFile);
+
+ }
+
+ }
+ }
+
+ public function actionDeleteImage(){
+
+ $this->enableCsrfValidation = false;
+
+ $request = Yii::$app->request->post();
+
+ if($request){
+ if ($request['old_img']) {
+ $this->deleteImages($request['old_img']);
+ }
+ if(isset($request['action']) && $request['action']=='save'){
+ $object = str_replace('-', '\\',$request['model']);
+ $model = new $object;
+ $model = $model->findOne($request['id']);
+ $model->$request['field'] = $request['new_url'];
+ $model->save();
+ }
+ }
+
+ }
+
+
+ public function actionDownloadPhoto()
+ {
+
+ $model = new ImageSizerForm();
+
+ $request = Yii::$app->request->post();
+
+ if ($request) {
+
+ $model->multi = isset($request['multi'])? 1 : 0;
+
+ $model->file = UploadedFile::getInstance($model, 'file');
+
+ $md5_file = md5_file($model->file->tempName).rand(1, 1000);
+
+ $imgDir = Yii::getAlias('@storage/'.$this->getUserPath().'/'.$md5_file.'/');
+
+ $imageOrigAlias = Yii::getAlias($imgDir.'original'.'.'.$model->file->extension);
+
+ if(!is_dir($imgDir)) {
+ mkdir($imgDir, 0755, true);
+ }
+
+ $model->file->saveAs($imageOrigAlias);
+
+
+ if(isset($request['size'] )){
+
+ $request['size'] = ArrayHelper::toArray(json_decode($request['size']));
+
+ foreach($request['size'] as $size){
+ if($size['width'] && $size['height']){
+
+ $imageAlias = Yii::getAlias($imgDir.$size['width'].'x'.$size['height'].'.'.$model->file->extension);
+
+ $imageLink = '/storage/'.$this->getUserPath().'/'.$md5_file.'/'.$size['width'].'x'.$size['height'].'.'.$model->file->extension;
+
+ $this->resizeImg($size['width'], $size['height'], $imageOrigAlias,$imageAlias);
+
+ }
+ }
+
+ } else {
+
+ $imageLink = '/storage/'.$this->getUserPath().'/'.$md5_file.'/'.'original'.'.'.$model->file->extension;
+
+ }
+
+
+ if($model->multi){
+ $view = $this->renderPartial('/_gallery_item', [
+ 'item' => ['image'=>$imageLink],
+ 'field'=>$request['field']
+ ]);
+ return json_encode(['link'=>$imageLink,
+ 'view' =>$view,
+
+ ]);
+
+
+ } else {
+ $view = $this->renderPartial('/_one_item', [
+ 'item' => ['image'=>$imageLink],
+ 'field'=>$request['field']
+ ]);
+ return json_encode(['link'=>$imageLink,
+ 'view' =>$view,
+ ]);
+ }
+
+
+ }
+ }
+
+
+ public function getex($filename) {
+ return end(explode(".", $filename));
+ }
+
+
+ public function actionImagesUpload(){
+
+ if($_FILES['upload'])
+ {
+ if (($_FILES['upload'] == "none") OR (empty($_FILES['upload']['name'])) )
+ {
+ $message = "Вы не выбрали файл";
+ }
+ else if ($_FILES['upload']["size"] == 0 OR $_FILES['upload']["size"] > 2050000)
+ {
+ $message = "Размер файла не соответствует нормам";
+ }
+ else if (($_FILES['upload']["type"] != "image/jpeg") AND ($_FILES['upload']["type"] != "image/jpeg") AND ($_FILES['upload']["type"] != "image/png") AND ($_FILES['upload']['type'] != 'image/gif'))
+ {
+ $message = "Допускается загрузка только картинок JPG и PNG.";
+ }
+ else if (!is_uploaded_file($_FILES['upload']["tmp_name"]))
+ {
+ $message = "Что-то пошло не так. Попытайтесь загрузить файл ещё раз.";
+ }
+ else{
+ $name =$_FILES['upload']['name'].'.'.$this->getex($_FILES['upload']['name']);
+
+ $path = "../../storage/".$this->getUserPath()."/images/";
+ if(!is_dir($path)) {
+ mkdir($path, 0755, true);
+ }
+
+
+
+ move_uploaded_file($_FILES['upload']['tmp_name'], $path.$name);
+
+ $full_path = '/storage/'.$this->getUserPath().'/images/'.$name;
+
+ $message = "Файл ".$_FILES['upload']['name']." загружен";
+
+
+ }
+ $callback = $_REQUEST['CKEditorFuncNum'];
+ echo '';
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/common/modules/file/models/ImageSizerForm.php b/common/modules/file/models/ImageSizerForm.php
new file mode 100644
index 0000000..4f141fb
--- /dev/null
+++ b/common/modules/file/models/ImageSizerForm.php
@@ -0,0 +1,38 @@
+ 255],
+ [['model', 'form',], 'string'],
+ [['file','img','price_list'], 'file'],
+ ];
+ }
+}
\ No newline at end of file
diff --git a/common/modules/file/views/_gallery_item.php b/common/modules/file/views/_gallery_item.php
new file mode 100644
index 0000000..f04a44d
--- /dev/null
+++ b/common/modules/file/views/_gallery_item.php
@@ -0,0 +1,9 @@
+
+
+
+ = Html::img($item['image'])?>
+
+
\ No newline at end of file
diff --git a/common/modules/file/views/_one_item.php b/common/modules/file/views/_one_item.php
new file mode 100644
index 0000000..4534483
--- /dev/null
+++ b/common/modules/file/views/_one_item.php
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+ = Html::a( Html::img($item['image']),'#',['class'=>'thumbnail']) ?>
+
+
+
diff --git a/common/modules/file/widgets/ImageUploader.php b/common/modules/file/widgets/ImageUploader.php
new file mode 100644
index 0000000..48d7787
--- /dev/null
+++ b/common/modules/file/widgets/ImageUploader.php
@@ -0,0 +1,63 @@
+render('image_sizer',
+ [
+ 'model'=>$this->model,
+ 'size' => $this->size,
+ 'field' => $this->field,
+ 'height' => $this->height,
+ 'width' => $this->width,
+ 'multi' => $this->multi,
+ 'name' => $this->name,
+ 'remover' => $this->remover
+ ]);
+
+ }
+
+ public function getGallery(){
+ if($this->gallery){
+ $array = explode(",", $this->gallery);
+ if(count($array) > 1){
+ array_pop($array);
+ }
+ return $array;
+ } else {
+ return array();
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/common/modules/file/widgets/views/image_sizer.php b/common/modules/file/widgets/views/image_sizer.php
new file mode 100644
index 0000000..510f883
--- /dev/null
+++ b/common/modules/file/widgets/views/image_sizer.php
@@ -0,0 +1,203 @@
+tableSchema->primaryKey[0];
+
+?>
+
+
+
+
+ = Html::activeHiddenInput( $model,$field,['id' => "{$field}_picture_link"]) ?>
+
+
+
+
+
+
+
+
+
+ $field): ?>
+
+
+
+ $field):?>
+ = Html::a(Html::img($model->$field),'#',['class'=>'thumbnail']) ?>
+
+
+
+
+
+
+
+
+ =$name?>
+
+ = Html::activeFileInput( new ImageSizerForm(),'file',['id'=>$field, 'data-url'=>Yii::$app->getUrlManager()->createUrl('file/uploader/download-photo')]);?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ =$name?>
+
+ = Html::activeFileInput( new ImageSizerForm(),'file',['id'=>$field, 'data-url'=>Yii::$app->getUrlManager()->createUrl('file/uploader/download-photo'), 'multiple'=> 'multiple' ]);?>
+
+
+ = Html::activeHiddenInput( $model,$field,['id' => "{$field}_picture_link"]) ?>
+
+
+
+
+
+ context->getGallery() as $image){
+ echo $this->render('@common/modules/file/views/_gallery_item', [ 'item' => ['image'=>$image]]);
+ }
+ ?>
+
+
+
+
diff --git a/common/modules/product/Module.php b/common/modules/product/Module.php
new file mode 100644
index 0000000..4cc65a6
--- /dev/null
+++ b/common/modules/product/Module.php
@@ -0,0 +1,24 @@
+ [
+ 'category_group' => 1,
+ 'brand_group' => 2,
+ ],
+];
\ No newline at end of file
diff --git a/common/modules/product/controllers/DefaultController.php b/common/modules/product/controllers/DefaultController.php
new file mode 100644
index 0000000..609a4c4
--- /dev/null
+++ b/common/modules/product/controllers/DefaultController.php
@@ -0,0 +1,20 @@
+render('index');
+ }
+}
diff --git a/common/modules/product/controllers/ManageController.php b/common/modules/product/controllers/ManageController.php
new file mode 100644
index 0000000..fdfe194
--- /dev/null
+++ b/common/modules/product/controllers/ManageController.php
@@ -0,0 +1,124 @@
+ [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Lists all Product models.
+ * @return mixed
+ */
+ public function actionIndex()
+ {
+ $searchModel = new ProductSearch();
+ $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
+
+ return $this->render('index', [
+ 'searchModel' => $searchModel,
+ 'dataProvider' => $dataProvider,
+ ]);
+ }
+
+ /**
+ * Displays a single Product model.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionView($id)
+ {
+ return $this->render('view', [
+ 'model' => $this->findModel($id),
+ ]);
+ }
+
+ /**
+ * Creates a new Product model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return mixed
+ */
+ public function actionCreate()
+ {
+ $model = new Product();
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+ return $this->redirect(['view', 'id' => $model->product_id]);
+ } else {
+ return $this->render('create', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Updates an existing Product model.
+ * If update is successful, the browser will be redirected to the 'view' page.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionUpdate($id)
+ {
+ $model = $this->findModel($id);
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+ return $this->redirect(['view', 'id' => $model->product_id]);
+ } else {
+ return $this->render('update', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Deletes an existing Product model.
+ * If deletion is successful, the browser will be redirected to the 'index' page.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionDelete($id)
+ {
+ $this->findModel($id)->delete();
+
+ return $this->redirect(['index']);
+ }
+
+ /**
+ * Finds the Product model based on its primary key value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * @param integer $id
+ * @return Product the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($id)
+ {
+ if (($model = Product::findOne($id)) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
+}
diff --git a/common/modules/product/helpers/ProductHelper.php b/common/modules/product/helpers/ProductHelper.php
new file mode 100644
index 0000000..fc2b49c
--- /dev/null
+++ b/common/modules/product/helpers/ProductHelper.php
@@ -0,0 +1,24 @@
+getModule('product')->params['category_group'];
+ }
+
+ public static function getBrandGroupId() {
+ return \Yii::$app->getModule('product')->params['brand_group'];
+ }
+
+ public static function getCategories() {
+ return TaxOption::find()->getTree(self::getCategoryGroupId());
+ }
+
+ public static function getBrands() {
+ return TaxOption::find()->where(['tax_group_id' => self::getBrandGroupId()]);
+ }
+}
\ No newline at end of file
diff --git a/common/modules/product/models/Product.php b/common/modules/product/models/Product.php
new file mode 100644
index 0000000..1fbc8f7
--- /dev/null
+++ b/common/modules/product/models/Product.php
@@ -0,0 +1,106 @@
+ relationBehavior::className(),
+ 'relations' => [
+ 'product_categories' => 'entity1' // Product category
+ ]
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function tableName()
+ {
+ return '{{%product}}';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['tax_brand_id'], 'integer'],
+ [['name'], 'string', 'max' => 150],
+ [['categories'], 'safe'],
+// [['product_id'], 'exist', 'skipOnError' => true, 'targetClass' => Product::className(), 'targetAttribute' => ['product_id' => 'product_id']],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'product_id' => Yii::t('product', 'Product ID'),
+ 'name' => Yii::t('product', 'Name'),
+ 'tax_brand_id' => Yii::t('product', 'Brand'),
+ 'brand' => Yii::t('product', 'Brand'),
+ 'categories' => Yii::t('product', 'Categories'), // relation behavior field
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getBrand()
+ {
+ return $this->hasOne(TaxOption::className(), ['tax_option_id' => 'tax_brand_id']);
+ }
+
+ public function getFullName()
+ {
+ return $this->brandname .' '. $this->name;
+ }
+
+ public function getBrandName()
+ {
+ return $this->getBrand()->one()->valueRenderHTML;
+ }
+
+ public function getCategory() {
+ /** @var ActiveQuery $categories */
+ $categories = $this->getRelations('product_categories');
+ $count = $categories->count();
+ if ($count == 0)
+ return 'None';
+ return $categories->one()->ValueRenderFlash . ($count > 1 ? ' + '. $count : '');
+ }
+
+ /**
+ * @inheritdoc
+ * @return ProductQuery the active query used by this AR class.
+ */
+ public static function find()
+ {
+ return new ProductQuery(get_called_class());
+ }
+}
diff --git a/common/modules/product/models/ProductCategory.php b/common/modules/product/models/ProductCategory.php
new file mode 100644
index 0000000..a5d1245
--- /dev/null
+++ b/common/modules/product/models/ProductCategory.php
@@ -0,0 +1,60 @@
+ true, 'targetClass' => TaxOption::className(), 'targetAttribute' => ['dev_category_id' => 'tax_option_id']],
+ [['product_id'], 'exist', 'skipOnError' => true, 'targetClass' => Product::className(), 'targetAttribute' => ['product_id' => 'product_id']],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'product_id' => Yii::t('product', 'Product'),
+ 'dev_category_id' => Yii::t('product', 'Category'),
+ ];
+ }
+
+ public function getProduct() {
+ return $this->getEntity1();
+ }
+
+ public function getCategory() {
+ return $this->getEntity2();
+ }
+}
diff --git a/common/modules/product/models/ProductQuery.php b/common/modules/product/models/ProductQuery.php
new file mode 100644
index 0000000..373c74b
--- /dev/null
+++ b/common/modules/product/models/ProductQuery.php
@@ -0,0 +1,34 @@
+andWhere('[[status]]=1');
+ }*/
+
+ /**
+ * @inheritdoc
+ * @return Product[]|array
+ */
+ public function all($db = null)
+ {
+ return parent::all($db);
+ }
+
+ /**
+ * @inheritdoc
+ * @return Product|array|null
+ */
+ public function one($db = null)
+ {
+ return parent::one($db);
+ }
+}
diff --git a/common/modules/product/models/ProductSearch.php b/common/modules/product/models/ProductSearch.php
new file mode 100644
index 0000000..4d2c42d
--- /dev/null
+++ b/common/modules/product/models/ProductSearch.php
@@ -0,0 +1,70 @@
+ $query,
+ ]);
+
+ $this->load($params);
+
+ if (!$this->validate()) {
+ // uncomment the following line if you do not want to return any records when validation fails
+ // $query->where('0=1');
+ return $dataProvider;
+ }
+
+ // grid filtering conditions
+ $query->andFilterWhere([
+ 'tax_brand_id' => $this->tax_brand_id,
+ 'product_id' => $this->product_id,
+ ]);
+
+ $query->andFilterWhere(['like', 'name', $this->name]);
+
+ return $dataProvider;
+ }
+}
diff --git a/common/modules/product/views/default/index.php b/common/modules/product/views/default/index.php
new file mode 100644
index 0000000..218cc44
--- /dev/null
+++ b/common/modules/product/views/default/index.php
@@ -0,0 +1,12 @@
+
+
= $this->context->action->uniqueId ?>
+
+ This is the view content for action "= $this->context->action->id ?>".
+ The action belongs to the controller "= get_class($this->context) ?>"
+ in the "= $this->context->module->id ?>" module.
+
+
+ You may customize this page by editing the following file:
+ = __FILE__ ?>
+
+
diff --git a/common/modules/product/views/manage/_form.php b/common/modules/product/views/manage/_form.php
new file mode 100644
index 0000000..f1233cb
--- /dev/null
+++ b/common/modules/product/views/manage/_form.php
@@ -0,0 +1,43 @@
+
+
+
diff --git a/common/modules/product/views/manage/_search.php b/common/modules/product/views/manage/_search.php
new file mode 100644
index 0000000..f051c6a
--- /dev/null
+++ b/common/modules/product/views/manage/_search.php
@@ -0,0 +1,31 @@
+
+
+
+
+ ['index'],
+ 'method' => 'get',
+ ]); ?>
+
+ = $form->field($model, 'name') ?>
+
+ = $form->field($model, 'tax_brand_id') ?>
+
+ = $form->field($model, 'product_id') ?>
+
+
+ = Html::submitButton(Yii::t('product', 'Search'), ['class' => 'btn btn-primary']) ?>
+ = Html::resetButton(Yii::t('product', 'Reset'), ['class' => 'btn btn-default']) ?>
+
+
+
+
+
diff --git a/common/modules/product/views/manage/create.php b/common/modules/product/views/manage/create.php
new file mode 100644
index 0000000..e5f7902
--- /dev/null
+++ b/common/modules/product/views/manage/create.php
@@ -0,0 +1,21 @@
+title = Yii::t('product', 'Create Product');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('product', 'Products'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/common/modules/product/views/manage/index.php b/common/modules/product/views/manage/index.php
new file mode 100644
index 0000000..82254a2
--- /dev/null
+++ b/common/modules/product/views/manage/index.php
@@ -0,0 +1,34 @@
+title = Yii::t('product', 'Products');
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+ render('_search', ['model' => $searchModel]); ?>
+
+
+ = Html::a(Yii::t('product', 'Create Product'), ['create'], ['class' => 'btn btn-success']) ?>
+
+ = GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'filterModel' => $searchModel,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+
+// 'product_id',
+ 'fullname',
+ 'brandname',
+ 'category',
+
+ ['class' => 'yii\grid\ActionColumn'],
+ ],
+ ]); ?>
+
diff --git a/common/modules/product/views/manage/update.php b/common/modules/product/views/manage/update.php
new file mode 100644
index 0000000..9ff3851
--- /dev/null
+++ b/common/modules/product/views/manage/update.php
@@ -0,0 +1,23 @@
+title = Yii::t('product', 'Update {modelClass}: ', [
+ 'modelClass' => 'Product',
+]) . ' ' . $model->name;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('product', 'Products'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->product_id]];
+$this->params['breadcrumbs'][] = Yii::t('product', 'Update');
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/common/modules/product/views/manage/view.php b/common/modules/product/views/manage/view.php
new file mode 100644
index 0000000..e6d9c7c
--- /dev/null
+++ b/common/modules/product/views/manage/view.php
@@ -0,0 +1,37 @@
+title = $model->name;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('product', 'Products'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+ = Html::a(Yii::t('product', 'Update'), ['update', 'id' => $model->product_id], ['class' => 'btn btn-primary']) ?>
+ = Html::a(Yii::t('product', 'Delete'), ['delete', 'id' => $model->product_id], [
+ 'class' => 'btn btn-danger',
+ 'data' => [
+ 'confirm' => Yii::t('product', 'Are you sure you want to delete this item?'),
+ 'method' => 'post',
+ ],
+ ]) ?>
+
+
+ = DetailView::widget([
+ 'model' => $model,
+ 'attributes' => [
+ 'name',
+ 'tax_brand_id',
+ 'product_id',
+ ],
+ ]) ?>
+
+
diff --git a/common/modules/relation/Module.php b/common/modules/relation/Module.php
new file mode 100644
index 0000000..b4f79b1
--- /dev/null
+++ b/common/modules/relation/Module.php
@@ -0,0 +1,40 @@
+render('index');
+ }
+}
diff --git a/common/modules/relation/controllers/ManageController.php b/common/modules/relation/controllers/ManageController.php
new file mode 100644
index 0000000..2b797e4
--- /dev/null
+++ b/common/modules/relation/controllers/ManageController.php
@@ -0,0 +1,214 @@
+ $relation) {
+ $list[] = [
+ 'key' => $key,
+ 'name' => $relation['name'],
+ 'entity1_label' => $relation['entity1']['label'],
+ 'entity1_model' => $relation['entity1']['model'],
+ 'entity2_label' => $relation['entity2']['label'],
+ 'entity2_model' => $relation['entity2']['model'],
+ ];
+ }
+ return $this->render('relations', [
+ 'relations' => $list
+ ]);
+ }
+ /**
+ * Renders the pars view for
+ * @return string
+ */
+ public function actionPars($relation)
+ {
+ $relation_key = strtolower($relation);
+ $relation = relationHelper::getRelation($relation_key);
+
+ $dataProvider = new ActiveDataProvider([
+ 'query' => $relation['via']['model']::find(),
+ ]);
+
+ return $this->render('pars', [
+ 'dataProvider' => $dataProvider,
+ 'relation_key' => $relation_key,
+ 'relation' => $relation,
+ ]);
+ }
+
+ public function actionCreate($relation) {
+ $relation_key = strtolower($relation);
+ $relation = relationHelper::getRelation($relation_key);
+
+ $model = new $relation['via']['model'];
+
+ $query1 = $relation['entity1']['model']::find();
+ if (!empty($relation['entity1']['where']))
+ $query1->where($relation['entity1']['where']);
+
+ $query2 = $relation['entity2']['model']::find();
+ if (!empty($relation['entity2']['where']))
+ $query2->where($relation['entity2']['where']);
+
+ if ($model->load(Yii::$app->request->post())) {
+ $model->save();
+ return $this->redirect(['pars', 'relation' => $relation_key]);
+// return $this->redirect(['update', 'id' => $model->{$relation['entity1']['linked_key']}. ':' .$model->{$relation['entity2']['linked_key']}]);
+ } else {
+ return $this->render('create', [
+ 'model' => $model,
+ 'items1' => $query1->all(),
+ 'items2' => $query2->all(),
+ 'relation_key' => $relation_key,
+ 'relation' => $relation,
+ ]);
+ }
+ }
+
+ /**
+ * Updates an existing TaxGroup model.
+ * If update is successful, the browser will be redirected to the 'view' page.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionUpdate($relation, $id) {
+ $relation_key = strtolower($relation);
+ $relation = relationHelper::getRelation($relation_key);
+
+ list($id1, $id2) = explode(':', $id);
+
+ $model = $this->findModel($relation_key, $id1, $id2);
+
+ $query1 = $relation['entity1']['model']::find();
+ if (!empty($relation['entity1']['where']))
+ $query1->where($relation['entity1']['where']);
+
+ $query2 = $relation['entity2']['model']::find();
+ if (!empty($relation['entity2']['where']))
+ $query2->where($relation['entity2']['where']);
+
+ if ($model->load(Yii::$app->request->post())) {
+ $connection = Yii::$app->getDb();
+ $transaction = $connection->beginTransaction();
+ try {
+ // Delete links from viaTable
+ $connection->createCommand()
+ ->update
+ (
+ $relation['linked_table'],
+ [
+ $relation['entity1']['linked_key'] => $model->getAttribute($relation['entity1']['linked_key']),
+ $relation['entity2']['linked_key'] => $model->getAttribute($relation['entity2']['linked_key'])
+ ],
+ $this->getWhere($relation_key, $id1, $id2)
+ )
+ ->execute();
+ $transaction->commit();
+ } catch (Exception $ex) {
+ $transaction->rollback();
+ throw $ex;
+ }
+
+ return $this->redirect(['pars', 'relation' => $relation_key]);
+ } else {
+ return $this->render('update', [
+ 'model' => $model,
+ 'items1' => $query1->all(),
+ 'items2' => $query2->all(),
+ 'relation_key' => $relation_key,
+ 'relation' => $relation,
+ ]);
+ }
+ }
+
+ /**
+ * Deletes an existing TaxGroup model.
+ * If deletion is successful, the browser will be redirected to the 'index' page.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionDelete($relation, $id)
+ {
+ $relation_key = strtolower($relation);
+ $relation = relationHelper::getRelation($relation_key);
+
+ list($id1, $id2) = explode(':', $id);
+
+ $connection = Yii::$app->getDb();
+ $transaction = $connection->beginTransaction();
+ try {
+ // Delete links from viaTable
+ $connection->createCommand()
+ ->delete
+ (
+ $relation['linked_table'],
+ $this->getWhere($relation_key, $id1, $id2)
+ )
+ ->execute();
+ $transaction->commit();
+ } catch (Exception $ex) {
+ $transaction->rollback();
+ throw $ex;
+ }
+
+ return $this->redirect(['pars', 'relation' => $relation_key]);
+ }
+
+ /**
+ * Finds the based model for relation on its primaries keys value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * @param string $relation
+ * @param integer $id1
+ * @param integer $id2
+ * @return ActiveRecord the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($relation, $id1, $id2)
+ {
+ $relation_key = strtolower($relation);
+ $relation = relationHelper::getRelation($relation_key);
+ if (($model = $relation['via']['model']::findOne($this->getWhere($relation_key, $id1, $id2))) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
+
+ protected function getWhere($relation_key, $id1, $id2) {
+ $relation = relationHelper::getRelation($relation_key);
+ // @todo Just think - if you need to search keys in the reverse order
+ $where = [
+ $relation['entity1']['linked_key'] => $id1,
+ $relation['entity2']['linked_key'] => $id2,
+ ];
+ if (!empty($relation['alias'])) {
+ $where[$relation['alias']] = $relation_key;
+ }
+ return $where;
+ }
+
+}
diff --git a/common/modules/relation/models/Relation.php b/common/modules/relation/models/Relation.php
new file mode 100644
index 0000000..bfe287f
--- /dev/null
+++ b/common/modules/relation/models/Relation.php
@@ -0,0 +1,78 @@
+ 50]
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'alias' => Yii::t('relation', 'Alias'),
+ 'entity1_id' => Yii::t('relation', 'Entity1 ID'),
+ 'entity2_id' => Yii::t('relation', 'Entity2 ID'),
+ 'entity1' => Yii::t('relation', 'Entity1 ID'),
+ 'entity2' => Yii::t('relation', 'Entity2 ID'),
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ * @return RelationQuery the active query used by this AR class.
+ */
+ public static function find()
+ {
+ return new RelationQuery(get_called_class());
+ }
+
+ public function getRelationSection() {
+ return relationHelper::getRelation($this->alias);
+ }
+
+ public function getEntity1() {
+ return $this->getEntity('entity1');
+ }
+
+ public function getEntity2() {
+ return $this->getEntity('entity2');
+ }
+
+ protected function getEntity($entity) {
+ $relation = $this->getRelationSection();
+ if (!$relation)
+ return;
+ return $this->hasOne($relation[$entity]['model']::className(), [$relation[$entity]['key'] => $relation[$entity]['linked_key']]);
+ }
+}
diff --git a/common/modules/relation/models/RelationQuery.php b/common/modules/relation/models/RelationQuery.php
new file mode 100644
index 0000000..9a04c33
--- /dev/null
+++ b/common/modules/relation/models/RelationQuery.php
@@ -0,0 +1,35 @@
+andWhere('[[status]]=1');
+ return $this;
+ }*/
+
+ /**
+ * @inheritdoc
+ * @return Relation[]|array
+ */
+ public function all($db = null)
+ {
+ return parent::all($db);
+ }
+
+ /**
+ * @inheritdoc
+ * @return Relation|array|null
+ */
+ public function one($db = null)
+ {
+ return parent::one($db);
+ }
+}
\ No newline at end of file
diff --git a/common/modules/relation/relationBehavior.php b/common/modules/relation/relationBehavior.php
new file mode 100644
index 0000000..3f83836
--- /dev/null
+++ b/common/modules/relation/relationBehavior.php
@@ -0,0 +1,224 @@
+relations as $relation_key => &$relation) {
+ if (is_string($relation)) {
+ // Get data from module's data
+ $relation_entity = $relation;
+ $relation = $this->_getRelationParams($relation_key);
+ $relation['inner'] = $relation[$relation_entity];
+ $relation['outer'] = $relation[$relation_entity == 'entity1' ? 'entity2' : 'entity1'];
+ $relation['linked_table'] = $relation['via']['model']::tableName();
+ }
+ $this->fields[$relation['field']] = $relation_key;
+ }
+ }
+
+ /*
+ * Events for auto-drive relations data
+ */
+ public function events()
+ {
+ return [
+ ActiveRecord::EVENT_AFTER_INSERT => 'relationsAfterSave',
+ ActiveRecord::EVENT_AFTER_UPDATE => 'relationsAfterSave',
+ ActiveRecord::EVENT_BEFORE_DELETE => 'relationBeforeDelete',
+ ];
+ }
+
+ public function relationsAfterSave($insert) {
+ if (is_array($modelPrimaryKey = $this->owner->getPrimaryKey())) {
+ throw new ErrorException('This behavior does not support composite primary keys');
+ }
+ foreach ($this->relations as $relation_key => $relation) {
+ if (empty($relation['field']))
+ continue;
+ $values = $this->{$relation['field']};
+
+ /** @var ActiveRecord $model */
+ $model = new $relation['inner']['model'];
+
+ $connection = $model::getDb();
+ $transaction = $connection->beginTransaction();
+
+ // @todo Refix to ActiveRecord format
+ try {
+ // Delete all links from viaTable
+ $connection->createCommand()
+ ->delete
+ (
+ $relation['linked_table'],
+ [$relation['inner']['linked_key'] => $this->owner->{$relation['inner']['key']}]
+ )
+ ->execute();
+
+ if (!empty($values)) {
+ foreach($values as $value) {
+ $connection->createCommand()
+ ->insert
+ (
+ $relation['linked_table'],
+ [
+ $relation['inner']['linked_key'] => $this->owner->{$relation['inner']['key']},
+ $relation['outer']['linked_key'] => $value
+ ]
+ )
+ ->execute();
+ }
+ }
+ $transaction->commit();
+
+// $model->link($relation_key, )
+ } catch (Exception $ex) {
+ var_dump($relation_key, $relation);exit;
+ $transaction->rollback();
+ throw $ex;
+ }
+ }
+ }
+
+ public function relationBeforeDelete() {
+ if (is_array($modelPrimaryKey = $this->owner->getPrimaryKey())) {
+ throw new ErrorException('This behavior does not support composite primary keys');
+ }
+ foreach ($this->relations as $relation_key => $relation) {
+ if (empty($relation['field']))
+ continue;
+ $values = $this->{$relation['field']};
+
+ /** @var ActiveRecord $model */
+ $model = new $relation['inner']['model'];
+
+ $connection = $model::getDb();
+ $transaction = $connection->beginTransaction();
+
+ // @todo Refix to ActiveRecord format
+ try {
+ // Delete all links from viaTable
+ $connection->createCommand()
+ ->delete
+ (
+ $relation['linked_table'],
+ [$relation['inner']['linked_key'] => $this->owner->{$relation['inner']['key']}]
+ )
+ ->execute();
+ $transaction->commit();
+ } catch (Exception $ex) {
+ $transaction->rollback();
+ throw $ex;
+ }
+ }
+ }
+
+ /**
+ * Get related data for $relation
+ * @params string $relation Relation key
+ */
+ public function getRelations($relation) {
+ $relation = $this->_getRelation($relation);
+ return
+ $this->owner
+ ->hasMany($relation['outer']['model'], [$relation['outer']['key'] => $relation['outer']['linked_key']])
+ ->viaTable($relation['linked_table'], [$relation['inner']['linked_key'] => $relation['inner']['key']]);
+ }
+
+ /*
+ * Get relation params for $relation
+ * @param string $relation Relation key
+ */
+ protected function _getRelation($relation) {
+ $relation = strtolower($relation);
+ return isset($this->relations[$relation]) ? $this->relations[$relation] : null;
+ }
+
+ /**
+ * Return relation data from main app config
+ * @params string $section Relations key
+ */
+ protected function _getRelationParams($section) {
+ $relation = relationHelper::getRelation($section);
+ if (!$relation)
+ throw new Exception('Relation "' . $section . '" not set on this application.');
+ return $relation;
+ }
+
+ protected function _getRelationNameByField($field) {
+ return isset($this->fields[$field]) ? $this->fields[$field] : null;
+ }
+
+ protected function _getRelationByField($field) {
+ return ( isset($this->fields[$field]) && isset($this->relations[$this->fields[$field]]) ) ? $this->relations[$this->fields[$field]] : null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function canGetProperty($name, $checkVars = true)
+ {
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function canSetProperty($name, $checkVars = true)
+ {
+ return array_key_exists($name, $this->fields) ?
+ true : parent::canSetProperty($name, $checkVars = true);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function __set($name, $value) {
+ if (isset($this->fields[$name])) {
+ $this->values[$name] = $value;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function __get($name) {
+ if (isset($this->values[$name])) {
+ return $this->values[$name];
+ } else {
+ $relation_key = $this->_getRelationNameByField($name);
+ if (!$relation_key)
+ return;
+
+ return $this->getRelations($relation_key);
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/modules/relation/relationHelper.php b/common/modules/relation/relationHelper.php
new file mode 100644
index 0000000..bdb6991
--- /dev/null
+++ b/common/modules/relation/relationHelper.php
@@ -0,0 +1,60 @@
+getModule('relation');
+
+ if (!is_array($module->relations))
+ return [];
+
+ return $module->relations;
+ }
+
+ /**
+ * Return one relation for key $name
+ * @param string $name
+ * @return string (@todo refix to relationOject)
+ */
+ public static function getRelation($name) {
+ $name = strtolower($name);
+ if (isset(self::$relations[$name])) {
+ return self::$relations[$name];
+ }
+ $relations = self::getRelations();
+ if (isset($relations[$name])) {
+ self::$relations[$name] = self::prepareRelation($relations[$name]);
+ } else {
+ self::$relations[$name] = null;
+ }
+ return self::$relations[$name];
+ }
+
+
+ private static function prepareRelation($relation) {
+ if (!isset($relation['linked_table']) && isset($relation['via']['model'])) {
+ $relation['linked_table'] = $relation['via']['model']::tableName();
+ }
+ return $relation;
+ }
+
+ /**
+ * @param string $name
+ * @return bool
+ */
+ public static function issetRelation($name) {
+ $relations = self::getRelations();
+ return isset($relations[$name]);
+ }
+}
\ No newline at end of file
diff --git a/common/modules/relation/relationObject.php b/common/modules/relation/relationObject.php
new file mode 100644
index 0000000..9dce6fa
--- /dev/null
+++ b/common/modules/relation/relationObject.php
@@ -0,0 +1,29 @@
+_getRelationParams($relation_key);
+ $relation['inner'] = $relation[$relation_entity];
+ $relation['outer'] = $relation[$relation_entity == 'entity1' ? 'entity2' : 'entity1'];
+ $relation['linked_table'] = $relation['via']['model']::tableName();
+ }
+
+ $this->moduleRelations = relationHelper::getRelations();
+
+
+ }
+}
\ No newline at end of file
diff --git a/common/modules/relation/relationQueryTrait.php b/common/modules/relation/relationQueryTrait.php
new file mode 100644
index 0000000..822556c
--- /dev/null
+++ b/common/modules/relation/relationQueryTrait.php
@@ -0,0 +1,27 @@
+modelClass;
+ self::$model = new $class;
+ }
+ return self::$model;
+ }
+
+ public function getRelations($relationKey) {
+
+ }
+}
\ No newline at end of file
diff --git a/common/modules/relation/views/default/index.php b/common/modules/relation/views/default/index.php
new file mode 100644
index 0000000..e8f0a30
--- /dev/null
+++ b/common/modules/relation/views/default/index.php
@@ -0,0 +1,12 @@
+
+
= $this->context->action->uniqueId ?>
+
+ This is the view content for action "= $this->context->action->id ?>".
+ The action belongs to the controller "= get_class($this->context) ?>"
+ in the "= $this->context->module->id ?>" module.
+
+
+ You may customize this page by editing the following file:
+ = __FILE__ ?>
+
+
diff --git a/common/modules/relation/views/manage/_form.php b/common/modules/relation/views/manage/_form.php
new file mode 100644
index 0000000..0c047f0
--- /dev/null
+++ b/common/modules/relation/views/manage/_form.php
@@ -0,0 +1,36 @@
+
+
+
diff --git a/common/modules/relation/views/manage/create.php b/common/modules/relation/views/manage/create.php
new file mode 100644
index 0000000..743430c
--- /dev/null
+++ b/common/modules/relation/views/manage/create.php
@@ -0,0 +1,25 @@
+title = Yii::t('relation', $relation['name']);
+$this->params['breadcrumbs'][] = ['label' => Yii::t('relation', 'Relations'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => Yii::t('relation', $relation['name']), 'url' => ['/relation/manage/pars', 'relation' => $relation_key]];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ 'items1' => $items1,
+ 'items2' => $items2,
+ 'relation_key' => $relation_key,
+ 'relation' => $relation,
+ ]) ?>
+
+
diff --git a/common/modules/relation/views/manage/pars.php b/common/modules/relation/views/manage/pars.php
new file mode 100644
index 0000000..c82accf
--- /dev/null
+++ b/common/modules/relation/views/manage/pars.php
@@ -0,0 +1,54 @@
+title = Yii::t('relation', 'Relation items for {relation}', ['relation' => $relation['name']]);
+$this->params['breadcrumbs'][] = ['label' => Yii::t('relation', 'Relations'), 'url' => ['/relation/manage']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+ = Html::a(Yii::t('relation', 'Create relation'), ['create?relation='. $relation_key], ['class' => 'btn btn-success']) ?>
+
+
+ = GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+ 'alias',
+ 'entity1.'. $relation['entity1']['listField'],
+ 'entity2.'. $relation['entity2']['listField'],
+ [
+ 'class' => 'yii\grid\ActionColumn',
+ 'template' => '{update} {delete}',
+ 'buttons' => [
+ 'update' => function ($url, $model) {
+ return Html::a('
', $url, [
+ 'title' => Yii::t('relation', 'Edit par'),
+ ]);
+ },
+ 'delete' => function ($url, $model) {
+ return Html::a('
', $url, [
+ 'title' => Yii::t('relation', 'Delete par'),
+ ]);
+ },
+ ],
+ 'urlCreator' => function ($action, $model, $key, $index) use ($relation, $relation_key) {
+ if ($action === 'update') {
+ $url ='/admin/relation/manage/update?relation='. $relation_key .'&id='. $model->{$relation['entity1']['linked_key']} .':'. $model->{$relation['entity2']['linked_key']};
+ return $url;
+ }
+ if ($action === 'delete') {
+ $url ='/admin/relation/manage/delete?relation='. $relation_key .'&id='. $model->{$relation['entity1']['linked_key']} .':'. $model->{$relation['entity2']['linked_key']};
+ return $url;
+ }
+ }
+ ],
+ ],
+ ]); ?>
+
diff --git a/common/modules/relation/views/manage/relations.php b/common/modules/relation/views/manage/relations.php
new file mode 100644
index 0000000..46950ab
--- /dev/null
+++ b/common/modules/relation/views/manage/relations.php
@@ -0,0 +1,53 @@
+title = Yii::t('relation', 'Relations');
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ $relations,
+ 'sort' => [
+ 'attributes' => ['name', 'key', 'entity1_label', 'entity2_label'],
+ ],
+ 'pagination' => [
+ 'pageSize' => 10,
+ ],
+ ]);
+ ?>
+
+ = GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+ 'name',
+ 'key',
+ 'entity1_label',
+ 'entity2_label',
+ [
+ 'class' => 'yii\grid\ActionColumn',
+ 'template' => '{view}',
+ 'buttons' => [
+ 'view' => function ($url, $model) {
+ return Html::a(' ', $url, [
+ 'title' => Yii::t('relation', 'View pars'),
+ ]);
+ },
+ ],
+ 'urlCreator' => function ($action, $model) {
+ if ($action === 'view') {
+ $url ='/admin/relation/manage/pars?relation='. $model['key'];
+ return $url;
+ }
+ }
+ ],
+ ],
+ ]); ?>
+
diff --git a/common/modules/relation/views/manage/update.php b/common/modules/relation/views/manage/update.php
new file mode 100644
index 0000000..743430c
--- /dev/null
+++ b/common/modules/relation/views/manage/update.php
@@ -0,0 +1,25 @@
+title = Yii::t('relation', $relation['name']);
+$this->params['breadcrumbs'][] = ['label' => Yii::t('relation', 'Relations'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => Yii::t('relation', $relation['name']), 'url' => ['/relation/manage/pars', 'relation' => $relation_key]];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ 'items1' => $items1,
+ 'items2' => $items2,
+ 'relation_key' => $relation_key,
+ 'relation' => $relation,
+ ]) ?>
+
+
diff --git a/common/modules/rubrication/Module.php b/common/modules/rubrication/Module.php
new file mode 100644
index 0000000..ffc888c
--- /dev/null
+++ b/common/modules/rubrication/Module.php
@@ -0,0 +1,28 @@
+render('index');
+ }
+}
diff --git a/common/modules/rubrication/controllers/TaxGroupController.php b/common/modules/rubrication/controllers/TaxGroupController.php
new file mode 100644
index 0000000..f112d9c
--- /dev/null
+++ b/common/modules/rubrication/controllers/TaxGroupController.php
@@ -0,0 +1,152 @@
+ [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Lists all TaxGroup models.
+ * @return mixed
+ */
+ public function actionIndex()
+ {
+ $dataProvider = new ActiveDataProvider([
+ 'query' => TaxGroup::find(),
+ ]);
+
+ return $this->render('index', [
+ 'dataProvider' => $dataProvider,
+ ]);
+ }
+
+ /**
+ * Displays a single TaxGroup model.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionView($id)
+ {
+ return $this->render('view', [
+ 'model' => $this->findModel($id),
+ ]);
+ }
+
+ /**
+ * Creates a new TaxGroup model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return mixed
+ */
+ public function actionCreate()
+ {
+ $model = new TaxGroup();
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+ return $this->redirect(['view', 'id' => $model->tax_group_id]);
+ } else {
+ return $this->render('create', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Updates an existing TaxGroup model.
+ * If update is successful, the browser will be redirected to the 'view' page.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionUpdate($id)
+ {
+ $model = $this->findModel($id);
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+ return $this->redirect(['view', 'id' => $model->tax_group_id]);
+ } else {
+ return $this->render('update', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Deletes an existing TaxGroup model.
+ * If deletion is successful, the browser will be redirected to the 'index' page.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionDelete($id)
+ {
+ $this->findModel($id)->delete();
+
+ return $this->redirect(['index']);
+ }
+
+ /*
+ * Group-relations
+ */
+ public function actionRelation($id, $relations = ['tax_option_to_group', 'tax_option_to_option']) {
+ $group = $this->findModel($id);
+ $items = [];
+
+ foreach($relations as $relation) {
+ $rows = $group->getRelations($relation)->all();
+ $items = array_merge($items, $rows);
+ }
+
+ return $this->render('relations', [
+ 'items' => $items,
+ 'group' => $group,
+ ]);
+ }
+
+ /*
+ * Rebuilp MP-params for group options
+ */
+ public function actionRebuild($id) {
+ TaxOption::find()->rebuildMP($id);
+
+ return $this->redirect(['index']);
+ }
+
+ /**
+ * Finds the TaxGroup model based on its primary key value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * @param integer $id
+ * @return TaxGroup the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($id)
+ {
+ if (($model = TaxGroup::findOne($id)) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
+}
diff --git a/common/modules/rubrication/controllers/TaxOptionController.php b/common/modules/rubrication/controllers/TaxOptionController.php
new file mode 100644
index 0000000..3ca7b29
--- /dev/null
+++ b/common/modules/rubrication/controllers/TaxOptionController.php
@@ -0,0 +1,171 @@
+ [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Lists all TaxOption models.
+ * @return mixed
+ */
+ public function actionIndex()
+ {
+ $searchModel = new TaxOptionSearch();
+ $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
+
+ $group = TaxGroup::findOne(Yii::$app->request->queryParams['group']);
+
+ return $this->render('index', [
+ 'searchModel' => $searchModel,
+ 'dataProvider' => $dataProvider,
+ 'group' => $group,
+ ]);
+ }
+
+ /**
+ * Displays a single TaxOption model.
+ * @param string $id
+ * @return mixed
+ */
+ public function actionView($id)
+ {
+ $model = $this->findModel($id);
+ $group = TaxGroup::findOne($model->tax_group_id);
+ return $this->render('view', [
+ 'model' => $model,
+ 'group' => $group,
+ ]);
+ }
+
+ /**
+ * Creates a new TaxOption model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return mixed
+ */
+ public function actionCreate()
+ {
+ $model = new TaxOption();
+ $group = TaxGroup::findOne(Yii::$app->request->queryParams['group']);
+ $valueModelName = $this->getValueModelName($group);
+ $valueModel = new $valueModelName;
+
+ if ($model->load(Yii::$app->request->post()) && $valueModel->load(Yii::$app->request->post())) {
+ $model->save();
+
+ $valueModel->tax_option_id = $model->tax_option_id;
+ $valueModel->save();
+
+ $model->default_value = $valueModel->tax_value_id;
+ $model->save();
+
+ return is_null(Yii::$app->request->post('create_and_new')) ? $this->redirect(['view', 'id' => $model->tax_option_id]) : $this->redirect(array_merge(['create'], Yii::$app->request->queryParams));
+ } else {
+ $model->tax_group_id = $group->tax_group_id;
+ if (!empty(Yii::$app->request->queryParams['parent'])) {
+ $model->parent_id = Yii::$app->request->queryParams['parent'];
+ }
+ return $this->render('create', [
+ 'model' => $model,
+ 'group' => $group,
+ 'valueModel' => $valueModel,
+ ]);
+ }
+ }
+
+ /**
+ * Updates an existing TaxOption model.
+ * If update is successful, the browser will be redirected to the 'view' page.
+ * @param string $id
+ * @return mixed
+ */
+ public function actionUpdate($id)
+ {
+ $model = $this->findModel($id);
+ $group = TaxGroup::findOne($model->tax_group_id);
+ $valueModelName = $this->getValueModelName($group);
+ $valueModel = $valueModelName::findOne($model->default_value);
+
+ if ($model->load(Yii::$app->request->post()) && $valueModel->load(Yii::$app->request->post())) {
+ $model->save();
+ $valueModel->tax_option_id = $model->tax_option_id;
+ $valueModel->save();
+
+ $model->default_value = $valueModel->tax_value_id;
+ $model->save();
+
+ TaxOption::find()->rebuildMP($model->tax_group_id);
+
+ return $this->redirect(['view', 'id' => $model->tax_option_id]);
+ } else {
+ return $this->render('update', [
+ 'model' => $model,
+ 'group' => $group,
+ 'valueModel' => $valueModel,
+ ]);
+ }
+ }
+
+ /**
+ * Deletes an existing TaxOption model.
+ * If deletion is successful, the browser will be redirected to the 'index' page.
+ * @param string $id
+ * @return mixed
+ */
+ public function actionDelete($id)
+ {
+ $model = $this->findModel($id);
+ $group_id = $model->tax_group_id;
+
+ $model->delete();
+
+ return $this->redirect(['index', 'group' => $group_id]);
+ }
+
+ /**
+ * Finds the TaxOption model based on its primary key value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * @param string $id
+ * @return TaxOption the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($id)
+ {
+ if (($model = TaxOption::findOne($id)) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
+
+ protected function getValueModelName($group) {
+ $valueClass = '\common\modules\rubrication\models\TaxValue'. ucfirst($group->module);
+ return class_exists($valueClass) ? $valueClass : FALSE;
+ }
+}
diff --git a/common/modules/rubrication/helpers/RubricationHelper.php b/common/modules/rubrication/helpers/RubricationHelper.php
new file mode 100644
index 0000000..39e7cbb
--- /dev/null
+++ b/common/modules/rubrication/helpers/RubricationHelper.php
@@ -0,0 +1,43 @@
+ Yii::t('order', 'Invisible'),
+ 1 => Yii::t('order', 'Active'),
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ * Returns sort-interval of appropriate options and others
+ * @return array.
+ */
+ static public function SortArray($low = 0, $high = 100) {
+ return range($low, $high);
+ }
+
+ static public function OptionTypes() {
+ if (!is_null(self::$types)) {
+ return self::$types;
+ }
+
+ $module = \Yii::$app->getModule('rubrication');
+
+ if (!is_array($module->types))
+ return [];
+
+ return $module->types;
+
+ }
+}
\ No newline at end of file
diff --git a/common/modules/rubrication/models/TaxGroup.php b/common/modules/rubrication/models/TaxGroup.php
new file mode 100644
index 0000000..d9df989
--- /dev/null
+++ b/common/modules/rubrication/models/TaxGroup.php
@@ -0,0 +1,116 @@
+ relationBehavior::className(),
+ 'relations' => [
+// 'tax_option_to_group' => 'entity2',
+ ]
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function tableName()
+ {
+ return '{{%tax_group}}';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['alias', 'name', 'module'], 'required'],
+ [['description', 'settings'], 'string'],
+ [['hierarchical'], 'boolean'],
+ [['alias', 'module'], 'string', 'max' => 50],
+ [['name'], 'string', 'max' => 255],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'tax_group_id' => 'Tax Group ID',
+ 'alias' => 'Alias',
+ 'name' => 'Name',
+ 'description' => 'Description',
+ 'module' => 'Module',
+ 'hierarchical' => 'Hierarchical',
+// 'settings' => 'Settings',
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxGroupToGroups()
+ {
+ return $this->hasMany(TaxGroupToGroup::className(), ['tax_group1_id' => 'tax_group_id'])->inverseOf('taxGroup1');
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxGroupToGroups0()
+ {
+ return $this->hasMany(TaxGroupToGroup::className(), ['tax_group2_id' => 'tax_group_id'])->inverseOf('taxGroup2');
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxOptions()
+ {
+ return $this->hasMany(TaxOption::className(), ['tax_group_id' => 'tax_group_id'])->inverseOf('taxGroup');
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxOptionToGroups()
+ {
+ return $this->hasMany(TaxOptionToGroup::className(), ['tax_group_id' => 'tax_group_id'])->inverseOf('taxGroup');
+ }
+
+ public function getValueModelName($full_path = true) {
+ $valueClass = 'TaxValue'. ucfirst($this->module);
+ $fullClass = '\common\modules\rubrication\models\\'. $valueClass;
+ return class_exists($fullClass) ? $full_path ? $fullClass : $valueClass : FALSE;
+ }
+}
diff --git a/common/modules/rubrication/models/TaxGroupToGroup.php b/common/modules/rubrication/models/TaxGroupToGroup.php
new file mode 100644
index 0000000..6d3fe73
--- /dev/null
+++ b/common/modules/rubrication/models/TaxGroupToGroup.php
@@ -0,0 +1,69 @@
+ 50]
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'tax_group1_id' => Yii::t('app', 'Tax Group1 ID'),
+ 'tax_group2_id' => Yii::t('app', 'Tax Group2 ID'),
+ 'alias' => Yii::t('app', 'Alias'),
+ 'sort' => Yii::t('app', 'Sort'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxGroup1()
+ {
+ return $this->hasOne(TaxGroup::className(), ['tax_group_id' => 'tax_group1_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxGroup2()
+ {
+ return $this->hasOne(TaxGroup::className(), ['tax_group_id' => 'tax_group2_id']);
+ }
+}
diff --git a/common/modules/rubrication/models/TaxOption.php b/common/modules/rubrication/models/TaxOption.php
new file mode 100644
index 0000000..d25262b
--- /dev/null
+++ b/common/modules/rubrication/models/TaxOption.php
@@ -0,0 +1,232 @@
+ [
+ 'class' => ArtboxTreeBehavior::className(),
+ 'keyNameGroup' => 'tax_group_id',
+ ],
+ 'slug' => [
+ 'class' => 'common\behaviors\Slug',
+ 'in_attribute' => 'ValueRenderFlash',
+ 'out_attribute' => 'alias',
+ 'translit' => true
+ ],
+ ];
+ }
+
+ public function events() {
+ return $this->events();
+ return [
+// ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function tableName()
+ {
+ return '{{%tax_option}}';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ [['tax_group_id'], 'required'],
+ [['tax_group_id', 'parent_id', 'sort', 'default_value'], 'integer'],
+ [['alias'], 'string', 'max' => 50],
+ [['tax_group_id'], 'exist', 'skipOnError' => true, 'targetClass' => TaxGroup::className(), 'targetAttribute' => ['tax_group_id' => 'tax_group_id']],
+// [['parent_id'], 'exist', 'skipOnError' => true, 'targetClass' => TaxOption::className(), 'targetAttribute' => ['parent_id' => 'tax_option_id']],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'tax_option_id' => Yii::t('app', 'Tax Option ID'),
+ 'tax_group_id' => Yii::t('app', 'Tax Group ID'),
+ 'parent_id' => Yii::t('app', 'Parent ID'),
+ 'alias' => Yii::t('app', 'Alias'),
+ 'sort' => Yii::t('app', 'Sort'),
+ 'default_value' => Yii::t('app', 'Default Value'),
+ ];
+ }
+
+ public static function find() {
+ return new TaxOptionQuery(get_called_class());
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxEntityRelations()
+ {
+ return $this->hasMany(TaxEntityRelation::className(), ['tax_option_id' => 'tax_option_id'])->inverseOf('taxOption');
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getGroup()
+ {
+ return $this->getTaxGroup();
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxGroup()
+ {
+ return $this->hasOne(TaxGroup::className(), ['tax_group_id' => 'tax_group_id'])->inverseOf('taxOptions');
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxOptions()
+ {
+ return $this->hasMany(TaxOption::className(), ['parent_id' => 'tax_option_id'])->inverseOf('parent');
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxOptionToGroups()
+ {
+ return $this->hasMany(TaxOptionToGroup::className(), ['tax_option_id' => 'tax_option_id'])->inverseOf('taxOption');
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxOptionToOptions()
+ {
+ return $this->hasMany(TaxOptionToOption::className(), ['tax_option1_id' => 'tax_option_id'])->inverseOf('taxOption1');
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxOptionToOptions0()
+ {
+ return $this->hasMany(TaxOptionToOption::className(), ['tax_option2_id' => 'tax_option_id'])->inverseOf('taxOption2');
+ }
+
+ /**
+ */
+ public function getValue()
+ {
+ $valueClass = $this->getValueModelName();
+ if ($valueClass) {
+ return $this->hasOne($valueClass, ['tax_value_id' => 'default_value', 'tax_option_id' => 'tax_option_id'])->inverseOf('taxOption');
+ }
+ }
+
+ /**
+ */
+ public function getValueRenderFlash()
+ {
+ $valueClass = $this->getValueModelName();
+ $value = $this->getValue()->one();
+ if ($valueClass && method_exists($valueClass, 'getValueRenderFlash')) {
+ return $valueClass::getValueRenderFlash($value);
+ } elseif(!empty($value)) {
+ return $value->value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ */
+ public function getValueRenderHTML()
+ {
+ $valueClass = $this->getValueModelName();
+ $value = $this->getValue()->one();
+ if ($valueClass && method_exists($valueClass, 'getValueRenderHTML')) {
+ return $valueClass::getValueRenderHTML($value);
+ } else {
+ return $value->value;
+ }
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getValues()
+ {
+ if ($valueClass = $this->getValueModelName())
+ return $this->hasMany($valueClass, ['tax_option_id' => 'tax_option_id'])->inverseOf('taxOption')->cascadeOnDelete();
+ }
+
+ public function beforeSave($insert)
+ {
+ if (parent::beforeSave($insert)) {
+
+ if (empty($this->parent_id))
+ $this->parent_id = 0;
+
+
+
+ return true;
+ }
+ return false;
+ }
+
+// public function beforeDelete() {
+// if ( ($model = $this->getValueModelName()) !== FALSE ) {
+//
+// }
+// }
+
+ private function getValueModelName() {
+ $group = $this->getTaxGroup()->one();
+ $valueClass = '\common\modules\rubrication\models\TaxValue'. ucfirst($group->module);
+ return class_exists($valueClass) ? $valueClass : FALSE;
+ }
+}
diff --git a/common/modules/rubrication/models/TaxOptionQuery.php b/common/modules/rubrication/models/TaxOptionQuery.php
new file mode 100644
index 0000000..5113f13
--- /dev/null
+++ b/common/modules/rubrication/models/TaxOptionQuery.php
@@ -0,0 +1,37 @@
+andWhere('[[status]]=1');
+ }*/
+
+ /**
+ * @inheritdoc
+ * @return TaxOption[]|array
+ */
+ public function all($db = null)
+ {
+ return parent::all($db);
+ }
+
+ /**
+ * @inheritdoc
+ * @return TaxOption|array|null
+ */
+ public function one($db = null)
+ {
+ return parent::one($db);
+ }
+}
diff --git a/common/modules/rubrication/models/TaxOptionRelation.php b/common/modules/rubrication/models/TaxOptionRelation.php
new file mode 100644
index 0000000..f80a7f1
--- /dev/null
+++ b/common/modules/rubrication/models/TaxOptionRelation.php
@@ -0,0 +1,50 @@
+ 50]
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'tax_option1_id' => Yii::t('product', 'Tax Option1 ID'),
+ 'tax_option2_id' => Yii::t('product', 'Tax Option2 ID'),
+ 'alias' => Yii::t('product', 'Alias'),
+ 'sort' => Yii::t('product', 'Sort'),
+ ];
+ }
+}
diff --git a/common/modules/rubrication/models/TaxOptionSearch.php b/common/modules/rubrication/models/TaxOptionSearch.php
new file mode 100644
index 0000000..adfb6bc
--- /dev/null
+++ b/common/modules/rubrication/models/TaxOptionSearch.php
@@ -0,0 +1,85 @@
+tax_group_id = intval($params['group']);
+// $group = TaxGroup::findOne($this->tax_group_id);
+ unset($params['group']);
+ }
+// if (!empty($params['value_render_flat'])) {
+// $this->value_render_flat = trim($params['value_render_flat']);
+// }
+ $dataProvider = new ActiveDataProvider([
+ 'query' => $query,
+ ]);
+
+ $this->load($params);
+
+ if (!$this->validate()) {
+ // uncomment the following line if you do not want to return any records when validation fails
+ // $query->where('0=1');
+ return $dataProvider;
+ }
+
+// if (!empty($group)) {
+// $query->joinWith('Value');
+// }
+
+ // grid filtering conditions
+ $query->andFilterWhere([
+ 'tax_option_id' => $this->tax_option_id,
+ 'tax_group_id' => $this->tax_group_id,
+ 'parent_id' => $this->parent_id,
+ 'sort' => $this->sort,
+ ]);
+
+ $query->andFilterWhere(['like', 'alias', $this->alias]);
+ $query->andFilterWhere(['like', 'tax_value_string.value', $this->default_value]);
+
+ $query->orderBy(['path_int' => SORT_ASC, 'depth' => SORT_ASC, 'sort' => SORT_ASC]);
+
+ return $dataProvider;
+ }
+}
diff --git a/common/modules/rubrication/models/TaxValueInt.php b/common/modules/rubrication/models/TaxValueInt.php
new file mode 100644
index 0000000..1fa9ea2
--- /dev/null
+++ b/common/modules/rubrication/models/TaxValueInt.php
@@ -0,0 +1,57 @@
+ true, 'targetClass' => TaxOption::className(), 'targetAttribute' => ['tax_option_id' => 'tax_option_id']],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'tax_value_id' => Yii::t('app', 'Tax Value ID'),
+ 'tax_option_id' => Yii::t('app', 'Tax Option ID'),
+ 'value' => Yii::t('app', 'Value'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxOption()
+ {
+ return $this->hasOne(TaxOption::className(), ['tax_option_id' => 'tax_option_id'])->inverseOf('taxValues');
+ }
+}
diff --git a/common/modules/rubrication/models/TaxValueLink.php b/common/modules/rubrication/models/TaxValueLink.php
new file mode 100644
index 0000000..f2eb859
--- /dev/null
+++ b/common/modules/rubrication/models/TaxValueLink.php
@@ -0,0 +1,69 @@
+ 150],
+ [['link'], 'string', 'max' => 255],
+ [['tax_option_id'], 'exist', 'skipOnError' => true, 'targetClass' => TaxOption::className(), 'targetAttribute' => ['tax_option_id' => 'tax_option_id']],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'tax_value_id' => Yii::t('app', 'Tax Value ID'),
+ 'tax_option_id' => Yii::t('app', 'Tax Option ID'),
+ 'name' => Yii::t('app', 'Name'),
+ 'link' => Yii::t('app', 'Link'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxOption()
+ {
+ return $this->hasOne(TaxOption::className(), ['tax_option_id' => 'tax_option_id'])->inverseOf('taxValueLinks');
+ }
+
+ public static function getValueRenderFlash($value) {
+ return $value->name;
+ }
+
+ public static function getValueRenderHTML($value) {
+ return Html::a($value->name, $value->link);
+ }
+}
diff --git a/common/modules/rubrication/models/TaxValueString.php b/common/modules/rubrication/models/TaxValueString.php
new file mode 100644
index 0000000..d9f617a
--- /dev/null
+++ b/common/modules/rubrication/models/TaxValueString.php
@@ -0,0 +1,58 @@
+ 255],
+ [['tax_option_id'], 'exist', 'skipOnError' => true, 'targetClass' => TaxOption::className(), 'targetAttribute' => ['tax_option_id' => 'tax_option_id']],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'tax_value_id' => Yii::t('rubrication', 'Tax Value ID'),
+ 'tax_option_id' => Yii::t('rubrication', 'Tax Option ID'),
+ 'value' => Yii::t('rubrication', 'Value'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getTaxOption()
+ {
+ return $this->hasOne(TaxOption::className(), ['tax_option_id' => 'tax_option_id'])->inverseOf('taxValueStrings');
+ }
+}
diff --git a/common/modules/rubrication/views/default/index.php b/common/modules/rubrication/views/default/index.php
new file mode 100644
index 0000000..6b05acc
--- /dev/null
+++ b/common/modules/rubrication/views/default/index.php
@@ -0,0 +1,12 @@
+
+
= $this->context->action->uniqueId ?>
+
+ This is the view content for action "= $this->context->action->id ?>".
+ The action belongs to the controller "= get_class($this->context) ?>"
+ in the "= $this->context->module->id ?>" module.
+
+
+ You may customize this page by editing the following file:
+ = __FILE__ ?>
+
+
diff --git a/common/modules/rubrication/views/tax-group/_form.php b/common/modules/rubrication/views/tax-group/_form.php
new file mode 100644
index 0000000..b593821
--- /dev/null
+++ b/common/modules/rubrication/views/tax-group/_form.php
@@ -0,0 +1,34 @@
+
+
+
diff --git a/common/modules/rubrication/views/tax-group/create.php b/common/modules/rubrication/views/tax-group/create.php
new file mode 100644
index 0000000..e671dd1
--- /dev/null
+++ b/common/modules/rubrication/views/tax-group/create.php
@@ -0,0 +1,21 @@
+title = Yii::t('rubrication', 'Create Tax Group');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', 'Tax Groups'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/common/modules/rubrication/views/tax-group/index.php b/common/modules/rubrication/views/tax-group/index.php
new file mode 100644
index 0000000..5e266ad
--- /dev/null
+++ b/common/modules/rubrication/views/tax-group/index.php
@@ -0,0 +1,72 @@
+title = Yii::t('rubrication', 'Groups');
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
+
= Html::encode($this->title) ?>
+
+ = Html::a(Yii::t('rubrication', 'Create Group'), ['create'], ['class' => 'btn btn-success']) ?>
+
+
+ = GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+
+ 'name',
+ 'alias',
+ 'description:ntext',
+ 'module',
+ 'hierarchical:boolean',
+ // 'settings:ntext',
+
+ [
+ 'class' => 'yii\grid\ActionColumn',
+ 'template' => '{update} {options} {relations} {delete} {rebuild}',
+ 'buttons' => [
+ 'options' => function ($url, $model) {
+ return Html::a('
', $url, [
+ 'title' => Yii::t('rubrication', 'Options'),
+ ]);
+ },
+ 'relations' => function ($url, $model) {
+ return Html::a('', $url, [
+ 'title' => Yii::t('rubrication', 'Relations'),
+ ]);
+ },
+ 'rebuild' => function ($url, $model) {
+ return Html::a('', $url, [
+ 'title' => Yii::t('rubrication', 'Rebuild cache'),
+ ]);
+ }
+ ],
+ 'urlCreator' => function ($action, $model, $key, $index) {
+ if ($action === 'options') {
+ $url ='/admin/rubrication/tax-option?group='.$model->tax_group_id;
+ return $url;
+ } elseif ($action === 'relations') {
+ $url ='/admin/rubrication/tax-group/relation&id='.$model->tax_group_id;
+ return $url;
+ } elseif ($action === 'update') {
+ $url ='/admin/rubrication/tax-group/update?id='.$model->tax_group_id;
+ return $url;
+ } elseif ($action === 'delete') {
+ $url ='/admin/rubrication/tax-group/delete?id='.$model->tax_group_id;
+ return $url;
+ } elseif ($action === 'rebuild') {
+ $url ='/admin/rubrication/tax-group/rebuild?id='.$model->tax_group_id;
+ return $url;
+ }
+ }
+ ],
+ ],
+ ]); ?>
+
+
diff --git a/common/modules/rubrication/views/tax-group/relations.php b/common/modules/rubrication/views/tax-group/relations.php
new file mode 100644
index 0000000..2c0c884
--- /dev/null
+++ b/common/modules/rubrication/views/tax-group/relations.php
@@ -0,0 +1,58 @@
+title = Yii::t('relation', 'Relation for Group "{group}"', ['group' => $group->name]);
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', 'Tax Groups'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+//= Html::a(Yii::t('relation', 'Create relation'), ['create?relation='. $relation_key], ['class' => 'btn btn-success']) ?>
+
+
+ $items,
+ 'sort' => [
+ 'attributes' => ['name', 'alias', 'entity1_label', 'entity2_label'],
+ ],
+ 'pagination' => [
+ 'pageSize' => 10,
+ ],
+ ]);
+ ?>
+
+ = GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+ 'name',
+ 'alias',
+ 'entity1_label',
+ 'entity2_label',
+ /*[
+ 'class' => 'yii\grid\ActionColumn',
+ 'template' => '{view}',
+ 'buttons' => [
+ 'view' => function ($url, $model) {
+ return Html::a(' ', $url, [
+ 'title' => Yii::t('relation', 'View pars'),
+ ]);
+ },
+ ],
+ 'urlCreator' => function ($action, $model, $key, $index) {
+ if ($action === 'view') {
+ $url ='/admin/relation/manage/pars?relation='. $model['key'];
+ return $url;
+ }
+ }
+ ],*/
+ ],
+ ]); ?>
+
diff --git a/common/modules/rubrication/views/tax-group/update.php b/common/modules/rubrication/views/tax-group/update.php
new file mode 100644
index 0000000..7f3feaa
--- /dev/null
+++ b/common/modules/rubrication/views/tax-group/update.php
@@ -0,0 +1,23 @@
+title = Yii::t('rubrication', 'Update {modelClass}: ', [
+ 'modelClass' => 'Tax Group',
+]) . ' ' . $model->name;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', 'Groups'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->tax_group_id]];
+$this->params['breadcrumbs'][] = Yii::t('rubrication', 'Update');
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/common/modules/rubrication/views/tax-group/view.php b/common/modules/rubrication/views/tax-group/view.php
new file mode 100644
index 0000000..3074048
--- /dev/null
+++ b/common/modules/rubrication/views/tax-group/view.php
@@ -0,0 +1,42 @@
+title = $model->name;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', 'Tax Groups'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+ = Html::a(Yii::t('rubrication', 'Update'), ['update', 'id' => $model->tax_group_id], ['class' => 'btn btn-primary']) ?>
+ = Html::a(Yii::t('rubrication', 'Delete'), ['delete', 'id' => $model->tax_group_id], [
+ 'class' => 'btn btn-danger',
+ 'data' => [
+ 'confirm' => Yii::t('rubrication', 'Are you sure you want to delete this item?'),
+ 'method' => 'post',
+ ],
+ ]) ?>
+ = Html::a(Yii::t('rubrication', 'Create Option'), ['tax-option/create?group='. $model->tax_group_id], ['class' => 'btn btn-success']) ?>
+
+
+ = DetailView::widget([
+ 'model' => $model,
+ 'attributes' => [
+ 'tax_group_id',
+ 'alias',
+ 'name',
+ 'description:ntext',
+ 'module',
+ 'hierarchical:boolean',
+ 'settings:ntext',
+ ],
+ ]) ?>
+
+
diff --git a/common/modules/rubrication/views/tax-option/_form.php b/common/modules/rubrication/views/tax-option/_form.php
new file mode 100644
index 0000000..f29cdea
--- /dev/null
+++ b/common/modules/rubrication/views/tax-option/_form.php
@@ -0,0 +1,62 @@
+
+
+
diff --git a/common/modules/rubrication/views/tax-option/_search.php b/common/modules/rubrication/views/tax-option/_search.php
new file mode 100644
index 0000000..145b33d
--- /dev/null
+++ b/common/modules/rubrication/views/tax-option/_search.php
@@ -0,0 +1,37 @@
+
+
+
+
+ ['index'],
+ 'method' => 'get',
+ ]); ?>
+
+ = $form->field($model, 'tax_option_id') ?>
+
+ = $form->field($model, 'tax_group_id') ?>
+
+ = $form->field($model, 'parent_id') ?>
+
+ = $form->field($model, 'alias') ?>
+
+ = $form->field($model, 'sort') ?>
+
+ field($model, 'default_value') ?>
+
+
+ = Html::submitButton(Yii::t('rubrication', 'Search'), ['class' => 'btn btn-primary']) ?>
+ = Html::resetButton(Yii::t('rubrication', 'Reset'), ['class' => 'btn btn-default']) ?>
+
+
+
+
+
diff --git a/common/modules/rubrication/views/tax-option/create.php b/common/modules/rubrication/views/tax-option/create.php
new file mode 100644
index 0000000..71c91d6
--- /dev/null
+++ b/common/modules/rubrication/views/tax-option/create.php
@@ -0,0 +1,25 @@
+title = Yii::t('rubrication', 'Create Tax Option');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', 'Groups'), 'url' => ['tax-group/index']];
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', $group->name), 'url' => ['index', 'group' => $group->tax_group_id]];
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', Yii::t('rubrication', 'Options of {name}', ['name' => $group->name])), 'url' => ['index', 'group' => $group->tax_group_id]];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ 'group' => $group,
+ 'valueModel' => $valueModel,
+ ]) ?>
+
+
diff --git a/common/modules/rubrication/views/tax-option/index.php b/common/modules/rubrication/views/tax-option/index.php
new file mode 100644
index 0000000..986b02e
--- /dev/null
+++ b/common/modules/rubrication/views/tax-option/index.php
@@ -0,0 +1,137 @@
+title = Yii::t('rubrication', 'Options for group "{group}"', ['group' => $group->name]);
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', 'Groups'), 'url' => ['tax-group/index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+ render('_search', ['model' => $searchModel]); ?>
+
+
+ = Html::a(Yii::t('rubrication', 'Create Option'), ['create?group='. $group->tax_group_id], ['class' => 'btn btn-success']) ?>
+
+
+hierarchical) :?>
+ $dataProvider,
+// 'filterModel' => $searchModel,
+ 'columns' => [
+ [
+ 'label'=> Yii::t('rubrication', 'Value'),
+ 'content'=>function($data){
+ return str_repeat('-', $data->depth) .' '. $data->ValueRenderFlash;
+ }
+ ],
+ 'alias',
+ [
+ 'class' => 'yii\grid\ActionColumn',
+ 'template' => '{view} {update} {delete} {synonim}',
+ 'buttons' => [
+ 'synonim' => function ($url, $model) {
+ return Html::a('
', $url, [
+ 'title' => Yii::t('rubrication', 'Synonims'),
+ ]);
+ },
+ ],
+ 'urlCreator' => function ($action, $model, $key, $index) {
+ if ($action === 'view') {
+ $url = '/admin/rubrication/tax-option/view?id=' . $model->tax_option_id;
+ return $url;
+ } elseif ($action === 'update') {
+ $url ='/admin/rubrication/tax-option/update?id='.$model->tax_option_id;
+ return $url;
+ } elseif ($action === 'delete') {
+ $url ='/admin/rubrication/tax-option/delete?id='.$model->tax_option_id;
+ return $url;
+ } elseif ($action === 'synonim') {
+ $url ='/admin/rubrication/tax-synonim/delete?id='.$model->tax_option_id;
+ return $url;
+ }
+ }
+ ],
+ ],
+ ]);?>
+ $dataProvider,
+ 'keyNameId' => 'tax_option_id',
+ 'keyNameParentId' => 'parent_id',
+ 'rootParentId' => 0,
+ 'columns' => [
+ [
+ 'attribute' => 'valueRenderHTML',
+ 'label' => Yii::t('rubrication', 'Value'),
+ 'format' => 'html'
+ ],
+ 'alias',
+ [
+ 'class' => 'yii\grid\ActionColumn',
+ 'template' => '{update} {delete} {synonim}',
+ 'buttons' => [
+ 'synonim' => function ($url, $model) {
+ return Html::a('
', $url, [
+ 'title' => Yii::t('rubrication', 'Synonims'),
+ ]);
+ },
+ ],
+ 'urlCreator' => function ($action, $model, $key, $index) {
+ if ($action === 'update') {
+ $url ='/admin/rubrication/tax-option/update?id='.$model->tax_option_id;
+ return $url;
+ } elseif ($action === 'delete') {
+ $url ='/admin/rubrication/tax-option/delete?id='.$model->tax_option_id;
+ return $url;
+ } elseif ($action === 'synonim') {
+ $url ='/admin/rubrication/tax-synonim/delete?id='.$model->tax_option_id;
+ return $url;
+ }
+ }
+ ],
+ ]
+ ]); */?>
+
+ $dataProvider,
+// 'filterModel' => $searchModel,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+ 'valueRenderHTML',
+ 'alias',
+ [
+ 'class' => 'yii\grid\ActionColumn',
+ 'template' => '{update} {delete} {synonim}',
+ 'buttons' => [
+ 'synonim' => function ($url, $model) {
+ return Html::a('
', $url, [
+ 'title' => Yii::t('rubrication', 'Synonims'),
+ ]);
+ },
+ ],
+ 'urlCreator' => function ($action, $model, $key, $index) {
+ if ($action === 'update') {
+ $url ='/admin/rubrication/tax-option/update?id='.$model->tax_option_id;
+ return $url;
+ } elseif ($action === 'delete') {
+ $url ='/admin/rubrication/tax-option/delete?id='.$model->tax_option_id;
+ return $url;
+ } elseif ($action === 'synonim') {
+ $url ='/admin/rubrication/tax-synonim/delete?id='.$model->tax_option_id;
+ return $url;
+ }
+ }
+ ],
+ ],
+ ]);?>
+
+
diff --git a/common/modules/rubrication/views/tax-option/update.php b/common/modules/rubrication/views/tax-option/update.php
new file mode 100644
index 0000000..5c7164f
--- /dev/null
+++ b/common/modules/rubrication/views/tax-option/update.php
@@ -0,0 +1,26 @@
+title = Yii::t('rubrication', 'Update {modelClass}: ', [
+ 'modelClass' => 'Tax Option',
+]) . ' ' . $model->tax_option_id;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', 'Groups'), 'url' => ['tax-group/index']];
+$this->params['breadcrumbs'][] = ['label' => $group->name, 'url' => ['view', 'id' => $group->tax_group_id]];
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', Yii::t('rubrication', 'Options of {name}', ['name' => $group->name])), 'url' => ['index', 'group' => $group->tax_group_id]];
+$this->params['breadcrumbs'][] = Yii::t('rubrication', 'Update');
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ 'group' => $group,
+ 'valueModel' => $valueModel,
+ ]) ?>
+
+
diff --git a/common/modules/rubrication/views/tax-option/value/_fields_int.php b/common/modules/rubrication/views/tax-option/value/_fields_int.php
new file mode 100644
index 0000000..6a35b2f
--- /dev/null
+++ b/common/modules/rubrication/views/tax-option/value/_fields_int.php
@@ -0,0 +1 @@
+= $form->field($valueModel, 'value')->textInput() ?>
\ No newline at end of file
diff --git a/common/modules/rubrication/views/tax-option/value/_fields_link.php b/common/modules/rubrication/views/tax-option/value/_fields_link.php
new file mode 100644
index 0000000..faebf0c
--- /dev/null
+++ b/common/modules/rubrication/views/tax-option/value/_fields_link.php
@@ -0,0 +1,2 @@
+= $form->field($valueModel, 'name')->textInput(['maxlength' => true]) ?>
+= $form->field($valueModel, 'link')->textInput(['maxlength' => true]) ?>
\ No newline at end of file
diff --git a/common/modules/rubrication/views/tax-option/value/_fields_string.php b/common/modules/rubrication/views/tax-option/value/_fields_string.php
new file mode 100644
index 0000000..6a35b2f
--- /dev/null
+++ b/common/modules/rubrication/views/tax-option/value/_fields_string.php
@@ -0,0 +1 @@
+= $form->field($valueModel, 'value')->textInput() ?>
\ No newline at end of file
diff --git a/common/modules/rubrication/views/tax-option/view.php b/common/modules/rubrication/views/tax-option/view.php
new file mode 100644
index 0000000..5cdc66e
--- /dev/null
+++ b/common/modules/rubrication/views/tax-option/view.php
@@ -0,0 +1,46 @@
+title = $model->valueRenderFlash;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', 'Groups'), 'url' => ['tax-group/index']];
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', $group->name), 'url' => ['index', 'group' => $group->tax_group_id]];
+$this->params['breadcrumbs'][] = ['label' => Yii::t('rubrication', Yii::t('rubrication', 'Options of {name}', ['name' => $group->name])), 'url' => ['index', 'group' => $group->tax_group_id]];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+ = Html::a(Yii::t('rubrication', 'Update'), ['update', 'id' => $model->tax_option_id], ['class' => 'btn btn-primary']) ?>
+ = Html::a(Yii::t('rubrication', 'Delete'), ['delete', 'id' => $model->tax_option_id], [
+ 'class' => 'btn btn-danger',
+ 'data' => [
+ 'confirm' => Yii::t('rubrication', 'Are you sure you want to delete this item?'),
+ 'method' => 'post',
+ ],
+ ]) ?>
+ = Html::a(Yii::t('rubrication', 'Create Option'), ['tax-option/create?group='. $model->tax_group_id], ['class' => 'btn btn-success']) ?>
+ parent_id)) :?>
+ = Html::a(Yii::t('rubrication', 'Create Option By {name}', ['name' => $model->parent->ValueRenderFlash]), ['tax-option/create?group='. $model->tax_group_id .'&parent='. $model->parent->tax_option_id], ['class' => 'btn btn-success']) ?>
+
+
+
+ = DetailView::widget([
+ 'model' => $model,
+ 'attributes' => [
+ 'tax_option_id',
+ 'ValueRenderFlash',
+ 'alias',
+ 'parent.ValueRenderFlash',
+ 'group.name',
+ 'sort',
+ ],
+ ]) ?>
+
+
diff --git a/common/widgets/Alert.php b/common/widgets/Alert.php
new file mode 100644
index 0000000..8f1e590
--- /dev/null
+++ b/common/widgets/Alert.php
@@ -0,0 +1,79 @@
+session->setFlash('error', 'This is the message');
+ * \Yii::$app->session->setFlash('success', 'This is the message');
+ * \Yii::$app->session->setFlash('info', 'This is the message');
+ * ```
+ *
+ * Multiple messages could be set as follows:
+ *
+ * ```php
+ * \Yii::$app->session->setFlash('error', ['Error 1', 'Error 2']);
+ * ```
+ *
+ * @author Kartik Visweswaran
+ * @author Alexander Makarov
+ */
+class Alert extends \yii\bootstrap\Widget
+{
+ /**
+ * @var array the alert types configuration for the flash messages.
+ * This array is setup as $key => $value, where:
+ * - $key is the name of the session flash variable
+ * - $value is the bootstrap alert type (i.e. danger, success, info, warning)
+ */
+ public $alertTypes = [
+ 'error' => 'alert-danger',
+ 'danger' => 'alert-danger',
+ 'success' => 'alert-success',
+ 'info' => 'alert-info',
+ 'warning' => 'alert-warning'
+ ];
+
+ /**
+ * @var array the options for rendering the close button tag.
+ */
+ public $closeButton = [];
+
+ public function init()
+ {
+ parent::init();
+
+ $session = \Yii::$app->session;
+ $flashes = $session->getAllFlashes();
+ $appendCss = isset($this->options['class']) ? ' ' . $this->options['class'] : '';
+
+ foreach ($flashes as $type => $data) {
+ if (isset($this->alertTypes[$type])) {
+ $data = (array) $data;
+ foreach ($data as $i => $message) {
+ /* initialize css class for each alert box */
+ $this->options['class'] = $this->alertTypes[$type] . $appendCss;
+
+ /* assign unique id to each alert box */
+ $this->options['id'] = $this->getId() . '-' . $type . '-' . $i;
+
+ echo \yii\bootstrap\Alert::widget([
+ 'body' => $message,
+ 'closeButton' => $this->closeButton,
+ 'options' => $this->options,
+ ]);
+ }
+
+ $session->removeFlash($type);
+ }
+ }
+ }
+}
diff --git a/common/widgets/FieldEditor.php b/common/widgets/FieldEditor.php
new file mode 100644
index 0000000..62261a4
--- /dev/null
+++ b/common/widgets/FieldEditor.php
@@ -0,0 +1,48 @@
+item_id && $this->model){
+ $widgetData = $this->findModel();
+ } else {
+ $widgetData= [new Fields()];
+ }
+
+ return $this->render($this->template.'_field',['model'=>ArrayHelper::toArray($widgetData)]);
+ }
+
+ protected function findModel()
+ {
+
+ if (($model = Fields::find()->where([
+ 'table_id'=>$this->item_id,
+ 'table_name'=>$this->model,
+ 'field_type'=>$this->template,
+ 'language'=>$this->language,
+ ])->all())) {
+
+ return $model;
+
+ } else {
+ return [new Fields()];
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/widgets/views/education_field.php b/common/widgets/views/education_field.php
new file mode 100644
index 0000000..ca6ad41
--- /dev/null
+++ b/common/widgets/views/education_field.php
@@ -0,0 +1,83 @@
+
+
+
+Образование
+
+
+ добавить еще
+
+
+
+
+
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..0090d0f
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,50 @@
+{
+ "name": "yiisoft/yii2-app-advanced",
+ "description": "Yii 2 Advanced Project Template",
+ "keywords": ["yii2", "framework", "advanced", "project template"],
+ "homepage": "http://www.yiiframework.com/",
+ "type": "project",
+ "license": "BSD-3-Clause",
+ "support": {
+ "issues": "https://github.com/yiisoft/yii2/issues?state=open",
+ "forum": "http://www.yiiframework.com/forum/",
+ "wiki": "http://www.yiiframework.com/wiki/",
+ "irc": "irc://irc.freenode.net/yii",
+ "source": "https://github.com/yiisoft/yii2"
+ },
+ "minimum-stability": "dev",
+ "require": {
+ "php": ">=5.4.0",
+ "yiisoft/yii2": ">=2.0.6",
+ "yiisoft/yii2-bootstrap": "*",
+ "yiisoft/yii2-swiftmailer": "*",
+ "dmstr/yii2-adminlte-asset": "2.*",
+ "yiisoft/yii2-jui": "^2.0",
+ "kartik-v/yii2-widget-select2": "@dev",
+ "mihaildev/yii2-ckeditor": "*",
+ "developeruz/yii2-db-rbac": "*",
+ "nodge/yii2-eauth": "*",
+ "yiisoft/yii2-imagine": "^2.0",
+ "mihaildev/yii2-elfinder": "^1.1",
+ "kartik-v/yii2-widget-colorinput": "*",
+ "2amigos/yii2-transliterator-helper": "*",
+ "rmrevin/yii2-comments": "1.4.*",
+ "bower-asset/admin-lte": "*",
+ "FortAwesome/Font-Awesome": "*"
+ },
+ "require-dev": {
+ "yiisoft/yii2-codeception": "*",
+ "yiisoft/yii2-debug": "*",
+ "yiisoft/yii2-gii": "*",
+ "yiisoft/yii2-faker": "*"
+ },
+ "config": {
+ "process-timeout": 1800
+ },
+ "extra": {
+ "asset-installer-paths": {
+ "npm-asset-library": "vendor/npm",
+ "bower-asset-library": "vendor/bower"
+ }
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..e30a2eb
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,1969 @@
+{
+ "_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"
+ ],
+ "hash": "7543c4377d8fa28fcee5a5d9e3aabcb8",
+ "content-hash": "7c7bea2b7f00d81aab75adce39789723",
+ "packages": [
+ {
+ "name": "2amigos/yii2-transliterator-helper",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/2amigos/yii2-transliterator-helper.git",
+ "reference": "1e4284351f4250a8f2ce553ea4f420fcbb424309"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/2amigos/yii2-transliterator-helper/zipball/1e4284351f4250a8f2ce553ea4f420fcbb424309",
+ "reference": "1e4284351f4250a8f2ce553ea4f420fcbb424309",
+ "shasum": ""
+ },
+ "require": {
+ "yiisoft/yii2": "*"
+ },
+ "type": "yii2-extension",
+ "autoload": {
+ "psr-4": {
+ "dosamigos\\transliterator\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Antonio Ramirez",
+ "email": "ramirez.cobos@gmail.com",
+ "homepage": "http://www.ramirezcobos.com"
+ }
+ ],
+ "description": "Transliterator Helper for Yii2.",
+ "keywords": [
+ "extension",
+ "helper",
+ "transliterator",
+ "yii"
+ ],
+ "time": "2014-06-23 14:01:30"
+ },
+ {
+ "name": "almasaeed2010/adminlte",
+ "version": "v2.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/almasaeed2010/AdminLTE.git",
+ "reference": "1ee281b3b99e8d8cccdc72fb8437c6888149cb46"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/almasaeed2010/AdminLTE/zipball/1ee281b3b99e8d8cccdc72fb8437c6888149cb46",
+ "reference": "1ee281b3b99e8d8cccdc72fb8437c6888149cb46",
+ "shasum": ""
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Abdullah Almsaeed",
+ "email": "support@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": "2015-10-23 14:50:49"
+ },
+ {
+ "name": "bower-asset/admin-lte",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/almasaeed2010/AdminLTE.git",
+ "reference": "fe147c9b2188bc3e4c651ca24581a6710d5421ff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/almasaeed2010/AdminLTE/zipball/fe147c9b2188bc3e4c651ca24581a6710d5421ff",
+ "reference": "fe147c9b2188bc3e4c651ca24581a6710d5421ff",
+ "shasum": ""
+ },
+ "type": "bower-asset-library",
+ "extra": {
+ "bower-asset-main": [
+ "index2.html",
+ "dist/css/AdminLTE.css",
+ "dist/js/app.js",
+ "build/less/AdminLTE.less"
+ ],
+ "bower-asset-ignore": [
+ "/.*",
+ "node_modules",
+ "bower_components",
+ "composer.json",
+ "documentation"
+ ]
+ },
+ "license": [
+ "MIT"
+ ],
+ "description": "Admin dashboard and control panel template",
+ "keywords": [
+ "admin",
+ "backend",
+ "bootstrap",
+ "css",
+ "html",
+ "js",
+ "responsive",
+ "template",
+ "theme"
+ ],
+ "time": "2016-03-14 01:14:03"
+ },
+ {
+ "name": "bower-asset/bootstrap",
+ "version": "v3.3.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twbs/bootstrap.git",
+ "reference": "16b48259a62f576e52c903c476bd42b90ab22482"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twbs/bootstrap/zipball/16b48259a62f576e52c903c476bd42b90ab22482",
+ "reference": "16b48259a62f576e52c903c476bd42b90ab22482",
+ "shasum": ""
+ },
+ "require": {
+ "bower-asset/jquery": ">=1.9.1"
+ },
+ "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/fontawesome",
+ "version": "v4.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/FortAwesome/Font-Awesome.git",
+ "reference": "fddd2c240452e6c8990c4ef75e0265b455aa7968"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/FortAwesome/Font-Awesome/zipball/fddd2c240452e6c8990c4ef75e0265b455aa7968",
+ "reference": "fddd2c240452e6c8990c4ef75e0265b455aa7968",
+ "shasum": ""
+ },
+ "type": "bower-asset-library",
+ "extra": {
+ "bower-asset-main": [
+ "less/font-awesome.less",
+ "scss/font-awesome.scss"
+ ],
+ "bower-asset-ignore": [
+ "*/.*",
+ "*.json",
+ "src",
+ "*.yml",
+ "Gemfile",
+ "Gemfile.lock",
+ "*.md"
+ ]
+ },
+ "license": [
+ "OFL-1.1",
+ "MIT",
+ "CC-BY-3.0"
+ ],
+ "description": "Font Awesome"
+ },
+ {
+ "name": "bower-asset/jquery",
+ "version": "2.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jquery/jquery-dist.git",
+ "reference": "788eaba2f83e7b7445c7a83a50c81c0704423874"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/788eaba2f83e7b7445c7a83a50c81c0704423874",
+ "reference": "788eaba2f83e7b7445c7a83a50c81c0704423874",
+ "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-ui",
+ "version": "1.11.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/components/jqueryui.git",
+ "reference": "c34f8dbf3ba57b3784b93f26119f436c0e8288e1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/components/jqueryui/zipball/c34f8dbf3ba57b3784b93f26119f436c0e8288e1",
+ "reference": "c34f8dbf3ba57b3784b93f26119f436c0e8288e1",
+ "shasum": ""
+ },
+ "require": {
+ "bower-asset/jquery": ">=1.6"
+ },
+ "type": "bower-asset-library",
+ "extra": {
+ "bower-asset-main": [
+ "jquery-ui.js"
+ ],
+ "bower-asset-ignore": []
+ }
+ },
+ {
+ "name": "bower-asset/jquery.inputmask",
+ "version": "3.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/RobinHerbots/jquery.inputmask.git",
+ "reference": "5a72c563b502b8e05958a524cdfffafe9987be38"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38",
+ "reference": "5a72c563b502b8e05958a524cdfffafe9987be38",
+ "shasum": ""
+ },
+ "require": {
+ "bower-asset/jquery": ">=1.7"
+ },
+ "type": "bower-asset-library",
+ "extra": {
+ "bower-asset-main": [
+ "./dist/inputmask/inputmask.js"
+ ],
+ "bower-asset-ignore": [
+ "**/*",
+ "!dist/*",
+ "!dist/inputmask/*",
+ "!dist/min/*",
+ "!dist/min/inputmask/*",
+ "!extra/bindings/*",
+ "!extra/dependencyLibs/*",
+ "!extra/phone-codes/*"
+ ]
+ },
+ "license": [
+ "http://opensource.org/licenses/mit-license.php"
+ ],
+ "description": "jquery.inputmask is a jquery plugin which create an input mask.",
+ "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": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cebe/markdown.git",
+ "reference": "e4499350d8a94c4c693a7e784295eff7a717ae67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cebe/markdown/zipball/e4499350d8a94c4c693a7e784295eff7a717ae67",
+ "reference": "e4499350d8a94c4c693a7e784295eff7a717ae67",
+ "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": "2016-02-09 22:09:46"
+ },
+ {
+ "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-10 17:49:58"
+ },
+ {
+ "name": "developeruz/yii2-db-rbac",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/developeruz/yii2-db-rbac.git",
+ "reference": "28c1b0ebcc45b6365af6f1e9949b4d9cfeaebf1b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/developeruz/yii2-db-rbac/zipball/28c1b0ebcc45b6365af6f1e9949b4d9cfeaebf1b",
+ "reference": "28c1b0ebcc45b6365af6f1e9949b4d9cfeaebf1b",
+ "shasum": ""
+ },
+ "require": {
+ "yiisoft/yii2": "*"
+ },
+ "type": "yii2-extension",
+ "autoload": {
+ "psr-4": {
+ "developeruz\\db_rbac\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Elvira Sheina",
+ "email": "elleuz@gmail.com",
+ "homepage": "http://developer.uz"
+ }
+ ],
+ "description": "Dynamic control of access rights in YII2",
+ "keywords": [
+ "rbac",
+ "yii"
+ ],
+ "time": "2015-10-03 05:56:47"
+ },
+ {
+ "name": "dmstr/yii2-adminlte-asset",
+ "version": "2.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dmstr/yii2-adminlte-asset.git",
+ "reference": "c842a15ceef4e903f70ac927ec3246e6d53e1148"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dmstr/yii2-adminlte-asset/zipball/c842a15ceef4e903f70ac927ec3246e6d53e1148",
+ "reference": "c842a15ceef4e903f70ac927ec3246e6d53e1148",
+ "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": "Backend theme for Yii2 Framework",
+ "keywords": [
+ "AdminLTE",
+ "extension",
+ "yii2"
+ ],
+ "time": "2015-11-06 10:35:36"
+ },
+ {
+ "name": "ezyang/htmlpurifier",
+ "version": "v4.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ezyang/htmlpurifier.git",
+ "reference": "ae1828d955112356f7677c465f94f7deb7d27a40"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/ae1828d955112356f7677c465f94f7deb7d27a40",
+ "reference": "ae1828d955112356f7677c465f94f7deb7d27a40",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2"
+ },
+ "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": "2015-08-05 01:03:42"
+ },
+ {
+ "name": "fortawesome/font-awesome",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/FortAwesome/Font-Awesome.git",
+ "reference": "03fd1951e930d4d4754204fdc532ca493e924a1b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/FortAwesome/Font-Awesome/zipball/03fd1951e930d4d4754204fdc532ca493e924a1b",
+ "reference": "03fd1951e930d4d4754204fdc532ca493e924a1b",
+ "shasum": ""
+ },
+ "require-dev": {
+ "jekyll": "1.0.2",
+ "lessc": "1.4.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0.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-03-10 18:47:19"
+ },
+ {
+ "name": "imagine/imagine",
+ "version": "0.5.x-dev",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/avalanche123/Imagine.git",
+ "reference": "343580fceed1f89220481ac98480e92f47d91e6c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/avalanche123/Imagine/zipball/343580fceed1f89220481ac98480e92f47d91e6c",
+ "reference": "343580fceed1f89220481ac98480e92f47d91e6c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "sami/sami": "dev-master"
+ },
+ "suggest": {
+ "ext-gd": "to use the GD implementation",
+ "ext-gmagick": "to use the Gmagick implementation",
+ "ext-imagick": "to use the Imagick implementation"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-develop": "0.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Imagine": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bulat Shakirzyanov",
+ "email": "mallluhuct@gmail.com",
+ "homepage": "http://avalanche123.com"
+ }
+ ],
+ "description": "Image processing for PHP 5.3",
+ "homepage": "http://imagine.readthedocs.org/",
+ "keywords": [
+ "drawing",
+ "graphics",
+ "image manipulation",
+ "image processing"
+ ],
+ "time": "2014-06-13 10:54:04"
+ },
+ {
+ "name": "kartik-v/yii2-krajee-base",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/kartik-v/yii2-krajee-base.git",
+ "reference": "3e491e51ed742663b239cd6e0b7f76d403bed7e1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/kartik-v/yii2-krajee-base/zipball/3e491e51ed742663b239cd6e0b7f76d403bed7e1",
+ "reference": "3e491e51ed742663b239cd6e0b7f76d403bed7e1",
+ "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": "2016-03-03 12:24:13"
+ },
+ {
+ "name": "kartik-v/yii2-widget-colorinput",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/kartik-v/yii2-widget-colorinput.git",
+ "reference": "18537fcdab0f5491d5eebff8e2464ef6a616ee4c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/kartik-v/yii2-widget-colorinput/zipball/18537fcdab0f5491d5eebff8e2464ef6a616ee4c",
+ "reference": "18537fcdab0f5491d5eebff8e2464ef6a616ee4c",
+ "shasum": ""
+ },
+ "require": {
+ "kartik-v/yii2-krajee-base": "*"
+ },
+ "type": "yii2-extension",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "kartik\\color\\": ""
+ }
+ },
+ "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 enhanced Yii 2 widget encapsulating the HTML 5 color input (sub repo split from yii2-widgets)",
+ "homepage": "https://github.com/kartik-v/yii2-widget-colorinput",
+ "keywords": [
+ "HTML5",
+ "color",
+ "extension",
+ "form",
+ "input",
+ "jquery",
+ "plugin",
+ "widget",
+ "yii2"
+ ],
+ "time": "2016-02-02 14:28:12"
+ },
+ {
+ "name": "kartik-v/yii2-widget-select2",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/kartik-v/yii2-widget-select2.git",
+ "reference": "cb2a5992cb96bd2939e30ec1c76eba418d6a30af"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/kartik-v/yii2-widget-select2/zipball/cb2a5992cb96bd2939e30ec1c76eba418d6a30af",
+ "reference": "cb2a5992cb96bd2939e30ec1c76eba418d6a30af",
+ "shasum": ""
+ },
+ "require": {
+ "kartik-v/yii2-krajee-base": "~1.7"
+ },
+ "type": "yii2-extension",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.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": "2016-03-10 11:33:59"
+ },
+ {
+ "name": "lusitanian/oauth",
+ "version": "v0.3.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Lusitanian/PHPoAuthLib.git",
+ "reference": "4ce8c488971410233eb3b1e6d9ac4e81debb41d5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/4ce8c488971410233eb3b1e6d9ac4e81debb41d5",
+ "reference": "4ce8c488971410233eb3b1e6d9ac4e81debb41d5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "3.7.*",
+ "predis/predis": "0.8.*@dev",
+ "symfony/http-foundation": "~2.1"
+ },
+ "suggest": {
+ "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client.",
+ "predis/predis": "Allows using the Redis storage backend.",
+ "symfony/http-foundation": "Allows using the Symfony Session storage backend."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "OAuth": "src",
+ "OAuth\\Unit": "tests"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Desberg",
+ "email": "david@daviddesberg.com"
+ },
+ {
+ "name": "Pieter Hordijk",
+ "email": "info@pieterhordijk.com"
+ }
+ ],
+ "description": "PHP 5.3+ oAuth 1/2 Library",
+ "keywords": [
+ "Authentication",
+ "authorization",
+ "oauth",
+ "security"
+ ],
+ "time": "2015-09-09 06:43:02"
+ },
+ {
+ "name": "mihaildev/yii2-ckeditor",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/MihailDev/yii2-ckeditor.git",
+ "reference": "d20aa7f6bcf610fee226d6eb15212a279875bf87"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/MihailDev/yii2-ckeditor/zipball/d20aa7f6bcf610fee226d6eb15212a279875bf87",
+ "reference": "d20aa7f6bcf610fee226d6eb15212a279875bf87",
+ "shasum": ""
+ },
+ "require": {
+ "yiisoft/yii2": "*"
+ },
+ "type": "yii2-extension",
+ "extra": {
+ "asset-installer-paths": {
+ "npm-asset-library": "vendor/npm",
+ "bower-asset-library": "vendor/bower"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "mihaildev\\ckeditor\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Mihail",
+ "email": "mihail.kucher@gmail.com",
+ "homepage": "https://github.com/MihailDev",
+ "role": "Developer"
+ }
+ ],
+ "description": "Yii2 CKEditor",
+ "homepage": "https://github.com/MihailDev/yii2-ckeditor",
+ "keywords": [
+ "CKEditor",
+ "editor",
+ "wysiwyg",
+ "yii"
+ ],
+ "time": "2014-11-19 22:04:08"
+ },
+ {
+ "name": "mihaildev/yii2-elfinder",
+ "version": "1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/MihailDev/yii2-elfinder.git",
+ "reference": "64b42572dec94e5c2d0d1630bce8292848a98f4b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/MihailDev/yii2-elfinder/zipball/64b42572dec94e5c2d0d1630bce8292848a98f4b",
+ "reference": "64b42572dec94e5c2d0d1630bce8292848a98f4b",
+ "shasum": ""
+ },
+ "require": {
+ "yiisoft/yii2-jui": "*"
+ },
+ "type": "yii2-extension",
+ "extra": {
+ "asset-installer-paths": {
+ "npm-asset-library": "vendor/npm",
+ "bower-asset-library": "vendor/bower"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "mihaildev\\elfinder\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Mihail",
+ "email": "mihail.kucher@gmail.com",
+ "homepage": "https://github.com/MihailDev",
+ "role": "Developer"
+ }
+ ],
+ "description": "Yii2 ElFinder",
+ "homepage": "https://github.com/MihailDev/yii2-elfinder",
+ "keywords": [
+ "elfinder",
+ "filemanager",
+ "yii"
+ ],
+ "time": "2015-07-03 07:08:52"
+ },
+ {
+ "name": "nodge/lightopenid",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Nodge/LightOpenID.git",
+ "reference": "a5492cc0c932c557b7e9b54a6e5bbd85cc5fa041"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Nodge/LightOpenID/zipball/a5492cc0c932c557b7e9b54a6e5bbd85cc5fa041",
+ "reference": "a5492cc0c932c557b7e9b54a6e5bbd85cc5fa041",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "openid.php",
+ "provider/provider.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT License"
+ ],
+ "authors": [
+ {
+ "name": "Mewp",
+ "homepage": "http://code.google.com/p/lightopenid/"
+ },
+ {
+ "name": "Ignat Ignatov",
+ "homepage": "https://github.com/iignatov/LightOpenID"
+ }
+ ],
+ "description": "Lightweight PHP5 library for easy OpenID authentication.",
+ "homepage": "https://github.com/Nodge/LightOpenID",
+ "keywords": [
+ "Authentication",
+ "OpenId"
+ ],
+ "time": "2013-08-31 16:48:56"
+ },
+ {
+ "name": "nodge/yii2-eauth",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Nodge/yii2-eauth.git",
+ "reference": "f45efd95e3853db33153cc1b856d1f648d221938"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Nodge/yii2-eauth/zipball/f45efd95e3853db33153cc1b856d1f648d221938",
+ "reference": "f45efd95e3853db33153cc1b856d1f648d221938",
+ "shasum": ""
+ },
+ "require": {
+ "lib-curl": "*",
+ "lusitanian/oauth": "~0.3.0",
+ "nodge/lightopenid": "~1.1.0",
+ "php": ">=5.4.0",
+ "yiisoft/yii2": "*"
+ },
+ "type": "yii2-extension",
+ "extra": {
+ "bootstrap": "nodge\\eauth\\Bootstrap"
+ },
+ "autoload": {
+ "psr-4": {
+ "nodge\\eauth\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "New BSD License"
+ ],
+ "authors": [
+ {
+ "name": "Maxim Zemskov",
+ "email": "nodge@yandex.ru",
+ "homepage": "http://nodge.ru/"
+ }
+ ],
+ "description": "Yii2 EAuth Extension. EAuth allows to authenticate users with accounts on other websites (Google, Facebook, Twitter, etc).",
+ "homepage": "https://github.com/Nodge/yii2-eauth",
+ "keywords": [
+ "Authentication",
+ "OpenId",
+ "eauth",
+ "extension",
+ "oauth",
+ "yii2"
+ ],
+ "time": "2016-01-13 18:15:48"
+ },
+ {
+ "name": "rmrevin/yii2-comments",
+ "version": "1.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/rmrevin/yii2-comments.git",
+ "reference": "c68ddf276fe24ece0173781a8f7391a2cf7e6a12"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/rmrevin/yii2-comments/zipball/c68ddf276fe24ece0173781a8f7391a2cf7e6a12",
+ "reference": "c68ddf276fe24ece0173781a8f7391a2cf7e6a12",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "rmrevin/yii2-fontawesome": "~2.10",
+ "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\\module\\Comments\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Revin",
+ "email": "xgismox@gmail.com",
+ "homepage": "http://rmrevin.ru/"
+ }
+ ],
+ "description": "Comments module for Yii2",
+ "keywords": [
+ "comment",
+ "module",
+ "widget",
+ "yii"
+ ],
+ "time": "2016-03-01 13:18:41"
+ },
+ {
+ "name": "rmrevin/yii2-fontawesome",
+ "version": "2.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/rmrevin/yii2-fontawesome.git",
+ "reference": "2efbfacb22be59f373d11a7e3dfa9213e2ba18a9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/rmrevin/yii2-fontawesome/zipball/2efbfacb22be59f373d11a7e3dfa9213e2ba18a9",
+ "reference": "2efbfacb22be59f373d11a7e3dfa9213e2ba18a9",
+ "shasum": ""
+ },
+ "require": {
+ "bower-asset/fontawesome": "4.5.*",
+ "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": "2015-11-26 15:24:53"
+ },
+ {
+ "name": "swiftmailer/swiftmailer",
+ "version": "5.x-dev",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/swiftmailer/swiftmailer.git",
+ "reference": "fffbc0e2a7e376dbb0a4b5f2ff6847330f20ccf9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/fffbc0e2a7e376dbb0a4b5f2ff6847330f20ccf9",
+ "reference": "fffbc0e2a7e376dbb0a4b5f2ff6847330f20ccf9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "mockery/mockery": "~0.9.1,<0.9.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.4-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "lib/swift_required.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Corbyn"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Swiftmailer, free feature-rich PHP mailer",
+ "homepage": "http://swiftmailer.org",
+ "keywords": [
+ "email",
+ "mail",
+ "mailer"
+ ],
+ "time": "2016-01-03 15:42:47"
+ },
+ {
+ "name": "yiisoft/yii2",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-framework.git",
+ "reference": "88ceadba3fc19ec90f72b6ba895347b7fecd3dfb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/88ceadba3fc19ec90f72b6ba895347b7fecd3dfb",
+ "reference": "88ceadba3fc19ec90f72b6ba895347b7fecd3dfb",
+ "shasum": ""
+ },
+ "require": {
+ "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable",
+ "bower-asset/jquery.inputmask": "~3.2.2",
+ "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"
+ }
+ ],
+ "description": "Yii PHP Framework Version 2",
+ "homepage": "http://www.yiiframework.com/",
+ "keywords": [
+ "framework",
+ "yii2"
+ ],
+ "time": "2016-03-14 12:11:50"
+ },
+ {
+ "name": "yiisoft/yii2-bootstrap",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-bootstrap.git",
+ "reference": "4133d6b26f48615de38ea1ec04eb00c4b7057bc9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-bootstrap/zipball/4133d6b26f48615de38ea1ec04eb00c4b7057bc9",
+ "reference": "4133d6b26f48615de38ea1ec04eb00c4b7057bc9",
+ "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-04 00:48:53"
+ },
+ {
+ "name": "yiisoft/yii2-composer",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-composer.git",
+ "reference": "d33d1046a5951f2f7823fe343f28ddc58b3421a4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/d33d1046a5951f2f7823fe343f28ddc58b3421a4",
+ "reference": "d33d1046a5951f2f7823fe343f28ddc58b3421a4",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^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-02-06 01:03:32"
+ },
+ {
+ "name": "yiisoft/yii2-imagine",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-imagine.git",
+ "reference": "d87e6a0d1adfd6fa5ef49c18228b2fc9bca04299"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-imagine/zipball/d87e6a0d1adfd6fa5ef49c18228b2fc9bca04299",
+ "reference": "d87e6a0d1adfd6fa5ef49c18228b2fc9bca04299",
+ "shasum": ""
+ },
+ "require": {
+ "imagine/imagine": "0.5.*",
+ "yiisoft/yii2": "*"
+ },
+ "type": "yii2-extension",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "yii\\imagine\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Antonio Ramirez",
+ "email": "amigo.cobos@gmail.com"
+ }
+ ],
+ "description": "The Imagine integration for the Yii framework",
+ "keywords": [
+ "helper",
+ "image",
+ "imagine",
+ "yii2"
+ ],
+ "time": "2016-02-21 23:09:41"
+ },
+ {
+ "name": "yiisoft/yii2-jui",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-jui.git",
+ "reference": "1425ab29929dd195f468d3c4eb340ab509f28b83"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-jui/zipball/1425ab29929dd195f468d3c4eb340ab509f28b83",
+ "reference": "1425ab29929dd195f468d3c4eb340ab509f28b83",
+ "shasum": ""
+ },
+ "require": {
+ "bower-asset/jquery-ui": "1.11.*@stable",
+ "yiisoft/yii2": ">=2.0.4"
+ },
+ "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\\jui\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Qiang Xue",
+ "email": "qiang.xue@gmail.com"
+ }
+ ],
+ "description": "The Jquery UI extension for the Yii framework",
+ "keywords": [
+ "jQuery UI",
+ "yii2"
+ ],
+ "time": "2015-12-24 06:23:53"
+ },
+ {
+ "name": "yiisoft/yii2-swiftmailer",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-swiftmailer.git",
+ "reference": "2cca1bb86444a6438b0720f8c60120a400982366"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-swiftmailer/zipball/2cca1bb86444a6438b0720f8c60120a400982366",
+ "reference": "2cca1bb86444a6438b0720f8c60120a400982366",
+ "shasum": ""
+ },
+ "require": {
+ "swiftmailer/swiftmailer": "~5.0",
+ "yiisoft/yii2": ">=2.0.4"
+ },
+ "type": "yii2-extension",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "yii\\swiftmailer\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Paul Klimov",
+ "email": "klimov.paul@gmail.com"
+ }
+ ],
+ "description": "The SwiftMailer integration for the Yii framework",
+ "keywords": [
+ "email",
+ "mail",
+ "mailer",
+ "swift",
+ "swiftmailer",
+ "yii2"
+ ],
+ "time": "2015-12-07 11:40:31"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "bower-asset/typeahead.js",
+ "version": "v0.11.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twitter/typeahead.js.git",
+ "reference": "588440f66559714280628a4f9799f0c4eb880a4a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twitter/typeahead.js/zipball/588440f66559714280628a4f9799f0c4eb880a4a",
+ "reference": "588440f66559714280628a4f9799f0c4eb880a4a",
+ "shasum": ""
+ },
+ "require": {
+ "bower-asset/jquery": ">=1.7"
+ },
+ "require-dev": {
+ "bower-asset/jasmine-ajax": "~1.3.1",
+ "bower-asset/jasmine-jquery": "~1.5.2",
+ "bower-asset/jquery": "~1.7"
+ },
+ "type": "bower-asset-library",
+ "extra": {
+ "bower-asset-main": "dist/typeahead.bundle.js"
+ }
+ },
+ {
+ "name": "fzaninotto/faker",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fzaninotto/Faker.git",
+ "reference": "588da2fff1b9da3acb9b6e1d7395e820102bde01"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/588da2fff1b9da3acb9b6e1d7395e820102bde01",
+ "reference": "588da2fff1b9da3acb9b6e1d7395e820102bde01",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "ext-intl": "*",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "~1.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Faker\\": "src/Faker/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "François Zaninotto"
+ }
+ ],
+ "description": "Faker is a PHP library that generates fake data for you.",
+ "keywords": [
+ "data",
+ "faker",
+ "fixtures"
+ ],
+ "time": "2016-03-10 07:12:46"
+ },
+ {
+ "name": "phpspec/php-diff",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/php-diff.git",
+ "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/php-diff/zipball/30e103d19519fe678ae64a60d77884ef3d71b28a",
+ "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Diff": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Chris Boulton",
+ "homepage": "http://github.com/chrisboulton",
+ "role": "Original developer"
+ }
+ ],
+ "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).",
+ "time": "2013-11-01 13:02:21"
+ },
+ {
+ "name": "yiisoft/yii2-codeception",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-codeception.git",
+ "reference": "ee239c244fd011f11c8827f97ebe7600d03f1841"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-codeception/zipball/ee239c244fd011f11c8827f97ebe7600d03f1841",
+ "reference": "ee239c244fd011f11c8827f97ebe7600d03f1841",
+ "shasum": ""
+ },
+ "require": {
+ "yiisoft/yii2": ">=2.0.4"
+ },
+ "type": "yii2-extension",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "yii\\codeception\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Mark Jebri",
+ "email": "mark.github@yandex.ru"
+ }
+ ],
+ "description": "The Codeception integration for the Yii framework",
+ "keywords": [
+ "codeception",
+ "yii2"
+ ],
+ "time": "2015-11-20 08:52:21"
+ },
+ {
+ "name": "yiisoft/yii2-debug",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-debug.git",
+ "reference": "b258732b10a706d3fa41829f13be727e49dabd09"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/b258732b10a706d3fa41829f13be727e49dabd09",
+ "reference": "b258732b10a706d3fa41829f13be727e49dabd09",
+ "shasum": ""
+ },
+ "require": {
+ "yiisoft/yii2": ">=2.0.4",
+ "yiisoft/yii2-bootstrap": "*"
+ },
+ "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": "2016-03-14 21:47:38"
+ },
+ {
+ "name": "yiisoft/yii2-faker",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-faker.git",
+ "reference": "186c77214e0a4b75f10380b4e6e5f82788922cb7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-faker/zipball/186c77214e0a4b75f10380b4e6e5f82788922cb7",
+ "reference": "186c77214e0a4b75f10380b4e6e5f82788922cb7",
+ "shasum": ""
+ },
+ "require": {
+ "fzaninotto/faker": "~1.4",
+ "yiisoft/yii2": "*"
+ },
+ "type": "yii2-extension",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "yii\\faker\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Mark Jebri",
+ "email": "mark.github@yandex.ru"
+ }
+ ],
+ "description": "Fixture generator. The Faker integration for the Yii framework.",
+ "keywords": [
+ "Fixture",
+ "faker",
+ "yii2"
+ ],
+ "time": "2015-12-18 01:52:12"
+ },
+ {
+ "name": "yiisoft/yii2-gii",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yiisoft/yii2-gii.git",
+ "reference": "ce42838abcbef076ebaf46147671d518ae69d028"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yiisoft/yii2-gii/zipball/ce42838abcbef076ebaf46147671d518ae69d028",
+ "reference": "ce42838abcbef076ebaf46147671d518ae69d028",
+ "shasum": ""
+ },
+ "require": {
+ "bower-asset/typeahead.js": "0.10.* | ~0.11.0",
+ "phpspec/php-diff": ">=1.0.2",
+ "yiisoft/yii2": ">=2.0.4",
+ "yiisoft/yii2-bootstrap": "~2.0"
+ },
+ "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\\gii\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Qiang Xue",
+ "email": "qiang.xue@gmail.com"
+ }
+ ],
+ "description": "The Gii extension for the Yii framework",
+ "keywords": [
+ "code generator",
+ "gii",
+ "yii2"
+ ],
+ "time": "2016-02-21 20:39:29"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": {
+ "kartik-v/yii2-widget-select2": 20
+ },
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.4.0"
+ },
+ "platform-dev": []
+}
diff --git a/console/config/.gitignore b/console/config/.gitignore
new file mode 100644
index 0000000..20da318
--- /dev/null
+++ b/console/config/.gitignore
@@ -0,0 +1,2 @@
+main-local.php
+params-local.php
\ No newline at end of file
diff --git a/console/config/bootstrap.php b/console/config/bootstrap.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/console/config/bootstrap.php
@@ -0,0 +1 @@
+ 'app-console',
+ 'basePath' => dirname(__DIR__),
+ 'bootstrap' => ['log'],
+ 'controllerNamespace' => 'console\controllers',
+ 'components' => [
+ 'log' => [
+ 'targets' => [
+ [
+ 'class' => 'yii\log\FileTarget',
+ 'levels' => ['error', 'warning'],
+ ],
+ ],
+ ],
+ ],
+ 'params' => $params,
+];
diff --git a/console/config/params.php b/console/config/params.php
new file mode 100644
index 0000000..7f754b9
--- /dev/null
+++ b/console/config/params.php
@@ -0,0 +1,4 @@
+ 'admin@example.com',
+];
diff --git a/console/controllers/.gitkeep b/console/controllers/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/console/controllers/.gitkeep
diff --git a/console/migrations/m130524_201442_init.php b/console/migrations/m130524_201442_init.php
new file mode 100644
index 0000000..6b649f4
--- /dev/null
+++ b/console/migrations/m130524_201442_init.php
@@ -0,0 +1,33 @@
+db->driverName === 'mysql') {
+ // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
+ $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
+ }
+
+ $this->createTable('{{%user}}', [
+ 'id' => $this->primaryKey(),
+ 'username' => $this->string()->notNull()->unique(),
+ 'auth_key' => $this->string(32)->notNull(),
+ 'password_hash' => $this->string()->notNull(),
+ 'password_reset_token' => $this->string()->unique(),
+ 'email' => $this->string()->notNull()->unique(),
+
+ 'status' => $this->smallInteger()->notNull()->defaultValue(10),
+ 'created_at' => $this->integer()->notNull(),
+ 'updated_at' => $this->integer()->notNull(),
+ ], $tableOptions);
+ }
+
+ public function down()
+ {
+ $this->dropTable('{{%user}}');
+ }
+}
diff --git a/console/migrations/m160126_071717_rubrication.php b/console/migrations/m160126_071717_rubrication.php
new file mode 100644
index 0000000..200cb49
--- /dev/null
+++ b/console/migrations/m160126_071717_rubrication.php
@@ -0,0 +1,135 @@
+db->driverName === 'mysql') {
+ // Only for MySQL
+ $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
+
+ // @todo https://habrahabr.ru/post/138947/
+ } elseif ($this->db->driverName === 'pgsql') {
+ // Only for PostgreSQL
+ // @todo use intarray field for tax_options
+ }
+ $this->createTable('{{%tax_group}}', [
+ 'tax_group_id' => $this->primaryKey(),
+ 'alias' => $this->string(50)->notNull(),
+ 'name' => $this->string(255)->notNull(),
+ 'description' => $this->text(),
+ 'module' => $this->string(50)->notNull(),
+ 'hierarchical' => $this->boolean()->notNull()->defaultValue(false),
+ 'settings' => $this->text()
+ ], $tableOptions);
+
+ $this->createTable('{{%tax_option}}', [
+ 'tax_option_id' => $this->bigPrimaryKey(),
+ 'tax_group_id' => $this->integer()->notNull(),
+ 'parent_id' => $this->integer()->notNull()->defaultValue(0),
+ 'path' => $this->string(),
+ 'depth' => $this->integer(),
+ 'sort' => $this->integer()->notNull()->defaultValue(0),
+ 'default_value' => $this->integer(),
+ 'alias' => $this->string(50)->notNull()->defaultValue(''),
+ ], $tableOptions);
+ $this->addForeignKey('fki_tax_option_tax_group_id', 'tax_option', 'tax_group_id', 'tax_group', 'tax_group_id', 'CASCADE', 'CASCADE');
+
+ $this->createTable('{{%tax_group_to_group}}', [
+ 'tax_group1_id' => $this->integer()->notNull(),
+ 'tax_group2_id' => $this->integer()->notNull(),
+ 'alias' => $this->string(50)->notNull(),
+ 'sort' => $this->integer(),
+ ], $tableOptions);
+ $this->addForeignKey('fki_tax_group_to_group1', 'tax_group_to_group', 'tax_group1_id', 'tax_group', 'tax_group_id', 'CASCADE', 'CASCADE');
+ $this->addForeignKey('fki_tax_group_to_group2', 'tax_group_to_group', 'tax_group2_id', 'tax_group', 'tax_group_id', 'CASCADE', 'CASCADE');
+ $this->addPrimaryKey('pki_tax_group_to_group', 'tax_group_to_group', ['tax_group1_id', 'tax_group2_id', 'alias']);
+
+ $this->createTable('{{%tax_option_to_group}}', [
+ 'tax_option_id' => $this->integer()->notNull(),
+ 'tax_group_id' => $this->integer()->notNull(),
+ 'alias' => $this->string(50)->notNull(),
+ 'sort' => $this->integer(),
+ ], $tableOptions);
+ $this->addForeignKey('tax_option_to_group_option_id', 'tax_option_to_group', 'tax_option_id', 'tax_option', 'tax_option_id', 'CASCADE', 'CASCADE');
+ $this->addForeignKey('tax_option_to_group_group_id', 'tax_option_to_group', 'tax_group_id', 'tax_group', 'tax_group_id', 'CASCADE', 'CASCADE');
+ $this->addPrimaryKey('pki_tax_option_to_group', 'tax_option_to_group', ['tax_option_id', 'tax_group_id', 'alias']);
+
+ $this->createTable('{{%tax_option_to_option}}', [
+ 'tax_option1_id' => $this->integer()->notNull(),
+ 'tax_option2_id' => $this->integer()->notNull(),
+ 'alias' => $this->string(50)->notNull(),
+ 'sort' => $this->integer(),
+ ], $tableOptions);
+ $this->addForeignKey('fki_tax_option_to_option1', 'tax_option_to_option', 'tax_option1_id', 'tax_option', 'tax_option_id', 'CASCADE', 'CASCADE');
+ $this->addForeignKey('fki_tax_option_to_option2', 'tax_option_to_option', 'tax_option2_id', 'tax_option', 'tax_option_id', 'CASCADE', 'CASCADE');
+ $this->addPrimaryKey('pki_tax_option_to_option', 'tax_option_to_option', ['tax_option1_id', 'tax_option2_id', 'alias']);
+
+ /*$this->createTable('{{%tax_entity_relation}}', [
+ 'tax_option_id' => $this->integer()->notNull(),
+ 'entity_id' => $this->integer()->notNull(),
+ 'entity_table_name' => $this->string(50)->notNull(),
+ 'entity_key_name' => $this->string(50)->notNull(),
+ 'alias' => $this->string(50)->notNull(),
+ ], $tableOptions);
+ $this->addForeignKey('fki_tax_entity_relation_option_id', 'tax_entity_relation', 'tax_option_id', 'tax_option', 'tax_option_id', 'CASCADE', 'CASCADE');
+ $this->addPrimaryKey('pki_tax_entity_relation', 'tax_entity_relation', ['tax_option_id', 'entity_id', 'entity_table_name', 'entity_key_name', 'alias']);*/
+
+ $this->createTable('{{%tax_value_int}}', [
+ 'tax_value_id' => $this->primaryKey(),
+ 'tax_option_id' => $this->integer()->notNull(),
+ 'value' => $this->integer(),
+ ], $tableOptions);
+ $this->addForeignKey('fki_tax_value_int_option_id', 'tax_value_int', 'tax_option_id', 'tax_option', 'tax_option_id', 'CASCADE', 'CASCADE');
+
+ $this->createTable('{{%tax_value_string}}', [
+ 'tax_value_id' => $this->primaryKey(),
+ 'tax_option_id' => $this->integer()->notNull(),
+ 'value' => $this->string(),
+ ], $tableOptions);
+ $this->addForeignKey('fki_tax_value_string_option_id', 'tax_value_string', 'tax_option_id', 'tax_option', 'tax_option_id', 'CASCADE', 'CASCADE');
+
+ $this->createTable('{{%tax_value_text}}', [
+ 'tax_value_id' => $this->primaryKey(),
+ 'tax_option_id' => $this->integer()->notNull(),
+ 'value' => $this->text(),
+ ], $tableOptions);
+ $this->addForeignKey('fki_tax_value_text_option_id', 'tax_value_text', 'tax_option_id', 'tax_option', 'tax_option_id', 'CASCADE', 'CASCADE');
+
+ $this->createTable('{{%tax_value_float}}', [
+ 'tax_value_id' => $this->bigPrimaryKey(),
+ 'tax_option_id' => $this->integer()->notNull(),
+ 'value' => $this->float(),
+ ], $tableOptions);
+ $this->addForeignKey('fki_tax_value_float_option_id', 'tax_value_float', 'tax_option_id', 'tax_option', 'tax_option_id', 'CASCADE', 'CASCADE');
+ }
+
+ public function safeDown()
+ {
+ $this->dropTable('{{%tax_value_int}}');
+ $this->dropTable('{{%tax_value_string}}');
+ $this->dropTable('{{%tax_value_text}}');
+ $this->dropTable('{{%tax_value_float}}');
+// $this->dropTable('{{%tax_entity_relation}}');
+ $this->dropTable('{{%tax_group_to_group}}');
+ $this->dropTable('{{%tax_option_to_group}}');
+ $this->dropTable('{{%tax_option_to_option}}');
+ $this->dropTable('{{%tax_option}}');
+ $this->dropTable('{{%tax_group}}');
+ }
+
+ /*
+ // Use safeUp/safeDown to run migration code within a transaction
+ public function safeUp()
+ {
+ }
+
+ public function safeDown()
+ {
+ }
+ */
+}
diff --git a/console/migrations/m160128_101543_fields.php b/console/migrations/m160128_101543_fields.php
new file mode 100644
index 0000000..cb49e9e
--- /dev/null
+++ b/console/migrations/m160128_101543_fields.php
@@ -0,0 +1,29 @@
+createTable('{{%fields}}', [
+ 'id' => $this->primaryKey(),
+ 'table_name' => $this->string(255)->notNull(),
+ 'table_id' => $this->integer(),
+ 'value' => $this->string(255),
+ 'field_name' => $this->string(),
+ 'field_type' => $this->string(32)->notNull(),
+ 'language' => $this->string(3),
+ 'key' => $this->integer(),
+ 'parent_key' => $this->integer()
+ ], $tableOptions);
+
+ }
+
+ public function down()
+ {
+ $this->dropTable('{{%fields}}');
+ }
+}
diff --git a/console/migrations/m160208_111900_blog.php b/console/migrations/m160208_111900_blog.php
new file mode 100644
index 0000000..ca88b1c
--- /dev/null
+++ b/console/migrations/m160208_111900_blog.php
@@ -0,0 +1,30 @@
+createTable('{{%blog}}', [
+ 'blog_id' => $this->primaryKey(),
+ 'user_id' => $this->integer()->notNull(),
+ 'name' => $this->string(255)->notNull(),
+ 'link' => $this->string(255),
+ 'date_add' => $this->timestamp()->notNull(),
+ 'user_add_id' => $this->integer(),
+ 'view_count' => $this->integer()->defaultValue(0),
+ 'description' => $this->text(),
+ 'cover' => $this->string(255),
+ ], $tableOptions);
+
+ }
+
+ public function down()
+ {
+ $this->dropTable('{{%blog}}');
+ }
+
+}
diff --git a/console/migrations/m160304_054017_realtion.php b/console/migrations/m160304_054017_realtion.php
new file mode 100644
index 0000000..4f5b11c
--- /dev/null
+++ b/console/migrations/m160304_054017_realtion.php
@@ -0,0 +1,44 @@
+db->driverName === 'mysql') {
+ // Only for MySQL
+ $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
+
+ // @todo https://habrahabr.ru/post/138947/
+ } elseif ($this->db->driverName === 'pgsql') {
+ // Only for PostgreSQL
+ // @todo use intarray field for tax_options
+ }
+ $this->createTable('{{%relation}}', [
+ 'alias' => $this->string(50)->notNull(),
+ 'entity1_id' => $this->integer()->notNull(),
+ 'entity2_id' => $this->integer()->notNull(),
+ ], $tableOptions);
+ $this->addPrimaryKey('relation_ukey', 'relation', ['alias', 'entity1_id', 'entity2_id']);
+ }
+
+ public function down()
+ {
+ $this->dropTable('{{%relation}}');
+
+ return false;
+ }
+
+ /*
+ // Use safeUp/safeDown to run migration code within a transaction
+ public function safeUp()
+ {
+ }
+
+ public function safeDown()
+ {
+ }
+ */
+}
diff --git a/console/migrations/m160304_065108_product.php b/console/migrations/m160304_065108_product.php
new file mode 100644
index 0000000..7925483
--- /dev/null
+++ b/console/migrations/m160304_065108_product.php
@@ -0,0 +1,50 @@
+db->driverName === 'mysql') {
+ // Only for MySQL
+ $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
+
+ // @todo https://habrahabr.ru/post/138947/
+ } elseif ($this->db->driverName === 'pgsql') {
+ // Only for PostgreSQL
+ // @todo use intarray field for tax_options
+ }
+ $this->createTable('{{%product}}', [
+ 'product_id' => $this->primaryKey(),
+ 'name' => $this->string(255)->notNull(),
+ ], $tableOptions);
+
+ $this->createTable('{{%product_category}}', [
+ 'product_id' => $this->integer()->notNull(),
+ 'category_id' => $this->integer()->notNull(),
+ ], $tableOptions);
+ $this->addForeignKey('fki_product_id', 'product_category', 'product_id', 'product', 'product_id', 'NO ACTION', 'NO ACTION');
+ $this->addForeignKey('fki_category_id', 'product_category', 'category_id', 'tax_option', 'tax_option_id', 'NO ACTION', 'NO ACTION');
+ }
+
+ public function down()
+ {
+ $this->dropTable('{{%product}}');
+ $this->dropTable('{{%product_category}}');
+
+ return false;
+ }
+
+ /*
+ // Use safeUp/safeDown to run migration code within a transaction
+ public function safeUp()
+ {
+ }
+
+ public function safeDown()
+ {
+ }
+ */
+}
diff --git a/console/models/.gitkeep b/console/models/.gitkeep
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/console/models/.gitkeep
@@ -0,0 +1 @@
+*
diff --git a/console/runtime/.gitignore b/console/runtime/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/console/runtime/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/environments/dev/backend/config/main-local.php b/environments/dev/backend/config/main-local.php
new file mode 100644
index 0000000..d9a8ceb
--- /dev/null
+++ b/environments/dev/backend/config/main-local.php
@@ -0,0 +1,25 @@
+ [
+ 'request' => [
+ // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+ 'cookieValidationKey' => '',
+ ],
+ ],
+];
+
+if (!YII_ENV_TEST) {
+ // configuration adjustments for 'dev' environment
+ $config['bootstrap'][] = 'debug';
+ $config['modules']['debug'] = [
+ 'class' => 'yii\debug\Module',
+ ];
+
+ $config['bootstrap'][] = 'gii';
+ $config['modules']['gii'] = [
+ 'class' => 'yii\gii\Module',
+ ];
+}
+
+return $config;
diff --git a/environments/dev/backend/config/params-local.php b/environments/dev/backend/config/params-local.php
new file mode 100644
index 0000000..d0b9c34
--- /dev/null
+++ b/environments/dev/backend/config/params-local.php
@@ -0,0 +1,3 @@
+run();
diff --git a/environments/dev/backend/web/index.php b/environments/dev/backend/web/index.php
new file mode 100644
index 0000000..6038167
--- /dev/null
+++ b/environments/dev/backend/web/index.php
@@ -0,0 +1,18 @@
+run();
diff --git a/environments/dev/common/config/main-local.php b/environments/dev/common/config/main-local.php
new file mode 100644
index 0000000..43db30e
--- /dev/null
+++ b/environments/dev/common/config/main-local.php
@@ -0,0 +1,20 @@
+ [
+ 'db' => [
+ 'class' => 'yii\db\Connection',
+ 'dsn' => 'mysql:host=localhost;dbname=yii2advanced',
+ 'username' => 'root',
+ 'password' => '',
+ 'charset' => 'utf8',
+ ],
+ 'mailer' => [
+ 'class' => 'yii\swiftmailer\Mailer',
+ 'viewPath' => '@common/mail',
+ // send all mails to a file by default. You have to set
+ // 'useFileTransport' to false and configure a transport
+ // for the mailer to send real emails.
+ 'useFileTransport' => true,
+ ],
+ ],
+];
diff --git a/environments/dev/common/config/params-local.php b/environments/dev/common/config/params-local.php
new file mode 100644
index 0000000..d0b9c34
--- /dev/null
+++ b/environments/dev/common/config/params-local.php
@@ -0,0 +1,3 @@
+ ['gii'],
+ 'modules' => [
+ 'gii' => 'yii\gii\Module',
+ ],
+];
diff --git a/environments/dev/console/config/params-local.php b/environments/dev/console/config/params-local.php
new file mode 100644
index 0000000..d0b9c34
--- /dev/null
+++ b/environments/dev/console/config/params-local.php
@@ -0,0 +1,3 @@
+ [
+ 'request' => [
+ // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+ 'cookieValidationKey' => '',
+ ],
+ ],
+];
+
+if (!YII_ENV_TEST) {
+ // configuration adjustments for 'dev' environment
+ $config['bootstrap'][] = 'debug';
+ $config['modules']['debug'] = [
+ 'class' => 'yii\debug\Module',
+ ];
+ $config['bootstrap'][] = 'gii';
+ $config['modules']['gii'] = [
+ 'class' => 'yii\gii\Module',
+ ];
+}
+
+return $config;
diff --git a/environments/dev/frontend/config/params-local.php b/environments/dev/frontend/config/params-local.php
new file mode 100644
index 0000000..d0b9c34
--- /dev/null
+++ b/environments/dev/frontend/config/params-local.php
@@ -0,0 +1,3 @@
+run();
diff --git a/environments/dev/frontend/web/index.php b/environments/dev/frontend/web/index.php
new file mode 100644
index 0000000..6038167
--- /dev/null
+++ b/environments/dev/frontend/web/index.php
@@ -0,0 +1,18 @@
+run();
diff --git a/environments/dev/yii b/environments/dev/yii
new file mode 100644
index 0000000..6f0c6d2
--- /dev/null
+++ b/environments/dev/yii
@@ -0,0 +1,28 @@
+#!/usr/bin/env php
+run();
+exit($exitCode);
diff --git a/environments/index.php b/environments/index.php
new file mode 100644
index 0000000..19c989d
--- /dev/null
+++ b/environments/index.php
@@ -0,0 +1,65 @@
+ [
+ * 'path' => 'directory storing the local files',
+ * 'skipFiles' => [
+ * // list of files that should only copied once and skipped if they already exist
+ * ],
+ * 'setWritable' => [
+ * // list of directories that should be set writable
+ * ],
+ * 'setExecutable' => [
+ * // list of files that should be set executable
+ * ],
+ * 'setCookieValidationKey' => [
+ * // list of config files that need to be inserted with automatically generated cookie validation keys
+ * ],
+ * 'createSymlink' => [
+ * // list of symlinks to be created. Keys are symlinks, and values are the targets.
+ * ],
+ * ],
+ * ];
+ * ```
+ */
+return [
+ 'Development' => [
+ 'path' => 'dev',
+ 'setWritable' => [
+ 'backend/runtime',
+ 'backend/web/assets',
+ 'frontend/runtime',
+ 'frontend/web/assets',
+ ],
+ 'setExecutable' => [
+ 'yii',
+ 'tests/codeception/bin/yii',
+ ],
+ 'setCookieValidationKey' => [
+ 'backend/config/main-local.php',
+ 'frontend/config/main-local.php',
+ ],
+ ],
+ 'Production' => [
+ 'path' => 'prod',
+ 'setWritable' => [
+ 'backend/runtime',
+ 'backend/web/assets',
+ 'frontend/runtime',
+ 'frontend/web/assets',
+ ],
+ 'setExecutable' => [
+ 'yii',
+ ],
+ 'setCookieValidationKey' => [
+ 'backend/config/main-local.php',
+ 'frontend/config/main-local.php',
+ ],
+ ],
+];
diff --git a/environments/prod/backend/config/main-local.php b/environments/prod/backend/config/main-local.php
new file mode 100644
index 0000000..af46ba3
--- /dev/null
+++ b/environments/prod/backend/config/main-local.php
@@ -0,0 +1,9 @@
+ [
+ 'request' => [
+ // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+ 'cookieValidationKey' => '',
+ ],
+ ],
+];
diff --git a/environments/prod/backend/config/params-local.php b/environments/prod/backend/config/params-local.php
new file mode 100644
index 0000000..d0b9c34
--- /dev/null
+++ b/environments/prod/backend/config/params-local.php
@@ -0,0 +1,3 @@
+run();
diff --git a/environments/prod/common/config/main-local.php b/environments/prod/common/config/main-local.php
new file mode 100644
index 0000000..84c4d9f
--- /dev/null
+++ b/environments/prod/common/config/main-local.php
@@ -0,0 +1,16 @@
+ [
+ 'db' => [
+ 'class' => 'yii\db\Connection',
+ 'dsn' => 'mysql:host=localhost;dbname=yii2advanced',
+ 'username' => 'root',
+ 'password' => '',
+ 'charset' => 'utf8',
+ ],
+ 'mailer' => [
+ 'class' => 'yii\swiftmailer\Mailer',
+ 'viewPath' => '@common/mail',
+ ],
+ ],
+];
diff --git a/environments/prod/common/config/params-local.php b/environments/prod/common/config/params-local.php
new file mode 100644
index 0000000..d0b9c34
--- /dev/null
+++ b/environments/prod/common/config/params-local.php
@@ -0,0 +1,3 @@
+ [
+ 'request' => [
+ // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+ 'cookieValidationKey' => '',
+ ],
+ ],
+];
diff --git a/environments/prod/frontend/config/params-local.php b/environments/prod/frontend/config/params-local.php
new file mode 100644
index 0000000..d0b9c34
--- /dev/null
+++ b/environments/prod/frontend/config/params-local.php
@@ -0,0 +1,3 @@
+run();
diff --git a/environments/prod/yii b/environments/prod/yii
new file mode 100644
index 0000000..1fe0342
--- /dev/null
+++ b/environments/prod/yii
@@ -0,0 +1,28 @@
+#!/usr/bin/env php
+run();
+exit($exitCode);
diff --git a/frontend/config/.gitignore b/frontend/config/.gitignore
new file mode 100644
index 0000000..20da318
--- /dev/null
+++ b/frontend/config/.gitignore
@@ -0,0 +1,2 @@
+main-local.php
+params-local.php
\ No newline at end of file
diff --git a/frontend/config/bootstrap.php b/frontend/config/bootstrap.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/frontend/config/bootstrap.php
@@ -0,0 +1 @@
+ 'app-frontend',
+ 'basePath' => dirname(__DIR__),
+ 'bootstrap' => ['log'],
+ 'controllerNamespace' => 'frontend\controllers',
+ 'components' => [
+ 'user' => [
+ 'identityClass' => 'common\models\User',
+ 'enableAutoLogin' => true,
+ 'identityCookie' => [
+ 'name' => '_frontendUser', // unique for frontend
+ ]
+ ],
+ 'request'=>[
+ 'cookieValidationKey' => 'ndahjhjjidasuidrqeswuiuirqw89',
+ 'csrfParam' => '_frontendCSRF',
+ 'class' => 'common\components\Request',
+
+ 'web'=> '/frontend/web'
+
+ ],
+
+ 'log' => [
+ 'traceLevel' => YII_DEBUG ? 3 : 0,
+ 'targets' => [
+ [
+ 'class' => 'yii\log\FileTarget',
+ 'levels' => ['error', 'warning'],
+ ],
+ ],
+ ],
+ 'errorHandler' => [
+ 'errorAction' => 'site/error',
+ ],
+
+ 'urlManager' => [
+ 'baseUrl' => '/',
+ 'enablePrettyUrl' => true,
+ 'showScriptName' => false,
+ 'rules' => [
+ ]
+ ]
+
+ ],
+ 'params' => $params,
+];
diff --git a/frontend/config/params.php b/frontend/config/params.php
new file mode 100644
index 0000000..7f754b9
--- /dev/null
+++ b/frontend/config/params.php
@@ -0,0 +1,4 @@
+ 'admin@example.com',
+];
diff --git a/frontend/controllers/SiteController.php b/frontend/controllers/SiteController.php
new file mode 100644
index 0000000..cf691ae
--- /dev/null
+++ b/frontend/controllers/SiteController.php
@@ -0,0 +1,213 @@
+ [
+ 'class' => AccessControl::className(),
+ 'only' => ['logout', 'signup'],
+ 'rules' => [
+ [
+ 'actions' => ['signup'],
+ 'allow' => true,
+ 'roles' => ['?'],
+ ],
+ [
+ 'actions' => ['logout'],
+ 'allow' => true,
+ 'roles' => ['@'],
+ ],
+ ],
+ ],
+ 'verbs' => [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'logout' => ['post'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function actions()
+ {
+ return [
+ 'error' => [
+ 'class' => 'yii\web\ErrorAction',
+ ],
+ 'captcha' => [
+ 'class' => 'yii\captcha\CaptchaAction',
+ 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
+ ],
+ ];
+ }
+
+ /**
+ * Displays homepage.
+ *
+ * @return mixed
+ */
+ public function actionIndex()
+ {
+ return $this->render('index');
+ }
+
+ /**
+ * Logs in a user.
+ *
+ * @return mixed
+ */
+ public function actionLogin()
+ {
+ if (!\Yii::$app->user->isGuest) {
+ return $this->goHome();
+ }
+
+ $model = new LoginForm();
+ if ($model->load(Yii::$app->request->post()) && $model->login()) {
+ return $this->goBack();
+ } else {
+ return $this->render('login', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Logs out the current user.
+ *
+ * @return mixed
+ */
+ public function actionLogout()
+ {
+ Yii::$app->user->logout();
+
+ return $this->goHome();
+ }
+
+ /**
+ * Displays contact page.
+ *
+ * @return mixed
+ */
+ public function actionContact()
+ {
+ $model = new ContactForm();
+ if ($model->load(Yii::$app->request->post()) && $model->validate()) {
+ if ($model->sendEmail(Yii::$app->params['adminEmail'])) {
+ Yii::$app->session->setFlash('success', 'Thank you for contacting us. We will respond to you as soon as possible.');
+ } else {
+ Yii::$app->session->setFlash('error', 'There was an error sending email.');
+ }
+
+ return $this->refresh();
+ } else {
+ return $this->render('contact', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Displays about page.
+ *
+ * @return mixed
+ */
+ public function actionAbout()
+ {
+ return $this->render('about');
+ }
+
+ /**
+ * Signs user up.
+ *
+ * @return mixed
+ */
+ public function actionSignup()
+ {
+ $model = new SignupForm();
+ if ($model->load(Yii::$app->request->post())) {
+ if ($user = $model->signup()) {
+ if (Yii::$app->getUser()->login($user)) {
+ return $this->goHome();
+ }
+ }
+ }
+
+ return $this->render('signup', [
+ 'model' => $model,
+ ]);
+ }
+
+ /**
+ * Requests password reset.
+ *
+ * @return mixed
+ */
+ public function actionRequestPasswordReset()
+ {
+ $model = new PasswordResetRequestForm();
+ if ($model->load(Yii::$app->request->post()) && $model->validate()) {
+ if ($model->sendEmail()) {
+ Yii::$app->session->setFlash('success', 'Check your email for further instructions.');
+
+ return $this->goHome();
+ } else {
+ Yii::$app->session->setFlash('error', 'Sorry, we are unable to reset password for email provided.');
+ }
+ }
+
+ return $this->render('requestPasswordResetToken', [
+ 'model' => $model,
+ ]);
+ }
+
+ /**
+ * Resets password.
+ *
+ * @param string $token
+ * @return mixed
+ * @throws BadRequestHttpException
+ */
+ public function actionResetPassword($token)
+ {
+ try {
+ $model = new ResetPasswordForm($token);
+ } catch (InvalidParamException $e) {
+ throw new BadRequestHttpException($e->getMessage());
+ }
+
+ if ($model->load(Yii::$app->request->post()) && $model->validate() && $model->resetPassword()) {
+ Yii::$app->session->setFlash('success', 'New password was saved.');
+
+ return $this->goHome();
+ }
+
+ return $this->render('resetPassword', [
+ 'model' => $model,
+ ]);
+ }
+}
diff --git a/frontend/models/ContactForm.php b/frontend/models/ContactForm.php
new file mode 100644
index 0000000..613abb5
--- /dev/null
+++ b/frontend/models/ContactForm.php
@@ -0,0 +1,59 @@
+ 'Verification Code',
+ ];
+ }
+
+ /**
+ * Sends an email to the specified email address using the information collected by this model.
+ *
+ * @param string $email the target email address
+ * @return boolean whether the email was sent
+ */
+ public function sendEmail($email)
+ {
+ return Yii::$app->mailer->compose()
+ ->setTo($email)
+ ->setFrom([$this->email => $this->name])
+ ->setSubject($this->subject)
+ ->setTextBody($this->body)
+ ->send();
+ }
+}
diff --git a/frontend/models/PasswordResetRequestForm.php b/frontend/models/PasswordResetRequestForm.php
new file mode 100644
index 0000000..c09f6f7
--- /dev/null
+++ b/frontend/models/PasswordResetRequestForm.php
@@ -0,0 +1,68 @@
+ 'trim'],
+ ['email', 'required'],
+ ['email', 'email'],
+ ['email', 'exist',
+ 'targetClass' => '\common\models\User',
+ 'filter' => ['status' => User::STATUS_ACTIVE],
+ 'message' => 'There is no user with such email.'
+ ],
+ ];
+ }
+
+ /**
+ * Sends an email with a link, for resetting the password.
+ *
+ * @return boolean whether the email was send
+ */
+ public function sendEmail()
+ {
+ /* @var $user User */
+ $user = User::findOne([
+ 'status' => User::STATUS_ACTIVE,
+ 'email' => $this->email,
+ ]);
+
+ if (!$user) {
+ return false;
+ }
+
+ if (!User::isPasswordResetTokenValid($user->password_reset_token)) {
+ $user->generatePasswordResetToken();
+ }
+
+ if (!$user->save()) {
+ return false;
+ }
+
+ return Yii::$app
+ ->mailer
+ ->compose(
+ ['html' => 'passwordResetToken-html', 'text' => 'passwordResetToken-text'],
+ ['user' => $user]
+ )
+ ->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot'])
+ ->setTo($this->email)
+ ->setSubject('Password reset for ' . \Yii::$app->name)
+ ->send();
+ }
+}
diff --git a/frontend/models/ResetPasswordForm.php b/frontend/models/ResetPasswordForm.php
new file mode 100644
index 0000000..dd48f52
--- /dev/null
+++ b/frontend/models/ResetPasswordForm.php
@@ -0,0 +1,65 @@
+_user = User::findByPasswordResetToken($token);
+ if (!$this->_user) {
+ throw new InvalidParamException('Wrong password reset token.');
+ }
+ parent::__construct($config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function rules()
+ {
+ return [
+ ['password', 'required'],
+ ['password', 'string', 'min' => 6],
+ ];
+ }
+
+ /**
+ * Resets password.
+ *
+ * @return boolean if password was reset.
+ */
+ public function resetPassword()
+ {
+ $user = $this->_user;
+ $user->setPassword($this->password);
+ $user->removePasswordResetToken();
+
+ return $user->save(false);
+ }
+}
diff --git a/frontend/models/SignupForm.php b/frontend/models/SignupForm.php
new file mode 100644
index 0000000..bd6722f
--- /dev/null
+++ b/frontend/models/SignupForm.php
@@ -0,0 +1,58 @@
+ 'trim'],
+ ['username', 'required'],
+ ['username', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This username has already been taken.'],
+ ['username', 'string', 'min' => 2, 'max' => 255],
+
+ ['email', 'filter', 'filter' => 'trim'],
+ ['email', 'required'],
+ ['email', 'email'],
+ ['email', 'string', 'max' => 255],
+ ['email', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This email address has already been taken.'],
+
+ ['password', 'required'],
+ ['password', 'string', 'min' => 6],
+ ];
+ }
+
+ /**
+ * Signs user up.
+ *
+ * @return User|null the saved model or null if saving fails
+ */
+ public function signup()
+ {
+ if (!$this->validate()) {
+ return null;
+ }
+
+ $user = new User();
+ $user->username = $this->username;
+ $user->email = $this->email;
+ $user->setPassword($this->password);
+ $user->generateAuthKey();
+
+ return $user->save() ? $user : null;
+ }
+}
diff --git a/frontend/runtime/.gitignore b/frontend/runtime/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/frontend/runtime/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/frontend/views/layouts/main.php b/frontend/views/layouts/main.php
new file mode 100644
index 0000000..c86aa9b
--- /dev/null
+++ b/frontend/views/layouts/main.php
@@ -0,0 +1,82 @@
+
+beginPage() ?>
+
+
+
+
+
+ = Html::csrfMetaTags() ?>
+ = Html::encode($this->title) ?>
+ head() ?>
+
+
+beginBody() ?>
+
+
+ 'My Company',
+ 'brandUrl' => Yii::$app->homeUrl,
+ 'options' => [
+ 'class' => 'navbar-inverse navbar-fixed-top',
+ ],
+ ]);
+ $menuItems = [
+ ['label' => 'Home', 'url' => ['/site/index']],
+ ['label' => 'About', 'url' => ['/site/about']],
+ ['label' => 'Contact', 'url' => ['/site/contact']],
+ ];
+ if (Yii::$app->user->isGuest) {
+ $menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']];
+ $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']];
+ } else {
+ $menuItems[] = '
'
+ . Html::beginForm(['/site/logout'], 'post')
+ . Html::submitButton(
+ 'Logout (' . Yii::$app->user->identity->username . ')',
+ ['class' => 'btn btn-link']
+ )
+ . Html::endForm()
+ . ' ';
+ }
+ echo Nav::widget([
+ 'options' => ['class' => 'navbar-nav navbar-right'],
+ 'items' => $menuItems,
+ ]);
+ NavBar::end();
+ ?>
+
+
+ = Breadcrumbs::widget([
+ 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
+ ]) ?>
+ = Alert::widget() ?>
+ = $content ?>
+
+
+
+
+
+endBody() ?>
+
+
+endPage() ?>
diff --git a/frontend/views/site/about.php b/frontend/views/site/about.php
new file mode 100644
index 0000000..8eb0764
--- /dev/null
+++ b/frontend/views/site/about.php
@@ -0,0 +1,16 @@
+title = 'About';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
= Html::encode($this->title) ?>
+
+
This is the About page. You may modify the following file to customize its content:
+
+
= __FILE__ ?>
+
diff --git a/frontend/views/site/contact.php b/frontend/views/site/contact.php
new file mode 100644
index 0000000..dcad18f
--- /dev/null
+++ b/frontend/views/site/contact.php
@@ -0,0 +1,45 @@
+title = 'Contact';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
diff --git a/frontend/views/site/error.php b/frontend/views/site/error.php
new file mode 100644
index 0000000..0ba2574
--- /dev/null
+++ b/frontend/views/site/error.php
@@ -0,0 +1,27 @@
+title = $name;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+ = nl2br(Html::encode($message)) ?>
+
+
+
+ The above error occurred while the Web server was processing your request.
+
+
+ Please contact us if you think this is a server error. Thank you.
+
+
+
diff --git a/frontend/views/site/index.php b/frontend/views/site/index.php
new file mode 100644
index 0000000..f780610
--- /dev/null
+++ b/frontend/views/site/index.php
@@ -0,0 +1,53 @@
+title = 'My Yii Application';
+?>
+
+
+
+
Congratulations!
+
+
You have successfully created your Yii-powered application.
+
+
Get started with Yii
+
+
+
+
+
+
+
Heading
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
+
+
Yii Documentation »
+
+
+
Heading
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
+
+
Yii Forum »
+
+
+
Heading
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur.
+
+
Yii Extensions »
+
+
+
+
+
diff --git a/frontend/views/site/login.php b/frontend/views/site/login.php
new file mode 100644
index 0000000..56ea98e
--- /dev/null
+++ b/frontend/views/site/login.php
@@ -0,0 +1,39 @@
+title = 'Login';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
= Html::encode($this->title) ?>
+
+
Please fill out the following fields to login:
+
+
+
+ 'login-form']); ?>
+
+ = $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
+
+ = $form->field($model, 'password')->passwordInput() ?>
+
+ = $form->field($model, 'rememberMe')->checkbox() ?>
+
+
+ If you forgot your password you can = Html::a('reset it', ['site/request-password-reset']) ?>.
+
+
+
+ = Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
+
+
+
+
+
+
diff --git a/frontend/views/site/requestPasswordResetToken.php b/frontend/views/site/requestPasswordResetToken.php
new file mode 100644
index 0000000..9f6822e
--- /dev/null
+++ b/frontend/views/site/requestPasswordResetToken.php
@@ -0,0 +1,31 @@
+title = 'Request password reset';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
= Html::encode($this->title) ?>
+
+
Please fill out your email. A link to reset password will be sent there.
+
+
+
+ 'request-password-reset-form']); ?>
+
+ = $form->field($model, 'email')->textInput(['autofocus' => true]) ?>
+
+
+ = Html::submitButton('Send', ['class' => 'btn btn-primary']) ?>
+
+
+
+
+
+
diff --git a/frontend/views/site/resetPassword.php b/frontend/views/site/resetPassword.php
new file mode 100644
index 0000000..36ef452
--- /dev/null
+++ b/frontend/views/site/resetPassword.php
@@ -0,0 +1,31 @@
+title = 'Reset password';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
= Html::encode($this->title) ?>
+
+
Please choose your new password:
+
+
+
+ 'reset-password-form']); ?>
+
+ = $form->field($model, 'password')->passwordInput(['autofocus' => true]) ?>
+
+
+ = Html::submitButton('Save', ['class' => 'btn btn-primary']) ?>
+
+
+
+
+
+
diff --git a/frontend/views/site/signup.php b/frontend/views/site/signup.php
new file mode 100644
index 0000000..de9dad6
--- /dev/null
+++ b/frontend/views/site/signup.php
@@ -0,0 +1,35 @@
+title = 'Signup';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
= Html::encode($this->title) ?>
+
+
Please fill out the following fields to signup:
+
+
+
+ 'form-signup']); ?>
+
+ = $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
+
+ = $form->field($model, 'email') ?>
+
+ = $form->field($model, 'password')->passwordInput() ?>
+
+
+ = Html::submitButton('Signup', ['class' => 'btn btn-primary', 'name' => 'signup-button']) ?>
+
+
+
+
+
+
diff --git a/frontend/web/.gitignore b/frontend/web/.gitignore
new file mode 100644
index 0000000..25c74e6
--- /dev/null
+++ b/frontend/web/.gitignore
@@ -0,0 +1,2 @@
+/index.php
+/index-test.php
diff --git a/frontend/web/assets/.gitignore b/frontend/web/assets/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/frontend/web/assets/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/frontend/web/css/site.css b/frontend/web/css/site.css
new file mode 100644
index 0000000..25df5fb
--- /dev/null
+++ b/frontend/web/css/site.css
@@ -0,0 +1,105 @@
+html,
+body {
+ height: 100%;
+}
+
+.wrap {
+ min-height: 100%;
+ height: auto;
+ margin: 0 auto -60px;
+ padding: 0 0 60px;
+}
+
+.wrap > .container {
+ padding: 70px 15px 20px;
+}
+
+.footer {
+ height: 60px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ padding-top: 20px;
+}
+
+.jumbotron {
+ text-align: center;
+ background-color: transparent;
+}
+
+.jumbotron .btn {
+ font-size: 21px;
+ padding: 14px 24px;
+}
+
+.not-set {
+ color: #c55;
+ font-style: italic;
+}
+
+/* add sorting icons to gridview sort links */
+a.asc:after, a.desc:after {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ font-family: 'Glyphicons Halflings';
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+ padding-left: 5px;
+}
+
+a.asc:after {
+ content: "\e151";
+}
+
+a.desc:after {
+ content: "\e152";
+}
+
+.sort-numerical a.asc:after {
+ content: "\e153";
+}
+
+.sort-numerical a.desc:after {
+ content: "\e154";
+}
+
+.sort-ordinal a.asc:after {
+ content: "\e155";
+}
+
+.sort-ordinal a.desc:after {
+ content: "\e156";
+}
+
+.grid-view td {
+ white-space: nowrap;
+}
+
+.grid-view .filters input,
+.grid-view .filters select {
+ min-width: 50px;
+}
+
+.hint-block {
+ display: block;
+ margin-top: 5px;
+ color: #999;
+}
+
+.error-summary {
+ color: #a94442;
+ background: #fdf7f7;
+ border-left: 3px solid #eed3d7;
+ padding: 10px 20px;
+ margin: 0 0 15px 0;
+}
+
+/* align the logout "link" (button in form) of the navbar */
+.nav > li > form {
+ padding: 8px;
+}
+
+.nav > li > form > button:hover {
+ text-decoration: none;
+}
diff --git a/frontend/web/favicon.ico b/frontend/web/favicon.ico
new file mode 100644
index 0000000..580ed73
Binary files /dev/null and b/frontend/web/favicon.ico differ
diff --git a/frontend/web/robots.txt b/frontend/web/robots.txt
new file mode 100644
index 0000000..6f27bb6
--- /dev/null
+++ b/frontend/web/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
\ No newline at end of file
diff --git a/init b/init
new file mode 100644
index 0000000..6b8dd76
--- /dev/null
+++ b/init
@@ -0,0 +1,213 @@
+#!/usr/bin/env php
+
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+if (!extension_loaded('openssl')) {
+ die('The OpenSSL PHP extension is required by Yii2.');
+}
+
+$params = getParams();
+$root = str_replace('\\', '/', __DIR__);
+$envs = require("$root/environments/index.php");
+$envNames = array_keys($envs);
+
+echo "Yii Application Initialization Tool v1.0\n\n";
+
+$envName = null;
+if (empty($params['env']) || $params['env'] === '1') {
+ echo "Which environment do you want the application to be initialized in?\n\n";
+ foreach ($envNames as $i => $name) {
+ echo " [$i] $name\n";
+ }
+ echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] ';
+ $answer = trim(fgets(STDIN));
+
+ if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) {
+ echo "\n Quit initialization.\n";
+ exit(0);
+ }
+
+ if (isset($envNames[$answer])) {
+ $envName = $envNames[$answer];
+ }
+} else {
+ $envName = $params['env'];
+}
+
+if (!in_array($envName, $envNames)) {
+ $envsList = implode(', ', $envNames);
+ echo "\n $envName is not a valid environment. Try one of the following: $envsList. \n";
+ exit(2);
+}
+
+$env = $envs[$envName];
+
+if (empty($params['env'])) {
+ echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] ";
+ $answer = trim(fgets(STDIN));
+ if (strncasecmp($answer, 'y', 1)) {
+ echo "\n Quit initialization.\n";
+ exit(0);
+ }
+}
+
+echo "\n Start initialization ...\n\n";
+$files = getFileList("$root/environments/{$env['path']}");
+if (isset($env['skipFiles'])) {
+ $skipFiles = $env['skipFiles'];
+ array_walk($skipFiles, function(&$value) use($env, $root) { $value = "$root/$value"; });
+ $files = array_diff($files, array_intersect_key($env['skipFiles'], array_filter($skipFiles, 'file_exists')));
+}
+$all = false;
+foreach ($files as $file) {
+ if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all, $params)) {
+ break;
+ }
+}
+
+$callbacks = ['setCookieValidationKey', 'setWritable', 'setExecutable', 'createSymlink'];
+foreach ($callbacks as $callback) {
+ if (!empty($env[$callback])) {
+ $callback($root, $env[$callback]);
+ }
+}
+
+echo "\n ... initialization completed.\n\n";
+
+function getFileList($root, $basePath = '')
+{
+ $files = [];
+ $handle = opendir($root);
+ while (($path = readdir($handle)) !== false) {
+ if ($path === '.git' || $path === '.svn' || $path === '.' || $path === '..') {
+ continue;
+ }
+ $fullPath = "$root/$path";
+ $relativePath = $basePath === '' ? $path : "$basePath/$path";
+ if (is_dir($fullPath)) {
+ $files = array_merge($files, getFileList($fullPath, $relativePath));
+ } else {
+ $files[] = $relativePath;
+ }
+ }
+ closedir($handle);
+ return $files;
+}
+
+function copyFile($root, $source, $target, &$all, $params)
+{
+ if (!is_file($root . '/' . $source)) {
+ echo " skip $target ($source not exist)\n";
+ return true;
+ }
+ if (is_file($root . '/' . $target)) {
+ if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) {
+ echo " unchanged $target\n";
+ return true;
+ }
+ if ($all) {
+ echo " overwrite $target\n";
+ } else {
+ echo " exist $target\n";
+ echo " ...overwrite? [Yes|No|All|Quit] ";
+
+
+ $answer = !empty($params['overwrite']) ? $params['overwrite'] : trim(fgets(STDIN));
+ if (!strncasecmp($answer, 'q', 1)) {
+ return false;
+ } else {
+ if (!strncasecmp($answer, 'y', 1)) {
+ echo " overwrite $target\n";
+ } else {
+ if (!strncasecmp($answer, 'a', 1)) {
+ echo " overwrite $target\n";
+ $all = true;
+ } else {
+ echo " skip $target\n";
+ return true;
+ }
+ }
+ }
+ }
+ file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
+ return true;
+ }
+ echo " generate $target\n";
+ @mkdir(dirname($root . '/' . $target), 0777, true);
+ file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
+ return true;
+}
+
+function getParams()
+{
+ $rawParams = [];
+ if (isset($_SERVER['argv'])) {
+ $rawParams = $_SERVER['argv'];
+ array_shift($rawParams);
+ }
+
+ $params = [];
+ foreach ($rawParams as $param) {
+ if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {
+ $name = $matches[1];
+ $params[$name] = isset($matches[3]) ? $matches[3] : true;
+ } else {
+ $params[] = $param;
+ }
+ }
+ return $params;
+}
+
+function setWritable($root, $paths)
+{
+ foreach ($paths as $writable) {
+ if (is_dir("$root/$writable")) {
+ echo " chmod 0777 $writable\n";
+ @chmod("$root/$writable", 0777);
+ } else {
+ echo "\n Error. Directory $writable does not exist. \n";
+ }
+ }
+}
+
+function setExecutable($root, $paths)
+{
+ foreach ($paths as $executable) {
+ echo " chmod 0755 $executable\n";
+ @chmod("$root/$executable", 0755);
+ }
+}
+
+function setCookieValidationKey($root, $paths)
+{
+ foreach ($paths as $file) {
+ echo " generate cookie validation key in $file\n";
+ $file = $root . '/' . $file;
+ $length = 32;
+ $bytes = openssl_random_pseudo_bytes($length);
+ $key = strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.');
+ $content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($file));
+ file_put_contents($file, $content);
+ }
+}
+
+function createSymlink($root, $links) {
+ foreach ($links as $link => $target) {
+ echo " symlink " . $root . "/" . $target . " " . $root . "/" . $link . "\n";
+ //first removing folders to avoid errors if the folder already exists
+ @rmdir($root . "/" . $link);
+ @symlink($root . "/" . $target, $root . "/" . $link);
+ }
+}
diff --git a/init.bat b/init.bat
new file mode 100644
index 0000000..e50c242
--- /dev/null
+++ b/init.bat
@@ -0,0 +1,20 @@
+@echo off
+
+rem -------------------------------------------------------------
+rem Yii command line init script for Windows.
+rem
+rem @author Qiang Xue
+rem @link http://www.yiiframework.com/
+rem @copyright Copyright (c) 2008 Yii Software LLC
+rem @license http://www.yiiframework.com/license/
+rem -------------------------------------------------------------
+
+@setlocal
+
+set YII_PATH=%~dp0
+
+if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
+
+"%PHP_COMMAND%" "%YII_PATH%init" %*
+
+@endlocal
diff --git a/requirements.php b/requirements.php
new file mode 100644
index 0000000..fd84f47
--- /dev/null
+++ b/requirements.php
@@ -0,0 +1,132 @@
+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 = array(
+ // Database :
+ array(
+ 'name' => 'PDO extension',
+ 'mandatory' => true,
+ 'condition' => extension_loaded('pdo'),
+ 'by' => 'All DB-related classes',
+ ),
+ array(
+ 'name' => 'PDO SQLite extension',
+ 'mandatory' => false,
+ 'condition' => extension_loaded('pdo_sqlite'),
+ 'by' => 'All DB-related classes',
+ 'memo' => 'Required for SQLite database.',
+ ),
+ array(
+ 'name' => 'PDO MySQL extension',
+ 'mandatory' => false,
+ 'condition' => extension_loaded('pdo_mysql'),
+ 'by' => 'All DB-related classes',
+ 'memo' => 'Required for MySQL database.',
+ ),
+ array(
+ 'name' => 'PDO PostgreSQL extension',
+ 'mandatory' => false,
+ 'condition' => extension_loaded('pdo_pgsql'),
+ 'by' => 'All DB-related classes',
+ 'memo' => 'Required for PostgreSQL database.',
+ ),
+ // Cache :
+ array(
+ '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.' : ''
+ ),
+ array(
+ 'name' => 'APC extension',
+ 'mandatory' => false,
+ 'condition' => extension_loaded('apc'),
+ 'by' => 'ApcCache ',
+ ),
+ // CAPTCHA:
+ array(
+ 'name' => 'GD PHP extension with FreeType support',
+ 'mandatory' => false,
+ 'condition' => $gdOK,
+ 'by' => 'Captcha ',
+ 'memo' => $gdMemo,
+ ),
+ array(
+ 'name' => 'ImageMagick PHP extension with PNG support',
+ 'mandatory' => false,
+ 'condition' => $imagickOK,
+ 'by' => 'Captcha ',
+ 'memo' => $imagickMemo,
+ ),
+ // PHP ini :
+ 'phpExposePhp' => array(
+ 'name' => 'Expose PHP',
+ 'mandatory' => false,
+ 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"),
+ 'by' => 'Security reasons',
+ 'memo' => '"expose_php" should be disabled at php.ini',
+ ),
+ 'phpAllowUrlInclude' => array(
+ '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' => array(
+ 'name' => 'PHP mail SMTP',
+ 'mandatory' => false,
+ 'condition' => strlen(ini_get('SMTP')) > 0,
+ 'by' => 'Email sending',
+ 'memo' => 'PHP mail SMTP server required',
+ ),
+);
+$requirementsChecker->checkYii()->check($requirements)->render();
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..ad7f016
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,58 @@
+This directory contains various tests for the advanced applications.
+
+Tests in `codeception` directory are developed with [Codeception PHP Testing Framework](http://codeception.com/).
+
+After creating and setting up the advanced application, follow these steps to prepare for the tests:
+
+1. Install Codeception if it's not yet installed:
+
+ ```
+ composer global require "codeception/codeception=2.0.*" "codeception/specify=*" "codeception/verify=*"
+ ```
+
+ If you've never used Composer for global packages run `composer global status`. It should output:
+
+ ```
+ Changed current directory to
+ ```
+
+ Then add `/vendor/bin` to you `PATH` environment variable. Now you're able to use `codecept` from command
+ line globally.
+
+2. Install faker extension by running the following from template root directory where `composer.json` is:
+
+ ```
+ composer require --dev yiisoft/yii2-faker:*
+ ```
+
+3. Create `yii2_advanced_tests` database then update it by applying migrations:
+
+ ```
+ codeception/bin/yii migrate
+ ```
+
+4. In order to be able to run acceptance tests you need to start a webserver. The simplest way is to use PHP built in
+ webserver. In the root directory where `common`, `frontend` etc. are execute the following:
+
+ ```
+ php -S localhost:8080
+ ```
+
+5. Now you can run the tests with the following commands, assuming you are in the `tests/codeception` directory:
+
+ ```
+ # frontend tests
+ cd frontend
+ codecept build
+ codecept run
+
+ # backend tests
+
+ cd backend
+ codecept build
+ codecept run
+
+ # etc.
+ ```
+
+ If you already have run `codecept build` for each application, you can skip that step and run all tests by a single `codecept run`.
diff --git a/tests/codeception.yml b/tests/codeception.yml
new file mode 100644
index 0000000..1a793ed
--- /dev/null
+++ b/tests/codeception.yml
@@ -0,0 +1,11 @@
+include:
+ - codeception/common
+ - codeception/console
+ - codeception/backend
+ - codeception/frontend
+
+paths:
+ log: codeception/_output
+
+settings:
+ colors: true
diff --git a/tests/codeception/_output/.gitignore b/tests/codeception/_output/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/tests/codeception/_output/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/tests/codeception/backend/.gitignore b/tests/codeception/backend/.gitignore
new file mode 100644
index 0000000..985dbb4
--- /dev/null
+++ b/tests/codeception/backend/.gitignore
@@ -0,0 +1,4 @@
+# these files are auto generated by codeception build
+/unit/UnitTester.php
+/functional/FunctionalTester.php
+/acceptance/AcceptanceTester.php
diff --git a/tests/codeception/backend/_bootstrap.php b/tests/codeception/backend/_bootstrap.php
new file mode 100644
index 0000000..a28a3d2
--- /dev/null
+++ b/tests/codeception/backend/_bootstrap.php
@@ -0,0 +1,23 @@
+wantTo('ensure login page works');
+
+$loginPage = LoginPage::openBy($I);
+
+$I->amGoingTo('submit login form with no data');
+$loginPage->login('', '');
+if (method_exists($I, 'wait')) {
+ $I->wait(3); // only for selenium
+}
+$I->expectTo('see validations errors');
+$I->see('Username cannot be blank.', '.help-block');
+$I->see('Password cannot be blank.', '.help-block');
+
+$I->amGoingTo('try to login with wrong credentials');
+$I->expectTo('see validations errors');
+$loginPage->login('admin', 'wrong');
+if (method_exists($I, 'wait')) {
+ $I->wait(3); // only for selenium
+}
+$I->expectTo('see validations errors');
+$I->see('Incorrect username or password.', '.help-block');
+
+$I->amGoingTo('try to login with correct credentials');
+$loginPage->login('erau', 'password_0');
+if (method_exists($I, 'wait')) {
+ $I->wait(3); // only for selenium
+}
+$I->expectTo('see that user is logged');
+$I->see('Logout (erau)', 'form button[type=submit]');
+$I->dontSeeLink('Login');
+$I->dontSeeLink('Signup');
+/** Uncomment if using WebDriver
+ * $I->click('Logout (erau)');
+ * $I->dontSeeLink('Logout (erau)');
+ * $I->seeLink('Login');
+ */
diff --git a/tests/codeception/backend/acceptance/_bootstrap.php b/tests/codeception/backend/acceptance/_bootstrap.php
new file mode 100644
index 0000000..411855e
--- /dev/null
+++ b/tests/codeception/backend/acceptance/_bootstrap.php
@@ -0,0 +1,2 @@
+wantTo('ensure login page works');
+
+$loginPage = LoginPage::openBy($I);
+
+$I->amGoingTo('submit login form with no data');
+$loginPage->login('', '');
+$I->expectTo('see validations errors');
+$I->see('Username cannot be blank.', '.help-block');
+$I->see('Password cannot be blank.', '.help-block');
+
+$I->amGoingTo('try to login with wrong credentials');
+$I->expectTo('see validations errors');
+$loginPage->login('admin', 'wrong');
+$I->expectTo('see validations errors');
+$I->see('Incorrect username or password.', '.help-block');
+
+$I->amGoingTo('try to login with correct credentials');
+$loginPage->login('erau', 'password_0');
+$I->expectTo('see that user is logged');
+$I->see('Logout (erau)', 'form button[type=submit]');
+$I->dontSeeLink('Login');
+$I->dontSeeLink('Signup');
diff --git a/tests/codeception/backend/functional/_bootstrap.php b/tests/codeception/backend/functional/_bootstrap.php
new file mode 100644
index 0000000..94f3fbd
--- /dev/null
+++ b/tests/codeception/backend/functional/_bootstrap.php
@@ -0,0 +1,2 @@
+run();
+exit($exitCode);
diff --git a/tests/codeception/bin/yii.bat b/tests/codeception/bin/yii.bat
new file mode 100644
index 0000000..d516b3a
--- /dev/null
+++ b/tests/codeception/bin/yii.bat
@@ -0,0 +1,20 @@
+@echo off
+
+rem -------------------------------------------------------------
+rem Yii command line bootstrap script for Windows.
+rem
+rem @author Qiang Xue
+rem @link http://www.yiiframework.com/
+rem @copyright Copyright (c) 2008 Yii Software LLC
+rem @license http://www.yiiframework.com/license/
+rem -------------------------------------------------------------
+
+@setlocal
+
+set YII_PATH=%~dp0
+
+if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
+
+"%PHP_COMMAND%" "%YII_PATH%yii" %*
+
+@endlocal
diff --git a/tests/codeception/common/.gitignore b/tests/codeception/common/.gitignore
new file mode 100644
index 0000000..985dbb4
--- /dev/null
+++ b/tests/codeception/common/.gitignore
@@ -0,0 +1,4 @@
+# these files are auto generated by codeception build
+/unit/UnitTester.php
+/functional/FunctionalTester.php
+/acceptance/AcceptanceTester.php
diff --git a/tests/codeception/common/_bootstrap.php b/tests/codeception/common/_bootstrap.php
new file mode 100644
index 0000000..cea3ee5
--- /dev/null
+++ b/tests/codeception/common/_bootstrap.php
@@ -0,0 +1,15 @@
+actor->fillField('input[name="LoginForm[username]"]', $username);
+ $this->actor->fillField('input[name="LoginForm[password]"]', $password);
+ $this->actor->click('login-button');
+ }
+}
diff --git a/tests/codeception/common/_support/FixtureHelper.php b/tests/codeception/common/_support/FixtureHelper.php
new file mode 100644
index 0000000..63739ef
--- /dev/null
+++ b/tests/codeception/common/_support/FixtureHelper.php
@@ -0,0 +1,73 @@
+loadFixtures();
+ }
+
+ /**
+ * Method is called after all suite tests run
+ */
+ public function _afterSuite()
+ {
+ $this->unloadFixtures();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function globalFixtures()
+ {
+ return [
+ InitDbFixture::className(),
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function fixtures()
+ {
+ return [
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => '@tests/codeception/common/fixtures/data/init_login.php',
+ ],
+ ];
+ }
+}
diff --git a/tests/codeception/common/codeception.yml b/tests/codeception/common/codeception.yml
new file mode 100644
index 0000000..e8a3407
--- /dev/null
+++ b/tests/codeception/common/codeception.yml
@@ -0,0 +1,13 @@
+namespace: tests\codeception\common
+actor: Tester
+paths:
+ tests: .
+ log: _output
+ data: _data
+ helpers: _support
+settings:
+ bootstrap: _bootstrap.php
+ suite_class: \PHPUnit_Framework_TestSuite
+ colors: true
+ memory_limit: 1024M
+ log: true
diff --git a/tests/codeception/common/fixtures/UserFixture.php b/tests/codeception/common/fixtures/UserFixture.php
new file mode 100644
index 0000000..7153c8c
--- /dev/null
+++ b/tests/codeception/common/fixtures/UserFixture.php
@@ -0,0 +1,13 @@
+ 'erau',
+ 'auth_key' => 'tUu1qHcde0diwUol3xeI-18MuHkkprQI',
+ // password_0
+ 'password_hash' => '$2y$13$nJ1WDlBaGcbCdbNC5.5l4.sgy.OMEKCqtDQOdQ2OWpgiKRWYyzzne',
+ 'password_reset_token' => 'RkD_Jw0_8HEedzLk7MM-ZKEFfYR7VbMr_1392559490',
+ 'created_at' => '1392559490',
+ 'updated_at' => '1392559490',
+ 'email' => 'sfriesen@jenkins.info',
+ ],
+];
diff --git a/tests/codeception/common/templates/fixtures/user.php b/tests/codeception/common/templates/fixtures/user.php
new file mode 100644
index 0000000..d3f83b5
--- /dev/null
+++ b/tests/codeception/common/templates/fixtures/user.php
@@ -0,0 +1,17 @@
+getSecurity();
+
+return [
+ 'username' => $faker->userName,
+ 'email' => $faker->email,
+ 'auth_key' => $security->generateRandomString(),
+ 'password_hash' => $security->generatePasswordHash('password_' . $index),
+ 'password_reset_token' => $security->generateRandomString() . '_' . time(),
+ 'created_at' => time(),
+ 'updated_at' => time(),
+];
diff --git a/tests/codeception/common/unit.suite.yml b/tests/codeception/common/unit.suite.yml
new file mode 100644
index 0000000..a0582a5
--- /dev/null
+++ b/tests/codeception/common/unit.suite.yml
@@ -0,0 +1,6 @@
+# Codeception Test Suite Configuration
+
+# suite for unit (internal) tests.
+# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
+
+class_name: UnitTester
diff --git a/tests/codeception/common/unit/DbTestCase.php b/tests/codeception/common/unit/DbTestCase.php
new file mode 100644
index 0000000..2159a69
--- /dev/null
+++ b/tests/codeception/common/unit/DbTestCase.php
@@ -0,0 +1,11 @@
+ 'bayer.hudson',
+ 'auth_key' => 'HP187Mvq7Mmm3CTU80dLkGmni_FUH_lR',
+ //password_0
+ 'password_hash' => '$2y$13$EjaPFBnZOQsHdGuHI.xvhuDp1fHpo8hKRSk6yshqa9c5EG8s3C3lO',
+ 'password_reset_token' => 'ExzkCOaYc1L8IOBs4wdTGGbgNiG3Wz1I_1402312317',
+ 'created_at' => '1402312317',
+ 'updated_at' => '1402312317',
+ 'email' => 'nicole.paucek@schultz.info',
+ ],
+];
diff --git a/tests/codeception/common/unit/models/LoginFormTest.php b/tests/codeception/common/unit/models/LoginFormTest.php
new file mode 100644
index 0000000..54c8209
--- /dev/null
+++ b/tests/codeception/common/unit/models/LoginFormTest.php
@@ -0,0 +1,93 @@
+ [
+ 'user' => [
+ 'class' => 'yii\web\User',
+ 'identityClass' => 'common\models\User',
+ ],
+ ],
+ ]);
+ }
+
+ protected function tearDown()
+ {
+ Yii::$app->user->logout();
+ parent::tearDown();
+ }
+
+ public function testLoginNoUser()
+ {
+ $model = new LoginForm([
+ 'username' => 'not_existing_username',
+ 'password' => 'not_existing_password',
+ ]);
+
+ $this->specify('user should not be able to login, when there is no identity', function () use ($model) {
+ expect('model should not login user', $model->login())->false();
+ expect('user should not be logged in', Yii::$app->user->isGuest)->true();
+ });
+ }
+
+ public function testLoginWrongPassword()
+ {
+ $model = new LoginForm([
+ 'username' => 'bayer.hudson',
+ 'password' => 'wrong_password',
+ ]);
+
+ $this->specify('user should not be able to login with wrong password', function () use ($model) {
+ expect('model should not login user', $model->login())->false();
+ expect('error message should be set', $model->errors)->hasKey('password');
+ expect('user should not be logged in', Yii::$app->user->isGuest)->true();
+ });
+ }
+
+ public function testLoginCorrect()
+ {
+
+ $model = new LoginForm([
+ 'username' => 'bayer.hudson',
+ 'password' => 'password_0',
+ ]);
+
+ $this->specify('user should be able to login with correct credentials', function () use ($model) {
+ expect('model should login user', $model->login())->true();
+ expect('error message should not be set', $model->errors)->hasntKey('password');
+ expect('user should be logged in', Yii::$app->user->isGuest)->false();
+ });
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function fixtures()
+ {
+ return [
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => '@tests/codeception/common/unit/fixtures/data/models/user.php'
+ ],
+ ];
+ }
+}
diff --git a/tests/codeception/config/acceptance.php b/tests/codeception/config/acceptance.php
new file mode 100644
index 0000000..9318da5
--- /dev/null
+++ b/tests/codeception/config/acceptance.php
@@ -0,0 +1,7 @@
+ 'app-common',
+ 'basePath' => dirname(__DIR__),
+ ]
+);
diff --git a/tests/codeception/config/config.php b/tests/codeception/config/config.php
new file mode 100644
index 0000000..b478679
--- /dev/null
+++ b/tests/codeception/config/config.php
@@ -0,0 +1,26 @@
+ 'en-US',
+ 'controllerMap' => [
+ 'fixture' => [
+ 'class' => 'yii\faker\FixtureController',
+ 'fixtureDataPath' => '@tests/codeception/common/fixtures/data',
+ 'templatePath' => '@tests/codeception/common/templates/fixtures',
+ 'namespace' => 'tests\codeception\common\fixtures',
+ ],
+ ],
+ 'components' => [
+ 'db' => [
+ 'dsn' => 'mysql:host=localhost;dbname=yii2_advanced_tests',
+ ],
+ 'mailer' => [
+ 'useFileTransport' => true,
+ ],
+ 'urlManager' => [
+ 'showScriptName' => true,
+ ],
+ ],
+];
diff --git a/tests/codeception/config/console/unit.php b/tests/codeception/config/console/unit.php
new file mode 100644
index 0000000..4d3aeb0
--- /dev/null
+++ b/tests/codeception/config/console/unit.php
@@ -0,0 +1,14 @@
+ [
+ 'request' => [
+ // it's not recommended to run functional tests with CSRF validation enabled
+ 'enableCsrfValidation' => false,
+ // but if you absolutely need it set cookie domain to localhost
+ /*
+ 'csrfCookie' => [
+ 'domain' => 'localhost',
+ ],
+ */
+ ],
+ ],
+];
\ No newline at end of file
diff --git a/tests/codeception/config/unit.php b/tests/codeception/config/unit.php
new file mode 100644
index 0000000..6bd08d3
--- /dev/null
+++ b/tests/codeception/config/unit.php
@@ -0,0 +1,7 @@
+ $value) {
+ $inputType = $field === 'body' ? 'textarea' : 'input';
+ $this->actor->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value);
+ }
+ $this->actor->click('contact-button');
+ }
+}
diff --git a/tests/codeception/frontend/_pages/SignupPage.php b/tests/codeception/frontend/_pages/SignupPage.php
new file mode 100644
index 0000000..0e1cefa
--- /dev/null
+++ b/tests/codeception/frontend/_pages/SignupPage.php
@@ -0,0 +1,27 @@
+ $value) {
+ $inputType = $field === 'body' ? 'textarea' : 'input';
+ $this->actor->fillField($inputType . '[name="SignupForm[' . $field . ']"]', $value);
+ }
+ $this->actor->click('signup-button');
+ }
+}
diff --git a/tests/codeception/frontend/acceptance.suite.yml b/tests/codeception/frontend/acceptance.suite.yml
new file mode 100644
index 0000000..1828a04
--- /dev/null
+++ b/tests/codeception/frontend/acceptance.suite.yml
@@ -0,0 +1,28 @@
+# Codeception Test Suite Configuration
+
+# suite for acceptance tests.
+# perform tests in browser using the Selenium-like tools.
+# powered by Mink (http://mink.behat.org).
+# (tip: that's what your customer will see).
+# (tip: test your ajax and javascript by one of Mink drivers).
+
+# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
+
+class_name: AcceptanceTester
+modules:
+ enabled:
+ - PhpBrowser
+ - tests\codeception\common\_support\FixtureHelper
+# you can use WebDriver instead of PhpBrowser to test javascript and ajax.
+# This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium
+# "restart" option is used by the WebDriver to start each time per test-file new session and cookies,
+# it is useful if you want to login in your app in each test.
+# - WebDriver
+ config:
+ PhpBrowser:
+# PLEASE ADJUST IT TO THE ACTUAL ENTRY POINT WITHOUT PATH INFO
+ url: http://localhost:8080
+# WebDriver:
+# url: http://localhost:8080
+# browser: firefox
+# restart: true
diff --git a/tests/codeception/frontend/acceptance/AboutCept.php b/tests/codeception/frontend/acceptance/AboutCept.php
new file mode 100644
index 0000000..50bf9ba
--- /dev/null
+++ b/tests/codeception/frontend/acceptance/AboutCept.php
@@ -0,0 +1,10 @@
+wantTo('ensure that about works');
+AboutPage::openBy($I);
+$I->see('About', 'h1');
diff --git a/tests/codeception/frontend/acceptance/ContactCept.php b/tests/codeception/frontend/acceptance/ContactCept.php
new file mode 100644
index 0000000..5e989a5
--- /dev/null
+++ b/tests/codeception/frontend/acceptance/ContactCept.php
@@ -0,0 +1,56 @@
+wantTo('ensure that contact works');
+
+$contactPage = ContactPage::openBy($I);
+
+$I->see('Contact', 'h1');
+
+$I->amGoingTo('submit contact form with no data');
+$contactPage->submit([]);
+if (method_exists($I, 'wait')) {
+ $I->wait(3); // only for selenium
+}
+$I->expectTo('see validations errors');
+$I->see('Contact', 'h1');
+$I->see('Name cannot be blank', '.help-block');
+$I->see('Email cannot be blank', '.help-block');
+$I->see('Subject cannot be blank', '.help-block');
+$I->see('Body cannot be blank', '.help-block');
+$I->see('The verification code is incorrect', '.help-block');
+
+$I->amGoingTo('submit contact form with not correct email');
+$contactPage->submit([
+ 'name' => 'tester',
+ 'email' => 'tester.email',
+ 'subject' => 'test subject',
+ 'body' => 'test content',
+ 'verifyCode' => 'testme',
+]);
+if (method_exists($I, 'wait')) {
+ $I->wait(3); // only for selenium
+}
+$I->expectTo('see that email address is wrong');
+$I->dontSee('Name cannot be blank', '.help-block');
+$I->see('Email is not a valid email address.', '.help-block');
+$I->dontSee('Subject cannot be blank', '.help-block');
+$I->dontSee('Body cannot be blank', '.help-block');
+$I->dontSee('The verification code is incorrect', '.help-block');
+
+$I->amGoingTo('submit contact form with correct data');
+$contactPage->submit([
+ 'name' => 'tester',
+ 'email' => 'tester@example.com',
+ 'subject' => 'test subject',
+ 'body' => 'test content',
+ 'verifyCode' => 'testme',
+]);
+if (method_exists($I, 'wait')) {
+ $I->wait(3); // only for selenium
+}
+$I->see('Thank you for contacting us. We will respond to you as soon as possible.');
diff --git a/tests/codeception/frontend/acceptance/HomeCept.php b/tests/codeception/frontend/acceptance/HomeCept.php
new file mode 100644
index 0000000..9566a2e
--- /dev/null
+++ b/tests/codeception/frontend/acceptance/HomeCept.php
@@ -0,0 +1,12 @@
+wantTo('ensure that home page works');
+$I->amOnPage(Yii::$app->homeUrl);
+$I->see('My Company');
+$I->seeLink('About');
+$I->click('About');
+$I->see('This is the About page.');
diff --git a/tests/codeception/frontend/acceptance/LoginCept.php b/tests/codeception/frontend/acceptance/LoginCept.php
new file mode 100644
index 0000000..7c433af
--- /dev/null
+++ b/tests/codeception/frontend/acceptance/LoginCept.php
@@ -0,0 +1,34 @@
+wantTo('ensure login page works');
+
+$loginPage = LoginPage::openBy($I);
+
+$I->amGoingTo('submit login form with no data');
+$loginPage->login('', '');
+$I->expectTo('see validations errors');
+$I->see('Username cannot be blank.', '.help-block');
+$I->see('Password cannot be blank.', '.help-block');
+
+$I->amGoingTo('try to login with wrong credentials');
+$I->expectTo('see validations errors');
+$loginPage->login('admin', 'wrong');
+$I->expectTo('see validations errors');
+$I->see('Incorrect username or password.', '.help-block');
+
+$I->amGoingTo('try to login with correct credentials');
+$loginPage->login('erau', 'password_0');
+$I->expectTo('see that user is logged');
+$I->see('Logout (erau)', 'form button[type=submit]');
+$I->dontSeeLink('Login');
+$I->dontSeeLink('Signup');
+/** Uncomment if using WebDriver
+ * $I->click('Logout (erau)');
+ * $I->dontSeeLink('Logout (erau)');
+ * $I->seeLink('Login');
+ */
diff --git a/tests/codeception/frontend/acceptance/SignupCest.php b/tests/codeception/frontend/acceptance/SignupCest.php
new file mode 100644
index 0000000..e8431e5
--- /dev/null
+++ b/tests/codeception/frontend/acceptance/SignupCest.php
@@ -0,0 +1,82 @@
+ 'tester.email@example.com',
+ 'username' => 'tester',
+ ]);
+ }
+
+ /**
+ * This method is called when test fails.
+ * @param \Codeception\Event\FailEvent $event
+ */
+ public function _fail($event)
+ {
+ }
+
+ /**
+ * @param \codeception_frontend\AcceptanceTester $I
+ * @param \Codeception\Scenario $scenario
+ */
+ public function testUserSignup($I, $scenario)
+ {
+ $I->wantTo('ensure that signup works');
+
+ $signupPage = SignupPage::openBy($I);
+ $I->see('Signup', 'h1');
+ $I->see('Please fill out the following fields to signup:');
+
+ $I->amGoingTo('submit signup form with no data');
+
+ $signupPage->submit([]);
+
+ $I->expectTo('see validation errors');
+ $I->see('Username cannot be blank.', '.help-block');
+ $I->see('Email cannot be blank.', '.help-block');
+ $I->see('Password cannot be blank.', '.help-block');
+
+ $I->amGoingTo('submit signup form with not correct email');
+ $signupPage->submit([
+ 'username' => 'tester',
+ 'email' => 'tester.email',
+ 'password' => 'tester_password',
+ ]);
+
+ $I->expectTo('see that email address is wrong');
+ $I->dontSee('Username cannot be blank.', '.help-block');
+ $I->dontSee('Password cannot be blank.', '.help-block');
+ $I->see('Email is not a valid email address.', '.help-block');
+
+ $I->amGoingTo('submit signup form with correct email');
+ $signupPage->submit([
+ 'username' => 'tester',
+ 'email' => 'tester.email@example.com',
+ 'password' => 'tester_password',
+ ]);
+
+ $I->expectTo('see that user logged in');
+ $I->see('Logout (tester)', 'form button[type=submit]');
+ }
+}
diff --git a/tests/codeception/frontend/acceptance/_bootstrap.php b/tests/codeception/frontend/acceptance/_bootstrap.php
new file mode 100644
index 0000000..b0a40ef
--- /dev/null
+++ b/tests/codeception/frontend/acceptance/_bootstrap.php
@@ -0,0 +1,2 @@
+wantTo('ensure that about works');
+AboutPage::openBy($I);
+$I->see('About', 'h1');
diff --git a/tests/codeception/frontend/functional/ContactCept.php b/tests/codeception/frontend/functional/ContactCept.php
new file mode 100644
index 0000000..a93d7e4
--- /dev/null
+++ b/tests/codeception/frontend/functional/ContactCept.php
@@ -0,0 +1,47 @@
+wantTo('ensure that contact works');
+
+$contactPage = ContactPage::openBy($I);
+
+$I->see('Contact', 'h1');
+
+$I->amGoingTo('submit contact form with no data');
+$contactPage->submit([]);
+$I->expectTo('see validations errors');
+$I->see('Contact', 'h1');
+$I->see('Name cannot be blank', '.help-block');
+$I->see('Email cannot be blank', '.help-block');
+$I->see('Subject cannot be blank', '.help-block');
+$I->see('Body cannot be blank', '.help-block');
+$I->see('The verification code is incorrect', '.help-block');
+
+$I->amGoingTo('submit contact form with not correct email');
+$contactPage->submit([
+ 'name' => 'tester',
+ 'email' => 'tester.email',
+ 'subject' => 'test subject',
+ 'body' => 'test content',
+ 'verifyCode' => 'testme',
+]);
+$I->expectTo('see that email address is wrong');
+$I->dontSee('Name cannot be blank', '.help-block');
+$I->see('Email is not a valid email address.', '.help-block');
+$I->dontSee('Subject cannot be blank', '.help-block');
+$I->dontSee('Body cannot be blank', '.help-block');
+$I->dontSee('The verification code is incorrect', '.help-block');
+
+$I->amGoingTo('submit contact form with correct data');
+$contactPage->submit([
+ 'name' => 'tester',
+ 'email' => 'tester@example.com',
+ 'subject' => 'test subject',
+ 'body' => 'test content',
+ 'verifyCode' => 'testme',
+]);
+$I->see('Thank you for contacting us. We will respond to you as soon as possible.');
diff --git a/tests/codeception/frontend/functional/HomeCept.php b/tests/codeception/frontend/functional/HomeCept.php
new file mode 100644
index 0000000..f340061
--- /dev/null
+++ b/tests/codeception/frontend/functional/HomeCept.php
@@ -0,0 +1,12 @@
+wantTo('ensure that home page works');
+$I->amOnPage(Yii::$app->homeUrl);
+$I->see('My Company');
+$I->seeLink('About');
+$I->click('About');
+$I->see('This is the About page.');
diff --git a/tests/codeception/frontend/functional/LoginCept.php b/tests/codeception/frontend/functional/LoginCept.php
new file mode 100644
index 0000000..e1d2f9b
--- /dev/null
+++ b/tests/codeception/frontend/functional/LoginCept.php
@@ -0,0 +1,29 @@
+wantTo('ensure login page works');
+
+$loginPage = LoginPage::openBy($I);
+
+$I->amGoingTo('submit login form with no data');
+$loginPage->login('', '');
+$I->expectTo('see validations errors');
+$I->see('Username cannot be blank.', '.help-block');
+$I->see('Password cannot be blank.', '.help-block');
+
+$I->amGoingTo('try to login with wrong credentials');
+$I->expectTo('see validations errors');
+$loginPage->login('admin', 'wrong');
+$I->expectTo('see validations errors');
+$I->see('Incorrect username or password.', '.help-block');
+
+$I->amGoingTo('try to login with correct credentials');
+$loginPage->login('erau', 'password_0');
+$I->expectTo('see that user is logged');
+$I->see('Logout (erau)', 'form button[type=submit]');
+$I->dontSeeLink('Login');
+$I->dontSeeLink('Signup');
diff --git a/tests/codeception/frontend/functional/SignupCest.php b/tests/codeception/frontend/functional/SignupCest.php
new file mode 100644
index 0000000..16410de
--- /dev/null
+++ b/tests/codeception/frontend/functional/SignupCest.php
@@ -0,0 +1,88 @@
+loadFixtures();
+ }
+
+ /**
+ * This method is called when test fails.
+ * @param \codeception_frontend\FunctionalTester $I
+ */
+ public function _failed($I)
+ {
+
+ }
+
+ /**
+ *
+ * @param \codeception_frontend\FunctionalTester $I
+ * @param \Codeception\Scenario $scenario
+ */
+ public function testUserSignup($I, $scenario)
+ {
+ $I->wantTo('ensure that signup works');
+
+ $signupPage = SignupPage::openBy($I);
+ $I->see('Signup', 'h1');
+ $I->see('Please fill out the following fields to signup:');
+
+ $I->amGoingTo('submit signup form with no data');
+
+ $signupPage->submit([]);
+
+ $I->expectTo('see validation errors');
+ $I->see('Username cannot be blank.', '.help-block');
+ $I->see('Email cannot be blank.', '.help-block');
+ $I->see('Password cannot be blank.', '.help-block');
+
+ $I->amGoingTo('submit signup form with not correct email');
+ $signupPage->submit([
+ 'username' => 'tester',
+ 'email' => 'tester.email',
+ 'password' => 'tester_password',
+ ]);
+
+ $I->expectTo('see that email address is wrong');
+ $I->dontSee('Username cannot be blank.', '.help-block');
+ $I->dontSee('Password cannot be blank.', '.help-block');
+ $I->see('Email is not a valid email address.', '.help-block');
+
+ $I->amGoingTo('submit signup form with correct email');
+ $signupPage->submit([
+ 'username' => 'tester',
+ 'email' => 'tester.email@example.com',
+ 'password' => 'tester_password',
+ ]);
+
+ $I->expectTo('see that user is created');
+ $I->seeRecord('common\models\User', [
+ 'username' => 'tester',
+ 'email' => 'tester.email@example.com',
+ ]);
+
+ $I->expectTo('see that user logged in');
+ $I->see('Logout (tester)', 'form button[type=submit]');
+ }
+}
diff --git a/tests/codeception/frontend/functional/_bootstrap.php b/tests/codeception/frontend/functional/_bootstrap.php
new file mode 100644
index 0000000..1abc491
--- /dev/null
+++ b/tests/codeception/frontend/functional/_bootstrap.php
@@ -0,0 +1,3 @@
+ 'okirlin',
+ 'auth_key' => 'iwTNae9t34OmnK6l4vT4IeaTk-YWI2Rv',
+ 'password_hash' => '$2y$13$CXT0Rkle1EMJ/c1l5bylL.EylfmQ39O5JlHJVFpNn618OUS1HwaIi',
+ 'password_reset_token' => 't5GU9NwpuGYSfb7FEZMAxqtuz2PkEvv_' . time(),
+ 'created_at' => '1391885313',
+ 'updated_at' => '1391885313',
+ 'email' => 'brady.renner@rutherford.com',
+ ],
+ [
+ 'username' => 'troy.becker',
+ 'auth_key' => 'EdKfXrx88weFMV0vIxuTMWKgfK2tS3Lp',
+ 'password_hash' => '$2y$13$g5nv41Px7VBqhS3hVsVN2.MKfgT3jFdkXEsMC4rQJLfaMa7VaJqL2',
+ 'password_reset_token' => '4BSNyiZNAuxjs5Mty990c47sVrgllIi_' . time(),
+ 'created_at' => '1391885313',
+ 'updated_at' => '1391885313',
+ 'email' => 'nicolas.dianna@hotmail.com',
+ 'status' => '0',
+ ],
+];
diff --git a/tests/codeception/frontend/unit/models/ContactFormTest.php b/tests/codeception/frontend/unit/models/ContactFormTest.php
new file mode 100644
index 0000000..9aaf595
--- /dev/null
+++ b/tests/codeception/frontend/unit/models/ContactFormTest.php
@@ -0,0 +1,59 @@
+mailer->fileTransportCallback = function ($mailer, $message) {
+ return 'testing_message.eml';
+ };
+ }
+
+ protected function tearDown()
+ {
+ unlink($this->getMessageFile());
+ parent::tearDown();
+ }
+
+ public function testContact()
+ {
+ $model = new ContactForm();
+
+ $model->attributes = [
+ 'name' => 'Tester',
+ 'email' => 'tester@example.com',
+ 'subject' => 'very important letter subject',
+ 'body' => 'body of current message',
+ ];
+
+ $model->sendEmail('admin@example.com');
+
+ $this->specify('email should be send', function () {
+ expect('email file should exist', file_exists($this->getMessageFile()))->true();
+ });
+
+ $this->specify('message should contain correct data', function () use ($model) {
+ $emailMessage = file_get_contents($this->getMessageFile());
+
+ expect('email should contain user name', $emailMessage)->contains($model->name);
+ expect('email should contain sender email', $emailMessage)->contains($model->email);
+ expect('email should contain subject', $emailMessage)->contains($model->subject);
+ expect('email should contain body', $emailMessage)->contains($model->body);
+ });
+ }
+
+ private function getMessageFile()
+ {
+ return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml';
+ }
+}
diff --git a/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php b/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php
new file mode 100644
index 0000000..ced8cce
--- /dev/null
+++ b/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php
@@ -0,0 +1,87 @@
+mailer->fileTransportCallback = function ($mailer, $message) {
+ return 'testing_message.eml';
+ };
+ }
+
+ protected function tearDown()
+ {
+ @unlink($this->getMessageFile());
+
+ parent::tearDown();
+ }
+
+ public function testSendEmailWrongUser()
+ {
+ $this->specify('no user with such email, message should not be sent', function () {
+
+ $model = new PasswordResetRequestForm();
+ $model->email = 'not-existing-email@example.com';
+
+ expect('email not sent', $model->sendEmail())->false();
+
+ });
+
+ $this->specify('user is not active, message should not be sent', function () {
+
+ $model = new PasswordResetRequestForm();
+ $model->email = $this->user[1]['email'];
+
+ expect('email not sent', $model->sendEmail())->false();
+
+ });
+ }
+
+ public function testSendEmailCorrectUser()
+ {
+ $model = new PasswordResetRequestForm();
+ $model->email = $this->user[0]['email'];
+ $user = User::findOne(['password_reset_token' => $this->user[0]['password_reset_token']]);
+
+ expect('email sent', $model->sendEmail())->true();
+ expect('user has valid token', $user->password_reset_token)->notNull();
+
+ $this->specify('message has correct format', function () use ($model) {
+
+ expect('message file exists', file_exists($this->getMessageFile()))->true();
+
+ $message = file_get_contents($this->getMessageFile());
+ expect('message "from" is correct', $message)->contains(Yii::$app->params['supportEmail']);
+ expect('message "to" is correct', $message)->contains($model->email);
+
+ });
+ }
+
+ public function fixtures()
+ {
+ return [
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => '@tests/codeception/frontend/unit/fixtures/data/models/user.php'
+ ],
+ ];
+ }
+
+ private function getMessageFile()
+ {
+ return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml';
+ }
+}
diff --git a/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php b/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php
new file mode 100644
index 0000000..a4dd021
--- /dev/null
+++ b/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php
@@ -0,0 +1,43 @@
+user[0]['password_reset_token']);
+ expect('password should be resetted', $form->resetPassword())->true();
+ }
+
+ public function fixtures()
+ {
+ return [
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => '@tests/codeception/frontend/unit/fixtures/data/models/user.php'
+ ],
+ ];
+ }
+}
diff --git a/tests/codeception/frontend/unit/models/SignupFormTest.php b/tests/codeception/frontend/unit/models/SignupFormTest.php
new file mode 100644
index 0000000..4d08e8c
--- /dev/null
+++ b/tests/codeception/frontend/unit/models/SignupFormTest.php
@@ -0,0 +1,52 @@
+ 'some_username',
+ 'email' => 'some_email@example.com',
+ 'password' => 'some_password',
+ ]);
+
+ $user = $model->signup();
+
+ $this->assertInstanceOf('common\models\User', $user, 'user should be valid');
+
+ expect('username should be correct', $user->username)->equals('some_username');
+ expect('email should be correct', $user->email)->equals('some_email@example.com');
+ expect('password should be correct', $user->validatePassword('some_password'))->true();
+ }
+
+ public function testNotCorrectSignup()
+ {
+ $model = new SignupForm([
+ 'username' => 'troy.becker',
+ 'email' => 'nicolas.dianna@hotmail.com',
+ 'password' => 'some_password',
+ ]);
+
+ expect('username and email are in use, user should not be created', $model->signup())->null();
+ }
+
+ public function fixtures()
+ {
+ return [
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => '@tests/codeception/frontend/unit/fixtures/data/models/user.php',
+ ],
+ ];
+ }
+}
diff --git a/yii.bat b/yii.bat
new file mode 100644
index 0000000..d516b3a
--- /dev/null
+++ b/yii.bat
@@ -0,0 +1,20 @@
+@echo off
+
+rem -------------------------------------------------------------
+rem Yii command line bootstrap script for Windows.
+rem
+rem @author Qiang Xue
+rem @link http://www.yiiframework.com/
+rem @copyright Copyright (c) 2008 Yii Software LLC
+rem @license http://www.yiiframework.com/license/
+rem -------------------------------------------------------------
+
+@setlocal
+
+set YII_PATH=%~dp0
+
+if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
+
+"%PHP_COMMAND%" "%YII_PATH%yii" %*
+
+@endlocal
--
libgit2 0.21.4