diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..1669168 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory" : "vendor/bower" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30507e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# yii console command +/yii + +# phpstorm project files +.idea + +# netbeans project files +nbproject + +# zend studio for eclipse project files +.buildpath +.project +.settings + +# windows thumbnail cache +Thumbs.db + +# composer vendor dir +/vendor + +# composer itself is not needed +composer.phar +composer.lock + +# Mac DS_Store Files +.DS_Store + +# phpunit itself is not needed +phpunit.phar +# local phpunit config +/phpunit.xml 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/backend/assets/AppAsset.php b/backend/assets/AppAsset.php new file mode 100644 index 0000000..c262142 --- /dev/null +++ b/backend/assets/AppAsset.php @@ -0,0 +1,29 @@ + + * @since 2.0 + */ +class AppAsset extends AssetBundle +{ + public $basePath = '@webroot'; + public $baseUrl = '@web'; + public $css = [ + 'css/site.css', + ]; + public $js = [ + ]; + public $depends = [ + 'yii\web\YiiAsset', + 'yii\bootstrap\BootstrapAsset', + ]; +} diff --git a/backend/config/.gitignore b/backend/config/.gitignore new file mode 100644 index 0000000..20da318 --- /dev/null +++ b/backend/config/.gitignore @@ -0,0 +1,2 @@ +main-local.php +params-local.php \ No newline at end of file diff --git a/backend/config/bootstrap.php b/backend/config/bootstrap.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/backend/config/bootstrap.php @@ -0,0 +1 @@ + 'app-backend', + 'basePath' => dirname(__DIR__), + 'controllerNamespace' => 'backend\controllers', + 'bootstrap' => ['log'], + 'modules' => [ + 'auth' => [ + 'class' => 'funson86\auth\Module', + 'controllerNamespace' => 'funson86\auth\controllers' + ], + ], + 'components' => [ + 'user' => [ + 'identityClass' => 'common\models\User', + 'enableAutoLogin' => true, + ], + 'urlManager' => [ + 'enablePrettyUrl' => true, + 'showScriptName' => false, + //'enableStrictParsing' => true, + 'rules' => [ + '/' => '/view', + '//' => '/', + '/' => '/', + ], + ], + 'log' => [ + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + 'levels' => ['error', 'warning'], + ], + ], + ], + 'errorHandler' => [ + 'errorAction' => 'site/error', + ], + ], + 'params' => $params, +]; diff --git a/backend/config/params.php b/backend/config/params.php new file mode 100644 index 0000000..7f754b9 --- /dev/null +++ b/backend/config/params.php @@ -0,0 +1,4 @@ + 'admin@example.com', +]; diff --git a/backend/controllers/RoleController.php b/backend/controllers/RoleController.php new file mode 100644 index 0000000..8458d4f --- /dev/null +++ b/backend/controllers/RoleController.php @@ -0,0 +1,177 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['post'], + ], + ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['@'] + ] + ] + ], + ]; + } + + public function actionIndex() + { + //if(!Yii::$app->user->can('viewRole')) throw new ForbiddenHttpException(Yii::t('app', 'No Auth')); + + $searchModel = new AuthSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->get(), Auth::TYPE_ROLE); + return $this->render('index', [ + 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel, + ]); + } + + public function actionCreate() + { + //if(!Yii::$app->user->can('createRole')) throw new ForbiddenHttpException(Yii::t('app', 'No Auth')); + + $model = new Auth(); + if ($model->load(Yii::$app->request->post())) { + $permissions = $this->preparePermissions(Yii::$app->request->post()); + if($model->createRole($permissions)) { + Yii::$app->session->setFlash('success', " '$model->name' " . Yii::t('app', 'successfully saved')); + return $this->redirect(['view', 'name' => $model->name]); + } + else + { + $permissions = $this->getPermissions(); + $model->_permissions = Yii::$app->request->post()['Auth']['_permissions']; + return $this->render('create', [ + 'model' => $model, + 'permissions' => $permissions + ] + ); + } + } else { + $permissions = $this->getPermissions(); + return $this->render('create', [ + 'model' => $model, + 'permissions' => $permissions + ] + ); + } + } + + public function actionUpdate($name) + { + //if(!Yii::$app->user->can('updateRole')) throw new ForbiddenHttpException(Yii::t('app', 'No Auth')); + + if($name == 'admin') { + Yii::$app->session->setFlash('success', Yii::t('app', 'The Administrator has all permissions')); + return $this->redirect(['view', 'name' => $name]); + } + $model = $this->findModel($name); + if ($model->load(Yii::$app->request->post())) { + $permissions = $this->preparePermissions(Yii::$app->request->post()); + if($model->updateRole($name, $permissions)) { + Yii::$app->session->setFlash('success', " '$model->name' " . Yii::t('app', 'successfully updated')); + return $this->redirect(['view', 'name' => $name]); + } + } else { + $permissions = $this->getPermissions(); + $model->loadRolePermissions($name); + return $this->render('update', [ + 'model' => $model, + 'permissions' => $permissions, + ] + ); + } + } + + public function actionDelete($name) + { + //if(!Yii::$app->user->can('deleteRole')) throw new ForbiddenHttpException(Yii::t('app', 'No Auth')); + + if ($name) { + if(!Auth::hasUsersByRole($name)) { + $auth = Yii::$app->getAuthManager(); + $role = $auth->getRole($name); + + // clear asset permissions + $permissions = $auth->getPermissionsByRole($name); + foreach($permissions as $permission) { + $auth->removeChild($role, $permission); + } + if($auth->remove($role)) { + Yii::$app->session->setFlash('success', " '$name' " . Yii::t('app', 'successfully removed')); + } + } else { + Yii::$app->session->setFlash('warning', " '$name' " . Yii::t('app', 'still used')); + } + } + return $this->redirect(['index']); + } + + public function actionView($name) + { + $model = $this->findModel($name); + $model->loadRolePermissions($name); + $permissions = $this->getPermissions(); + return $this->render('view', [ + 'model' => $model, + 'permissions' => $permissions, + ]); + } + + protected function findModel($name) + { + if ($name) { + $auth = Yii::$app->getAuthManager(); + $model = new Auth(); + $role = $auth->getRole($name); + if ($role) { + $model->name = $role->name; + $model->description = $role->description; + $model->setIsNewRecord(false); + return $model; + } + } + throw new HttpException(404); + } + + protected function getPermissions() { + $models = Auth::find()->where(['type' => Auth::TYPE_PERMISSION])->all(); + $permissions = []; + foreach($models as $model) { + $permissions[$model->name] = $model->name . ' (' . $model->description . ')'; + } + return $permissions; + } + + protected function preparePermissions($post) { + return (isset($post['Auth']['_permissions']) && + is_array($post['Auth']['_permissions'])) ? $post['Auth']['_permissions'] : []; + } +} diff --git a/backend/controllers/SiteController.php b/backend/controllers/SiteController.php new file mode 100644 index 0000000..de69c80 --- /dev/null +++ b/backend/controllers/SiteController.php @@ -0,0 +1,85 @@ + [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'actions' => ['login', 'error'], + 'allow' => true, + ], + [ + 'actions' => ['logout', 'index'], + 'allow' => true, + 'roles' => ['@'], + ], + ], + ], + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'logout' => ['post'], + ], + ], + ]; + } + + /** + * @inheritdoc + */ + public function actions() + { + return [ + 'error' => [ + 'class' => 'yii\web\ErrorAction', + ], + ]; + } + + public function actionIndex() + { + return $this->render('index'); + } + + public function actionLogin() + { + $this->layout = 'guest'; + + if (!\Yii::$app->user->isGuest) { + return $this->goHome(); + } + + $model = new LoginForm(); + if ($model->load(Yii::$app->request->post()) && $model->login()) { + return $this->goBack(); + } else { + return $this->render('login', [ + 'model' => $model, + ]); + } + } + + public function actionLogout() + { + Yii::$app->user->logout(); + + return $this->goHome(); + } +} diff --git a/backend/controllers/UserController.php b/backend/controllers/UserController.php new file mode 100644 index 0000000..a709ace --- /dev/null +++ b/backend/controllers/UserController.php @@ -0,0 +1,150 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['post'], + ], + ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['@'] + ] + ] + ], + ]; + } + + /** + * Lists all User models. + * @return mixed + */ + public function actionIndex() + { + //if(!Yii::$app->user->can('viewUser')) throw new ForbiddenHttpException(Yii::t('app', 'No Auth')); + + $searchModel = new UserSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + $arrayStatus = User::getArrayStatus(); + $arrayRole = User::getArrayRole(); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + 'arrayStatus' => $arrayStatus, + 'arrayRole' => $arrayRole, + ]); + } + + /** + * Displays a single User model. + * @param integer $id + * @return mixed + */ + public function actionView($id) + { + //if(!Yii::$app->user->can('viewUser')) throw new ForbiddenHttpException(Yii::t('app', 'No Auth')); + + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new User model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + //if(!Yii::$app->user->can('createUser')) throw new ForbiddenHttpException(Yii::t('app', 'No Auth')); + + $model = new User(['scenario' => 'admin-create']); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + //Yii::$app->authManager->assign(Yii::$app->authManager->getRole($model->role), $model->id); + return $this->redirect(['view', 'id' => $model->id]); + } else { + return $this->render('create', [ + 'model' => $model, + ]); + } + } + + /** + * Updates an existing User model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + */ + public function actionUpdate($id) + { + //if(!Yii::$app->user->can('updateUser')) throw new ForbiddenHttpException(Yii::t('app', 'No Auth')); + + $model = $this->findModel($id); + $model->setScenario('admin-update'); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + Yii::$app->authManager->revokeAll($id); + Yii::$app->authManager->assign(Yii::$app->authManager->getRole($model->role), $id); + return $this->redirect(['view', 'id' => $model->id]); + } else { + return $this->render('update', [ + 'model' => $model, + ]); + } + } + + /** + * Deletes an existing User model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + */ + public function actionDelete($id) + { + //if(!Yii::$app->user->can('deleteUser')) throw new ForbiddenHttpException(Yii::t('app', 'No Auth')); + + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the User model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return User the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = User::findOne($id)) !== null) { + return $model; + } else { + throw new NotFoundHttpException('The requested page does not exist.'); + } + } +} diff --git a/backend/messages/zh-CN/app.php b/backend/messages/zh-CN/app.php new file mode 100644 index 0000000..3df8956 --- /dev/null +++ b/backend/messages/zh-CN/app.php @@ -0,0 +1,77 @@ + '启用', + 'STATUS_INACTIVE' => '禁用', + 'STATUS_DELETED' => '删除', + 'YES' => '是', + 'NO' => '否', + 'SEX_MALE' => '男', + 'SEX_FEMALE' => '女', + 'MAINTAIN_STATUS_FIRST' => '首次保养', + 'MAINTAIN_STATUS_REGULAR' => '定期保养', + 'PROMPT_STATUS' => '请筛选', + 'Please Filter' => '请筛选', + 'Please Select' => '请选择', + 'No Option' => '无', + + 'Create ' => '创建', + 'Create' => '创建', + 'Update ' => '更新', + 'Update' => '更新', + 'Delete' => '删除', + 'Are you sure you want to delete this item?' => '您确定要删除此项吗?', + 'Login' => '登录', + 'Incorrect username or password.' => '帐号或密码不正确', + 'Home' => '首页', + 'Logout' => '注销', + 'Online' => '在线', + 'Dashboard' => '控制面板', + 'The file "{file}" is not an image.' => '文件 "{file}" 不是一个图像文件。', + 'Hello, {name}' => '你好,{name}', + + 'System' => '系统', + 'Settings' => '设置', + 'User' => '用户', + 'Users' => '用户', + 'Role' => '角色', + 'Roles' => '角色', + 'Auth Role' => '角色', + 'Auth Roles' => '角色', + 'Name' => '名称', + 'Description' => '描述', + 'Permissions' => '权限', + 'successfully saved' => '创建成功', + 'The Administrator has all permissions' => '该管理员拥有所有权限', + 'successfully updated' => '更新成功', + 'successfully removed' => '删除成功', + 'still used' => '还在使用', + + 'Username' => '账户', + 'Password' => '密码', + 'Repassword' => '确认密码', + 'Remember Me' => '自动登录', + 'Email' => '电子邮箱', + 'Auth Role' => '角色', + 'Status' => '状态', + 'Created At' => '创建时间', + 'Updated At' => '更新时间', + 'Create User Id' => '创建用户', + 'Update User Id' => '更新用户', + + 'Directly Input Time' => '可直接输入日期,格式:2015-01-01', + + 'Blog' => '博客', + 'Catalog' => '分类', + 'Post' => '文章', + 'Comment' => '评论', + 'Tag' => '标签', + + +]; \ No newline at end of file diff --git a/backend/messages/zh-CN/auth.php b/backend/messages/zh-CN/auth.php new file mode 100644 index 0000000..8276d06 --- /dev/null +++ b/backend/messages/zh-CN/auth.php @@ -0,0 +1,31 @@ + '名称', + 'Description' => '描述', + 'Operation List' => '权限列表', + + 'basic' => '基本', + 'user' => '管理员', + 'role' => '角色', + + 'backendLogin' => '后台登录', + + 'viewUser' => '查看管理员', + 'createUser' => '创建管理员', + 'updateUser' => '更新管理员', + 'deleteUser' => '删除管理员', + + 'viewRole' => '查看角色', + 'createRole' => '创建角色', + 'updateRole' => '更新角色', + 'deleteRole' => '删除角色', + +]; \ No newline at end of file diff --git a/backend/messages/zh-CN/error.php b/backend/messages/zh-CN/error.php new file mode 100644 index 0000000..9aac359 --- /dev/null +++ b/backend/messages/zh-CN/error.php @@ -0,0 +1,12 @@ + '错误', + +]; \ No newline at end of file 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/models/Auth.php b/backend/models/Auth.php new file mode 100644 index 0000000..aad9033 --- /dev/null +++ b/backend/models/Auth.php @@ -0,0 +1,168 @@ + '/^[a-zA-Z0-9_-]+$/'], + ['name', 'string', 'min' => 3, 'max' => 64], + ['name', 'validatePermission'], + + ['description', 'string', 'min' => 1, 'max' => 400], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'name' => Yii::t('app', 'Name'), + 'description' => Yii::t('app', 'Description'), + ]; + } + + public function validatePermission() + { + if (!$this->hasErrors()) { + $auth = Yii::$app->getAuthManager(); + if ($this->isNewRecord && $auth->getPermission($this->name)) { + $this->addError('name', Yii::t('auth', 'This name already exists.')); + } + if ($this->isNewRecord && $auth->getRole($this->name)) { + $this->addError('name', Yii::t('auth', 'This name already exists.')); + } + } + } + + public function createPermission() + { + if ($this->validate()) { + $auth = Yii::$app->getAuthManager(); + $permission = $auth->createPermission($this->name); + $permission->description = $this->description; + $auth->add($permission); + + $admin = $auth->getRole('admin'); + return $auth->addChild($admin, $permission); + } + return false; + } + + public function updatePermission($name) + { + if ($this->validate()) { + $auth = Yii::$app->getAuthManager(); + $permission = $auth->getPermission($name); + $permission->description = $this->description; + return $auth->update($name, $permission); + } + return false; + } + + public function createRole($permissions) + { + if ($this->validate()) { + $auth = Yii::$app->getAuthManager(); + $role = $auth->createRole($this->name); + $role->description = $this->description;; + if ($auth->add($role)) { + foreach ($permissions as $permission) { + $obj = $auth->getPermission($permission); + $auth->addChild($role, $obj); + } + return true; + } + } + return false; + } + + public function updateRole($name, $permissions) + { + if ($this->validate()) { + $auth = Yii::$app->getAuthManager(); + $role = $auth->getRole($name); + $role->description = $this->description; + // save role + if ($auth->update($name, $role)) { + // remove old permissions + $oldPermissions = $auth->getPermissionsByRole($name); + foreach($oldPermissions as $permission) { + $auth->removeChild($role, $permission); + } + + // add new permissions + foreach ($permissions as $permission) { + $obj = $auth->getPermission($permission); + $auth->addChild($role, $obj); + } + return true; + } + } + return false; + } + + public function loadRolePermissions($name) { + $models = Yii::$app->authManager->getPermissionsByRole($name); + foreach($models as $model) { + $this->_permissions[] = $model->name; + } + } + + public static function hasUsersByRole($name) { + $tablePrefix = Yii::$app->getDb()->tablePrefix; + return Auth::find() + ->where(['name' => $name]) + ->leftJoin("{$tablePrefix}auth_assignment", ['item_name' => $name]) + ->count(); + } + + public static function hasRolesByPermission($name) { + $tablePrefix = Yii::$app->getDb()->tablePrefix; + return Auth::find() + ->where(['name' => $name]) + ->leftJoin("{$tablePrefix}auth_item_child", ['child' => $name]) + ->count(); + } +} diff --git a/backend/models/AuthSearch.php b/backend/models/AuthSearch.php new file mode 100644 index 0000000..ae57a34 --- /dev/null +++ b/backend/models/AuthSearch.php @@ -0,0 +1,57 @@ +where(['type' => $type]); + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => $this->recordsPerPage, + ] + ]); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + $this->addCondition($query, 'name', true); + $this->addCondition($query, 'description', true); + return $dataProvider; + } + + protected function addCondition($query, $attribute, $partialMatch = false) + { + $value = $this->$attribute; + if (trim($value) === '') { + return; + } + if ($partialMatch) { + $query->andWhere(['like', $attribute, $value]); + } else { + $query->andWhere([$attribute => $value]); + } + } +} diff --git a/backend/models/User.php b/backend/models/User.php new file mode 100644 index 0000000..a2b1de4 --- /dev/null +++ b/backend/models/User.php @@ -0,0 +1,155 @@ +_statusLabel === null) { + $statuses = self::getArrayStatus(); + $this->_statusLabel = $statuses[$this->status]; + } + return $this->_statusLabel; + } + + /** + * @inheritdoc + */ + public static function getArrayStatus() + { + return [ + self::STATUS_ACTIVE => Yii::t('app', 'STATUS_ACTIVE'), + self::STATUS_INACTIVE => Yii::t('app', 'STATUS_INACTIVE'), + self::STATUS_DELETED => Yii::t('app', 'STATUS_DELETED'), + ]; + } + + public static function getArrayRole() + { + return ArrayHelper::map(Yii::$app->authManager->getRoles(), 'name', 'description'); + } + + public function getRoleLabel() + { + + if ($this->_roleLabel === null) { + $roles = self::getArrayRole(); + $this->_roleLabel = $roles[$this->role]; + } + return $this->_roleLabel; + } + + public static function getArrayAuthRole() + { + return ArrayHelper::map(AuthRole::find()->all(), 'id', 'name'); + } + + public function getAuthRoleLabel() + { + + if ($this->_authRoleLabel === null) { + $roles = self::getArrayAuthRole(); + $this->_authRoleLabel = $roles[$this->auth_role]; + } + return $this->_authRoleLabel; + } + + /** + * @inheritdoc + */ + public function rules() + { + return [ + [['username', 'email'], 'required'], + [['password', 'repassword'], 'required', 'on' => ['admin-create']], + [['username', 'email', 'password', 'repassword'], 'trim'], + [['password', 'repassword'], 'string', 'min' => 6, 'max' => 30], + // Unique + [['username', 'email'], 'unique'], + // Username + ['username', 'match', 'pattern' => '/^[a-zA-Z0-9_-]+$/'], + ['username', 'string', 'min' => 3, 'max' => 30], + // E-mail + ['email', 'string', 'max' => 100], + ['email', 'email'], + // Repassword + ['repassword', 'compare', 'compareAttribute' => 'password'], + //['status', 'default', 'value' => self::STATUS_ACTIVE], + ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_DELETED]], + + // Status + ['role', 'in', 'range' => array_keys(self::getArrayRole())], + ]; + } + + /** + * @inheritdoc + */ + public function scenarios() + { + return [ + 'admin-create' => ['username', 'email', 'password', 'repassword', 'status', 'role', 'auth_role'], + 'admin-update' => ['username', 'email', 'password', 'repassword', 'status', 'role', 'auth_role'] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + $labels = parent::attributeLabels(); + + return array_merge( + $labels, + [ + 'password' => Yii::t('app', 'Password'), + 'repassword' => Yii::t('app', 'Repassword'), + ] + ); + } + + /** + * @inheritdoc + */ + public function beforeSave($insert) + { + if (parent::beforeSave($insert)) { + if ($this->isNewRecord || (!$this->isNewRecord && $this->password)) { + $this->setPassword($this->password); + $this->generateAuthKey(); + $this->generatePasswordResetToken(); + } + return true; + } + return false; + } +} diff --git a/backend/models/UserSearch.php b/backend/models/UserSearch.php new file mode 100644 index 0000000..c404655 --- /dev/null +++ b/backend/models/UserSearch.php @@ -0,0 +1,72 @@ + $query, + ]); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + $query->andFilterWhere([ + 'id' => $this->id, + 'role' => $this->role, + 'auth_role' => $this->auth_role, + 'status' => $this->status, + //'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + ]); + + $query->andFilterWhere(['like', 'username', $this->username]) + ->andFilterWhere(['like', 'auth_key', $this->auth_key]) + ->andFilterWhere(['like', 'password_hash', $this->password_hash]) + ->andFilterWhere(['like', 'password_reset_token', $this->password_reset_token]) + ->andFilterWhere(['like', 'email', $this->email]) + ->andFilterWhere(['>', 'created_at', strtotime($this->created_at)]); + + return $dataProvider; + } +} diff --git a/backend/runtime/.gitignore b/backend/runtime/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/backend/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/backend/views/layouts/error.php b/backend/views/layouts/error.php new file mode 100644 index 0000000..b1cf5c1 --- /dev/null +++ b/backend/views/layouts/error.php @@ -0,0 +1,27 @@ + +beginPage() ?> + + + + <?= Html::encode($this->title); ?> + + head(); ?> + + +beginBody() ?> + +endBody() ?> + + +endPage() ?> + diff --git a/backend/views/layouts/guest.php b/backend/views/layouts/guest.php new file mode 100644 index 0000000..b1cf5c1 --- /dev/null +++ b/backend/views/layouts/guest.php @@ -0,0 +1,27 @@ + +beginPage() ?> + + + + <?= Html::encode($this->title); ?> + + head(); ?> + + +beginBody() ?> + +endBody() ?> + + +endPage() ?> + diff --git a/backend/views/layouts/main.php b/backend/views/layouts/main.php new file mode 100644 index 0000000..05f6a0a --- /dev/null +++ b/backend/views/layouts/main.php @@ -0,0 +1,51 @@ + +beginPage() ?> + + + + + + + <?= Html::encode($this->title) ?> + head() ?> + + + beginBody() ?> +
+ render('//layouts/top-menu.php') ?> + +
+
+

