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..85cd96a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,55 @@
+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
+ tests/ contains tests for common classes
+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
+ tests/ contains tests for backend application
+ 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
+ tests/ contains tests for frontend application
+ 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
+```
diff --git a/Vagrantfile b/Vagrantfile
new file mode 100644
index 0000000..689a92c
--- /dev/null
+++ b/Vagrantfile
@@ -0,0 +1,72 @@
+require 'yaml'
+require 'fileutils'
+
+domains = {
+ frontend: 'y2aa-frontend.dev',
+ backend: 'y2aa-backend.dev'
+}
+
+config = {
+ local: './vagrant/config/vagrant-local.yml',
+ example: './vagrant/config/vagrant-local.example.yml'
+}
+
+# copy config from example if local config not exists
+FileUtils.cp config[:example], config[:local] unless File.exist?(config[:local])
+# read config
+options = YAML.load_file config[:local]
+
+# check github token
+if options['github_token'].nil? || options['github_token'].to_s.length != 40
+ puts "You must place REAL GitHub token into configuration:\n/yii2-app-advancded/vagrant/config/vagrant-local.yml"
+ exit
+end
+
+# vagrant configurate
+Vagrant.configure(2) do |config|
+ # select the box
+ config.vm.box = 'ubuntu/trusty64'
+
+ # should we ask about box updates?
+ config.vm.box_check_update = options['box_check_update']
+
+ config.vm.provider 'virtualbox' do |vb|
+ # machine cpus count
+ vb.cpus = options['cpus']
+ # machine memory size
+ vb.memory = options['memory']
+ # machine name (for VirtualBox UI)
+ vb.name = options['machine_name']
+ end
+
+ # machine name (for vagrant console)
+ config.vm.define options['machine_name']
+
+ # machine name (for guest machine console)
+ config.vm.hostname = options['machine_name']
+
+ # network settings
+ config.vm.network 'private_network', ip: options['ip']
+
+ # sync: folder 'yii2-app-advanced' (host machine) -> folder '/app' (guest machine)
+ config.vm.synced_folder './', '/app', owner: 'vagrant', group: 'vagrant'
+
+ # disable folder '/vagrant' (guest machine)
+ config.vm.synced_folder '.', '/vagrant', disabled: true
+
+ # hosts settings (host machine)
+ config.vm.provision :hostmanager
+ config.hostmanager.enabled = true
+ config.hostmanager.manage_host = true
+ config.hostmanager.ignore_private_ip = false
+ config.hostmanager.include_offline = true
+ config.hostmanager.aliases = domains.values
+
+ # provisioners
+ config.vm.provision 'shell', path: './vagrant/provision/once-as-root.sh', args: [options['timezone']]
+ config.vm.provision 'shell', path: './vagrant/provision/once-as-vagrant.sh', args: [options['github_token']], privileged: false
+ config.vm.provision 'shell', path: './vagrant/provision/always-as-root.sh', run: 'always'
+
+ # post-install message (vagrant console)
+ config.vm.post_up_message = "Frontend URL: http://#{domains[:frontend]}\nBackend URL: http://#{domains[:backend]}"
+end
diff --git a/backend/assets/AppAsset.php b/backend/assets/AppAsset.php
new file mode 100644
index 0000000..940c0af
--- /dev/null
+++ b/backend/assets/AppAsset.php
@@ -0,0 +1,23 @@
+ 'app-backend',
+ 'basePath' => dirname(__DIR__),
+ 'controllerNamespace' => 'backend\controllers',
+ 'bootstrap' => ['log'],
+ 'modules' => [],
+ 'components' => [
+ 'request' => [
+ 'csrfParam' => '_csrf-backend',
+ ],
+ 'user' => [
+ 'identityClass' => 'common\models\User',
+ 'enableAutoLogin' => true,
+ 'identityCookie' => ['name' => '_identity-backend', 'httpOnly' => true],
+ ],
+ 'session' => [
+ // this is the name of the session cookie used for login on the backend
+ 'name' => 'advanced-backend',
+ ],
+ 'log' => [
+ 'traceLevel' => YII_DEBUG ? 3 : 0,
+ 'targets' => [
+ [
+ 'class' => 'yii\log\FileTarget',
+ 'levels' => ['error', 'warning'],
+ ],
+ ],
+ ],
+ 'errorHandler' => [
+ 'errorAction' => 'site/error',
+ ],
+ /*
+ 'urlManager' => [
+ 'enablePrettyUrl' => true,
+ 'showScriptName' => false,
+ 'rules' => [
+ ],
+ ],
+ */
+ ],
+ '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/config/test.php b/backend/config/test.php
new file mode 100644
index 0000000..a944532
--- /dev/null
+++ b/backend/config/test.php
@@ -0,0 +1,4 @@
+ 'app-backend-tests',
+];
diff --git a/backend/controllers/SiteController.php b/backend/controllers/SiteController.php
new file mode 100644
index 0000000..9624dcc
--- /dev/null
+++ b/backend/controllers/SiteController.php
@@ -0,0 +1,98 @@
+ [
+ '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',
+ ],
+ ];
+ }
+
+ /**
+ * Displays homepage.
+ *
+ * @return string
+ */
+ public function actionIndex()
+ {
+ return $this->render('index');
+ }
+
+ /**
+ * Login action.
+ *
+ * @return string
+ */
+ 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,
+ ]);
+ }
+ }
+
+ /**
+ * Logout action.
+ *
+ * @return string
+ */
+ 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/tests/_bootstrap.php b/backend/tests/_bootstrap.php
new file mode 100644
index 0000000..83a1f26
--- /dev/null
+++ b/backend/tests/_bootstrap.php
@@ -0,0 +1,9 @@
+ '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/backend/tests/_output/.gitignore b/backend/tests/_output/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/backend/tests/_output/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/backend/tests/_support/.gitignore b/backend/tests/_support/.gitignore
new file mode 100644
index 0000000..36e264c
--- /dev/null
+++ b/backend/tests/_support/.gitignore
@@ -0,0 +1 @@
+_generated
diff --git a/backend/tests/_support/FunctionalTester.php b/backend/tests/_support/FunctionalTester.php
new file mode 100644
index 0000000..80a79ed
--- /dev/null
+++ b/backend/tests/_support/FunctionalTester.php
@@ -0,0 +1,25 @@
+haveFixtures([
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => codecept_data_dir() . 'login_data.php'
+ ]
+ ]);
+ }
+ /**
+ * @param FunctionalTester $I
+ */
+ public function loginUser(FunctionalTester $I)
+ {
+ $I->amOnPage('/site/login');
+ $I->fillField('Username', 'erau');
+ $I->fillField('Password', 'password_0');
+ $I->click('login-button');
+
+ $I->see('Logout (erau)', 'form button[type=submit]');
+ $I->dontSeeLink('Login');
+ $I->dontSeeLink('Signup');
+ }
+}
diff --git a/backend/tests/functional/_bootstrap.php b/backend/tests/functional/_bootstrap.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/backend/tests/functional/_bootstrap.php
@@ -0,0 +1 @@
+
+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']],
+ ];
+ if (Yii::$app->user->isGuest) {
+ $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 logout']
+ )
+ . 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/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..f780610
--- /dev/null
+++ b/backend/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/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..2f085b3
--- /dev/null
+++ b/backend/web/css/site.css
@@ -0,0 +1,120 @@
+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 > button.logout {
+ padding: 15px;
+ border: none;
+}
+
+@media(max-width:767px) {
+ .nav li > form > button.logout {
+ display:block;
+ text-align: left;
+ width: 100%;
+ padding: 10px 15px;
+ }
+}
+
+.nav > li > form > button.logout:focus,
+.nav > li > form > button.logout:hover {
+ text-decoration: none;
+}
+
+.nav > li > form > button.logout:focus {
+ outline: 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/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/codeception.yml b/codeception.yml
new file mode 100644
index 0000000..af20104
--- /dev/null
+++ b/codeception.yml
@@ -0,0 +1,9 @@
+# global codeception file to run tests from all apps
+include:
+ - common
+ - frontend
+ - backend
+paths:
+ log: console/runtime/logs
+settings:
+ colors: true
\ No newline at end of file
diff --git a/common/codeception.yml b/common/codeception.yml
new file mode 100644
index 0000000..de5d76a
--- /dev/null
+++ b/common/codeception.yml
@@ -0,0 +1,15 @@
+namespace: common\tests
+actor: Tester
+paths:
+ tests: tests
+ log: tests/_output
+ data: tests/_data
+ helpers: tests/_support
+settings:
+ bootstrap: _bootstrap.php
+ colors: true
+ memory_limit: 1024M
+modules:
+ config:
+ Yii2:
+ configFile: 'config/test-local.php'
diff --git a/common/config/.gitignore b/common/config/.gitignore
new file mode 100644
index 0000000..42799dd
--- /dev/null
+++ b/common/config/.gitignore
@@ -0,0 +1,3 @@
+main-local.php
+params-local.php
+test-local.php
diff --git a/common/config/bootstrap.php b/common/config/bootstrap.php
new file mode 100644
index 0000000..93d0185
--- /dev/null
+++ b/common/config/bootstrap.php
@@ -0,0 +1,5 @@
+ dirname(dirname(__DIR__)) . '/vendor',
+ 'components' => [
+ 'cache' => [
+ 'class' => 'yii\caching\FileCache',
+ ],
+ ],
+];
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/config/test.php b/common/config/test.php
new file mode 100644
index 0000000..c952c41
--- /dev/null
+++ b/common/config/test.php
@@ -0,0 +1,11 @@
+ 'app-common-tests',
+ 'basePath' => dirname(__DIR__),
+ 'components' => [
+ 'user' => [
+ 'class' => 'yii\web\User',
+ 'identityClass' => 'common\models\User',
+ ],
+ ],
+];
diff --git a/common/fixtures/User.php b/common/fixtures/User.php
new file mode 100644
index 0000000..d4b0f36
--- /dev/null
+++ b/common/fixtures/User.php
@@ -0,0 +1,9 @@
+
+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..aab1d5d
--- /dev/null
+++ b/common/mail/layouts/text.php
@@ -0,0 +1,14 @@
+
+
+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/LoginForm.php b/common/models/LoginForm.php
new file mode 100644
index 0000000..c4869d4
--- /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 bool 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..2f4508f
--- /dev/null
+++ b/common/models/User.php
@@ -0,0 +1,189 @@
+ 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 bool
+ */
+ 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 bool 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/tests/_bootstrap.php b/common/tests/_bootstrap.php
new file mode 100644
index 0000000..d5a93de
--- /dev/null
+++ b/common/tests/_bootstrap.php
@@ -0,0 +1,9 @@
+ '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/common/tests/_output/.gitignore b/common/tests/_output/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/common/tests/_output/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/common/tests/_support/.gitignore b/common/tests/_support/.gitignore
new file mode 100644
index 0000000..36e264c
--- /dev/null
+++ b/common/tests/_support/.gitignore
@@ -0,0 +1 @@
+_generated
diff --git a/common/tests/_support/UnitTester.php b/common/tests/_support/UnitTester.php
new file mode 100644
index 0000000..91cb34f
--- /dev/null
+++ b/common/tests/_support/UnitTester.php
@@ -0,0 +1,25 @@
+tester->haveFixtures([
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => codecept_data_dir() . 'user.php'
+ ]
+ ]);
+ }
+
+ public function testLoginNoUser()
+ {
+ $model = new LoginForm([
+ 'username' => 'not_existing_username',
+ 'password' => 'not_existing_password',
+ ]);
+
+ 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',
+ ]);
+
+ 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',
+ ]);
+
+ 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();
+ }
+}
diff --git a/common/widgets/Alert.php b/common/widgets/Alert.php
new file mode 100644
index 0000000..69f1c40
--- /dev/null
+++ b/common/widgets/Alert.php
@@ -0,0 +1,75 @@
+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/composer.json b/composer.json
new file mode 100644
index 0000000..7a70d4a
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,42 @@
+{
+ "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": "stable",
+ "require": {
+ "php": ">=5.4.0",
+ "yiisoft/yii2": "~2.0.6",
+ "yiisoft/yii2-bootstrap": "~2.0.0",
+ "yiisoft/yii2-swiftmailer": "~2.0.0"
+ },
+ "require-dev": {
+ "yiisoft/yii2-debug": "~2.0.0",
+ "yiisoft/yii2-gii": "~2.0.0",
+ "yiisoft/yii2-faker": "~2.0.0",
+
+ "codeception/base": "^2.2.3",
+ "codeception/verify": "~0.3.1"
+ },
+ "config": {
+ "process-timeout": 1800
+ },
+ "extra": {
+ "asset-installer-paths": {
+ "npm-asset-library": "vendor/npm",
+ "bower-asset-library": "vendor/bower"
+ }
+ },
+ "scripts": {
+ "post-install-cmd": "php init --env=Development --overwrite=n"
+ }
+}
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/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..1d75140
--- /dev/null
+++ b/environments/dev/backend/web/index.php
@@ -0,0 +1,17 @@
+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 @@
+ [
+ 'db' => [
+ 'dsn' => 'mysql:host=localhost;dbname=yii2advanced_test',
+ ]
+ ],
+ ]
+);
diff --git a/environments/dev/console/config/main-local.php b/environments/dev/console/config/main-local.php
new file mode 100644
index 0000000..1d2118f
--- /dev/null
+++ b/environments/dev/console/config/main-local.php
@@ -0,0 +1,7 @@
+ ['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..1d75140
--- /dev/null
+++ b/environments/dev/frontend/web/index.php
@@ -0,0 +1,17 @@
+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/dev/yii_test b/environments/dev/yii_test
new file mode 100644
index 0000000..880050e
--- /dev/null
+++ b/environments/dev/yii_test
@@ -0,0 +1,27 @@
+#!/usr/bin/env php
+run();
+exit($exitCode);
diff --git a/environments/dev/yii_test.bat b/environments/dev/yii_test.bat
new file mode 100644
index 0000000..854e08b
--- /dev/null
+++ b/environments/dev/yii_test.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_test" %*
+
+@endlocal
diff --git a/environments/index.php b/environments/index.php
new file mode 100644
index 0000000..5c3cca3
--- /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',
+ 'yii_test',
+ ],
+ '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/assets/AppAsset.php b/frontend/assets/AppAsset.php
new file mode 100644
index 0000000..8b5f017
--- /dev/null
+++ b/frontend/assets/AppAsset.php
@@ -0,0 +1,23 @@
+ 'app-frontend',
+ 'basePath' => dirname(__DIR__),
+ 'bootstrap' => ['log'],
+ 'controllerNamespace' => 'frontend\controllers',
+ 'components' => [
+ 'request' => [
+ 'csrfParam' => '_csrf-frontend',
+ ],
+ 'user' => [
+ 'identityClass' => 'common\models\User',
+ 'enableAutoLogin' => true,
+ 'identityCookie' => ['name' => '_identity-frontend', 'httpOnly' => true],
+ ],
+ 'session' => [
+ // this is the name of the session cookie used for login on the frontend
+ 'name' => 'advanced-frontend',
+ ],
+ 'log' => [
+ 'traceLevel' => YII_DEBUG ? 3 : 0,
+ 'targets' => [
+ [
+ 'class' => 'yii\log\FileTarget',
+ 'levels' => ['error', 'warning'],
+ ],
+ ],
+ ],
+ 'errorHandler' => [
+ 'errorAction' => 'site/error',
+ ],
+ /*
+ 'urlManager' => [
+ '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/config/test.php b/frontend/config/test.php
new file mode 100644
index 0000000..cf9dfcc
--- /dev/null
+++ b/frontend/config/test.php
@@ -0,0 +1,4 @@
+ 'app-frontend-tests',
+];
diff --git a/frontend/controllers/SiteController.php b/frontend/controllers/SiteController.php
new file mode 100644
index 0000000..67b5aa0
--- /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..d0cc9cb
--- /dev/null
+++ b/frontend/models/ContactForm.php
@@ -0,0 +1,60 @@
+ '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 bool 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..823643f
--- /dev/null
+++ b/frontend/models/PasswordResetRequestForm.php
@@ -0,0 +1,68 @@
+ '\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 bool 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..fb36dd0
--- /dev/null
+++ b/frontend/models/ResetPasswordForm.php
@@ -0,0 +1,64 @@
+_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 bool 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..57a51d8
--- /dev/null
+++ b/frontend/models/SignupForm.php
@@ -0,0 +1,58 @@
+ '\common\models\User', 'message' => 'This username has already been taken.'],
+ ['username', 'string', 'min' => 2, 'max' => 255],
+
+ ['email', '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/tests/_bootstrap.php b/frontend/tests/_bootstrap.php
new file mode 100644
index 0000000..83a1f26
--- /dev/null
+++ b/frontend/tests/_bootstrap.php
@@ -0,0 +1,9 @@
+ '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/frontend/tests/_data/user.php b/frontend/tests/_data/user.php
new file mode 100644
index 0000000..3670e09
--- /dev/null
+++ b/frontend/tests/_data/user.php
@@ -0,0 +1,23 @@
+ '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/frontend/tests/_output/.gitignore b/frontend/tests/_output/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/frontend/tests/_output/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/frontend/tests/_support/.gitignore b/frontend/tests/_support/.gitignore
new file mode 100644
index 0000000..36e264c
--- /dev/null
+++ b/frontend/tests/_support/.gitignore
@@ -0,0 +1 @@
+_generated
diff --git a/frontend/tests/_support/FunctionalTester.php b/frontend/tests/_support/FunctionalTester.php
new file mode 100644
index 0000000..7475d8a
--- /dev/null
+++ b/frontend/tests/_support/FunctionalTester.php
@@ -0,0 +1,33 @@
+see($message, '.help-block');
+ }
+
+ public function dontSeeValidationError($message)
+ {
+ $this->dontSee($message, '.help-block');
+ }
+}
diff --git a/frontend/tests/_support/UnitTester.php b/frontend/tests/_support/UnitTester.php
new file mode 100644
index 0000000..8c708e7
--- /dev/null
+++ b/frontend/tests/_support/UnitTester.php
@@ -0,0 +1,25 @@
+amOnPage(Url::toRoute('/site/index'));
+ $I->see('My Company');
+ $I->seeLink('About');
+ $I->click('About');
+ $I->see('This is the About page.');
+ }
+}
diff --git a/frontend/tests/acceptance/_bootstrap.php b/frontend/tests/acceptance/_bootstrap.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/frontend/tests/acceptance/_bootstrap.php
@@ -0,0 +1 @@
+amOnRoute('site/about');
+ $I->see('About', 'h1');
+ }
+}
diff --git a/frontend/tests/functional/ContactCest.php b/frontend/tests/functional/ContactCest.php
new file mode 100644
index 0000000..bdda991
--- /dev/null
+++ b/frontend/tests/functional/ContactCest.php
@@ -0,0 +1,59 @@
+amOnPage(['site/contact']);
+ }
+
+ public function checkContact(FunctionalTester $I)
+ {
+ $I->see('Contact', 'h1');
+ }
+
+ public function checkContactSubmitNoData(FunctionalTester $I)
+ {
+ $I->submitForm('#contact-form', []);
+ $I->see('Contact', 'h1');
+ $I->seeValidationError('Name cannot be blank');
+ $I->seeValidationError('Email cannot be blank');
+ $I->seeValidationError('Subject cannot be blank');
+ $I->seeValidationError('Body cannot be blank');
+ $I->seeValidationError('The verification code is incorrect');
+ }
+
+ public function checkContactSubmitNotCorrectEmail(FunctionalTester $I)
+ {
+ $I->submitForm('#contact-form', [
+ 'ContactForm[name]' => 'tester',
+ 'ContactForm[email]' => 'tester.email',
+ 'ContactForm[subject]' => 'test subject',
+ 'ContactForm[body]' => 'test content',
+ 'ContactForm[verifyCode]' => 'testme',
+ ]);
+ $I->seeValidationError('Email is not a valid email address.');
+ $I->dontSeeValidationError('Name cannot be blank');
+ $I->dontSeeValidationError('Subject cannot be blank');
+ $I->dontSeeValidationError('Body cannot be blank');
+ $I->dontSeeValidationError('The verification code is incorrect');
+ }
+
+ public function checkContactSubmitCorrectData(FunctionalTester $I)
+ {
+ $I->submitForm('#contact-form', [
+ 'ContactForm[name]' => 'tester',
+ 'ContactForm[email]' => 'tester@example.com',
+ 'ContactForm[subject]' => 'test subject',
+ 'ContactForm[body]' => 'test content',
+ 'ContactForm[verifyCode]' => 'testme',
+ ]);
+ $I->seeEmailIsSent();
+ $I->see('Thank you for contacting us. We will respond to you as soon as possible.');
+ }
+}
diff --git a/frontend/tests/functional/HomeCest.php b/frontend/tests/functional/HomeCest.php
new file mode 100644
index 0000000..8f99b48
--- /dev/null
+++ b/frontend/tests/functional/HomeCest.php
@@ -0,0 +1,17 @@
+amOnPage(\Yii::$app->homeUrl);
+ $I->see('My Company');
+ $I->seeLink('About');
+ $I->click('About');
+ $I->see('This is the About page.');
+ }
+}
\ No newline at end of file
diff --git a/frontend/tests/functional/LoginCest.php b/frontend/tests/functional/LoginCest.php
new file mode 100644
index 0000000..696aeda
--- /dev/null
+++ b/frontend/tests/functional/LoginCest.php
@@ -0,0 +1,49 @@
+haveFixtures([
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => codecept_data_dir() . 'login_data.php'
+ ]
+ ]);
+ $I->amOnRoute('site/login');
+ }
+
+ protected function formParams($login, $password)
+ {
+ return [
+ 'LoginForm[username]' => $login,
+ 'LoginForm[password]' => $password,
+ ];
+ }
+
+ public function checkEmpty(FunctionalTester $I)
+ {
+ $I->submitForm('#login-form', $this->formParams('', ''));
+ $I->seeValidationError('Username cannot be blank.');
+ $I->seeValidationError('Password cannot be blank.');
+ }
+
+ public function checkWrongPassword(FunctionalTester $I)
+ {
+ $I->submitForm('#login-form', $this->formParams('admin', 'wrong'));
+ $I->seeValidationError('Incorrect username or password.');
+ }
+
+ public function checkValidLogin(FunctionalTester $I)
+ {
+ $I->submitForm('#login-form', $this->formParams('erau', 'password_0'));
+ $I->see('Logout (erau)', 'form button[type=submit]');
+ $I->dontSeeLink('Login');
+ $I->dontSeeLink('Signup');
+ }
+}
diff --git a/frontend/tests/functional/SignupCest.php b/frontend/tests/functional/SignupCest.php
new file mode 100644
index 0000000..8cfde9a
--- /dev/null
+++ b/frontend/tests/functional/SignupCest.php
@@ -0,0 +1,57 @@
+amOnRoute('site/signup');
+ }
+
+ public function signupWithEmptyFields(FunctionalTester $I)
+ {
+ $I->see('Signup', 'h1');
+ $I->see('Please fill out the following fields to signup:');
+ $I->submitForm($this->formId, []);
+ $I->seeValidationError('Username cannot be blank.');
+ $I->seeValidationError('Email cannot be blank.');
+ $I->seeValidationError('Password cannot be blank.');
+
+ }
+
+ public function signupWithWrongEmail(FunctionalTester $I)
+ {
+ $I->submitForm(
+ $this->formId, [
+ 'SignupForm[username]' => 'tester',
+ 'SignupForm[email]' => 'ttttt',
+ 'SignupForm[password]' => 'tester_password',
+ ]
+ );
+ $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');
+ }
+
+ public function signupSuccessfully(FunctionalTester $I)
+ {
+ $I->submitForm($this->formId, [
+ 'SignupForm[username]' => 'tester',
+ 'SignupForm[email]' => 'tester.email@example.com',
+ 'SignupForm[password]' => 'tester_password',
+ ]);
+
+ $I->seeRecord('common\models\User', [
+ 'username' => 'tester',
+ 'email' => 'tester.email@example.com',
+ ]);
+
+ $I->see('Logout (tester)', 'form button[type=submit]');
+ }
+}
diff --git a/frontend/tests/functional/_bootstrap.php b/frontend/tests/functional/_bootstrap.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/frontend/tests/functional/_bootstrap.php
@@ -0,0 +1 @@
+attributes = [
+ 'name' => 'Tester',
+ 'email' => 'tester@example.com',
+ 'subject' => 'very important letter subject',
+ 'body' => 'body of current message',
+ ];
+
+ expect_that($model->sendEmail('admin@example.com'));
+
+ // using Yii2 module actions to check email was sent
+ $this->tester->seeEmailIsSent();
+
+ $emailMessage = $this->tester->grabLastSentEmail();
+ expect('valid email is sent', $emailMessage)->isInstanceOf('yii\mail\MessageInterface');
+ expect($emailMessage->getTo())->hasKey('admin@example.com');
+ expect($emailMessage->getFrom())->hasKey('tester@example.com');
+ expect($emailMessage->getSubject())->equals('very important letter subject');
+ expect($emailMessage->toString())->contains('body of current message');
+ }
+}
diff --git a/frontend/tests/unit/models/PasswordResetRequestFormTest.php b/frontend/tests/unit/models/PasswordResetRequestFormTest.php
new file mode 100644
index 0000000..eba7203
--- /dev/null
+++ b/frontend/tests/unit/models/PasswordResetRequestFormTest.php
@@ -0,0 +1,59 @@
+tester->haveFixtures([
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => codecept_data_dir() . 'user.php'
+ ]
+ ]);
+ }
+
+ public function testSendMessageWithWrongEmailAddress()
+ {
+ $model = new PasswordResetRequestForm();
+ $model->email = 'not-existing-email@example.com';
+ expect_not($model->sendEmail());
+ }
+
+ public function testNotSendEmailsToInactiveUser()
+ {
+ $user = $this->tester->grabFixture('user', 1);
+ $model = new PasswordResetRequestForm();
+ $model->email = $user['email'];
+ expect_not($model->sendEmail());
+ }
+
+ public function testSendEmailSuccessfully()
+ {
+ $userFixture = $this->tester->grabFixture('user', 0);
+
+ $model = new PasswordResetRequestForm();
+ $model->email = $userFixture['email'];
+ $user = User::findOne(['password_reset_token' => $userFixture['password_reset_token']]);
+
+ expect_that($model->sendEmail());
+ expect_that($user->password_reset_token);
+
+ $emailMessage = $this->tester->grabLastSentEmail();
+ expect('valid email is sent', $emailMessage)->isInstanceOf('yii\mail\MessageInterface');
+ expect($emailMessage->getTo())->hasKey($model->email);
+ expect($emailMessage->getFrom())->hasKey(Yii::$app->params['supportEmail']);
+ }
+}
diff --git a/frontend/tests/unit/models/ResetPasswordFormTest.php b/frontend/tests/unit/models/ResetPasswordFormTest.php
new file mode 100644
index 0000000..74929bf
--- /dev/null
+++ b/frontend/tests/unit/models/ResetPasswordFormTest.php
@@ -0,0 +1,44 @@
+tester->haveFixtures([
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => codecept_data_dir() . 'user.php'
+ ],
+ ]);
+ }
+
+ public function testResetWrongToken()
+ {
+ $this->tester->expectException('yii\base\InvalidParamException', function() {
+ new ResetPasswordForm('');
+ });
+
+ $this->tester->expectException('yii\base\InvalidParamException', function() {
+ new ResetPasswordForm('notexistingtoken_1391882543');
+ });
+ }
+
+ public function testResetCorrectToken()
+ {
+ $user = $this->tester->grabFixture('user', 0);
+ $form = new ResetPasswordForm($user['password_reset_token']);
+ expect_that($form->resetPassword());
+ }
+
+}
diff --git a/frontend/tests/unit/models/SignupFormTest.php b/frontend/tests/unit/models/SignupFormTest.php
new file mode 100644
index 0000000..23a3463
--- /dev/null
+++ b/frontend/tests/unit/models/SignupFormTest.php
@@ -0,0 +1,59 @@
+tester->haveFixtures([
+ 'user' => [
+ 'class' => UserFixture::className(),
+ 'dataFile' => codecept_data_dir() . 'user.php'
+ ]
+ ]);
+ }
+
+ public function testCorrectSignup()
+ {
+ $model = new SignupForm([
+ 'username' => 'some_username',
+ 'email' => 'some_email@example.com',
+ 'password' => 'some_password',
+ ]);
+
+ $user = $model->signup();
+
+ expect($user)->isInstanceOf('common\models\User');
+
+ expect($user->username)->equals('some_username');
+ expect($user->email)->equals('some_email@example.com');
+ expect($user->validatePassword('some_password'))->true();
+ }
+
+ public function testNotCorrectSignup()
+ {
+ $model = new SignupForm([
+ 'username' => 'troy.becker',
+ 'email' => 'nicolas.dianna@hotmail.com',
+ 'password' => 'some_password',
+ ]);
+
+ expect_not($model->signup());
+ expect_that($model->getErrors('username'));
+ expect_that($model->getErrors('email'));
+
+ expect($model->getFirstError('username'))
+ ->equals('This username has already been taken.');
+ expect($model->getFirstError('email'))
+ ->equals('This email address has already been taken.');
+ }
+}
diff --git a/frontend/views/layouts/main.php b/frontend/views/layouts/main.php
new file mode 100644
index 0000000..2a799f3
--- /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 logout']
+ )
+ . 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..dc48410
--- /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..5f1e6b9
--- /dev/null
+++ b/frontend/web/css/site.css
@@ -0,0 +1,120 @@
+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 > button.logout {
+ padding: 15px;
+ border: none;
+}
+
+@media(max-width:767px) {
+ .nav li > form > button.logout {
+ display:block;
+ text-align: left;
+ width: 100%;
+ padding: 10px 15px;
+ }
+}
+
+.nav > li > form > button.logout:focus,
+.nav > li > form > button.logout:hover {
+ text-decoration: none;
+}
+
+.nav > li > form > button.logout:focus {
+ outline: 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 100755
index 0000000..7aaf086
--- /dev/null
+++ b/init
@@ -0,0 +1,299 @@
+#!/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")) {
+ if (@chmod("$root/$writable", 0777)) {
+ echo " chmod 0777 $writable\n";
+ } else {
+ printError("Operation chmod not permitted for directory $writable.");
+ }
+ } else {
+ printError("Directory $writable does not exist.");
+ }
+ }
+}
+
+function setExecutable($root, $paths)
+{
+ foreach ($paths as $executable) {
+ if (file_exists("$root/$executable")) {
+ if (@chmod("$root/$executable", 0755)) {
+ echo " chmod 0755 $executable\n";
+ } else {
+ printError("Operation chmod not permitted for $executable.");
+ }
+ } else {
+ printError("$executable does not exist.");
+ }
+ }
+}
+
+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) {
+ //first removing folders to avoid errors if the folder already exists
+ @rmdir($root . "/" . $link);
+ //next removing existing symlink in order to update the target
+ if (is_link($root . "/" . $link)) {
+ @unlink($root . "/" . $link);
+ }
+ if (@symlink($root . "/" . $target, $root . "/" . $link)) {
+ echo " symlink $root/$target $root/$link\n";
+ } else {
+ printError("Cannot create symlink $root/$target $root/$link.");
+ }
+ }
+}
+
+/**
+ * Prints error message.
+ * @param string $message message
+ */
+function printError($message)
+{
+ echo "\n " . formatMessage("Error. $message", ['fg-red']) . " \n";
+}
+
+/**
+ * Returns true if the stream supports colorization. ANSI colors are disabled if not supported by the stream.
+ *
+ * - windows without ansicon
+ * - not tty consoles
+ *
+ * @return boolean true if the stream supports ANSI colors, otherwise false.
+ */
+function ansiColorsSupported()
+{
+ return DIRECTORY_SEPARATOR === '\\'
+ ? getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON'
+ : function_exists('posix_isatty') && @posix_isatty(STDOUT);
+}
+
+/**
+ * Get ANSI code of style.
+ * @param string $name style name
+ * @return integer ANSI code of style.
+ */
+function getStyleCode($name)
+{
+ $styles = [
+ 'bold' => 1,
+ 'fg-black' => 30,
+ 'fg-red' => 31,
+ 'fg-green' => 32,
+ 'fg-yellow' => 33,
+ 'fg-blue' => 34,
+ 'fg-magenta' => 35,
+ 'fg-cyan' => 36,
+ 'fg-white' => 37,
+ 'bg-black' => 40,
+ 'bg-red' => 41,
+ 'bg-green' => 42,
+ 'bg-yellow' => 43,
+ 'bg-blue' => 44,
+ 'bg-magenta' => 45,
+ 'bg-cyan' => 46,
+ 'bg-white' => 47,
+ ];
+ return $styles[$name];
+}
+
+/**
+ * Formats message using styles if STDOUT supports it.
+ * @param string $message message
+ * @param string[] $styles styles
+ * @return string formatted message.
+ */
+function formatMessage($message, $styles)
+{
+ if (empty($styles) || !ansiColorsSupported()) {
+ return $message;
+ }
+
+ return sprintf("\x1b[%sm", implode(';', array_map('getStyleCode', $styles))) . $message . "\x1b[0m";
+}
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/vagrant/config/.gitignore b/vagrant/config/.gitignore
new file mode 100644
index 0000000..0685a56
--- /dev/null
+++ b/vagrant/config/.gitignore
@@ -0,0 +1,2 @@
+# local configuration
+vagrant-local.yml
\ No newline at end of file
diff --git a/vagrant/config/vagrant-local.example.yml b/vagrant/config/vagrant-local.example.yml
new file mode 100644
index 0000000..32dd3ab
--- /dev/null
+++ b/vagrant/config/vagrant-local.example.yml
@@ -0,0 +1,22 @@
+# Your personal GitHub token
+github_token:
+# Read more: https://github.com/blog/1509-personal-api-tokens
+# You can generate it here: https://github.com/settings/tokens
+
+# Guest OS timezone
+timezone: Europe/London
+
+# Are we need check box updates for every 'vagrant up'?
+box_check_update: false
+
+# Virtual machine name
+machine_name: y2aa
+
+# Virtual machine IP
+ip: 192.168.83.137
+
+# Virtual machine CPU cores number
+cpus: 1
+
+# Virtual machine RAM
+memory: 512
diff --git a/vagrant/nginx/app.conf b/vagrant/nginx/app.conf
new file mode 100644
index 0000000..fc8fe40
--- /dev/null
+++ b/vagrant/nginx/app.conf
@@ -0,0 +1,77 @@
+server {
+ charset utf-8;
+ client_max_body_size 128M;
+ sendfile off;
+
+ listen 80; ## listen for ipv4
+ #listen [::]:80 default_server ipv6only=on; ## listen for ipv6
+
+ server_name y2aa-frontend.dev;
+ root /app/frontend/web/;
+ index index.php;
+
+ access_log /app/vagrant/nginx/log/frontend-access.log;
+ error_log /app/vagrant/nginx/log/frontend-error.log;
+
+ location / {
+ # Redirect everything that isn't a real file to index.php
+ try_files $uri $uri/ /index.php$is_args$args;
+ }
+
+ # uncomment to avoid processing of calls to non-existing static files by Yii
+ #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
+ # try_files $uri =404;
+ #}
+ #error_page 404 /404.html;
+
+ location ~ \.php$ {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ #fastcgi_pass 127.0.0.1:9000;
+ fastcgi_pass unix:/var/run/php5-fpm.sock;
+ try_files $uri =404;
+ }
+
+ location ~ /\.(ht|svn|git) {
+ deny all;
+ }
+}
+
+server {
+ charset utf-8;
+ client_max_body_size 128M;
+ sendfile off;
+
+ listen 80; ## listen for ipv4
+ #listen [::]:80 default_server ipv6only=on; ## listen for ipv6
+
+ server_name y2aa-backend.dev;
+ root /app/backend/web/;
+ index index.php;
+
+ access_log /app/vagrant/nginx/log/backend-access.log;
+ error_log /app/vagrant/nginx/log/backend-error.log;
+
+ location / {
+ # Redirect everything that isn't a real file to index.php
+ try_files $uri $uri/ /index.php$is_args$args;
+ }
+
+ # uncomment to avoid processing of calls to non-existing static files by Yii
+ #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
+ # try_files $uri =404;
+ #}
+ #error_page 404 /404.html;
+
+ location ~ \.php$ {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ #fastcgi_pass 127.0.0.1:9000;
+ fastcgi_pass unix:/var/run/php5-fpm.sock;
+ try_files $uri =404;
+ }
+
+ location ~ /\.(ht|svn|git) {
+ deny all;
+ }
+}
diff --git a/vagrant/nginx/log/.gitignore b/vagrant/nginx/log/.gitignore
new file mode 100644
index 0000000..c15cedd
--- /dev/null
+++ b/vagrant/nginx/log/.gitignore
@@ -0,0 +1,5 @@
+# nginx logs
+backend-access.log
+backend-error.log
+frontend-access.log
+frontend-error.log
\ No newline at end of file
diff --git a/vagrant/provision/always-as-root.sh b/vagrant/provision/always-as-root.sh
new file mode 100644
index 0000000..f2465e7
--- /dev/null
+++ b/vagrant/provision/always-as-root.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+#== Bash helpers ==
+
+function info {
+ echo " "
+ echo "--> $1"
+ echo " "
+}
+
+#== Provision script ==
+
+info "Provision-script user: `whoami`"
+
+info "Restart web-stack"
+service php5-fpm restart
+service nginx restart
+service mysql restart
\ No newline at end of file
diff --git a/vagrant/provision/once-as-root.sh b/vagrant/provision/once-as-root.sh
new file mode 100644
index 0000000..1dd108c
--- /dev/null
+++ b/vagrant/provision/once-as-root.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+
+#== Import script args ==
+
+timezone=$(echo "$1")
+
+#== Bash helpers ==
+
+function info {
+ echo " "
+ echo "--> $1"
+ echo " "
+}
+
+#== Provision script ==
+
+info "Provision-script user: `whoami`"
+
+info "Allocate swap for MySQL 5.6"
+fallocate -l 2048M /swapfile
+chmod 600 /swapfile
+mkswap /swapfile
+swapon /swapfile
+echo '/swapfile none swap defaults 0 0' >> /etc/fstab
+
+info "Configure locales"
+update-locale LC_ALL="C"
+dpkg-reconfigure locales
+
+info "Configure timezone"
+echo ${timezone} | tee /etc/timezone
+dpkg-reconfigure --frontend noninteractive tzdata
+
+info "Prepare root password for MySQL"
+debconf-set-selections <<< "mysql-server-5.6 mysql-server/root_password password \"''\""
+debconf-set-selections <<< "mysql-server-5.6 mysql-server/root_password_again password \"''\""
+echo "Done!"
+
+info "Update OS software"
+apt-get update
+apt-get upgrade -y
+
+info "Install additional software"
+apt-get install -y git php5-curl php5-cli php5-intl php5-mysqlnd php5-gd php5-fpm nginx mysql-server-5.6
+
+info "Configure MySQL"
+sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/my.cnf
+echo "Done!"
+
+info "Configure PHP-FPM"
+sed -i 's/user = www-data/user = vagrant/g' /etc/php5/fpm/pool.d/www.conf
+sed -i 's/group = www-data/group = vagrant/g' /etc/php5/fpm/pool.d/www.conf
+sed -i 's/owner = www-data/owner = vagrant/g' /etc/php5/fpm/pool.d/www.conf
+echo "Done!"
+
+info "Configure NGINX"
+sed -i 's/user www-data/user vagrant/g' /etc/nginx/nginx.conf
+echo "Done!"
+
+info "Enabling site configuration"
+ln -s /app/vagrant/nginx/app.conf /etc/nginx/sites-enabled/app.conf
+echo "Done!"
+
+info "Initailize databases for MySQL"
+mysql -uroot <<< "CREATE DATABASE yii2advanced"
+mysql -uroot <<< "CREATE DATABASE yii2advanced_test"
+echo "Done!"
+
+info "Install composer"
+curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
\ No newline at end of file
diff --git a/vagrant/provision/once-as-vagrant.sh b/vagrant/provision/once-as-vagrant.sh
new file mode 100644
index 0000000..a9a5e33
--- /dev/null
+++ b/vagrant/provision/once-as-vagrant.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+
+#== Import script args ==
+
+github_token=$(echo "$1")
+
+#== Bash helpers ==
+
+function info {
+ echo " "
+ echo "--> $1"
+ echo " "
+}
+
+#== Provision script ==
+
+info "Provision-script user: `whoami`"
+
+info "Configure composer"
+composer config --global github-oauth.github.com ${github_token}
+echo "Done!"
+
+info "Install plugins for composer"
+composer global require "fxp/composer-asset-plugin:^1.2.0" --no-progress
+
+info "Install codeception"
+composer global require "codeception/codeception=2.0.*" "codeception/specify=*" "codeception/verify=*" --no-progress
+echo 'export PATH=/home/vagrant/.config/composer/vendor/bin:$PATH' | tee -a /home/vagrant/.profile
+
+info "Install project dependencies"
+cd /app
+composer --no-progress --prefer-dist install
+
+info "Init project"
+./init --env=Development --overwrite=y
+
+info "Apply migrations"
+./yii migrate <<< "yes"
+
+info "Create bash-alias 'app' for vagrant user"
+echo 'alias app="cd /app"' | tee /home/vagrant/.bash_aliases
+
+info "Enabling colorized prompt for guest console"
+sed -i "s/#force_color_prompt=yes/force_color_prompt=yes/" /home/vagrant/.bashrc
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