title ?>

+ isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]) ?> +
+ +
+ +
+
+
+ +
+
+

© My Company

+

+
+
+ + endBody() ?> + + +endPage() ?> diff --git a/backend/views/layouts/top-menu.php b/backend/views/layouts/top-menu.php new file mode 100644 index 0000000..3888561 --- /dev/null +++ b/backend/views/layouts/top-menu.php @@ -0,0 +1,75 @@ + 'My Company', + 'brandUrl' => Yii::$app->homeUrl, + 'options' => [ + 'class' => 'navbar-inverse navbar-fixed-top', + ], +]); +$menuItemsMain = [ + [ + 'label' => Yii::t('app', 'Blog'), + 'url' => ['#'], + 'active' => false, + 'items' => [ + [ + 'label' => Yii::t('app', 'Catalog'), + 'url' => ['/blog/blog-catalog'], + ], + [ + 'label' => Yii::t('app', 'Post'), + 'url' => ['/blog/blog-post'], + ], + [ + 'label' => Yii::t('app', 'Comment'), + 'url' => ['/blog/blog-comment'], + ], + [ + 'label' => Yii::t('app', 'Tag'), + 'url' => ['/blog/blog-tag'], + ], + ], + //'visible' => Yii::$app->user->can('readPost'), + ], + [ + 'label' => ' ' . Yii::t('app', 'System'), + 'url' => ['#'], + 'active' => false, + //'visible' => Yii::$app->user->can('haha'), + 'items' => [ + [ + 'label' => ' ' . Yii::t('app', 'User'), + 'url' => ['/user'], + ], + [ + 'label' => ' ' . Yii::t('app', 'Auth Role'), + 'url' => ['/auth'], + ], + ], + ], +]; +echo Nav::widget([ + 'options' => ['class' => 'navbar-nav navbar-left'], + 'items' => $menuItemsMain, + 'encodeLabels' => false, +]); +$menuItems = [ + ['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']], +]; +if (Yii::$app->user->isGuest) { + $menuItems[] = ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']]; +} else { + $menuItems[] = [ + 'label' => Yii::t('app', 'Logout') . '(' . Yii::$app->user->identity->username . ')', + 'url' => ['/site/logout'], + 'linkOptions' => ['data-method' => 'post'] + ]; +} +echo Nav::widget([ + 'options' => ['class' => 'navbar-nav navbar-right'], + 'items' => $menuItems, +]); +NavBar::end(); diff --git a/backend/views/role/_form.php b/backend/views/role/_form.php new file mode 100644 index 0000000..03d1082 --- /dev/null +++ b/backend/views/role/_form.php @@ -0,0 +1,51 @@ + + +
+ true, + /*'enableAjaxValidation' => true, + 'validateOnSubmit'=>true, + 'validateOnChange' => false*/ + ]); + ?> +
+
+
+ +
+
+ field($model, 'name')->textInput($model->isNewRecord ? [] : ['disabled' => 'disabled']) . + $form->field($model, 'description')->textarea(['style' => 'height: 100px']) . + Html::submitButton($model->isNewRecord ? Yii::t('app', 'Save') : Yii::t('app', 'Update'), [ + 'class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary' + ]); + ?> +
+ +
+
+ +
+
+
+ +
+ +
+ field($model, '_permissions')->checkboxList($permissions)->label('', ['hidden' => 'hidden']); ?> +
+
+
+ +
diff --git a/backend/views/role/create.php b/backend/views/role/create.php new file mode 100644 index 0000000..a75cfc1 --- /dev/null +++ b/backend/views/role/create.php @@ -0,0 +1,10 @@ +title = Yii::t('app', 'Create ') . Yii::t('app', 'Role'); +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Roles'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; + +echo $this->render('_form', [ + 'model' => $model, + 'permissions' => $permissions +]); diff --git a/backend/views/role/index.php b/backend/views/role/index.php new file mode 100644 index 0000000..7f7be07 --- /dev/null +++ b/backend/views/role/index.php @@ -0,0 +1,60 @@ +title = Yii::t('app', 'Roles'); +$this->params['breadcrumbs'][] = $this->title; +?> +
+ + render('_search', ['model' => $searchModel]); ?> + +

+ 'btn btn-success']) ?> +

+ + $dataProvider, + 'filterModel' => $searchModel, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + + [ + 'attribute' => 'name', + ], + 'description', + [ + //'header' => Yii::t('auth', 'Actions'), + 'class' => 'yii\grid\ActionColumn', + //'dropdown' => false, + //'vAlign' => 'middle', + 'urlCreator' => function ($action, $model, $key, $index) { + $link = '#'; + switch ($action) { + case 'view': + $link = Yii::$app->getUrlManager()->createUrl(['role/view', 'name' => $model->name]); + break; + case 'update': + $link = Yii::$app->getUrlManager()->createUrl(['role/update', 'name' => $model->name]); + break; + case 'delete': + $link = Yii::$app->getUrlManager()->createUrl(['role/delete', 'name' => $model->name]); + break; + } + return $link; + }, + //'viewOptions' => ['title' => Yii::t('auth', 'Details')], + //'updateOptions' => ['title' => Yii::t('auth', 'Edit page')], + //'deleteOptions' => ['title' => Yii::t('auth', 'Delete action')], + ], + //['class' => 'yii\grid\ActionColumn'], + ], + ]); ?> + +
diff --git a/backend/views/role/update.php b/backend/views/role/update.php new file mode 100644 index 0000000..2bd6531 --- /dev/null +++ b/backend/views/role/update.php @@ -0,0 +1,15 @@ +title = Yii::t('app', 'Update ') . Yii::t('app', '{name}', ['name' => $model->name]); +$this->params['breadcrumbs'] = [ + [ + 'label' => Yii::t('app', 'Roles'), + 'url' => ['/role'] + ], + $this->title +]; + +echo $this->render('_form', [ + 'model' => $model, + 'permissions' => $permissions, +]); \ No newline at end of file diff --git a/backend/views/role/view.php b/backend/views/role/view.php new file mode 100644 index 0000000..edd43e0 --- /dev/null +++ b/backend/views/role/view.php @@ -0,0 +1,57 @@ +title = Yii::t('app', 'Role') . " $model->name"; +$this->params['breadcrumbs'] = [ + [ + 'label' => Yii::t('app', 'Roles'), + 'url' => ['/role'] + ], + $model->name +]; +?> + +
+
+ + $model, + //'condensed' => true, + //'hover' => true, + //'mode' => DetailView::MODE_VIEW, + //'enableEditMode' => false, + /*'panel' => [ + 'heading' => Icon::show('user') . Yii::t('auth', 'Role') . + Html::a(Icon::show('user') . Yii::t('auth', 'Update'), ['update', 'name' => $model->name], ['class' => 'btn-success btn-sm btn-dv pull-right']), + //'type' => DetailView::TYPE_DEFAULT, + ],*/ + 'attributes' => [ + 'name', + 'description' + ], + ]); + ?> +
+ +
+
+
+ +
+ +
+
+ _permissions as $key): ?> + + + + +
+
+
+
+
diff --git a/backend/views/site/error.php b/backend/views/site/error.php new file mode 100644 index 0000000..05ee944 --- /dev/null +++ b/backend/views/site/error.php @@ -0,0 +1,28 @@ +title = $name; +$this->context->layout = 'error'; +?> +
+ +

title) ?>

+ +
+ +
+ +

+ 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..0159bef --- /dev/null +++ b/backend/views/site/index.php @@ -0,0 +1,52 @@ +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..f7a3aeb --- /dev/null +++ b/backend/views/site/login.php @@ -0,0 +1,25 @@ +title = Yii::t('app', 'Login'); +$this->params['breadcrumbs'][] = $this->title; +?> + diff --git a/backend/views/user/_form.php b/backend/views/user/_form.php new file mode 100644 index 0000000..1ae5d27 --- /dev/null +++ b/backend/views/user/_form.php @@ -0,0 +1,37 @@ + + +
+ + + + field($model, 'username')->textInput(['maxlength' => 255]) ?> + + field($model, 'password')->passwordInput(['maxlength' => 255]) ?> + + field($model, 'repassword')->passwordInput(['maxlength' => 255]) ?> + + field($model, 'email')->textInput(['maxlength' => 255]) ?> + + + + field($model, 'auth_role')->dropDownList(User::getArrayAuthRole()) ?> + + field($model, 'status')->dropDownList(User::getArrayStatus()) ?> + +
+ isNewRecord ? Yii::t('app', 'Create') : Yii::t('app', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> +
+ + + +
diff --git a/backend/views/user/_search.php b/backend/views/user/_search.php new file mode 100644 index 0000000..9a1c5d4 --- /dev/null +++ b/backend/views/user/_search.php @@ -0,0 +1,45 @@ + + + diff --git a/backend/views/user/create.php b/backend/views/user/create.php new file mode 100644 index 0000000..dbbb5b7 --- /dev/null +++ b/backend/views/user/create.php @@ -0,0 +1,19 @@ +title = Yii::t('app', 'Create ') . Yii::t('app', 'User'); +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Users'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/backend/views/user/index.php b/backend/views/user/index.php new file mode 100644 index 0000000..7fbf73e --- /dev/null +++ b/backend/views/user/index.php @@ -0,0 +1,91 @@ +title = Yii::t('app', 'Users'); +$this->params['breadcrumbs'][] = $this->title; +?> +
+ + render('_search', ['model' => $searchModel]); ?> + +

+ 'btn btn-success']) ?> +

+ + $dataProvider, + 'filterModel' => $searchModel, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + + 'id', + 'username', + // 'auth_key', + // 'password_hash', + // 'password_reset_token', + 'email:email', + /*[ + 'attribute' => 'role', + 'value' => function ($model) { + return $model->roleLabel; + }, + 'filter' => Html::activeDropDownList( + $searchModel, + 'role', + $arrayRole, + ['class' => 'form-control', 'prompt' => Yii::t('app', 'Please Filter')] + ) + ],*/ + [ + 'attribute' => 'auth_role', + 'value' => function ($model) { + return $model->authRoleLabel; + }, + 'filter' => Html::activeDropDownList( + $searchModel, + 'auth_role', + User::getArrayAuthRole(), + ['class' => 'form-control', 'prompt' => Yii::t('app', 'Please Filter')] + ) + ], + [ + 'attribute' => 'status', + 'format' => 'html', + 'value' => function ($model) { + if ($model->status === $model::STATUS_ACTIVE) { + $class = 'label-success'; + } elseif ($model->status === $model::STATUS_INACTIVE) { + $class = 'label-warning'; + } else { + $class = 'label-danger'; + } + + return '' . $model->statusLabel . ''; + }, + 'filter' => Html::activeDropDownList( + $searchModel, + 'status', + $arrayStatus, + ['class' => 'form-control', 'prompt' => Yii::t('app', 'Please Filter')] + ) + ], + //'created_at', + [ + 'attribute' => 'created_at', + 'format' => ['date', 'Y-M-d H:i:s'], + ], + //'updated_at', + + ['class' => 'yii\grid\ActionColumn'], + ], + ]); ?> + +
diff --git a/backend/views/user/update.php b/backend/views/user/update.php new file mode 100644 index 0000000..4cd19a2 --- /dev/null +++ b/backend/views/user/update.php @@ -0,0 +1,19 @@ +title = Yii::t('app', 'Update ') . Yii::t('app', 'User') . ' ' . $model->username; +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Users'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; +$this->params['breadcrumbs'][] = Yii::t('app', 'Update'); +?> +
+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/backend/views/user/view.php b/backend/views/user/view.php new file mode 100644 index 0000000..d88f1d5 --- /dev/null +++ b/backend/views/user/view.php @@ -0,0 +1,52 @@ +title = $model->id; +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Users'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

+ $model->id], ['class' => 'btn btn-primary']) ?> + $model->id], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => Yii::t('app', 'Are you sure you want to delete this item?'), + 'method' => 'post', + ], + ]) ?> +

+ $model, + 'attributes' => [ + 'id', + 'username', + //'auth_key', + //'password_hash', + //'password_reset_token', + 'email:email', + /*[ + 'attribute' => 'role', + 'value' => $model->roleLabel, + ],*/ + [ + 'attribute' => 'auth_role', + 'value' => $model->authRoleLabel, + ], + [ + 'attribute' => 'status', + 'value' => $model->statusLabel, + ], + 'created_at:datetime', + 'updated_at:datetime', + ], + ]) ?> + +
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/.htaccess b/backend/web/.htaccess new file mode 100644 index 0000000..f752f6a --- /dev/null +++ b/backend/web/.htaccess @@ -0,0 +1,5 @@ +RewriteEngine on + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . index.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..c7721f4 --- /dev/null +++ b/backend/web/css/site.css @@ -0,0 +1,180 @@ +html, +body { + height: 100%; +} + +.wrap { + min-height: 100%; + height: auto; + margin: 0 auto -60px; + padding: 0 0 60px; +} + +.wrap .container{ + width: 100%; +} + +.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 th { + white-space: nowrap; +} + +.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; +} + +.content-header { + background: #fbfbfb; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); + position: relative; + padding: 15px 15px 10px 20px; + display: block; +} + +.content-header h1{ + margin: 0; + font-size: 24px; +} + +.content-header > .breadcrumb { + float: right; + background: transparent; + margin-top: 0px; + margin-bottom: 0; + font-size: 12px; + padding: 7px 5px; + position: absolute; + top: 15px; + right: 10px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.content-header > .breadcrumb > li > a { + color: #444; + text-decoration: none; +} + +.content { + padding: 20px 15px; + overflow: auto; + display: block; +} + +.site-login { + width: 360px; + margin: 90px auto 0 auto; +} + +.site-login h1 { + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 0; + -webkit-border-bottom-left-radius: 0; + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-bottomright: 0; + -moz-border-radius-bottomleft: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + background: #3d9970; + box-shadow: inset 0px -3px 0px rgba(0, 0, 0, 0.2); + padding: 20px 10px; + text-align: center; + font-size: 26px; + font-weight: 300; + color: #fff; + margin-bottom: 0; +} + +.bg-gray { + background-color: #eaeaec !important; +} +.bg-olive { + background-color: #3d9970 !important; +} + +.site-login .body, .site-login .footer { + padding: 10px 20px; + background: #fff; + color: #444; +} + +.site-login .form-group { + margin-top: 20px; +} + 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..c6742d8 --- /dev/null +++ b/backend/web/robots.txt @@ -0,0 +1,2 @@ +User-Agent: * +Disallow: / diff --git a/common/config/.gitignore b/common/config/.gitignore new file mode 100644 index 0000000..97c0f01 --- /dev/null +++ b/common/config/.gitignore @@ -0,0 +1,2 @@ +main-local.php +params-local.php diff --git a/common/config/bootstrap.php b/common/config/bootstrap.php new file mode 100644 index 0000000..3724d78 --- /dev/null +++ b/common/config/bootstrap.php @@ -0,0 +1,6 @@ + 'Advanced-funson86', + 'language' => 'zh-CN', + 'timeZone' => 'Asia/Shanghai', + 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', + 'components' => [ + 'user' => [ + 'class' => 'funson86\auth\User', + 'enableAutoLogin' => true, + ], + 'authManager' => [ + 'class' => 'yii\rbac\DbManager', + //'defaultRoles' => ['guest'], + ], + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], + 'i18n' => [ + 'translations' => [ + '*' => [ + 'class' => 'yii\i18n\PhpMessageSource', + //'basePath' => '@app/messages', + //'sourceLanguage' => 'en', + 'fileMap' => [ + 'app' => 'app.php', + 'app/error' => 'error.php', + ], + ], + /*'yii' => [ + 'class' => 'yii\i18n\PhpMessageSource', + 'sourceLanguage' => 'zh-CN', + 'basePath' => '@app/messages' + ],*/ + ], + ], + 'formatter' => [ + 'dateFormat' => 'yyyy-MM-dd', + 'datetimeFormat' => 'yyyy-MM-dd HH:mm:ss', + 'decimalSeparator' => ',', + 'thousandSeparator' => ' ', + 'currencyCode' => 'CNY', + ], + ], +]; diff --git a/common/config/params.php b/common/config/params.php new file mode 100644 index 0000000..4ec9ba6 --- /dev/null +++ b/common/config/params.php @@ -0,0 +1,6 @@ + 'admin@example.com', + 'supportEmail' => 'support@example.com', + 'user.passwordResetTokenExpire' => 3600, +]; diff --git a/common/mail/layouts/html.php b/common/mail/layouts/html.php new file mode 100644 index 0000000..bddbc61 --- /dev/null +++ b/common/mail/layouts/html.php @@ -0,0 +1,22 @@ + +beginPage() ?> + + + + + <?= Html::encode($this->title) ?> + head() ?> + + + beginBody() ?> + + endBody() ?> + + +endPage() ?> diff --git a/common/mail/passwordResetToken.php b/common/mail/passwordResetToken.php new file mode 100644 index 0000000..4093dc9 --- /dev/null +++ b/common/mail/passwordResetToken.php @@ -0,0 +1,14 @@ +urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]); +?> + +Hello username) ?>, + +Follow the link below to reset your password: + + diff --git a/common/models/Catalog.php b/common/models/Catalog.php new file mode 100644 index 0000000..822d404 --- /dev/null +++ b/common/models/Catalog.php @@ -0,0 +1,121 @@ +asArray()->all()), 'id', 'label') + * @param int $parentId parent catalog id + * @param array $array catalog array list + * @param int $level catalog level, will affect $repeat + * @param int $add times of $repeat + * @param string $repeat symbols or spaces to be added for sub catalog + * @return array catalog collections + */ + static public function get($parentId = 0, $array = [], $level = 0, $add = 2, $repeat = ' ') + { + $strRepeat = ''; + // add some spaces or symbols for non top level categories + if ($level > 1) { + for ($j = 0; $j < $level; $j++) { + $strRepeat .= $repeat; + } + } + + $newArray = array (); + //performance is not very good here + foreach ((array)$array as $v) { + if ($v['parent_id'] == $parentId) { + $item = (array)$v; + $item['label'] = $strRepeat . (isset($v['title']) ? $v['title'] : $v['name']); + $newArray[] = $item; + + $tempArray = self::get($v['id'], $array, ($level + $add), $add, $repeat); + if ($tempArray) { + $newArray = array_merge($newArray, $tempArray); + } + } + } + return $newArray; + } + + + /** + * Get all children id as a array + * Usage: + * $ids = Catalog::getCatalogIdStr($id, CmsCatalog::find()->all()); + * $shows = CmsShow::find()->where(['catalog_id' => $ids,])->all(); + * @param int $parentId parent catalog id + * @param array $array catalog array list + * @return array catalog Id collections. eg: [2, 3, 7, 8] + */ + static public function getArraySubCatalogId($parentId = 0, $array = []) + { + $result[] = $parentId; + foreach ((array)$array as $v) { + if ($v['parent_id'] == $parentId) { + $tempResult = self::getArraySubCatalogId($v['id'], $array); + if ($tempResult) { + $result = array_merge($result, $tempResult); + } + } + } + return $result; + } + + /** + * Get the root catalog id + * Usage: $rootId = Catalog::getArraySubCatalogId($id, Catalog::find()->asArray()->all()); + * @param int $id parent catalog id + * @param array $array catalog array list + * @return int root catalog id + */ + static public function getRootCatalogId($id = 0, $array = []) + { + if (0 == $id) + return 0; + + foreach ((array)$array as $v) { + if ($v['id'] == $id) { + $parentId = $v['parent_id']; + if(0 == $parentId) + return $id; + else + return self::getRootCatalogId($parentId, $array); + } + } + } + + /** + * Get the root catalog id, then get the sub catalog of the root calalog + * Usage: $rootId = Catalog::getArraySubCatalogId($id, Catalog::find()->asArray()->all()); + * @param int $id parent catalog id + * @param array $array catalog array list + * @return array the sub catalog of root catalog Id collections. + */ + static public function getRootCatalogSub2($id = 0, $array = []) + { + $arrayResult = array(); + $rootId = self::getRootCatalogId($id, $array); + foreach ((array)$array as $v) { + if ($v['parent_id'] == $rootId) { + array_push($arrayResult, $v); + } + } + + return $arrayResult; + } + +} \ No newline at end of file diff --git a/common/models/LoginForm.php b/common/models/LoginForm.php new file mode 100644 index 0000000..69cdc54 --- /dev/null +++ b/common/models/LoginForm.php @@ -0,0 +1,90 @@ + Yii::t('app', 'Username'), + 'password' => Yii::t('app', 'Password'), + 'rememberMe' => Yii::t('app', 'Remember Me'), + ]; + } + + /** + * Validates the password. + * This method serves as the inline validation for password. + * + * @param string $attribute the attribute currently being validated + * @param array $params the additional name-value pairs given in the rule + */ + public function validatePassword($attribute, $params) + { + if (!$this->hasErrors()) { + $user = $this->getUser(); + if (!$user || !$user->validatePassword($this->password)) { + $this->addError($attribute, Yii::t('app', 'Incorrect username or password.')); + } + } + } + + /** + * Logs in a user using the provided username and password. + * + * @return boolean whether the user is logged in successfully + */ + public function login() + { + if ($this->validate()) { + return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0); + } else { + return false; + } + } + + /** + * Finds user by [[username]] + * + * @return User|null + */ + public function getUser() + { + if ($this->_user === false) { + $this->_user = User::findByUsername($this->username); + } + + return $this->_user; + } +} diff --git a/common/models/Profile.php b/common/models/Profile.php new file mode 100644 index 0000000..f9649eb --- /dev/null +++ b/common/models/Profile.php @@ -0,0 +1,99 @@ + 64] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'user_id' => Yii::t('app', 'User ID'), + 'name' => Yii::t('app', 'Real Name'), + 'surname' => Yii::t('app', 'Surname'), + 'avatar_url' => Yii::t('app', 'Avatar Url'), + ]; + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getUser() + { + return $this->hasOne(User::className(), ['id' => 'user_id']); + } + + /** + * Before save. + * + */ + /*public function beforeSave($insert) + { + if(parent::beforeSave($insert)) + { + // add your code here + return true; + } + else + return false; + }*/ + + /** + * After save. + * + */ + /*public function afterSave($insert, $changedAttributes) + { + parent::afterSave($insert, $changedAttributes); + // add your code here + }*/ + +} diff --git a/common/models/Status.php b/common/models/Status.php new file mode 100644 index 0000000..45aef83 --- /dev/null +++ b/common/models/Status.php @@ -0,0 +1,85 @@ +_status === null) { + $this->_status = new Status($this->status); + } + return $this->_status; +} + +index.php +[ + 'attribute' => 'status', + 'format' => 'html', + 'value' => function ($model) { + if ($model->status === Status::STATUS_ACTIVE) { + $class = 'label-success'; + } elseif ($model->status === Status::STATUS_INACTIVE) { + $class = 'label-warning'; + } else { + $class = 'label-danger'; + } + + return '' . $model->getStatus()->label . ''; + }, + 'filter' => Html::activeDropDownList( + $searchModel, + 'status', + Status::labels(), + ['class' => 'form-control', 'prompt' => Yii::t('app', 'Please Select')] + ) +], + +_form.php +field($model, 'status')->dropDownList(\common\models\Status::labels()) ?> + +view.php +[ + 'attribute' => 'status', + 'value' => $model->getStatus()->label, +], + + */ + +namespace common\models; + +use Yii; + +class Status +{ + const STATUS_INACTIVE = 0; + const STATUS_ACTIVE = 1; + const STATUS_DELETED = -1; + + public $id; + public $label; + + public function __construct($id = null) + { + if ($id !== null) { + $this->id = $id; + $this->label = $this->getLabel($id); + } + } + + public static function labels() + { + return [ + self::STATUS_ACTIVE => Yii::t('app', 'STATUS_ACTIVE'), + self::STATUS_INACTIVE => Yii::t('app', 'STATUS_INACTIVE'), + self::STATUS_DELETED => Yii::t('app', 'STATUS_DELETED'), + ]; + } + + public function getLabel($id) + { + $labels = self::labels(); + return isset($labels[$id]) ? $labels[$id] : null; + } + +} diff --git a/common/models/User.php b/common/models/User.php new file mode 100644 index 0000000..9521bc0 --- /dev/null +++ b/common/models/User.php @@ -0,0 +1,222 @@ + self::STATUS_ACTIVE], + ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]], + + ['role', 'default', 'value' => self::ROLE_USER], + ['role', 'in', 'range' => [self::ROLE_USER]], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'id' => Yii::t('app', 'ID'), + 'username' => Yii::t('app', 'Username'), + 'password' => Yii::t('app', 'Password'), + 'repassword' => Yii::t('app', 'Repassword'), + 'email' => Yii::t('app', 'Email'), + 'role' => Yii::t('app', 'Role'), + 'auth_role' => Yii::t('app', 'Auth Role'), + 'status' => Yii::t('app', 'Status'), + 'created_at' => Yii::t('app', 'Created At'), + 'updated_at' => Yii::t('app', 'Updated At'), + 'create_user_id' => Yii::t('app', 'Create User Id'), + 'update_user_id' => Yii::t('app', 'Update User Id'), + ]; + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getProfile() + { + return $this->hasOne(Profile::className(), ['user_id' => 'id']); + } + + /** + * @inheritdoc + */ + public static function findIdentity($id) + { + return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]); + } + + /** + * @inheritdoc + */ + public static function findIdentityByAccessToken($token, $type = null) + { + throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); + } + + /** + * Finds user by username + * + * @param string $username + * @return static|null + */ + public static function findByUsername($username) + { + return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]); + } + + /** + * Finds user by password reset token + * + * @param string $token password reset token + * @return static|null + */ + public static function findByPasswordResetToken($token) + { + if (!static::isPasswordResetTokenValid($token)) { + return null; + } + + return static::findOne([ + 'password_reset_token' => $token, + 'status' => self::STATUS_ACTIVE, + ]); + } + + /** + * Finds out if password reset token is valid + * + * @param string $token password reset token + * @return boolean + */ + public static function isPasswordResetTokenValid($token) + { + if (empty($token)) { + return false; + } + $expire = Yii::$app->params['user.passwordResetTokenExpire']; + $parts = explode('_', $token); + $timestamp = (int) end($parts); + return $timestamp + $expire >= time(); + } + + /** + * @inheritdoc + */ + public function getId() + { + return $this->getPrimaryKey(); + } + + /** + * @inheritdoc + */ + public function getAuthKey() + { + return $this->auth_key; + } + + /** + * @inheritdoc + */ + public function validateAuthKey($authKey) + { + return $this->getAuthKey() === $authKey; + } + + /** + * Validates password + * + * @param string $password password to validate + * @return boolean if password provided is valid for current user + */ + public function validatePassword($password) + { + return Yii::$app->security->validatePassword($password, $this->password_hash); + } + + /** + * Generates password hash from password and sets it to the model + * + * @param string $password + */ + public function setPassword($password) + { + $this->password_hash = Yii::$app->security->generatePasswordHash($password); + } + + /** + * Generates "remember me" authentication key + */ + public function generateAuthKey() + { + $this->auth_key = Yii::$app->security->generateRandomString(); + } + + /** + * Generates new password reset token + */ + public function generatePasswordResetToken() + { + $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time(); + } + + /** + * Removes password reset token + */ + public function removePasswordResetToken() + { + $this->password_reset_token = null; + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e89002f --- /dev/null +++ b/composer.json @@ -0,0 +1,38 @@ +{ + "name": "funson86/yii2-advanced-funson86", + "description": "Customized on Yii 2 Advanced Application Template", + "keywords": ["yii2", "framework", "advanced", "application template"], + "homepage": "http://www.mayicun.com/", + "type": "project", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "minimum-stability": "dev", + "require": { + "php": ">=5.4.0", + "yiisoft/yii2": "*", + "yiisoft/yii2-bootstrap": "*", + "yiisoft/yii2-swiftmailer": "*" + }, + "require-dev": { + "yiisoft/yii2-codeception": "*", + "yiisoft/yii2-debug": "*", + "yiisoft/yii2-gii": "*", + "yiisoft/yii2-faker": "*", + "funson86/yii2-auth": "*" + }, + "config": { + "process-timeout": 1800 + }, + "extra": { + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + } +} 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', 'gii'], + 'controllerNamespace' => 'console\controllers', + 'modules' => [ + 'gii' => 'yii\gii\Module', + ], + '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 diff --git a/console/migrations/m140608_201405_user_init.php b/console/migrations/m140608_201405_user_init.php new file mode 100644 index 0000000..9d4f01b --- /dev/null +++ b/console/migrations/m140608_201405_user_init.php @@ -0,0 +1,100 @@ +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' => Schema::TYPE_PK, + 'username' => Schema::TYPE_STRING . ' NOT NULL', + 'auth_key' => Schema::TYPE_STRING . '(32) NOT NULL', + 'token' => Schema::TYPE_STRING . '(64)', + 'access_token' => Schema::TYPE_STRING . '(255)', + 'password_hash' => Schema::TYPE_STRING . ' NOT NULL', + 'password_reset_token' => Schema::TYPE_STRING, + 'email' => Schema::TYPE_STRING . ' NOT NULL', + 'role' => Schema::TYPE_STRING . '(64) NOT NULL DEFAULT "user"', + + 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10', + 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', + 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', + ], $tableOptions); + + // Indexes + $this->createIndex('username', '{{%user}}', 'username', true); + $this->createIndex('role', '{{%user}}', 'role'); + $this->createIndex('status', '{{%user}}', 'status'); + $this->createIndex('created_at', '{{%user}}', 'created_at'); + + //profile table + $this->createTable( + '{{%profile}}', + [ + 'user_id' => Schema::TYPE_PK, + 'name' => Schema::TYPE_STRING . '(64)', + 'surname' => Schema::TYPE_STRING . '(64)', + 'avatar_url' => Schema::TYPE_STRING . '(64)' + ], + $tableOptions + ); + + // Foreign Keys + $this->addForeignKey('FK_profile_user', '{{%profile}}', 'user_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE'); + + // Users emails table + $this->createTable( + '{{%user_email}}', + [ + 'user_id' => Schema::TYPE_INTEGER . ' NOT NULL', + 'email' => Schema::TYPE_STRING . '(64)', + 'token' => Schema::TYPE_STRING . '(64)', + 'PRIMARY KEY (`user_id`, `token`)' + ], + $tableOptions + ); + + // Foreign Keys + $this->addForeignKey('FK_user_email_user', '{{%user_email}}', 'user_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE'); + + + // Add super administrator + $this->execute($this->getUserSql()); + $this->execute($this->getProfileSql()); + } + + /** + * @return string SQL to insert first user + */ + private function getUserSql() + { + $time = time(); + $password_hash = Yii::$app->getSecurity()->generatePasswordHash('qwe1234'); + $auth_key = Yii::$app->getSecurity()->generateRandomKey(); + return "INSERT INTO {{%user}} (`username`, `email`, `auth_key`, `password_hash`, `password_reset_token`, `role`, `status`, `created_at`, `updated_at`) + VALUES ('admin', 'admin@demo.com', '$auth_key', '$password_hash', '', 'admin', 1, $time, $time)"; + } + + /** + * @return string SQL to insert first profile + */ + private function getProfileSql() + { + return "INSERT INTO {{%profile}} (`user_id`, `name`, `surname`) VALUES (1, 'Administration', 'Site')"; + } + + public function down() + { + $this->dropTable('{{%user_email}}'); + $this->dropTable('{{%profile}}'); + $this->dropTable('{{%user}}'); + } +} diff --git a/console/migrations/m140608_201406_rbac_init.php b/console/migrations/m140608_201406_rbac_init.php new file mode 100644 index 0000000..3b6fd4f --- /dev/null +++ b/console/migrations/m140608_201406_rbac_init.php @@ -0,0 +1,99 @@ +getAuthManager(); + if (!$authManager instanceof DbManager) { + throw new InvalidConfigException('You should configure "authManager" component to use database before executing this migration.'); + } + return $authManager; + } + + public function up() + { + $authManager = $this->getAuthManager(); + + $tableOptions = null; + if ($this->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_general_ci ENGINE=InnoDB'; + } + + $this->createTable($authManager->ruleTable, [ + 'name' => Schema::TYPE_STRING . '(64) NOT NULL', + 'data' => Schema::TYPE_TEXT, + 'created_at' => Schema::TYPE_INTEGER, + 'updated_at' => Schema::TYPE_INTEGER, + 'PRIMARY KEY (name)', + ], $tableOptions); + + $this->createTable($authManager->itemTable, [ + 'name' => Schema::TYPE_STRING . '(64) NOT NULL', + 'type' => Schema::TYPE_INTEGER . ' NOT NULL', + 'description' => Schema::TYPE_TEXT, + 'rule_name' => Schema::TYPE_STRING . '(64)', + 'data' => Schema::TYPE_TEXT, + 'created_at' => Schema::TYPE_INTEGER, + 'updated_at' => Schema::TYPE_INTEGER, + 'PRIMARY KEY (name)', + 'FOREIGN KEY (rule_name) REFERENCES ' . $authManager->ruleTable . ' (name) ON DELETE SET NULL ON UPDATE CASCADE', + ], $tableOptions); + $this->createIndex('idx-auth_item-type', $authManager->itemTable, 'type'); + + $this->createTable($authManager->itemChildTable, [ + 'parent' => Schema::TYPE_STRING . '(64) NOT NULL', + 'child' => Schema::TYPE_STRING . '(64) NOT NULL', + 'PRIMARY KEY (parent, child)', + 'FOREIGN KEY (parent) REFERENCES ' . $authManager->itemTable . ' (name) ON DELETE CASCADE ON UPDATE CASCADE', + 'FOREIGN KEY (child) REFERENCES ' . $authManager->itemTable . ' (name) ON DELETE CASCADE ON UPDATE CASCADE', + ], $tableOptions); + + $this->createTable($authManager->assignmentTable, [ + 'item_name' => Schema::TYPE_STRING . '(64) NOT NULL', + 'user_id' => Schema::TYPE_STRING . '(64) NOT NULL', + 'created_at' => Schema::TYPE_INTEGER, + 'PRIMARY KEY (item_name, user_id)', + 'FOREIGN KEY (item_name) REFERENCES ' . $authManager->itemTable . ' (name) ON DELETE CASCADE ON UPDATE CASCADE', + ], $tableOptions); + + $this->crateDefAuth(); + } + + public function down() + { + $authManager = $this->getAuthManager(); + + $this->dropTable($authManager->assignmentTable); + $this->dropTable($authManager->itemChildTable); + $this->dropTable($authManager->itemTable); + $this->dropTable($authManager->ruleTable); + } + + private function crateDefAuth() { + $auth = Yii::$app->getAuthManager(); + + // Create default roles + $admin = $auth->createRole('admin'); + $admin->description = 'Administrator'; + $auth->add($admin); + $auth->assign($admin, 1); + + $user = $auth->createRole('user'); + $user->description = 'User'; + $auth->add($user); + + $guest = $auth->createRole('guest'); + $guest->description = 'Guest'; + $auth->add($guest); + } +} diff --git a/console/migrations/rbac/m140708_201431_rbac_init.php b/console/migrations/rbac/m140708_201431_rbac_init.php new file mode 100644 index 0000000..bb6102f --- /dev/null +++ b/console/migrations/rbac/m140708_201431_rbac_init.php @@ -0,0 +1,71 @@ +crateAuth(); + } + + public function down() + { + $authManager = Yii::$app->getAuthManager(); + + $createPost = $authManager->getPermission('createPost'); + $readPost = $authManager->getPermission('readPost'); + $updatePost = $authManager->getPermission('updatePost'); + + $reader = $authManager->getRole('reader'); + $author = $authManager->getRole('author'); + + $authManager->revoke($reader, 1); + $authManager->revoke($author, 1); + + $authManager->remove($reader); + $authManager->remove($author); + + $authManager->remove($createPost); + $authManager->remove($readPost); + $authManager->remove($updatePost); + } + + private function crateAuth() { + $auth = Yii::$app->getAuthManager(); + + // add "createPost" permission + $createPost = $auth->createPermission('createPost'); + $createPost->description = 'create a post'; + $auth->add($createPost); + + // add "readPost" permission + $readPost = $auth->createPermission('readPost'); + $readPost->description = 'read a post'; + $auth->add($readPost); + + // add "updatePost" permission + $updatePost = $auth->createPermission('updatePost'); + $updatePost->description = 'update post'; + $auth->add($updatePost); + + // add "reader" role and give this role the "readPost" permission + $reader = $auth->createRole('reader'); + $auth->add($reader); + $auth->addChild($reader, $readPost); + + // add "author" role and give this role the "createPost" permission + // as well as the permissions of the "reader" role + $author = $auth->createRole('author'); + $auth->add($author); + $auth->addChild($author, $createPost); + $auth->addChild($author, $reader); + + // usually implemented in your User model. + $auth->assign($reader, 1); + $auth->assign($author, 1); + } +} 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..d9e3809 --- /dev/null +++ b/environments/dev/backend/config/main-local.php @@ -0,0 +1,21 @@ + [ + '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'] = 'yii\debug\Module'; + + $config['bootstrap'][] = 'gii'; + $config['modules']['gii'] = 'yii\gii\Module'; +} + +return $config; diff --git a/environments/dev/backend/config/params-local.php b/environments/dev/backend/config/params-local.php new file mode 100644 index 0000000..d0b9c34 --- /dev/null +++ b/environments/dev/backend/config/params-local.php @@ -0,0 +1,3 @@ +run(); diff --git a/environments/dev/backend/web/index.php b/environments/dev/backend/web/index.php new file mode 100644 index 0000000..6038167 --- /dev/null +++ b/environments/dev/backend/web/index.php @@ -0,0 +1,18 @@ +run(); diff --git a/environments/dev/common/config/main-local.php b/environments/dev/common/config/main-local.php new file mode 100644 index 0000000..43db30e --- /dev/null +++ b/environments/dev/common/config/main-local.php @@ -0,0 +1,20 @@ + [ + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + ], + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + 'viewPath' => '@common/mail', + // send all mails to a file by default. You have to set + // 'useFileTransport' to false and configure a transport + // for the mailer to send real emails. + 'useFileTransport' => true, + ], + ], +]; diff --git a/environments/dev/common/config/params-local.php b/environments/dev/common/config/params-local.php new file mode 100644 index 0000000..d0b9c34 --- /dev/null +++ b/environments/dev/common/config/params-local.php @@ -0,0 +1,3 @@ + [ + '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'] = 'yii\debug\Module'; + + $config['bootstrap'][] = 'gii'; + $config['modules']['gii'] = 'yii\gii\Module'; +} + +return $config; diff --git a/environments/dev/frontend/config/params-local.php b/environments/dev/frontend/config/params-local.php new file mode 100644 index 0000000..d0b9c34 --- /dev/null +++ b/environments/dev/frontend/config/params-local.php @@ -0,0 +1,3 @@ +run(); diff --git a/environments/dev/frontend/web/index.php b/environments/dev/frontend/web/index.php new file mode 100644 index 0000000..6038167 --- /dev/null +++ b/environments/dev/frontend/web/index.php @@ -0,0 +1,18 @@ +run(); diff --git a/environments/dev/yii b/environments/dev/yii new file mode 100644 index 0000000..8cc5827 --- /dev/null +++ b/environments/dev/yii @@ -0,0 +1,32 @@ +#!/usr/bin/env php +run(); +exit($exitCode); diff --git a/environments/index.php b/environments/index.php new file mode 100644 index 0000000..5bb0acf --- /dev/null +++ b/environments/index.php @@ -0,0 +1,61 @@ + [ + * 'path' => 'directory storing the local files', + * 'setWritable' => [ + * // list of directories that should be set writable + * ], + * 'setExecutable' => [ + * // list of directories 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', + ], + '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..c8b6f3f --- /dev/null +++ b/environments/prod/yii @@ -0,0 +1,32 @@ +#!/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..995e3dc --- /dev/null +++ b/frontend/assets/AppAsset.php @@ -0,0 +1,29 @@ + + * @since 2.0 + */ +class AppAsset extends AssetBundle +{ + public $basePath = '@webroot'; + public $baseUrl = '@web'; + public $css = [ + 'css/site.css', + ]; + public $js = [ + ]; + public $depends = [ + 'yii\web\YiiAsset', + 'yii\bootstrap\BootstrapAsset', + ]; +} diff --git a/frontend/config/.gitignore b/frontend/config/.gitignore new file mode 100644 index 0000000..20da318 --- /dev/null +++ b/frontend/config/.gitignore @@ -0,0 +1,2 @@ +main-local.php +params-local.php \ No newline at end of file diff --git a/frontend/config/bootstrap.php b/frontend/config/bootstrap.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/frontend/config/bootstrap.php @@ -0,0 +1 @@ + 'app-frontend', + 'basePath' => dirname(__DIR__), + 'bootstrap' => ['log'], + 'controllerNamespace' => 'frontend\controllers', + 'components' => [ + 'user' => [ + 'identityClass' => 'common\models\User', + 'enableAutoLogin' => true, + ], + 'urlManager' => [ + 'enablePrettyUrl' => true, + 'showScriptName' => false, + //'enableStrictParsing' => true, + 'rules' => [ + '/' => '/view', + '//' => '/', + '/' => '/', + ], + ], + 'log' => [ + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + 'levels' => ['error', 'warning'], + ], + ], + ], + 'errorHandler' => [ + 'errorAction' => 'site/error', + ], + ], + 'params' => $params, +]; diff --git a/frontend/config/params.php b/frontend/config/params.php new file mode 100644 index 0000000..7f754b9 --- /dev/null +++ b/frontend/config/params.php @@ -0,0 +1,4 @@ + 'admin@example.com', +]; diff --git a/frontend/controllers/SiteController.php b/frontend/controllers/SiteController.php new file mode 100644 index 0000000..38f6650 --- /dev/null +++ b/frontend/controllers/SiteController.php @@ -0,0 +1,211 @@ + [ + 'class' => AccessControl::className(), + 'only' => ['logout', 'signup', 'change-password'], + 'rules' => [ + [ + 'actions' => ['signup'], + 'allow' => true, + 'roles' => ['?'], + ], + [ + 'actions' => ['logout', 'change-password'], + '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, + ], + ]; + } + + public function actionIndex() + { + return $this->render('index'); + } + + 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, + ]); + } + } + + public function actionLogout() + { + Yii::$app->user->logout(); + + return $this->goHome(); + } + + 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, + ]); + } + } + + public function actionAbout() + { + return $this->render('about'); + } + + 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, + ]); + } + + public function actionRequestPasswordReset() + { + $model = new PasswordResetRequestForm(); + if ($model->load(Yii::$app->request->post()) && $model->validate()) { + if ($model->sendEmail()) { + Yii::$app->getSession()->setFlash('success', Yii::t('app', 'Check your email for further instructions.')); + + return $this->goHome(); + } else { + Yii::$app->getSession()->setFlash('error', Yii::t('app', 'Sorry, we are unable to reset password for email provided.')); + } + } + + return $this->render('requestPasswordResetToken', [ + 'model' => $model, + ]); + } + + 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->getSession()->setFlash('success', Yii::t('app', 'New password was saved.')); + + return $this->goHome(); + } + + return $this->render('resetPassword', [ + 'model' => $model, + ]); + } + + public function actionChangePassword() + { + try { + $model = new ChangePasswordForm(); + } catch (InvalidParamException $e) { + throw new BadRequestHttpException($e->getMessage()); + } + + if ($model->load(Yii::$app->request->post()) && $model->validate() && $model->changePassword()) { + Yii::$app->getSession()->setFlash('success', Yii::t('app', 'New password was saved.')); + + return $this->goHome(); + } + + return $this->render('changePassword', [ + 'model' => $model, + ]); + } + + public function actionProfile() + { + $model = Profile::findOne(['user_id' => Yii::$app->user->id]); + if (!$model) { + $model = new Profile(); + $model->user_id = Yii::$app->user->id; + } + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + Yii::$app->getSession()->setFlash('success', Yii::t('app', 'New password was saved.')); + + return $this->goHome(); + } + + return $this->render('profile', [ + 'model' => $model, + ]); + } +} diff --git a/frontend/messages/zh-CN/app.php b/frontend/messages/zh-CN/app.php new file mode 100644 index 0000000..359917c --- /dev/null +++ b/frontend/messages/zh-CN/app.php @@ -0,0 +1,100 @@ + '启用', + 'STATUS_INACTIVE' => '禁用', + 'STATUS_DELETED' => '删除', + 'YES' => '是', + 'NO' => '否', + 'SEX_MALE' => '男', + 'SEX_FEMALE' => '女', + 'MAINTAIN_STATUS_FIRST' => '首次保养', + 'MAINTAIN_STATUS_REGULAR' => '定期保养', + 'PROMPT_STATUS' => '请筛选', + 'Please Filter' => '请筛选', + 'Please Select' => '请选择', + 'No Option' => '无', + + 'Create ' => '创建', + 'Create' => '创建', + 'Update ' => '更新', + 'Update' => '更新', + 'Delete' => '删除', + 'Are you sure you want to delete this item?' => '您确定要删除此项吗?', + 'Login' => '登录', + 'Incorrect username or password.' => '帐号或密码不正确', + 'Home' => '首页', + 'Logout' => '注销', + 'Online' => '在线', + 'Dashboard' => '控制面板', + 'The file "{file}" is not an image.' => '文件 "{file}" 不是一个图像文件。', + 'Hello, {name}' => '你好,{name}', + + 'System' => '系统', + 'Settings' => '设置', + 'User' => '用户', + 'Users' => '用户', + 'Role' => '角色', + 'Roles' => '角色', + 'Auth Role' => '角色', + 'Auth Roles' => '角色', + 'Name' => '名称', + 'Description' => '描述', + 'Permissions' => '权限', + 'successfully saved' => '创建成功', + 'The Administrator has all permissions' => '该管理员拥有所有权限', + 'successfully updated' => '更新成功', + 'successfully removed' => '删除成功', + 'still used' => '还在使用', + + 'Username' => '账户', + 'Password' => '密码', + 'Repassword' => '确认密码', + 'Remember Me' => '自动登录', + 'Email' => '电子邮箱', + 'Role' => '角色', + 'Status' => '状态', + 'Created At' => '创建时间', + 'Updated At' => '更新时间', + 'Create User Id' => '创建用户', + 'Update User Id' => '更新用户', + + 'Directly Input Time' => '可直接输入日期,格式:2015-01-01', + + 'Please fill out the following fields to login:' => '请输入账户和密码登录:', + 'If you forgot your password you can ' => '忘记密码?请', + 'reset it' => '重置', + 'Signup' => '注册', + 'This username has already been taken.' => '该账户已被使用,请更换', + 'This email address has already been taken.' => '该邮箱已被使用,请更换', + 'Please fill out the following fields to signup:' => '请填写以下字段进行注册:', + 'Request password reset' => '请求密码重置', + 'Please fill out your email. A link to reset password will be sent there.' => '请输入您的邮箱,重置密码的链接将会发送到您的邮箱里', + 'Send' => '发送', + 'There is no user with such email.' => '不存在使用该邮箱的用户', + 'Check your email for further instructions.' => '请检查邮件来完成下一步动作', + 'Sorry, we are unable to reset password for email provided.' => '对不起,我们无法对您提供的邮箱进行重置', + 'New password was saved.' => '已成功设置为新密码', + 'Reset password' => '重新设置密码', + 'Please choose your new password:' => '请输入你的新密码', + 'Password reset token cannot be blank.' => '密码重置token不能为空,请检查', + 'Wrong password reset token.' => '错误的密码重置token,请检查', + 'Save' => '保存', + 'Change Password' => '修改密码', + 'Old Password' => '当前密码', + 'New Password' => '新密码', + 'Incorrect old password.' => '当前密码不正确', + 'Please fill out the following fields to change password' => '请重复输入两次密码来修改密码', + 'Profile' => '资料', + 'Real Name' => '姓名', + 'Surname' => '昵称', + 'Avatar Url' => '头像', + + +]; \ No newline at end of file diff --git a/frontend/models/ChangePasswordForm.php b/frontend/models/ChangePasswordForm.php new file mode 100644 index 0000000..b37fa60 --- /dev/null +++ b/frontend/models/ChangePasswordForm.php @@ -0,0 +1,85 @@ + 6], + ['repassword', 'compare', 'compareAttribute' => 'password'], + ['oldpassword', 'validatePassword'], + ]; + } + + public function attributeLabels() + { + return [ + 'oldpassword' => Yii::t('app', 'Old Password'), + 'password' => Yii::t('app', 'New Password'), + 'repassword' => Yii::t('app', 'Repassword'), + ]; + } + /** + * Resets password. + * + * @return boolean if password was reset. + */ + public function changePassword() + { + $user = User::findOne(Yii::$app->user->identity->id); + $user->password = $this->password; + + return $user->save(); + } + + + /** + * Validates the password. + * This method serves as the inline validation for password. + * + * @param string $attribute the attribute currently being validated + * @param array $params the additional name-value pairs given in the rule + */ + public function validatePassword($attribute, $params) + { + if (!$this->hasErrors()) { + $user = $this->getUser(); + if (!$user || !$user->validatePassword($this->oldpassword)) { + $this->addError($attribute, Yii::t('app', 'Incorrect old password.')); + } + } + } + + + /** + * Finds user by [[username]] + * + * @return User|null + */ + public function getUser() + { + if ($this->_user === false) {var_dump(Yii::$app->user->identity->id); + $this->_user = User::findOne(Yii::$app->user->identity->id); + } + + return $this->_user; + } +} diff --git a/frontend/models/ContactForm.php b/frontend/models/ContactForm.php new file mode 100644 index 0000000..613abb5 --- /dev/null +++ b/frontend/models/ContactForm.php @@ -0,0 +1,59 @@ + 'Verification Code', + ]; + } + + /** + * Sends an email to the specified email address using the information collected by this model. + * + * @param string $email the target email address + * @return boolean whether the email was sent + */ + public function sendEmail($email) + { + return Yii::$app->mailer->compose() + ->setTo($email) + ->setFrom([$this->email => $this->name]) + ->setSubject($this->subject) + ->setTextBody($this->body) + ->send(); + } +} diff --git a/frontend/models/PasswordResetRequestForm.php b/frontend/models/PasswordResetRequestForm.php new file mode 100644 index 0000000..ab5984a --- /dev/null +++ b/frontend/models/PasswordResetRequestForm.php @@ -0,0 +1,77 @@ + 'trim'], + ['email', 'required'], + ['email', 'email'], + ['email', 'exist', + 'targetClass' => '\common\models\User', + 'filter' => ['status' => User::STATUS_ACTIVE], + 'message' => Yii::t('app', 'There is no user with such email.') + ], + ]; + } + + public function attributeLabels() + { + return [ + 'id' => Yii::t('app', 'ID'), + 'username' => Yii::t('app', 'Username'), + 'password' => Yii::t('app', 'Password'), + 'repassword' => Yii::t('app', 'Repassword'), + 'email' => Yii::t('app', 'Email'), + 'role' => Yii::t('app', 'Role'), + 'status' => Yii::t('app', 'Status'), + 'created_at' => Yii::t('app', 'Created At'), + 'updated_at' => Yii::t('app', 'Updated At'), + 'create_user_id' => Yii::t('app', 'Create User Id'), + 'update_user_id' => Yii::t('app', 'Update User Id'), + ]; + } + + /** + * Sends an email with a link, for resetting the password. + * + * @return boolean whether the email was send + */ + public function sendEmail() + { + /* @var $user User */ + $user = User::findOne([ + 'status' => User::STATUS_ACTIVE, + 'email' => $this->email, + ]); + + if ($user) { + if (!User::isPasswordResetTokenValid($user->password_reset_token)) { + $user->generatePasswordResetToken(); + } + + if ($user->save()) { + return \Yii::$app->mailer->compose('passwordResetToken', ['user' => $user]) + ->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot']) + ->setTo($this->email) + ->setSubject('Password reset for ' . \Yii::$app->name) + ->send(); + } + } + + return false; + } +} diff --git a/frontend/models/ResetPasswordForm.php b/frontend/models/ResetPasswordForm.php new file mode 100644 index 0000000..9c3c9f0 --- /dev/null +++ b/frontend/models/ResetPasswordForm.php @@ -0,0 +1,81 @@ + Yii::t('app', 'ID'), + 'username' => Yii::t('app', 'Username'), + 'password' => Yii::t('app', 'Password'), + 'repassword' => Yii::t('app', 'Repassword'), + 'email' => Yii::t('app', 'Email'), + 'role' => Yii::t('app', 'Role'), + 'status' => Yii::t('app', 'Status'), + 'created_at' => Yii::t('app', 'Created At'), + 'updated_at' => Yii::t('app', 'Updated At'), + 'create_user_id' => Yii::t('app', 'Create User Id'), + 'update_user_id' => Yii::t('app', 'Update User Id'), + ]; + } + + /** + * Creates a form model given a token. + * + * @param string $token + * @param array $config name-value pairs that will be used to initialize the object properties + * @throws \yii\base\InvalidParamException if token is empty or not valid + */ + public function __construct($token, $config = []) + { + if (empty($token) || !is_string($token)) { + throw new InvalidParamException(Yii::t('app', 'Password reset token cannot be blank.')); + } + $this->_user = User::findByPasswordResetToken($token); + if (!$this->_user) { + throw new InvalidParamException(Yii::t('app', 'Wrong password reset token.')); + } + parent::__construct($config); + } + + /** + * @inheritdoc + */ + public function rules() + { + return [ + ['password', 'required'], + ['password', 'string', 'min' => 6], + ]; + } + + /** + * Resets password. + * + * @return boolean if password was reset. + */ + public function resetPassword() + { + $user = $this->_user; + $user->password = $this->password; + $user->removePasswordResetToken(); + + return $user->save(); + } +} diff --git a/frontend/models/SignupForm.php b/frontend/models/SignupForm.php new file mode 100644 index 0000000..6c92702 --- /dev/null +++ b/frontend/models/SignupForm.php @@ -0,0 +1,74 @@ + 'trim'], + ['username', 'required'], + ['username', 'unique', 'targetClass' => '\frontend\models\User', 'message' => Yii::t('app', 'This username has already been taken.')], + ['username', 'string', 'min' => 2, 'max' => 255], + + ['email', 'filter', 'filter' => 'trim'], + ['email', 'required'], + ['email', 'email'], + ['email', 'unique', 'targetClass' => '\frontend\models\User', 'message' => Yii::t('app', 'This email address has already been taken.')], + + ['password', 'required'], + ['password', 'string', 'min' => 6], + ]; + } + + public function attributeLabels() + { + return [ + 'id' => Yii::t('app', 'ID'), + 'username' => Yii::t('app', 'Username'), + 'password' => Yii::t('app', 'Password'), + 'repassword' => Yii::t('app', 'Repassword'), + 'email' => Yii::t('app', 'Email'), + 'role' => Yii::t('app', 'Role'), + 'status' => Yii::t('app', 'Status'), + 'created_at' => Yii::t('app', 'Created At'), + 'updated_at' => Yii::t('app', 'Updated At'), + 'create_user_id' => Yii::t('app', 'Create User Id'), + 'update_user_id' => Yii::t('app', 'Update User Id'), + ]; + } + + /** + * Signs user up. + * + * @return User|null the saved model or null if saving fails + */ + public function signup() + { + if ($this->validate()) { + $user = new User(); + $user->username = $this->username; + $user->email = $this->email; + $user->setPassword($this->password); + $user->generateAuthKey(); + if ($user->save()) { + return $user; + } + } + + return null; + } +} diff --git a/frontend/models/User.php b/frontend/models/User.php new file mode 100644 index 0000000..1236d5a --- /dev/null +++ b/frontend/models/User.php @@ -0,0 +1,101 @@ +authManager->getRoles(), 'name', 'description'); + } + + public function getRoleLabel() + { + + if ($this->_roleLabel === null) { + $roles = self::getArrayRole(); + $this->_roleLabel = $roles[$this->role]; + } + return $this->_roleLabel; + } + + /** + * @inheritdoc + */ + public function rules() + { + return [ + [['username', 'email'], 'required'], + [['password', 'repassword'], 'required', 'on' => ['admin-create']], + [['username', 'email', 'password', 'repassword'], 'trim'], + [['password', 'repassword'], 'string', 'min' => 6, 'max' => 30], + // Unique + [['username', 'email'], 'unique'], + // Username + ['username', 'match', 'pattern' => '/^[a-zA-Z0-9_-]+$/'], + ['username', 'string', 'min' => 3, 'max' => 30], + // E-mail + ['email', 'string', 'max' => 100], + ['email', 'email'], + // Repassword + ['repassword', 'compare', 'compareAttribute' => 'password'], + //['status', 'default', 'value' => self::STATUS_ACTIVE], + ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_DELETED]], + + // Status + ['role', 'in', 'range' => array_keys(self::getArrayRole())], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + $labels = parent::attributeLabels(); + + return array_merge( + $labels, + [ + 'password' => Yii::t('app', 'Password'), + 'repassword' => Yii::t('app', 'Repassword') + ] + ); + } + + /** + * @inheritdoc + */ + public function beforeSave($insert) + { + if (parent::beforeSave($insert)) { + if ($this->isNewRecord || (!$this->isNewRecord && $this->password)) { + $this->setPassword($this->password); + $this->generateAuthKey(); + $this->generatePasswordResetToken(); + } + return true; + } + return false; + } +} diff --git a/frontend/runtime/.gitignore b/frontend/runtime/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/frontend/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/frontend/views/layouts/main.php b/frontend/views/layouts/main.php new file mode 100644 index 0000000..d4e68e6 --- /dev/null +++ b/frontend/views/layouts/main.php @@ -0,0 +1,76 @@ + +beginPage() ?> + + + + + + + <?= 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[] = [ + 'label' => 'Logout (' . Yii::$app->user->identity->username . ')', + 'url' => ['/site/logout'], + 'linkOptions' => ['data-method' => 'post'] + ]; + } + echo Nav::widget([ + 'options' => ['class' => 'navbar-nav navbar-right'], + 'items' => $menuItems, + ]); + NavBar::end(); + ?> + +
+ isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]) ?> + + +
+
+ +
+
+

© My Company

+

+
+
+ + endBody() ?> + + +endPage() ?> diff --git a/frontend/views/site/about.php b/frontend/views/site/about.php new file mode 100644 index 0000000..813ed30 --- /dev/null +++ b/frontend/views/site/about.php @@ -0,0 +1,14 @@ +title = 'About'; +$this->params['breadcrumbs'][] = $this->title; +?> +
+

title) ?>

+ +

This is the About page. You may modify the following file to customize its content:

+ + +
diff --git a/frontend/views/site/changePassword.php b/frontend/views/site/changePassword.php new file mode 100644 index 0000000..8acb7cd --- /dev/null +++ b/frontend/views/site/changePassword.php @@ -0,0 +1,33 @@ +title = Yii::t('app', 'Change Password'); +$this->params['breadcrumbs'][] = $this->title; +?> +
+

title) ?>

+ +

+ +
+
+ 'form-change-password']); ?> + + field($model, 'oldpassword')->passwordInput() ?> + + field($model, 'password')->passwordInput() ?> + + field($model, 'repassword')->passwordInput() ?> + +
+ 'btn btn-primary', 'name' => 'update-button']) ?> +
+ +
+
+
diff --git a/frontend/views/site/contact.php b/frontend/views/site/contact.php new file mode 100644 index 0000000..60bddd3 --- /dev/null +++ b/frontend/views/site/contact.php @@ -0,0 +1,37 @@ +title = 'Contact'; +$this->params['breadcrumbs'][] = $this->title; +?> +
+

title) ?>

+ +

+ If you have business inquiries or other questions, please fill out the following form to contact us. Thank you. +

+ +
+
+ 'contact-form']); ?> + field($model, 'name') ?> + field($model, 'email') ?> + field($model, 'subject') ?> + field($model, 'body')->textArea(['rows' => 6]) ?> + field($model, 'verifyCode')->widget(Captcha::className(), [ + 'template' => '
{image}
{input}
', + ]) ?> +
+ 'btn btn-primary', 'name' => 'contact-button']) ?> +
+ +
+
+ +
diff --git a/frontend/views/site/error.php b/frontend/views/site/error.php new file mode 100644 index 0000000..b9812c4 --- /dev/null +++ b/frontend/views/site/error.php @@ -0,0 +1,27 @@ +title = $name; +?> +
+ +

title) ?>

+ +
+ +
+ +

+ 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..a00ee4d --- /dev/null +++ b/frontend/views/site/index.php @@ -0,0 +1,51 @@ +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..837e33d --- /dev/null +++ b/frontend/views/site/login.php @@ -0,0 +1,32 @@ +title = Yii::t('app', 'Login'); +$this->params['breadcrumbs'][] = $this->title; +?> + diff --git a/frontend/views/site/profile.php b/frontend/views/site/profile.php new file mode 100644 index 0000000..16f5fbe --- /dev/null +++ b/frontend/views/site/profile.php @@ -0,0 +1,27 @@ +title = Yii::t('app', 'Profile'); +$this->params['breadcrumbs'][] = $this->title; +?> + diff --git a/frontend/views/site/requestPasswordResetToken.php b/frontend/views/site/requestPasswordResetToken.php new file mode 100644 index 0000000..ad836e4 --- /dev/null +++ b/frontend/views/site/requestPasswordResetToken.php @@ -0,0 +1,27 @@ +title = Yii::t('app', 'Request password reset'); +$this->params['breadcrumbs'][] = $this->title; +?> +
+

title) ?>

+ +

+ +
+
+ 'request-password-reset-form']); ?> + field($model, 'email') ?> +
+ 'btn btn-primary']) ?> +
+ +
+
+
diff --git a/frontend/views/site/resetPassword.php b/frontend/views/site/resetPassword.php new file mode 100644 index 0000000..cd73aba --- /dev/null +++ b/frontend/views/site/resetPassword.php @@ -0,0 +1,27 @@ +title = Yii::t('app', 'Reset password'); +$this->params['breadcrumbs'][] = $this->title; +?> +
+

title) ?>

+ +

+ +
+
+ 'reset-password-form']); ?> + field($model, 'password')->passwordInput() ?> +
+ 'btn btn-primary']) ?> +
+ +
+
+
diff --git a/frontend/views/site/signup.php b/frontend/views/site/signup.php new file mode 100644 index 0000000..76cab2c --- /dev/null +++ b/frontend/views/site/signup.php @@ -0,0 +1,29 @@ +title = Yii::t('app', 'Signup'); +$this->params['breadcrumbs'][] = $this->title; +?> + 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/.htaccess b/frontend/web/.htaccess new file mode 100644 index 0000000..f752f6a --- /dev/null +++ b/frontend/web/.htaccess @@ -0,0 +1,5 @@ +RewriteEngine on + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . index.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..698be70 --- /dev/null +++ b/frontend/web/css/site.css @@ -0,0 +1,91 @@ +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 th { + white-space: nowrap; +} + +.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; +} 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/frontend/widgets/Alert.php b/frontend/widgets/Alert.php new file mode 100644 index 0000000..5b20488 --- /dev/null +++ b/frontend/widgets/Alert.php @@ -0,0 +1,79 @@ +getSession()->setFlash('error', 'This is the message'); + * \Yii::$app->getSession()->setFlash('success', 'This is the message'); + * \Yii::$app->getSession()->setFlash('info', 'This is the message'); + * ``` + * + * Multiple messages could be set as follows: + * + * ```php + * \Yii::$app->getSession()->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->getSession(); + $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/init b/init new file mode 100644 index 0000000..e6278db --- /dev/null +++ b/init @@ -0,0 +1,205 @@ +#!/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('mcrypt')) { + die('The mcrypt 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']}"); +$all = false; +foreach ($files as $file) { + if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all, $params)) { + break; + } +} + +$callbacks = ['setCookieValidationKey', 'setWritable', 'setExecutable']; +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 === '.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) { + echo " chmod 0777 $writable\n"; + @chmod("$root/$writable", 0777); + } +} + +function setExecutable($root, $paths) +{ + foreach ($paths as $executable) { + echo " chmod 0755 $executable\n"; + @chmod("$root/$executable", 0755); + } +} + +function setCookieValidationKey($root, $paths) +{ + foreach ($paths as $file) { + echo " generate cookie validation key in $file\n"; + $file = $root . '/' . $file; + $length = 32; + $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + $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($links) +{ + foreach ($links as $link => $target) { + echo " symlink $target as $link\n"; + if (!is_link($link)) { + symlink($target, $link); + } + } +} diff --git a/init.bat b/init.bat new file mode 100644 index 0000000..e50c242 --- /dev/null +++ b/init.bat @@ -0,0 +1,20 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line init script for Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright (c) 2008 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%init" %* + +@endlocal diff --git a/requirements.php b/requirements.php new file mode 100644 index 0000000..fd84f47 --- /dev/null +++ b/requirements.php @@ -0,0 +1,132 @@ +Error'; + echo '

The path to yii framework seems to be incorrect.

'; + echo '

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

'; + echo '

Please refer to the README on how to install Yii.

'; +} + +require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); +$requirementsChecker = new YiiRequirementChecker(); + +$gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.'; +$gdOK = $imagickOK = false; + +if (extension_loaded('imagick')) { + $imagick = new Imagick(); + $imagickFormats = $imagick->queryFormats('PNG'); + if (in_array('PNG', $imagickFormats)) { + $imagickOK = true; + } else { + $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.'; + } +} + +if (extension_loaded('gd')) { + $gdInfo = gd_info(); + if (!empty($gdInfo['FreeType Support'])) { + $gdOK = true; + } else { + $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.'; + } +} + +/** + * Adjust requirements according to your application specifics. + */ +$requirements = array( + // Database : + array( + 'name' => 'PDO extension', + 'mandatory' => true, + 'condition' => extension_loaded('pdo'), + 'by' => 'All DB-related classes', + ), + array( + 'name' => 'PDO SQLite extension', + 'mandatory' => false, + 'condition' => extension_loaded('pdo_sqlite'), + 'by' => 'All DB-related classes', + 'memo' => 'Required for SQLite database.', + ), + array( + 'name' => 'PDO MySQL extension', + 'mandatory' => false, + 'condition' => extension_loaded('pdo_mysql'), + 'by' => 'All DB-related classes', + 'memo' => 'Required for MySQL database.', + ), + array( + 'name' => 'PDO PostgreSQL extension', + 'mandatory' => false, + 'condition' => extension_loaded('pdo_pgsql'), + 'by' => 'All DB-related classes', + 'memo' => 'Required for PostgreSQL database.', + ), + // Cache : + array( + 'name' => 'Memcache extension', + 'mandatory' => false, + 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), + 'by' => 'MemCache', + 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : '' + ), + array( + 'name' => 'APC extension', + 'mandatory' => false, + 'condition' => extension_loaded('apc'), + 'by' => 'ApcCache', + ), + // CAPTCHA: + array( + 'name' => 'GD PHP extension with FreeType support', + 'mandatory' => false, + 'condition' => $gdOK, + 'by' => 'Captcha', + 'memo' => $gdMemo, + ), + array( + 'name' => 'ImageMagick PHP extension with PNG support', + 'mandatory' => false, + 'condition' => $imagickOK, + 'by' => 'Captcha', + 'memo' => $imagickMemo, + ), + // PHP ini : + 'phpExposePhp' => array( + 'name' => 'Expose PHP', + 'mandatory' => false, + 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), + 'by' => 'Security reasons', + 'memo' => '"expose_php" should be disabled at php.ini', + ), + 'phpAllowUrlInclude' => array( + 'name' => 'PHP allow url include', + 'mandatory' => false, + 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), + 'by' => 'Security reasons', + 'memo' => '"allow_url_include" should be disabled at php.ini', + ), + 'phpSmtp' => array( + 'name' => 'PHP mail SMTP', + 'mandatory' => false, + 'condition' => strlen(ini_get('SMTP')) > 0, + 'by' => 'Email sending', + 'memo' => 'PHP mail SMTP server required', + ), +); +$requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..ad7f016 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,58 @@ +This directory contains various tests for the advanced applications. + +Tests in `codeception` directory are developed with [Codeception PHP Testing Framework](http://codeception.com/). + +After creating and setting up the advanced application, follow these steps to prepare for the tests: + +1. Install Codeception if it's not yet installed: + + ``` + composer global require "codeception/codeception=2.0.*" "codeception/specify=*" "codeception/verify=*" + ``` + + If you've never used Composer for global packages run `composer global status`. It should output: + + ``` + Changed current directory to + ``` + + Then add `/vendor/bin` to you `PATH` environment variable. Now you're able to use `codecept` from command + line globally. + +2. Install faker extension by running the following from template root directory where `composer.json` is: + + ``` + composer require --dev yiisoft/yii2-faker:* + ``` + +3. Create `yii2_advanced_tests` database then update it by applying migrations: + + ``` + codeception/bin/yii migrate + ``` + +4. In order to be able to run acceptance tests you need to start a webserver. The simplest way is to use PHP built in + webserver. In the root directory where `common`, `frontend` etc. are execute the following: + + ``` + php -S localhost:8080 + ``` + +5. Now you can run the tests with the following commands, assuming you are in the `tests/codeception` directory: + + ``` + # frontend tests + cd frontend + codecept build + codecept run + + # backend tests + + cd backend + codecept build + codecept run + + # etc. + ``` + + If you already have run `codecept build` for each application, you can skip that step and run all tests by a single `codecept run`. diff --git a/tests/codeception.yml b/tests/codeception.yml new file mode 100644 index 0000000..1a793ed --- /dev/null +++ b/tests/codeception.yml @@ -0,0 +1,11 @@ +include: + - codeception/common + - codeception/console + - codeception/backend + - codeception/frontend + +paths: + log: codeception/_output + +settings: + colors: true diff --git a/tests/codeception/_output/.gitignore b/tests/codeception/_output/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/codeception/_output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/codeception/backend/.gitignore b/tests/codeception/backend/.gitignore new file mode 100644 index 0000000..985dbb4 --- /dev/null +++ b/tests/codeception/backend/.gitignore @@ -0,0 +1,4 @@ +# these files are auto generated by codeception build +/unit/UnitTester.php +/functional/FunctionalTester.php +/acceptance/AcceptanceTester.php diff --git a/tests/codeception/backend/_bootstrap.php b/tests/codeception/backend/_bootstrap.php new file mode 100644 index 0000000..a28a3d2 --- /dev/null +++ b/tests/codeception/backend/_bootstrap.php @@ -0,0 +1,23 @@ +wantTo('ensure login page works'); + +$loginPage = LoginPage::openBy($I); + +$I->amGoingTo('submit login form with no data'); +$loginPage->login('', ''); +if (method_exists($I, 'wait')) { + $I->wait(3); // only for selenium +} +$I->expectTo('see validations errors'); +$I->see('Username cannot be blank.', '.help-block'); +$I->see('Password cannot be blank.', '.help-block'); + +$I->amGoingTo('try to login with wrong credentials'); +$I->expectTo('see validations errors'); +$loginPage->login('admin', 'wrong'); +if (method_exists($I, 'wait')) { + $I->wait(3); // only for selenium +} +$I->expectTo('see validations errors'); +$I->see('Incorrect username or password.', '.help-block'); + +$I->amGoingTo('try to login with correct credentials'); +$loginPage->login('erau', 'password_0'); +if (method_exists($I, 'wait')) { + $I->wait(3); // only for selenium +} +$I->expectTo('see that user is logged'); +$I->seeLink('Logout (erau)'); +$I->dontSeeLink('Login'); +$I->dontSeeLink('Signup'); +/** Uncomment if using WebDriver + * $I->click('Logout (erau)'); + * $I->dontSeeLink('Logout (erau)'); + * $I->seeLink('Login'); + */ diff --git a/tests/codeception/backend/acceptance/_bootstrap.php b/tests/codeception/backend/acceptance/_bootstrap.php new file mode 100644 index 0000000..411855e --- /dev/null +++ b/tests/codeception/backend/acceptance/_bootstrap.php @@ -0,0 +1,2 @@ +wantTo('ensure login page works'); + +$loginPage = LoginPage::openBy($I); + +$I->amGoingTo('submit login form with no data'); +$loginPage->login('', ''); +$I->expectTo('see validations errors'); +$I->see('Username cannot be blank.', '.help-block'); +$I->see('Password cannot be blank.', '.help-block'); + +$I->amGoingTo('try to login with wrong credentials'); +$I->expectTo('see validations errors'); +$loginPage->login('admin', 'wrong'); +$I->expectTo('see validations errors'); +$I->see('Incorrect username or password.', '.help-block'); + +$I->amGoingTo('try to login with correct credentials'); +$loginPage->login('erau', 'password_0'); +$I->expectTo('see that user is logged'); +$I->seeLink('Logout (erau)'); +$I->dontSeeLink('Login'); +$I->dontSeeLink('Signup'); diff --git a/tests/codeception/backend/functional/_bootstrap.php b/tests/codeception/backend/functional/_bootstrap.php new file mode 100644 index 0000000..94f3fbd --- /dev/null +++ b/tests/codeception/backend/functional/_bootstrap.php @@ -0,0 +1,2 @@ + [ + 'fixture' => [ + 'class' => 'yii\faker\FixtureController', + 'fixtureDataPath' => '@tests/codeception/common/fixtures/data', + 'templatePath' => '@tests/codeception/common/templates/fixtures', + 'namespace' => 'tests\codeception\common\fixtures', + ], + ], + ] +); + +$application = new yii\console\Application($config); +$exitCode = $application->run(); +exit($exitCode); diff --git a/tests/codeception/bin/yii.bat b/tests/codeception/bin/yii.bat new file mode 100644 index 0000000..d516b3a --- /dev/null +++ b/tests/codeception/bin/yii.bat @@ -0,0 +1,20 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line bootstrap script for Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright (c) 2008 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%yii" %* + +@endlocal diff --git a/tests/codeception/common/.gitignore b/tests/codeception/common/.gitignore new file mode 100644 index 0000000..985dbb4 --- /dev/null +++ b/tests/codeception/common/.gitignore @@ -0,0 +1,4 @@ +# these files are auto generated by codeception build +/unit/UnitTester.php +/functional/FunctionalTester.php +/acceptance/AcceptanceTester.php diff --git a/tests/codeception/common/_bootstrap.php b/tests/codeception/common/_bootstrap.php new file mode 100644 index 0000000..cea3ee5 --- /dev/null +++ b/tests/codeception/common/_bootstrap.php @@ -0,0 +1,15 @@ +actor->fillField('input[name="LoginForm[username]"]', $username); + $this->actor->fillField('input[name="LoginForm[password]"]', $password); + $this->actor->click('login-button'); + } +} diff --git a/tests/codeception/common/_support/FixtureHelper.php b/tests/codeception/common/_support/FixtureHelper.php new file mode 100644 index 0000000..cc2a311 --- /dev/null +++ b/tests/codeception/common/_support/FixtureHelper.php @@ -0,0 +1,60 @@ +loadFixtures(); + } + + /** + * Method is called after all suite tests run + */ + public function _afterSuite() + { + $this->unloadFixtures(); + } + + /** + * @inheritdoc + */ + public function fixtures() + { + return [ + 'user' => [ + 'class' => UserFixture::className(), + 'dataFile' => '@tests/codeception/common/fixtures/data/init_login.php', + ], + ]; + } +} diff --git a/tests/codeception/common/codeception.yml b/tests/codeception/common/codeception.yml new file mode 100644 index 0000000..e8a3407 --- /dev/null +++ b/tests/codeception/common/codeception.yml @@ -0,0 +1,13 @@ +namespace: tests\codeception\common +actor: Tester +paths: + tests: . + log: _output + data: _data + helpers: _support +settings: + bootstrap: _bootstrap.php + suite_class: \PHPUnit_Framework_TestSuite + colors: true + memory_limit: 1024M + log: true diff --git a/tests/codeception/common/fixtures/UserFixture.php b/tests/codeception/common/fixtures/UserFixture.php new file mode 100644 index 0000000..7153c8c --- /dev/null +++ b/tests/codeception/common/fixtures/UserFixture.php @@ -0,0 +1,13 @@ + 'erau', + 'auth_key' => 'tUu1qHcde0diwUol3xeI-18MuHkkprQI', + // password_0 + 'password_hash' => '$2y$13$nJ1WDlBaGcbCdbNC5.5l4.sgy.OMEKCqtDQOdQ2OWpgiKRWYyzzne', + 'password_reset_token' => 'RkD_Jw0_8HEedzLk7MM-ZKEFfYR7VbMr_1392559490', + 'created_at' => '1392559490', + 'updated_at' => '1392559490', + 'email' => 'sfriesen@jenkins.info', + ], +]; diff --git a/tests/codeception/common/templates/fixtures/user.php b/tests/codeception/common/templates/fixtures/user.php new file mode 100644 index 0000000..d3f83b5 --- /dev/null +++ b/tests/codeception/common/templates/fixtures/user.php @@ -0,0 +1,17 @@ +getSecurity(); + +return [ + 'username' => $faker->userName, + 'email' => $faker->email, + 'auth_key' => $security->generateRandomString(), + 'password_hash' => $security->generatePasswordHash('password_' . $index), + 'password_reset_token' => $security->generateRandomString() . '_' . time(), + 'created_at' => time(), + 'updated_at' => time(), +]; diff --git a/tests/codeception/common/unit.suite.yml b/tests/codeception/common/unit.suite.yml new file mode 100644 index 0000000..a0582a5 --- /dev/null +++ b/tests/codeception/common/unit.suite.yml @@ -0,0 +1,6 @@ +# Codeception Test Suite Configuration + +# suite for unit (internal) tests. +# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. + +class_name: UnitTester diff --git a/tests/codeception/common/unit/DbTestCase.php b/tests/codeception/common/unit/DbTestCase.php new file mode 100644 index 0000000..2159a69 --- /dev/null +++ b/tests/codeception/common/unit/DbTestCase.php @@ -0,0 +1,11 @@ + 'bayer.hudson', + 'auth_key' => 'HP187Mvq7Mmm3CTU80dLkGmni_FUH_lR', + //password_0 + 'password_hash' => '$2y$13$EjaPFBnZOQsHdGuHI.xvhuDp1fHpo8hKRSk6yshqa9c5EG8s3C3lO', + 'password_reset_token' => 'ExzkCOaYc1L8IOBs4wdTGGbgNiG3Wz1I_1402312317', + 'created_at' => '1402312317', + 'updated_at' => '1402312317', + 'email' => 'nicole.paucek@schultz.info', + ], +]; diff --git a/tests/codeception/common/unit/models/LoginFormTest.php b/tests/codeception/common/unit/models/LoginFormTest.php new file mode 100644 index 0000000..7fd64f6 --- /dev/null +++ b/tests/codeception/common/unit/models/LoginFormTest.php @@ -0,0 +1,94 @@ + [ + 'user' => [ + 'class' => 'yii\web\User', + 'identityClass' => 'common\models\User', + ], + ], + ]); + } + + protected function tearDown() + { + Yii::$app->user->logout(); + parent::tearDown(); + } + + public function testLoginNoUser() + { + $model = new LoginForm([ + 'username' => 'not_existing_username', + 'password' => 'not_existing_password', + ]); + + $this->specify('user should not be able to login, when there is no identity', function () use ($model) { + expect('model should not login user', $model->login())->false(); + expect('user should not be logged in', Yii::$app->user->isGuest)->true(); + }); + } + + public function testLoginWrongPassword() + { + $model = new LoginForm([ + 'username' => 'bayer.hudson', + 'password' => 'wrong_password', + ]); + + $this->specify('user should not be able to login with wrong password', function () use ($model) { + expect('model should not login user', $model->login())->false(); + expect('error message should be set', $model->errors)->hasKey('password'); + expect('user should not be logged in', Yii::$app->user->isGuest)->true(); + }); + } + + public function testLoginCorrect() + { + + $model = new LoginForm([ + 'username' => 'bayer.hudson', + 'password' => 'password_0', + ]); + + $this->specify('user should be able to login with correct credentials', function () use ($model) { + expect('model should login user', $model->login())->true(); + expect('error message should not be set', $model->errors)->hasntKey('password'); + expect('user should be logged in', Yii::$app->user->isGuest)->false(); + }); + } + + /** + * @inheritdoc + */ + public function fixtures() + { + return [ + 'user' => [ + 'class' => UserFixture::className(), + 'dataFile' => '@tests/codeception/common/unit/fixtures/data/models/user.php' + ], + ]; + } + +} diff --git a/tests/codeception/config/acceptance.php b/tests/codeception/config/acceptance.php new file mode 100644 index 0000000..9318da5 --- /dev/null +++ b/tests/codeception/config/acceptance.php @@ -0,0 +1,7 @@ + 'app-common', + 'basePath' => dirname(__DIR__), + ] +); diff --git a/tests/codeception/config/config.php b/tests/codeception/config/config.php new file mode 100644 index 0000000..59e485a --- /dev/null +++ b/tests/codeception/config/config.php @@ -0,0 +1,17 @@ + [ + 'db' => [ + 'dsn' => 'mysql:host=localhost;dbname=yii2_advanced_tests', + ], + 'mailer' => [ + 'useFileTransport' => true, + ], + 'urlManager' => [ + 'showScriptName' => true, + ], + ], +]; diff --git a/tests/codeception/config/console/unit.php b/tests/codeception/config/console/unit.php new file mode 100644 index 0000000..4d3aeb0 --- /dev/null +++ b/tests/codeception/config/console/unit.php @@ -0,0 +1,14 @@ + [ + 'request' => [ + // it's not recommended to run functional tests with CSRF validation enabled + 'enableCsrfValidation' => false, + // but if you absolutely need it set cookie domain to localhost + /* + 'csrfCookie' => [ + 'domain' => 'localhost', + ], + */ + ], + ], +]; \ No newline at end of file diff --git a/tests/codeception/config/unit.php b/tests/codeception/config/unit.php new file mode 100644 index 0000000..6bd08d3 --- /dev/null +++ b/tests/codeception/config/unit.php @@ -0,0 +1,7 @@ + $value) { + $inputType = $field === 'body' ? 'textarea' : 'input'; + $this->actor->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value); + } + $this->actor->click('contact-button'); + } +} diff --git a/tests/codeception/frontend/_pages/SignupPage.php b/tests/codeception/frontend/_pages/SignupPage.php new file mode 100644 index 0000000..0e1cefa --- /dev/null +++ b/tests/codeception/frontend/_pages/SignupPage.php @@ -0,0 +1,27 @@ + $value) { + $inputType = $field === 'body' ? 'textarea' : 'input'; + $this->actor->fillField($inputType . '[name="SignupForm[' . $field . ']"]', $value); + } + $this->actor->click('signup-button'); + } +} diff --git a/tests/codeception/frontend/acceptance.suite.yml b/tests/codeception/frontend/acceptance.suite.yml new file mode 100644 index 0000000..1828a04 --- /dev/null +++ b/tests/codeception/frontend/acceptance.suite.yml @@ -0,0 +1,28 @@ +# Codeception Test Suite Configuration + +# suite for acceptance tests. +# perform tests in browser using the Selenium-like tools. +# powered by Mink (http://mink.behat.org). +# (tip: that's what your customer will see). +# (tip: test your ajax and javascript by one of Mink drivers). + +# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. + +class_name: AcceptanceTester +modules: + enabled: + - PhpBrowser + - tests\codeception\common\_support\FixtureHelper +# you can use WebDriver instead of PhpBrowser to test javascript and ajax. +# This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium +# "restart" option is used by the WebDriver to start each time per test-file new session and cookies, +# it is useful if you want to login in your app in each test. +# - WebDriver + config: + PhpBrowser: +# PLEASE ADJUST IT TO THE ACTUAL ENTRY POINT WITHOUT PATH INFO + url: http://localhost:8080 +# WebDriver: +# url: http://localhost:8080 +# browser: firefox +# restart: true diff --git a/tests/codeception/frontend/acceptance/AboutCept.php b/tests/codeception/frontend/acceptance/AboutCept.php new file mode 100644 index 0000000..7f7f97e --- /dev/null +++ b/tests/codeception/frontend/acceptance/AboutCept.php @@ -0,0 +1,8 @@ +wantTo('ensure that about works'); +AboutPage::openBy($I); +$I->see('About', 'h1'); diff --git a/tests/codeception/frontend/acceptance/ContactCept.php b/tests/codeception/frontend/acceptance/ContactCept.php new file mode 100644 index 0000000..f589460 --- /dev/null +++ b/tests/codeception/frontend/acceptance/ContactCept.php @@ -0,0 +1,54 @@ +wantTo('ensure that contact works'); + +$contactPage = ContactPage::openBy($I); + +$I->see('Contact', 'h1'); + +$I->amGoingTo('submit contact form with no data'); +$contactPage->submit([]); +if (method_exists($I, 'wait')) { + $I->wait(3); // only for selenium +} +$I->expectTo('see validations errors'); +$I->see('Contact', 'h1'); +$I->see('Name cannot be blank', '.help-block'); +$I->see('Email cannot be blank', '.help-block'); +$I->see('Subject cannot be blank', '.help-block'); +$I->see('Body cannot be blank', '.help-block'); +$I->see('The verification code is incorrect', '.help-block'); + +$I->amGoingTo('submit contact form with not correct email'); +$contactPage->submit([ + 'name' => 'tester', + 'email' => 'tester.email', + 'subject' => 'test subject', + 'body' => 'test content', + 'verifyCode' => 'testme', +]); +if (method_exists($I, 'wait')) { + $I->wait(3); // only for selenium +} +$I->expectTo('see that email adress is wrong'); +$I->dontSee('Name cannot be blank', '.help-block'); +$I->see('Email is not a valid email address.', '.help-block'); +$I->dontSee('Subject cannot be blank', '.help-block'); +$I->dontSee('Body cannot be blank', '.help-block'); +$I->dontSee('The verification code is incorrect', '.help-block'); + +$I->amGoingTo('submit contact form with correct data'); +$contactPage->submit([ + 'name' => 'tester', + 'email' => 'tester@example.com', + 'subject' => 'test subject', + 'body' => 'test content', + 'verifyCode' => 'testme', +]); +if (method_exists($I, 'wait')) { + $I->wait(3); // only for selenium +} +$I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/tests/codeception/frontend/acceptance/HomeCept.php b/tests/codeception/frontend/acceptance/HomeCept.php new file mode 100644 index 0000000..5cf8379 --- /dev/null +++ b/tests/codeception/frontend/acceptance/HomeCept.php @@ -0,0 +1,10 @@ +wantTo('ensure that home page works'); +$I->amOnPage(Yii::$app->homeUrl); +$I->see('My Company'); +$I->seeLink('About'); +$I->click('About'); +$I->see('This is the About page.'); diff --git a/tests/codeception/frontend/acceptance/LoginCept.php b/tests/codeception/frontend/acceptance/LoginCept.php new file mode 100644 index 0000000..e4363a8 --- /dev/null +++ b/tests/codeception/frontend/acceptance/LoginCept.php @@ -0,0 +1,32 @@ +wantTo('ensure login page works'); + +$loginPage = LoginPage::openBy($I); + +$I->amGoingTo('submit login form with no data'); +$loginPage->login('', ''); +$I->expectTo('see validations errors'); +$I->see('Username cannot be blank.', '.help-block'); +$I->see('Password cannot be blank.', '.help-block'); + +$I->amGoingTo('try to login with wrong credentials'); +$I->expectTo('see validations errors'); +$loginPage->login('admin', 'wrong'); +$I->expectTo('see validations errors'); +$I->see('Incorrect username or password.', '.help-block'); + +$I->amGoingTo('try to login with correct credentials'); +$loginPage->login('erau', 'password_0'); +$I->expectTo('see that user is logged'); +$I->seeLink('Logout (erau)'); +$I->dontSeeLink('Login'); +$I->dontSeeLink('Signup'); +/** Uncomment if using WebDriver + * $I->click('Logout (erau)'); + * $I->dontSeeLink('Logout (erau)'); + * $I->seeLink('Login'); + */ diff --git a/tests/codeception/frontend/acceptance/SignupCest.php b/tests/codeception/frontend/acceptance/SignupCest.php new file mode 100644 index 0000000..ab4b7bb --- /dev/null +++ b/tests/codeception/frontend/acceptance/SignupCest.php @@ -0,0 +1,82 @@ + 'tester.email@example.com', + 'username' => 'tester', + ]); + } + + /** + * This method is called when test fails. + * @param \Codeception\Event\FailEvent $event + */ + public function _fail($event) + { + } + + /** + * @param \codeception_frontend\AcceptanceTester $I + * @param \Codeception\Scenario $scenario + */ + public function testUserSignup($I, $scenario) + { + $I->wantTo('ensure that signup works'); + + $signupPage = SignupPage::openBy($I); + $I->see('Signup', 'h1'); + $I->see('Please fill out the following fields to signup:'); + + $I->amGoingTo('submit signup form with no data'); + + $signupPage->submit([]); + + $I->expectTo('see validation errors'); + $I->see('Username cannot be blank.', '.help-block'); + $I->see('Email cannot be blank.', '.help-block'); + $I->see('Password cannot be blank.', '.help-block'); + + $I->amGoingTo('submit signup form with not correct email'); + $signupPage->submit([ + 'username' => 'tester', + 'email' => 'tester.email', + 'password' => 'tester_password', + ]); + + $I->expectTo('see that email address is wrong'); + $I->dontSee('Username cannot be blank.', '.help-block'); + $I->dontSee('Password cannot be blank.', '.help-block'); + $I->see('Email is not a valid email address.', '.help-block'); + + $I->amGoingTo('submit signup form with correct email'); + $signupPage->submit([ + 'username' => 'tester', + 'email' => 'tester.email@example.com', + 'password' => 'tester_password', + ]); + + $I->expectTo('see that user logged in'); + $I->seeLink('Logout (tester)'); + } +} diff --git a/tests/codeception/frontend/acceptance/_bootstrap.php b/tests/codeception/frontend/acceptance/_bootstrap.php new file mode 100644 index 0000000..b0a40ef --- /dev/null +++ b/tests/codeception/frontend/acceptance/_bootstrap.php @@ -0,0 +1,2 @@ +wantTo('ensure that about works'); +AboutPage::openBy($I); +$I->see('About', 'h1'); diff --git a/tests/codeception/frontend/functional/ContactCept.php b/tests/codeception/frontend/functional/ContactCept.php new file mode 100644 index 0000000..1aaac3f --- /dev/null +++ b/tests/codeception/frontend/functional/ContactCept.php @@ -0,0 +1,45 @@ +wantTo('ensure that contact works'); + +$contactPage = ContactPage::openBy($I); + +$I->see('Contact', 'h1'); + +$I->amGoingTo('submit contact form with no data'); +$contactPage->submit([]); +$I->expectTo('see validations errors'); +$I->see('Contact', 'h1'); +$I->see('Name cannot be blank', '.help-block'); +$I->see('Email cannot be blank', '.help-block'); +$I->see('Subject cannot be blank', '.help-block'); +$I->see('Body cannot be blank', '.help-block'); +$I->see('The verification code is incorrect', '.help-block'); + +$I->amGoingTo('submit contact form with not correct email'); +$contactPage->submit([ + 'name' => 'tester', + 'email' => 'tester.email', + 'subject' => 'test subject', + 'body' => 'test content', + 'verifyCode' => 'testme', +]); +$I->expectTo('see that email adress is wrong'); +$I->dontSee('Name cannot be blank', '.help-block'); +$I->see('Email is not a valid email address.', '.help-block'); +$I->dontSee('Subject cannot be blank', '.help-block'); +$I->dontSee('Body cannot be blank', '.help-block'); +$I->dontSee('The verification code is incorrect', '.help-block'); + +$I->amGoingTo('submit contact form with correct data'); +$contactPage->submit([ + 'name' => 'tester', + 'email' => 'tester@example.com', + 'subject' => 'test subject', + 'body' => 'test content', + 'verifyCode' => 'testme', +]); +$I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/tests/codeception/frontend/functional/HomeCept.php b/tests/codeception/frontend/functional/HomeCept.php new file mode 100644 index 0000000..0f5b2ff --- /dev/null +++ b/tests/codeception/frontend/functional/HomeCept.php @@ -0,0 +1,9 @@ +wantTo('ensure that home page works'); +$I->amOnPage(Yii::$app->homeUrl); +$I->see('My Company'); +$I->seeLink('About'); +$I->click('About'); +$I->see('This is the About page.'); diff --git a/tests/codeception/frontend/functional/LoginCept.php b/tests/codeception/frontend/functional/LoginCept.php new file mode 100644 index 0000000..0fb3477 --- /dev/null +++ b/tests/codeception/frontend/functional/LoginCept.php @@ -0,0 +1,27 @@ +wantTo('ensure login page works'); + +$loginPage = LoginPage::openBy($I); + +$I->amGoingTo('submit login form with no data'); +$loginPage->login('', ''); +$I->expectTo('see validations errors'); +$I->see('Username cannot be blank.', '.help-block'); +$I->see('Password cannot be blank.', '.help-block'); + +$I->amGoingTo('try to login with wrong credentials'); +$I->expectTo('see validations errors'); +$loginPage->login('admin', 'wrong'); +$I->expectTo('see validations errors'); +$I->see('Incorrect username or password.', '.help-block'); + +$I->amGoingTo('try to login with correct credentials'); +$loginPage->login('erau', 'password_0'); +$I->expectTo('see that user is logged'); +$I->seeLink('Logout (erau)'); +$I->dontSeeLink('Login'); +$I->dontSeeLink('Signup'); diff --git a/tests/codeception/frontend/functional/SignupCest.php b/tests/codeception/frontend/functional/SignupCest.php new file mode 100644 index 0000000..525b037 --- /dev/null +++ b/tests/codeception/frontend/functional/SignupCest.php @@ -0,0 +1,90 @@ + 'tester.email@example.com', + 'username' => 'tester', + ]); + } + + /** + * This method is called when test fails. + * @param \Codeception\Event\FailEvent $event + */ + public function _fail($event) + { + + } + + /** + * + * @param \codeception_frontend\FunctionalTester $I + * @param \Codeception\Scenario $scenario + */ + public function testUserSignup($I, $scenario) + { + $I->wantTo('ensure that signup works'); + + $signupPage = SignupPage::openBy($I); + $I->see('Signup', 'h1'); + $I->see('Please fill out the following fields to signup:'); + + $I->amGoingTo('submit signup form with no data'); + + $signupPage->submit([]); + + $I->expectTo('see validation errors'); + $I->see('Username cannot be blank.', '.help-block'); + $I->see('Email cannot be blank.', '.help-block'); + $I->see('Password cannot be blank.', '.help-block'); + + $I->amGoingTo('submit signup form with not correct email'); + $signupPage->submit([ + 'username' => 'tester', + 'email' => 'tester.email', + 'password' => 'tester_password', + ]); + + $I->expectTo('see that email address is wrong'); + $I->dontSee('Username cannot be blank.', '.help-block'); + $I->dontSee('Password cannot be blank.', '.help-block'); + $I->see('Email is not a valid email address.', '.help-block'); + + $I->amGoingTo('submit signup form with correct email'); + $signupPage->submit([ + 'username' => 'tester', + 'email' => 'tester.email@example.com', + 'password' => 'tester_password', + ]); + + $I->expectTo('see that user is created'); + $I->seeRecord('common\models\User', [ + 'username' => 'tester', + 'email' => 'tester.email@example.com', + ]); + + $I->expectTo('see that user logged in'); + $I->seeLink('Logout (tester)'); + } +} diff --git a/tests/codeception/frontend/functional/_bootstrap.php b/tests/codeception/frontend/functional/_bootstrap.php new file mode 100644 index 0000000..1abc491 --- /dev/null +++ b/tests/codeception/frontend/functional/_bootstrap.php @@ -0,0 +1,3 @@ + 'okirlin', + 'auth_key' => 'iwTNae9t34OmnK6l4vT4IeaTk-YWI2Rv', + 'password_hash' => '$2y$13$CXT0Rkle1EMJ/c1l5bylL.EylfmQ39O5JlHJVFpNn618OUS1HwaIi', + 'password_reset_token' => 't5GU9NwpuGYSfb7FEZMAxqtuz2PkEvv_' . time(), + 'created_at' => '1391885313', + 'updated_at' => '1391885313', + 'email' => 'brady.renner@rutherford.com', + ], + [ + 'username' => 'troy.becker', + 'auth_key' => 'EdKfXrx88weFMV0vIxuTMWKgfK2tS3Lp', + 'password_hash' => '$2y$13$g5nv41Px7VBqhS3hVsVN2.MKfgT3jFdkXEsMC4rQJLfaMa7VaJqL2', + 'password_reset_token' => '4BSNyiZNAuxjs5Mty990c47sVrgllIi_' . time(), + 'created_at' => '1391885313', + 'updated_at' => '1391885313', + 'email' => 'nicolas.dianna@hotmail.com', + 'status' => '0', + ], +]; diff --git a/tests/codeception/frontend/unit/models/ContactFormTest.php b/tests/codeception/frontend/unit/models/ContactFormTest.php new file mode 100644 index 0000000..9aaf595 --- /dev/null +++ b/tests/codeception/frontend/unit/models/ContactFormTest.php @@ -0,0 +1,59 @@ +mailer->fileTransportCallback = function ($mailer, $message) { + return 'testing_message.eml'; + }; + } + + protected function tearDown() + { + unlink($this->getMessageFile()); + parent::tearDown(); + } + + public function testContact() + { + $model = new ContactForm(); + + $model->attributes = [ + 'name' => 'Tester', + 'email' => 'tester@example.com', + 'subject' => 'very important letter subject', + 'body' => 'body of current message', + ]; + + $model->sendEmail('admin@example.com'); + + $this->specify('email should be send', function () { + expect('email file should exist', file_exists($this->getMessageFile()))->true(); + }); + + $this->specify('message should contain correct data', function () use ($model) { + $emailMessage = file_get_contents($this->getMessageFile()); + + expect('email should contain user name', $emailMessage)->contains($model->name); + expect('email should contain sender email', $emailMessage)->contains($model->email); + expect('email should contain subject', $emailMessage)->contains($model->subject); + expect('email should contain body', $emailMessage)->contains($model->body); + }); + } + + private function getMessageFile() + { + return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml'; + } +} diff --git a/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php b/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php new file mode 100644 index 0000000..ec65a0e --- /dev/null +++ b/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php @@ -0,0 +1,88 @@ +mailer->fileTransportCallback = function ($mailer, $message) { + return 'testing_message.eml'; + }; + } + + protected function tearDown() + { + @unlink($this->getMessageFile()); + + parent::tearDown(); + } + + public function testSendEmailWrongUser() + { + $this->specify('no user with such email, message should not be send', function () { + + $model = new PasswordResetRequestForm(); + $model->email = 'not-existing-email@example.com'; + + expect('email not send', $model->sendEmail())->false(); + + }); + + $this->specify('user is not active, message should not be send', function () { + + $model = new PasswordResetRequestForm(); + $model->email = $this->user[1]['email']; + + expect('email not send', $model->sendEmail())->false(); + + }); + } + + public function testSendEmailCorrectUser() + { + $model = new PasswordResetRequestForm(); + $model->email = $this->user[0]['email']; + $user = User::findOne(['password_reset_token' => $this->user[0]['password_reset_token']]); + + expect('email sent', $model->sendEmail())->true(); + expect('user has valid token', $user->password_reset_token)->notNull(); + + $this->specify('message has correct format', function () use ($model) { + + expect('message file exists', file_exists($this->getMessageFile()))->true(); + + $message = file_get_contents($this->getMessageFile()); + expect('message "from" is correct', $message)->contains(Yii::$app->params['supportEmail']); + expect('message "to" is correct', $message)->contains($model->email); + + }); + } + + public function fixtures() + { + return [ + 'user' => [ + 'class' => UserFixture::className(), + 'dataFile' => '@tests/codeception/frontend/unit/fixtures/data/models/user.php' + ], + ]; + } + + private function getMessageFile() + { + return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml'; + } + +} diff --git a/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php b/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php new file mode 100644 index 0000000..a2f5012 --- /dev/null +++ b/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php @@ -0,0 +1,44 @@ +user[0]['password_reset_token']); + expect('password should be resetted', $form->resetPassword())->true(); + } + + public function fixtures() + { + return [ + 'user' => [ + 'class' => UserFixture::className(), + 'dataFile' => '@tests/codeception/frontend/unit/fixtures/data/models/user.php' + ], + ]; + } + +} diff --git a/tests/codeception/frontend/unit/models/SignupFormTest.php b/tests/codeception/frontend/unit/models/SignupFormTest.php new file mode 100644 index 0000000..4d869f1 --- /dev/null +++ b/tests/codeception/frontend/unit/models/SignupFormTest.php @@ -0,0 +1,53 @@ + 'some_username', + 'email' => 'some_email@example.com', + 'password' => 'some_password', + ]); + + $user = $model->signup(); + + $this->assertInstanceOf('common\models\User', $user, 'user should be valid'); + + expect('username should be correct', $user->username)->equals('some_username'); + expect('email should be correct', $user->email)->equals('some_email@example.com'); + expect('password should be correct', $user->validatePassword('some_password'))->true(); + } + + public function testNotCorrectSignup() + { + $model = new SignupForm([ + 'username' => 'troy.becker', + 'email' => 'nicolas.dianna@hotmail.com', + 'password' => 'some_password', + ]); + + expect('username and email are in use, user should not be created', $model->signup())->null(); + } + + public function fixtures() + { + return [ + 'user' => [ + 'class' => UserFixture::className(), + 'dataFile' => '@tests/codeception/frontend/unit/fixtures/data/models/user.php', + ], + ]; + } + +} diff --git a/yii.bat b/yii.bat new file mode 100644 index 0000000..d516b3a --- /dev/null +++ b/yii.bat @@ -0,0 +1,20 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line bootstrap script for Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright (c) 2008 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%yii" %* + +@endlocal